Модуль uniset2-iec61131.js реализует стандартные функциональные блоки по IEC 61131-3:2013, Edition 3 (Tables 36–45). Все блоки вызываются циклически из uniset_on_step() через метод update().
load("uniset2-iec61131.js");
Бистабильные элементы
RS — Reset-dominant bistable (Table 36)
Триггер с доминантным сбросом. При одновременной подаче SET и RESET1 — выход сбрасывается.
Body: Q1 := NOT RESET1 AND (SET OR Q1)
| SET | RESET1 | Q1 |
| 0 | 0 | Q1 (без изменений) |
| 1 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 1 | 0 (reset dominant) |
const rs = new RS();
rs.update(SET, RESET1); // -> rs.Q1
SR — Set-dominant bistable (Table 37)
Триггер с доминантной установкой. При одновременной подаче SET1 и RESET — выход устанавливается.
Body: Q1 := SET1 OR (NOT RESET AND Q1)
| SET1 | RESET | Q1 |
| 0 | 0 | Q1 (без изменений) |
| 1 | 0 | 1 |
| 0 | 1 | 0 |
| 1 | 1 | 1 (set dominant) |
const sr = new SR();
sr.update(SET1, RESET); // -> sr.Q1
Детекторы фронтов
R_TRIG — Rising edge detector (Table 38)
Детектор нарастающего фронта. Q = true в течение одного цикла при переходе CLK из false в true.
Body: Q := CLK AND NOT M; M := CLK;
const rt = new R_TRIG();
function uniset_on_step() {
rt.update(in_Button);
if (rt.Q) {
// Нажатие кнопки (один цикл)
}
}
F_TRIG — Falling edge detector (Table 39)
Детектор спадающего фронта. Q = true в течение одного цикла при переходе CLK из true в false.
Body: Q := NOT CLK AND M; M := CLK;
const ft = new F_TRIG();
function uniset_on_step() {
ft.update(in_Button);
if (ft.Q) {
// Отпускание кнопки (один цикл)
}
}
Счётчики
CTU — Up counter (Table 40)
Счётчик вверх. Считает нарастающие фронты на CU. Q = true когда CV >= PV.
Body:
IF RESET THEN CV := 0;
ELSIF CU THEN CV := CV + 1;
END_IF;
Q := (CV >= PV);
| Вход/Выход | Тип | Описание |
| CU | BOOL (R_EDGE) | Счётный вход (по фронту) |
| RESET | BOOL | Сброс CV в 0 |
| PV | INT | Уставка (задаётся в конструкторе) |
| Q | BOOL | Выход: CV >= PV |
| CV | INT | Текущее значение счётчика |
const ctu = new CTU(10); // PV = 10
function uniset_on_step() {
ctu.update(in_Pulse, in_Reset);
out_CountReached = ctu.Q ? 1 : 0;
}
CTD — Down counter (Table 41)
Счётчик вниз. Считает вниз по фронтам CD. Q = true когда CV <= 0.
Body:
IF LOAD THEN CV := PV;
ELSIF CD AND (CV > 0) THEN CV := CV - 1;
END_IF;
Q := (CV <= 0);
| Вход/Выход | Тип | Описание |
| CD | BOOL (R_EDGE) | Счётный вход (по фронту) |
| LOAD | BOOL | Загрузка PV в CV |
| PV | INT | Уставка (задаётся в конструкторе) |
| Q | BOOL | Выход: CV <= 0 |
| CV | INT | Текущее значение счётчика |
const ctd = new CTD(5); // PV = 5
ctd.update(false, true); // LOAD: CV = 5
ctd.update(pulse, false); // счёт вниз по фронтам
CTUD — Up/Down counter (Table 42)
Реверсивный счётчик. Считает вверх по CU, вниз по CD. RESET имеет приоритет над LOAD.
Body:
IF RESET THEN CV := 0;
ELSIF LOAD THEN CV := PV;
ELSE
IF CU THEN CV := CV + 1; END_IF;
IF CD AND (CV > 0) THEN CV := CV - 1; END_IF;
END_IF;
QU := (CV >= PV);
QD := (CV <= 0);
| Вход/Выход | Тип | Описание |
| CU | BOOL (R_EDGE) | Счёт вверх (по фронту) |
| CD | BOOL (R_EDGE) | Счёт вниз (по фронту) |
| RESET | BOOL | Сброс CV в 0 |
| LOAD | BOOL | Загрузка PV в CV |
| PV | INT | Уставка (задаётся в конструкторе) |
| QU | BOOL | Выход: CV >= PV |
| QD | BOOL | Выход: CV <= 0 |
| CV | INT | Текущее значение счётчика |
const ctud = new CTUD(100); // PV = 100
ctud.update(up_pulse, down_pulse, reset, load);
// ctud.QU, ctud.QD, ctud.CV
Таймеры
Все таймеры используют Date.now() для отслеживания времени и должны вызываться циклически из uniset_on_step(). Точность ограничена интервалом цикла (~150 мс по умолчанию).
TON — On-delay timer (Table 43)
Таймер задержки включения. При IN=true выход Q становится true через PT миллисекунд. При IN=false — Q и ET сбрасываются немедленно.
IN: ___/‾‾‾‾‾‾‾‾‾‾‾‾\___
Q: _________/‾‾‾‾‾‾\___
ET: ___/‾‾‾‾‾|PT|___\___
<-PT->
| Вход/Выход | Тип | Описание |
| IN | BOOL | Вход разрешения |
| PT | TIME (ms) | Уставка времени (задаётся в конструкторе) |
| Q | BOOL | Выход (true после задержки) |
| ET | TIME (ms) | Прошедшее время (0..PT) |
const ton = new TON(3000); // PT = 3 секунды
function uniset_on_step() {
ton.update(in_Enable);
out_Delayed = ton.Q ? 1 : 0;
}
TOF — Off-delay timer (Table 44)
Таймер задержки выключения. При IN=true — Q немедленно true. При IN=false — Q остаётся true ещё PT миллисекунд.
IN: ___/‾‾‾‾‾‾\____________
Q: ___/‾‾‾‾‾‾‾‾‾‾‾‾\______
ET: ___________/‾‾‾‾‾|PT|___
<-PT->
| Вход/Выход | Тип | Описание |
| IN | BOOL | Вход разрешения |
| PT | TIME (ms) | Уставка времени (задаётся в конструкторе) |
| Q | BOOL | Выход (задержанное выключение) |
| ET | TIME (ms) | Прошедшее время (0..PT) |
const tof = new TOF(5000); // PT = 5 секунд
function uniset_on_step() {
tof.update(in_MotorRunning);
out_CoolingFan = tof.Q ? 1 : 0; // вентилятор работает ещё 5с после останова
}
TP — Pulse timer (Table 45)
Таймер-формирователь импульса. По нарастающему фронту IN выдаёт импульс длительностью PT. Импульс выполняется до конца независимо от IN. Повторный запуск возможен только после завершения импульса И возврата IN в false.
IN: ___/‾‾‾‾‾‾‾‾‾‾‾‾\___/‾‾‾\___
Q: ___/‾‾‾‾‾‾\__________/‾‾‾‾‾‾\
ET: ___/‾‾‾‾‾‾|PT|___\___/‾‾‾‾‾‾|
<-PT-> <-PT->
| Вход/Выход | Тип | Описание |
| IN | BOOL | Вход запуска (по фронту) |
| PT | TIME (ms) | Длительность импульса (задаётся в конструкторе) |
| Q | BOOL | Выход импульса |
| ET | TIME (ms) | Прошедшее время (0..PT) |
const tp = new TP(500); // импульс 500 мс
function uniset_on_step() {
tp.update(in_Trigger);
out_Pulse = tp.Q ? 1 : 0;
}
Пример: управление термостатом
Полный пример программы на JScript с использованием стандартных функциональных блоков:
load("uniset2-iec61131.js");
uniset_inputs = [
{ name: "AI_Temperature_S" },
{ name: "DI_Enable_S" },
{ name: "DI_Reset_S" }
];
uniset_outputs = [
{ name: "DO_Heater_C" },
{ name: "DO_Alarm_C" },
{ name: "AO_CycleCount_C" }
];
// Конфигурация (аналог STRUCT в ST)
let config = { setpoint: 20.0, hysteresis: 2.0, maxTemp: 95.0 };
let state = 0;
// Функциональные блоки
const startEdge = new R_TRIG(); // детектор нарастающего фронта
const onDelay = new TON(5000); // задержка включения 5с
const cycleCounter = new CTU(100); // счётчик до 100
const heaterLatch = new RS(); // RS-триггер для нагревателя
function uniset_on_step() {
// Масштабирование: целочисленный датчик → REAL (scale 100)
let temperature = in_AI_Temperature_S / 100;
// Детекция фронта кнопки Enable
startEdge.update(!!in_DI_Enable_S);
if (startEdge.Q) {
state = 1;
}
// Автомат состояний
switch (state) {
case 0: // Ожидание
out_DO_Heater_C = 0;
break;
case 1: // Работа
// Гистерезисное управление нагревателем через RS-триггер
heaterLatch.update(
temperature < config.setpoint - config.hysteresis, // SET: включить если холодно
temperature > config.setpoint + config.hysteresis // RESET1: выключить если горячо
);
out_DO_Heater_C = heaterLatch.Q1 ? 1 : 0;
// Подсчёт циклов включения нагревателя
onDelay.update(heaterLatch.Q1);
cycleCounter.update(onDelay.Q, !!in_DI_Reset_S);
out_AO_CycleCount_C = cycleCounter.CV;
break;
case 2: // Авария
out_DO_Heater_C = 0;
break;
}
// Контроль перегрева
if (temperature > config.maxTemp) {
out_DO_Alarm_C = 1;
state = 2;
}
// Сброс
if (in_DI_Reset_S) {
state = 0;
out_DO_Alarm_C = 0;
}
}
Запуск:
uniset2-jscript --js-file thermostat.js --confile configure.xml --js-name JSProxy1
Сводная таблица
| Блок | Тип | Таблица IEC | Описание |
| RS | Бистабильный | Table 36 | Триггер с доминантным сбросом |
| SR | Бистабильный | Table 37 | Триггер с доминантной установкой |
| R_TRIG | Детектор фронта | Table 38 | Нарастающий фронт |
| F_TRIG | Детектор фронта | Table 39 | Спадающий фронт |
| CTU | Счётчик | Table 40 | Счётчик вверх |
| CTD | Счётчик | Table 41 | Счётчик вниз |
| CTUD | Счётчик | Table 42 | Реверсивный счётчик |
| TON | Таймер | Table 43 | Задержка включения |
| TOF | Таймер | Table 44 | Задержка выключения |
| TP | Таймер | Table 45 | Формирователь импульса |
Ссылки