1. Назначение
С помощью контроллера CCU825-PLС и функции программируемой логики (ПЛК) реализована система контроля и регулирования температуры воздуха в помещениях, оборудованных системой приточной вентиляции.
На следующих рисунках показано два варианта системы — с каналами догрева и без.
Вентилятор обдува калорифера (двигатель турбины) и жалюзи подачи воздуха управляются одним сигналом.
2. Режимы работы
Режимы работы задаются следующими профилями в конфигурации контроллера. Показаны настройки профилей для системы приточной вентиляции без каналов догрева.
-
ОСТАНОВКА
Номер профиля задается в программе константой
COMMAND_STOP
.Переводит систему в ждущий режим. Вентилятор выключен, жалюзи закрыты. Температура в калорифере поддерживается в диапазоне границ термодатчика обратной воды.
-
ЛЕТО
Номер профиля задается в программе константой
COMMAND_SUMMER
.Переводит систему в летний режим, если температура улицы выше верхней границы уличного датчика. В противном случае команда игнорируется.
Вентилятор включен, жалюзи открыты, КЗР полностью закрыт.
-
РАБОТА_24_25
Номер профиля задается в программе константой
COMMAND_WORK1
. -
РАБОТА_27_28
Номер профиля задается в программе константой
COMMAND_WORK2
. -
РАБОТА_30_31
Номер профиля задается в программе константой
COMMAND_WORK3
.
В рабочих режимах перед включением вентилятора и открытием жалюзи осуществляется прогрев калорифера до значения температуры обратной воды, которое задано в программе константой TEMP_WATER_HEAT
.
В рабочих режимах поддерживается температура канала в диапазоне границ термодатчика канала. Если температура обратной воды опустится ниже нижней границы термодатчика обратной воды, то регулирование будет осуществляться по термодатчику обратной воды до тех пор, пока температура не войдет в заданный диапазон.
3. Формирование сигналов управления
Реализован закон управления как в приборе ОВЕН ТРМ33.
Управление запорно-регулирующим клапаном (КЗР) производится при помощи двух сигналов (КЗРоткр. и КЗРзакр.) c широтно-импульсной модуляцией (ШИМ).
\$D_i\$ — длительность управляющего импульса в миллисекундах.
\$E_i = T_{уст.} - T_i\$ — величина рассогласования в текущем шаге регулирования.
\$DeltaE_i = E_i - E_{i-1}\$ — величина изменения рассогласования по сравнению с предыдущим вычислением \$D_{i-1}\$.
\$K\$ и \$tau\$ — коэффициенты регулятора.
Коэффициент \$K\$ (общий коэффициент усиления) определяет чувствительность регулятора как к величине рассогласования контролируемой им температуры, так и к скорости ее изменения.
Коэффициент \$tau\$ (коэффициент при дифференциальной составляющей) определяет чувствительность регулятора к резким изменениям контролируемой им температуры.
Направление перемещения КЗР определяется по знаку, полученному при вычислении \$D_i\$. При положительном значении \$D_i\$ формируется управляющий импульс на открытие КЗР, а при отрицательном значении — управляющий импульс на его закрытие.
При значениях \$D_i\$, численно больших шага регулирования, сигнал управления выдается непрерывно.
При управлении процессами с медленно изменяющимися во времени параметрами возможны ситуации, при которых температура объекта в течение шага регулирования будет меняться незначительно. В этом случае дифференциальная составляющая регулятора \$DeltaE_i = 0\$ перестает оказывать влияние на длительность управляющих импульсов, что может негативно отразиться на качестве регулирования. Во избежание таких ситуаций предусмотрена возможность увеличения интервала времени между соседними вычислениями \$D_i\$ и \$D_{i+1}\$. При этом длительность управляющего импульса вычисляется не в каждом шаге регулирования, а с пропуском некоторого их числа. В пропускаемых (для вычислений) шагах длительность импульсов управления остается неизменной и равной \$D_i\$. Параметр \$S\$ определяет в каком по счету шаге регулирования будет производиться последующее вычисление \$D_{i+1}\$.
4. Настройка коэффициентов регулятора
В процессе работы для достижения оптимального качества регулирования температуры может потребоваться изменение заданных для регулятора параметров настройки — \$S\$, \$K\$, \$tau\$.
Оптимальный выбор коэффициентов настройки регулятора позволяет максимально быстро и практически без перерегулирования температуры вывести объект на заданную уставку.
5. Результаты работы
6. Листинг программы для системы без каналов догрева
# Параметр S
const S = 1;
# Коэффициент K
const K_MAIN = 200;
# Коэффициент tau
const TAU_MAIN = 10;
# Шаг регулирования (в миллисекундах)
const CONTROL_STEP = 6000;
const S_IDLE = 60000 / CONTROL_STEP;
const FRACT = 8;
const K_2_5 = 640; # round(2.5 * 2^FRACT)
# Термодатчик уличный
const INPUT_TEMP_OUTDOOR = 1;
# Термодатчик на обратке
const INPUT_TEMP_WATER = 2;
# Термодатчик в канале
const INPUT_TEMP_MAIN_VENT_DUCT = 4;
# Термодатчик в помещении
const INPUT_TEMP_INDOOR = 5;
# Температура прогрева калорифера
const TEMP_WATER_HEAT = 60 << FRACT;
# Открытие КЗР
const OUTPUT_MAIN_VALVE_OPEN = 5;
# Закрытие КЗР
const OUTPUT_MAIN_VALVE_CLOSE = 6;
# Минимальная длительность управляющего импульса КЗР (в миллисекундах)
const VALVE_MIN_PULSE = 300;
# Длительность импульса полного открытия КЗР (в миллисекундах)
const VALVE_FULL_OPEN_PULSE = 105000;
# Длительность импульса полного закрытия КЗР (в миллисекундах)
const VALVE_FULL_CLOSE_PULSE = 105000;
# Длительность первого импульса закрытия КЗР в режиме ожидания (в миллисекундах)
const VALVE_IDLE_FIRST_CLOSE_PULSE = 90000;
# Длительность импульса открытия КЗР в режиме ожидания (в миллисекундах)
const VALVE_IDLE_OPEN_PULSE = 2000;
# Длительность импульса закрытия КЗР в режиме ожидания (в миллисекундах)
const VALVE_IDLE_CLOSE_PULSE = 2000;
# Вентилятор/жалюзи
const OUTPUT_FAN = 1;
# Капиллярный датчик на калорифере
const FREEZE = $EVT_INPUT3_ACTIVE;
# Пожарный датчик
const FIRE = $EVT_INPUT6_ACTIVE;
# Профиль ОСТАНОВКА
const COMMAND_STOP = $EVT_PROFILE3_APPLIED;
# Профиль ЛЕТО
const COMMAND_SUMMER = $EVT_PROFILE4_APPLIED;
# Профиль РАБОТА1
const COMMAND_WORK1 = $EVT_PROFILE5_APPLIED;
# Профиль РАБОТА2
const COMMAND_WORK2 = $EVT_PROFILE6_APPLIED;
# Профиль РАБОТА3
const COMMAND_WORK3 = $EVT_PROFILE7_APPLIED;
const MODE_IDLE = 0;
const MODE_SUMMER = 1;
const MODE_HEAT = 2;
const MODE_WORK = 3;
const MODE_FREEZE = 4;
var mode;
var main_valve_step, prev_main_valve_error, prev_main_valve_pulse;
proc main()
{
if S < 1 {
return;
}
var e = $get_event_id();
if e == $EVT_INIT {
init();
} else if e == $EVT_TIMER1 {
main_valve_control_step();
} else if e == FREEZE {
mode_freeze();
} else if e == FIRE {
mode_idle();
} else if e == COMMAND_STOP {
mode_idle();
} else if e == COMMAND_SUMMER {
mode_summer();
} else if e == COMMAND_WORK1 {
mode_work_or_heat();
} else if e == COMMAND_WORK2 {
mode_work_or_heat();
} else if e == COMMAND_WORK3 {
mode_work_or_heat();
}
}
proc init()
{
$set_event_mask($EM_INPUT | $EM_PROFILE);
$set_timer(1, CONTROL_STEP / 100);
apply_command(COMMAND_STOP);
}
proc mode_idle()
{
set_mode(MODE_IDLE);
fan_off();
main_valve_close_pulse(VALVE_IDLE_FIRST_CLOSE_PULSE);
}
proc mode_freeze()
{
set_mode(MODE_FREEZE);
}
proc mode_summer()
{
if get_temp_outdoor() > get_temp_outdoor_high_limit() {
set_mode(MODE_SUMMER);
fan_on();
main_valve_close_pulse(VALVE_FULL_CLOSE_PULSE);
}
}
proc mode_work_or_heat()
{
if mode == MODE_WORK {
mode_work();
} else {
mode_heat();
}
}
proc mode_work()
{
set_mode(MODE_WORK);
fan_on();
}
proc mode_heat()
{
set_mode(MODE_HEAT);
fan_off();
main_valve_open_pulse(VALVE_FULL_OPEN_PULSE);
}
proc main_valve_control_step()
{
if mode == MODE_SUMMER {
return;
}
if mode == MODE_FREEZE {
return;
}
var t = get_temp_water();
var t_sp_low = get_temp_water_low_limit();
var t_sp_high = get_temp_water_high_limit();
var t_sp = midpoint(t_sp_low, t_sp_high);
if mode == MODE_IDLE {
idle_control_step(t, t_sp, t_sp_low, t_sp_high);
return;
}
if mode == MODE_HEAT {
if t < get_temp_water_heat() {
return;
} else {
mode_work();
}
}
if t > t_sp_low {
t = get_temp_main_vent_duct();
t_sp_low = get_temp_main_vent_duct_low_limit();
t_sp_high = get_temp_main_vent_duct_high_limit();
if t > t_sp_low && t < t_sp_high {
return;
}
t_sp = midpoint(t_sp_low, t_sp_high);
}
var d = calc_main_valve_pulse(t, t_sp);
if abs(d) < VALVE_MIN_PULSE {
return;
}
if d > 0 {
main_valve_open(d);
} else {
main_valve_close(-d);
}
}
proc idle_control_step(t, t_sp, t_sp_low, t_sp_high)
{
main_valve_step = main_valve_step + 1;
if main_valve_step < S_IDLE {
return;
}
main_valve_step = 0;
var e = t_sp - t;
var de = e - prev_main_valve_error;
prev_main_valve_error = e;
if t > t_sp_high && de <= 0 {
main_valve_close_pulse(VALVE_IDLE_CLOSE_PULSE);
} else if t < t_sp_low && de >= 0 {
main_valve_open_pulse(VALVE_IDLE_OPEN_PULSE);
}
}
fun calc_main_valve_pulse(t, t_sp)
{
main_valve_step = main_valve_step + 1;
if main_valve_step < S {
return prev_main_valve_pulse;
}
main_valve_step = 0;
var e = t_sp - t;
var de = e - prev_main_valve_error;
prev_main_valve_error = e;
var d = calc_pulse(K_MAIN, TAU_MAIN, e, de);
if abs(prev_main_valve_pulse) < VALVE_MIN_PULSE {
d = d + prev_main_valve_pulse;
}
prev_main_valve_pulse = d;
return d;
}
fun calc_pulse(k, tau, e, de)
{
return K_2_5 * k * (e + tau * de) >> FRACT * 2;
}
fun get_temp_water_heat()
{
return TEMP_WATER_HEAT;
}
fun get_temp_water()
{
return $get_sensor_value(INPUT_TEMP_WATER, FRACT);
}
fun get_temp_water_low_limit()
{
return $get_sensor_low_limit(INPUT_TEMP_WATER, FRACT);
}
fun get_temp_water_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_WATER, FRACT);
}
fun get_temp_main_vent_duct()
{
return $get_sensor_value(INPUT_TEMP_MAIN_VENT_DUCT, FRACT);
}
fun get_temp_main_vent_duct_low_limit()
{
return $get_sensor_low_limit(INPUT_TEMP_MAIN_VENT_DUCT, FRACT);
}
fun get_temp_main_vent_duct_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_MAIN_VENT_DUCT, FRACT);
}
fun get_temp_outdoor()
{
return $get_sensor_value(INPUT_TEMP_OUTDOOR, FRACT);
}
fun get_temp_outdoor_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_OUTDOOR, FRACT);
}
proc main_valve_open_pulse(d)
{
valve_open_pulse(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc main_valve_close_pulse(d)
{
valve_close_pulse(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc main_valve_open(d)
{
valve_open(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc main_valve_close(d)
{
valve_close(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc valve_open(n_open, n_close, d)
{
if d < CONTROL_STEP {
valve_open_pulse(n_open, n_close, d);
} else {
valve_open_state(n_open, n_close);
}
}
proc valve_close(n_open, n_close, d)
{
if d < CONTROL_STEP {
valve_close_pulse(n_open, n_close, d);
} else {
valve_close_state(n_open, n_close);
}
}
proc valve_open_pulse(n_open, n_close, d)
{
$set_output_state(n_close, $OFF);
valve_pulse(n_open, d);
}
proc valve_close_pulse(n_open, n_close, d)
{
$set_output_state(n_open, $OFF);
valve_pulse(n_close, d);
}
proc valve_pulse(n, d)
{
$set_output_pulse(n, $ON, d / 100);
}
proc valve_open_state(n_open, n_close)
{
$set_output_state(n_close, $OFF);
$set_output_state(n_open, $ON);
}
proc valve_close_state(n_open, n_close)
{
$set_output_state(n_open, $OFF);
$set_output_state(n_close, $ON);
}
proc fan_on()
{
$set_output_state(OUTPUT_FAN, $ON);
}
proc fan_off()
{
$set_output_state(OUTPUT_FAN, $OFF);
}
proc set_mode(m)
{
mode = m;
reset_calc_state();
}
proc reset_calc_state()
{
main_valve_step = 0;
prev_main_valve_error = 0;
prev_main_valve_pulse = 0;
}
proc apply_command(c)
{
$apply_profile(c - $EVT_PROFILE1_APPLIED + 1);
}
fun abs(x)
{
if x < 0 {
return -x;
} else {
return x;
}
}
fun midpoint(x, y)
{
return (x + y) / 2;
}
7. Листинг программы для системы с каналами догрева
# Параметр S
const S = 1;
# Коэффициент K основного канала
const K_MAIN = 200;
# Коэффициент tau основного канала
const TAU_MAIN = 10;
# Коэффициент K левого канала догрева
const K_LEFT = 20;
# Коэффициент tau левого канала догрева
const TAU_LEFT = 10;
# Коэффициент K правого канала догрева
const K_RIGHT = 20;
# Коэффициент tau правого канала догрева
const TAU_RIGHT = 10;
# Шаг регулирования (в миллисекундах)
const CONTROL_STEP = 6000;
const S_IDLE = 60000 / CONTROL_STEP;
const FRACT = 8;
const K_2_5 = 640; # round(2.5 * 2^FRACT)
# Термодатчик уличный
const INPUT_TEMP_OUTDOOR = 1;
# Термодатчик на обратке
const INPUT_TEMP_WATER = 2;
# Термодатчик в основном канале
const INPUT_TEMP_MAIN_VENT_DUCT = 4;
# Термодатчик в левом канале догрева
const INPUT_TEMP_LEFT_VENT_DUCT = 5;
# Термодатчик в правом канале догрева
const INPUT_TEMP_RIGHT_VENT_DUCT = 6;
# Термодатчик в помещении
const INPUT_TEMP_INDOOR = 7;
# Температура прогрева калорифера
const TEMP_WATER_HEAT = 60 << FRACT;
# Открытие основного КЗР
const OUTPUT_MAIN_VALVE_OPEN = 3;
# Закрытие основного КЗР
const OUTPUT_MAIN_VALVE_CLOSE = 4;
# Открытие левого КЗР
const OUTPUT_LEFT_VALVE_OPEN = 5;
# Закрытие левого КЗР
const OUTPUT_LEFT_VALVE_CLOSE = 6;
# Открытие правого КЗР
const OUTPUT_RIGHT_VALVE_OPEN = 2;
# Закрытие правого КЗР
const OUTPUT_RIGHT_VALVE_CLOSE = 7;
# Минимальная длительность управляющего импульса КЗР (в миллисекундах)
const VALVE_MIN_PULSE = 300;
# Длительность импульса полного открытия КЗР (в миллисекундах)
const VALVE_FULL_OPEN_PULSE = 105000;
# Длительность импульса полного закрытия КЗР (в миллисекундах)
const VALVE_FULL_CLOSE_PULSE = 105000;
# Длительность первого импульса закрытия КЗР в режиме ожидания (в миллисекундах)
const VALVE_IDLE_FIRST_CLOSE_PULSE = 90000;
# Длительность импульса открытия КЗР в режиме ожидания (в миллисекундах)
const VALVE_IDLE_OPEN_PULSE = 2000;
# Длительность импульса закрытия КЗР в режиме ожидания (в миллисекундах)
const VALVE_IDLE_CLOSE_PULSE = 2000;
# Вентилятор/жалюзи
const OUTPUT_FAN = 1;
# Капиллярный датчик на калорифере
const FREEZE = $EVT_INPUT3_ACTIVE;
# Пожарный датчик
const FIRE = $EVT_INPUT8_ACTIVE;
# Профиль ОСТАНОВКА
const COMMAND_STOP = $EVT_PROFILE3_APPLIED;
# Профиль ЛЕТО
const COMMAND_SUMMER = $EVT_PROFILE4_APPLIED;
# Профиль РАБОТА1
const COMMAND_WORK1 = $EVT_PROFILE5_APPLIED;
# Профиль РАБОТА2
const COMMAND_WORK2 = $EVT_PROFILE6_APPLIED;
# Профиль РАБОТА3
const COMMAND_WORK3 = $EVT_PROFILE7_APPLIED;
const MODE_IDLE = 0;
const MODE_SUMMER = 1;
const MODE_HEAT = 2;
const MODE_WORK = 3;
const MODE_FREEZE = 4;
var mode;
var main_valve_step, prev_main_valve_error, prev_main_valve_pulse;
var left_valve_step, prev_left_valve_error, prev_left_valve_pulse;
var right_valve_step, prev_right_valve_error, prev_right_valve_pulse;
proc main()
{
if S < 1 {
return;
}
var e = $get_event_id();
if e == $EVT_INIT {
init();
} else if e == $EVT_TIMER1 {
main_valve_control_step();
left_valve_control_step();
right_valve_control_step();
} else if e == FREEZE {
mode_freeze();
} else if e == FIRE {
mode_idle();
} else if e == COMMAND_STOP {
mode_idle();
} else if e == COMMAND_SUMMER {
mode_summer();
} else if e == COMMAND_WORK1 {
mode_work_or_heat();
} else if e == COMMAND_WORK2 {
mode_work_or_heat();
} else if e == COMMAND_WORK3 {
mode_work_or_heat();
}
}
proc init()
{
$set_event_mask($EM_INPUT | $EM_PROFILE);
$set_timer(1, CONTROL_STEP / 100);
apply_command(COMMAND_STOP);
}
proc mode_idle()
{
set_mode(MODE_IDLE);
fan_off();
main_valve_close_pulse(VALVE_IDLE_FIRST_CLOSE_PULSE);
left_valve_close_pulse(VALVE_FULL_CLOSE_PULSE);
right_valve_close_pulse(VALVE_FULL_CLOSE_PULSE);
}
proc mode_freeze()
{
set_mode(MODE_FREEZE);
}
proc mode_summer()
{
if get_temp_outdoor() > get_temp_outdoor_high_limit() {
set_mode(MODE_SUMMER);
fan_on();
main_valve_close_pulse(VALVE_FULL_CLOSE_PULSE);
left_valve_close_pulse(VALVE_FULL_CLOSE_PULSE);
right_valve_close_pulse(VALVE_FULL_CLOSE_PULSE);
}
}
proc mode_work_or_heat()
{
if mode == MODE_WORK {
mode_work();
} else {
mode_heat();
}
}
proc mode_work()
{
set_mode(MODE_WORK);
fan_on();
}
proc mode_heat()
{
set_mode(MODE_HEAT);
fan_off();
main_valve_open_pulse(VALVE_FULL_OPEN_PULSE);
}
proc main_valve_control_step()
{
if mode == MODE_SUMMER {
return;
}
if mode == MODE_FREEZE {
return;
}
var t = get_temp_water();
var t_sp_low = get_temp_water_low_limit();
var t_sp_high = get_temp_water_high_limit();
var t_sp = midpoint(t_sp_low, t_sp_high);
if mode == MODE_IDLE {
idle_control_step(t, t_sp, t_sp_low, t_sp_high);
return;
}
if mode == MODE_HEAT {
if t < get_temp_water_heat() {
return;
} else {
mode_work();
}
}
if t > t_sp_low {
t = get_temp_main_vent_duct();
t_sp_low = get_temp_main_vent_duct_low_limit();
t_sp_high = get_temp_main_vent_duct_high_limit();
if t > t_sp_low && t < t_sp_high {
return;
}
t_sp = midpoint(t_sp_low, t_sp_high);
}
var d = calc_main_valve_pulse(t, t_sp);
if abs(d) < VALVE_MIN_PULSE {
return;
}
if d > 0 {
main_valve_open(d);
} else {
main_valve_close(-d);
}
}
proc left_valve_control_step()
{
if mode != MODE_WORK {
return;
}
var t = get_temp_left_vent_duct();
var t_sp_low = get_temp_left_vent_duct_low_limit();
var t_sp_high = get_temp_left_vent_duct_high_limit();
if t > t_sp_low && t < t_sp_high {
return;
}
var t_sp = midpoint(t_sp_low, t_sp_high);
var d = calc_left_valve_pulse(t, t_sp);
if abs(d) < VALVE_MIN_PULSE {
return;
}
if d > 0 {
left_valve_open(d);
} else {
left_valve_close(-d);
}
}
proc right_valve_control_step()
{
if mode != MODE_WORK {
return;
}
var t = get_temp_right_vent_duct();
var t_sp_low = get_temp_right_vent_duct_low_limit();
var t_sp_high = get_temp_right_vent_duct_high_limit();
if t > t_sp_low && t < t_sp_high {
return;
}
var t_sp = midpoint(t_sp_low, t_sp_high);
var d = calc_right_valve_pulse(t, t_sp);
if abs(d) < VALVE_MIN_PULSE {
return;
}
if d > 0 {
right_valve_open(d);
} else {
right_valve_close(-d);
}
}
proc idle_control_step(t, t_sp, t_sp_low, t_sp_high)
{
main_valve_step = main_valve_step + 1;
if main_valve_step < S_IDLE {
return;
}
main_valve_step = 0;
var e = t_sp - t;
var de = e - prev_main_valve_error;
prev_main_valve_error = e;
if t > t_sp_high && de <= 0 {
main_valve_close_pulse(VALVE_IDLE_CLOSE_PULSE);
} else if t < t_sp_low && de >= 0 {
main_valve_open_pulse(VALVE_IDLE_OPEN_PULSE);
}
}
fun calc_main_valve_pulse(t, t_sp)
{
main_valve_step = main_valve_step + 1;
if main_valve_step < S {
return prev_main_valve_pulse;
}
main_valve_step = 0;
var e = t_sp - t;
var de = e - prev_main_valve_error;
prev_main_valve_error = e;
var d = calc_pulse(K_MAIN, TAU_MAIN, e, de);
if abs(prev_main_valve_pulse) < VALVE_MIN_PULSE {
d = d + prev_main_valve_pulse;
}
prev_main_valve_pulse = d;
return d;
}
fun calc_left_valve_pulse(t, t_sp)
{
left_valve_step = left_valve_step + 1;
if left_valve_step < S {
return prev_left_valve_pulse;
}
left_valve_step = 0;
var e = t_sp - t;
var de = e - prev_left_valve_error;
prev_left_valve_error = e;
var d = calc_pulse(K_LEFT, TAU_LEFT, e, de);
if abs(prev_left_valve_pulse) < VALVE_MIN_PULSE {
d = d + prev_left_valve_pulse;
}
prev_left_valve_pulse = d;
return d;
}
fun calc_right_valve_pulse(t, t_sp)
{
right_valve_step = right_valve_step + 1;
if right_valve_step < S {
return prev_right_valve_pulse;
}
right_valve_step = 0;
var e = t_sp - t;
var de = e - prev_right_valve_error;
prev_right_valve_error = e;
var d = calc_pulse(K_RIGHT, TAU_RIGHT, e, de);
if abs(prev_right_valve_pulse) < VALVE_MIN_PULSE {
d = d + prev_right_valve_pulse;
}
prev_right_valve_pulse = d;
return d;
}
fun calc_pulse(k, tau, e, de)
{
return K_2_5 * k * (e + tau * de) >> FRACT * 2;
}
fun get_temp_water_heat()
{
return TEMP_WATER_HEAT;
}
fun get_temp_water()
{
return $get_sensor_value(INPUT_TEMP_WATER, FRACT);
}
fun get_temp_water_low_limit()
{
return $get_sensor_low_limit(INPUT_TEMP_WATER, FRACT);
}
fun get_temp_water_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_WATER, FRACT);
}
fun get_temp_main_vent_duct()
{
return $get_sensor_value(INPUT_TEMP_MAIN_VENT_DUCT, FRACT);
}
fun get_temp_main_vent_duct_low_limit()
{
return $get_sensor_low_limit(INPUT_TEMP_MAIN_VENT_DUCT, FRACT);
}
fun get_temp_main_vent_duct_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_MAIN_VENT_DUCT, FRACT);
}
fun get_temp_left_vent_duct()
{
return $get_sensor_value(INPUT_TEMP_LEFT_VENT_DUCT, FRACT);
}
fun get_temp_left_vent_duct_low_limit()
{
return $get_sensor_low_limit(INPUT_TEMP_LEFT_VENT_DUCT, FRACT);
}
fun get_temp_left_vent_duct_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_LEFT_VENT_DUCT, FRACT);
}
fun get_temp_right_vent_duct()
{
return $get_sensor_value(INPUT_TEMP_RIGHT_VENT_DUCT, FRACT);
}
fun get_temp_right_vent_duct_low_limit()
{
return $get_sensor_low_limit(INPUT_TEMP_RIGHT_VENT_DUCT, FRACT);
}
fun get_temp_right_vent_duct_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_RIGHT_VENT_DUCT, FRACT);
}
fun get_temp_outdoor()
{
return $get_sensor_value(INPUT_TEMP_OUTDOOR, FRACT);
}
fun get_temp_outdoor_high_limit()
{
return $get_sensor_high_limit(INPUT_TEMP_OUTDOOR, FRACT);
}
proc main_valve_open_pulse(d)
{
valve_open_pulse(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc main_valve_close_pulse(d)
{
valve_close_pulse(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc left_valve_open_pulse(d)
{
valve_open_pulse(OUTPUT_LEFT_VALVE_OPEN, OUTPUT_LEFT_VALVE_CLOSE, d);
}
proc left_valve_close_pulse(d)
{
valve_close_pulse(OUTPUT_LEFT_VALVE_OPEN, OUTPUT_LEFT_VALVE_CLOSE, d);
}
proc right_valve_open_pulse(d)
{
valve_open_pulse(OUTPUT_RIGHT_VALVE_OPEN, OUTPUT_RIGHT_VALVE_CLOSE, d);
}
proc right_valve_close_pulse(d)
{
valve_close_pulse(OUTPUT_RIGHT_VALVE_OPEN, OUTPUT_RIGHT_VALVE_CLOSE, d);
}
proc main_valve_open(d)
{
valve_open(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc main_valve_close(d)
{
valve_close(OUTPUT_MAIN_VALVE_OPEN, OUTPUT_MAIN_VALVE_CLOSE, d);
}
proc left_valve_open(d)
{
valve_open(OUTPUT_LEFT_VALVE_OPEN, OUTPUT_LEFT_VALVE_CLOSE, d);
}
proc left_valve_close(d)
{
valve_close(OUTPUT_LEFT_VALVE_OPEN, OUTPUT_LEFT_VALVE_CLOSE, d);
}
proc right_valve_open(d)
{
valve_open(OUTPUT_RIGHT_VALVE_OPEN, OUTPUT_RIGHT_VALVE_CLOSE, d);
}
proc right_valve_close(d)
{
valve_close(OUTPUT_RIGHT_VALVE_OPEN, OUTPUT_RIGHT_VALVE_CLOSE, d);
}
proc valve_open(n_open, n_close, d)
{
if d < CONTROL_STEP {
valve_open_pulse(n_open, n_close, d);
} else {
valve_open_state(n_open, n_close);
}
}
proc valve_close(n_open, n_close, d)
{
if d < CONTROL_STEP {
valve_close_pulse(n_open, n_close, d);
} else {
valve_close_state(n_open, n_close);
}
}
proc valve_open_pulse(n_open, n_close, d)
{
$set_output_state(n_close, $OFF);
valve_pulse(n_open, d);
}
proc valve_close_pulse(n_open, n_close, d)
{
$set_output_state(n_open, $OFF);
valve_pulse(n_close, d);
}
proc valve_pulse(n, d)
{
$set_output_pulse(n, $ON, d / 100);
}
proc valve_open_state(n_open, n_close)
{
$set_output_state(n_close, $OFF);
$set_output_state(n_open, $ON);
}
proc valve_close_state(n_open, n_close)
{
$set_output_state(n_open, $OFF);
$set_output_state(n_close, $ON);
}
proc fan_on()
{
$set_output_state(OUTPUT_FAN, $ON);
}
proc fan_off()
{
$set_output_state(OUTPUT_FAN, $OFF);
}
proc set_mode(m)
{
mode = m;
reset_calc_state();
}
proc reset_calc_state()
{
main_valve_step = 0;
prev_main_valve_error = 0;
prev_main_valve_pulse = 0;
left_valve_step = 0;
prev_left_valve_error = 0;
prev_left_valve_pulse = 0;
right_valve_step = 0;
prev_right_valve_error = 0;
prev_right_valve_pulse = 0;
}
proc apply_command(c)
{
$apply_profile(c - $EVT_PROFILE1_APPLIED + 1);
}
fun abs(x)
{
if x < 0 {
return -x;
} else {
return x;
}
}
fun midpoint(x, y)
{
return (x + y) / 2;
}