diff --git a/.gitignore b/.gitignore index 84639c5..b2073a9 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ # directories # build results -*.opt \ No newline at end of file +*.opt +*.~u diff --git a/PLC_Library.library b/PLC_Library.library index 6c8f8c7..6693558 100644 Binary files a/PLC_Library.library and b/PLC_Library.library differ diff --git a/compiled library/PLC_Library v1.5.2.compiled-library b/compiled library/PLC_Library v1.5.2.compiled-library new file mode 100644 index 0000000..9f23f8a Binary files /dev/null and b/compiled library/PLC_Library v1.5.2.compiled-library differ diff --git a/plc_src/DUT/Structs/AnalogValueState.st b/plc_src/DUT/Structs/AnalogValueState.st new file mode 100644 index 0000000..835aba8 --- /dev/null +++ b/plc_src/DUT/Structs/AnalogValueState.st @@ -0,0 +1,25 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Structs + Name : AnalogValueState + Type : DUT + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +{attribute 'qualified_only'} +{attribute 'strict'} +TYPE AnalogValueState: +( + undefined := INT#0, (* 0 - не определено *) + fail_hi := INT#5, (* 5 - аварийный по верхнему уровню *) + warn_hi := INT#4, (* 4 - предупредительный по верхнему уровню, *) + normal := INT#1, (* 1 - норма *) + warn_low := INT#2, (* 2 - предупредительный по нижнему уровню *) + fail_low := INT#3, (* 3 - аварийный по нижнему уровню *) + no_signal := - INT#1 (* -1 - нет связи с датчиком *) +) := undefined; +END_TYPE + diff --git a/plc_src/POU/Fastwel/AIM721_AI.st b/plc_src/POU/Fastwel/AIM721_AI.st new file mode 100644 index 0000000..9859a7f --- /dev/null +++ b/plc_src/POU/Fastwel/AIM721_AI.st @@ -0,0 +1,22 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM721_AI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM721_AI : REAL +(* +Перевод значений входов модуля AIM721 из единиц АЦП в 0..20 мА +*) +VAR_INPUT + ADC: DINT; +END_VAR + +(*----- IMPLEMENTATION -----*) +AIM721_AI := DINT_TO_REAL(ADC)*REAL#2.40802793E-006; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM722_AI.st b/plc_src/POU/Fastwel/AIM722_AI.st new file mode 100644 index 0000000..322ac87 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM722_AI.st @@ -0,0 +1,22 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM722_AI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM722_AI : REAL +(* +Перевод значений входов модуля AIM722 из единиц АЦП в 0..20 мА +*) +VAR_INPUT + ADC: DINT; +END_VAR + +(*----- IMPLEMENTATION -----*) +AIM722_AI := DINT_TO_REAL(ADC)*REAL#2.384185791015625E-006; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM723_AI.st b/plc_src/POU/Fastwel/AIM723_AI.st new file mode 100644 index 0000000..0422d25 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM723_AI.st @@ -0,0 +1,22 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM723_AI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM723_AI : REAL +(* +Перевод значений входов модуля AIM723 из единиц АЦП в 4..20 мА +*) +VAR_INPUT + ADC: DINT; +END_VAR + +(*----- IMPLEMENTATION -----*) +AIM723_AI := REAL#4.0 + DINT_TO_REAL(ADC)*REAL#1.93119072E-006; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM725_Status.st b/plc_src/POU/Fastwel/AIM725_Status.st new file mode 100644 index 0000000..8402521 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM725_Status.st @@ -0,0 +1,92 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM725_Status + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK AIM725_Status +(* +Модуль формирования статуса +Гистерезис по отключению и задержка по включению +-1 - нет связи с датчиком + 0 - не определено + 1 - норма + 2 - предупредительный по нижнему уровню + 3 - аварийный по нижнему уровню + 4 - предупредительный по верхнему уровню, + 5 - аварийный по верхнему уровню +*) +VAR_INPUT + Input: REAL; + fail_hi: REAL:=3.402823466E+38; + delta_fail_hi: REAL := 0; + time_fail_hi: TIME :=t#0s; + warn_hi: REAL:=3.402823466E+38; + delta_warn_hi: REAL := 0; + time_warn_hi: TIME :=t#0s; + warn_low: REAL:=-3.402823466E+38; + delta_warn_low: REAL := 0; + time_warn_low: TIME :=t#0s; + fail_low: REAL:=-3.402823466E+38; + delta_fail_low: REAL := 0; + time_fail_low: TIME :=t#0s; + Modul_status: BYTE; + Chanel_num: BYTE; +END_VAR +VAR_OUTPUT + Status: INT; + T_mod: INT; +END_VAR +VAR +(* !!! ВНУТРЕННИЕ НЕВЫХОДНЫЕ ПЕРЕМЕННЫЕ!!! *) + TON_fail_hi: TON; + TON_warn_hi: TON; + TON_warn_low: TON; + TON_fail_low: TON; +(* !!! ВНУТРЕННИЕ ВЫХОДНЫЕ ПЕРЕМЕННЫЕ!!! *) + fail_hi_st: BOOL; + warn_hi_st: BOOL; + normal_st: BOOL; + warn_low_st: BOOL; + fail_low_st: BOOL; + no_signal_st: BOOL; +END_VAR +(*----- IMPLEMENTATION -----*) +no_signal_st := (Input <= - REAL#200 OR ((Modul_status.0 OR Modul_status.4 OR Modul_status.5) AND Chanel_num = 1) OR ((Modul_status.1 OR Modul_status.6 OR Modul_status.7) AND Chanel_num = 2) OR Modul_status.2 OR Modul_status.3); +IF no_signal_st THEN T_mod := - INT#500; ELSE T_mod := REAL_TO_INT(Input); END_IF; + +TON_fail_low(IN := ((Input <= fail_low) AND NOT(no_signal_st)), PT := time_fail_low); +IF TON_fail_low.Q THEN fail_low_st := TRUE; ELSIF (Input > fail_low + delta_fail_low) THEN fail_low_st := FALSE; END_IF; + +TON_warn_low(IN := ((Input <= warn_low) AND NOT(no_signal_st) AND NOT(TON_fail_low.Q)), PT := time_warn_low); +IF TON_warn_low.Q THEN warn_low_st := TRUE; ELSIF (Input > warn_low + delta_warn_low) THEN warn_low_st := FALSE; END_IF; + +TON_fail_hi(IN := ((Input >= fail_hi) AND NOT(no_signal_st)), PT := time_fail_hi); +IF TON_fail_hi.Q THEN fail_hi_st := TRUE; ELSIF (Input < fail_hi - delta_fail_hi) THEN fail_hi_st := FALSE; END_IF; + +TON_warn_hi(IN := ((Input >= warn_hi) AND NOT(no_signal_st) AND NOT(TON_fail_hi.Q)), PT := time_warn_hi); +IF TON_warn_hi.Q THEN warn_hi_st := TRUE; ELSIF (Input < warn_hi - delta_warn_hi) THEN warn_hi_st := FALSE; END_IF; + +normal_st := (NOT(no_signal_st) AND NOT(fail_low_st) AND NOT(warn_low_st) AND NOT(warn_hi_st) AND NOT(fail_hi_st)); + +IF no_signal_st THEN + Status := - INT#1; +ELSIF fail_hi_st THEN + Status := INT#5; +ELSIF fail_low_st THEN + Status := INT#3; +ELSIF warn_hi_st THEN + Status := INT#4; +ELSIF warn_low_st THEN + Status := INT#2; +ELSIF normal_st THEN + Status := INT#1; +ELSE + Status := INT#0; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Fastwel/AIM726_AI.st b/plc_src/POU/Fastwel/AIM726_AI.st new file mode 100644 index 0000000..00e2c34 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM726_AI.st @@ -0,0 +1,21 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM726_AI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM726_AI : REAL +(* +Перевод значений входов модуля AIM726 из единиц АЦП в 0..40 В +*) +VAR_INPUT + ADC : DWORD; (*Единицы АЦП*) +END_VAR +(*----- IMPLEMENTATION -----*) +AIM726_AI := DWORD_TO_REAL(ADC) * REAL#4.768372E-006; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM727_AI.st b/plc_src/POU/Fastwel/AIM727_AI.st new file mode 100644 index 0000000..427b298 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM727_AI.st @@ -0,0 +1,22 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM727_AI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM727_AI : REAL +(* +Перевод значений входов модуля AIM727 из единиц АЦП в 0..40 V +*) +VAR_INPUT + ADC: DINT; (*Единицы АЦП*) +END_VAR + +(*----- IMPLEMENTATION -----*) +AIM727_AI := DINT_TO_REAL(ADC)*REAL#4.768372E-006; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM730_AO.st b/plc_src/POU/Fastwel/AIM730_AO.st new file mode 100644 index 0000000..f72c2d4 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM730_AO.st @@ -0,0 +1,31 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM730_AO + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM730_AO : WORD +(* +Перевод значений 0..20 мА или 4..20 мА в единицы АЦП выходов модуля AIM730 +Диапазон: +RANGE = 0 - 0..20 мА +RANGE = 1 - 4..20 мА +*) +VAR_INPUT + DAC : REAL; (*Миллиамперы*) + RANGE : BYTE; (*Диапазон: 0..20 мА - 0; 4..20 мА - 1 *) +END_VAR +(*----- IMPLEMENTATION -----*) +IF (RANGE = 1) AND (DAC < 4) THEN AIM730_AO := WORD#4; RETURN;END_IF; +CASE RANGE OF + 0 : AIM730_AO := REAL_TO_WORD(DAC/REAL#0.00030518); + 1 : AIM730_AO := REAL_TO_WORD((DAC - REAL#4)/REAL#0.00024414435); +ELSE + AIM730_AO := WORD#0; +END_CASE; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM731_AO.st b/plc_src/POU/Fastwel/AIM731_AO.st new file mode 100644 index 0000000..9a96e48 --- /dev/null +++ b/plc_src/POU/Fastwel/AIM731_AO.st @@ -0,0 +1,30 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM731_AO + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM731_AO : WORD +(* +Перевод значений -10..+10 В или 0..+10 В в единицы АЦП выходов модуля AIM731 +Диапазон: +RANGE = 0 - -10..+10 В +RANGE = 1 - 0..+10 В +*) +VAR_INPUT + DAC : REAL; (*Вольты*) + RANGE : BYTE; (*Диапазон -10..+10 В - 0; 0..+10 В - 1 *) +END_VAR +(*----- IMPLEMENTATION -----*) +CASE RANGE OF + 0 : AIM731_AO := REAL_TO_WORD((DAC + REAL#10.0)/REAL#0.00030518); + 1 : AIM731_AO := REAL_TO_WORD(DAC/REAL#0.00015259); +ELSE + AIM731_AO := 0; +END_CASE; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/AIM791_AI.st b/plc_src/POU/Fastwel/AIM791_AI.st new file mode 100644 index 0000000..ec3321d --- /dev/null +++ b/plc_src/POU/Fastwel/AIM791_AI.st @@ -0,0 +1,33 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : AIM791_AI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION AIM791_AI : REAL +(* +Перевод значений входов модуля AIM791 из единиц АЦП в 0..5 мА / 0..20 мА / 4..20 мА +Диапазон: +RANGE = 0 - 0..5 мА +RANGE = 1 - 0..20 мА +RANGE = 2 - 4..20 мА +*) +VAR_INPUT + ADC: WORD; + RANGE: BYTE := 0; +END_VAR + +(*----- IMPLEMENTATION -----*) +CASE RANGE OF + 0 : AIM791_AI := WORD_TO_REAL(ADC) * REAL#7.820248722E-005; + 1 : AIM791_AI := WORD_TO_REAL(ADC) * REAL#3.128099488E-004; + 2 : AIM791_AI := (WORD_TO_REAL(ADC) * REAL#3.128099488E-004) - REAL#4.0; +ELSE + AIM791_AI := REAL#0.0; +END_CASE; +END_FUNCTION diff --git a/plc_src/POU/Fastwel/DIM764_DI.st b/plc_src/POU/Fastwel/DIM764_DI.st new file mode 100644 index 0000000..35ae85f --- /dev/null +++ b/plc_src/POU/Fastwel/DIM764_DI.st @@ -0,0 +1,25 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Fastwel + Name : DIM764_DI + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION DIM764_DI : REAL +(*На выходе - Частота в Гц*) +VAR_INPUT + IN: DWORD; + RANGE: BYTE; (* 0-Period; 1-Interval; *) +END_VAR +(*----- IMPLEMENTATION -----*) +CASE RANGE OF + 0 : IF(IN=0) THEN DIM764_DI:=0; ELSE DIM764_DI := 50000000.0/DWORD_TO_REAL(IN); END_IF; + 1 : DIM764_DI:=DWORD_TO_REAL(IN)/50.0; +ELSE + DIM764_DI:=0; +END_CASE; +END_FUNCTION diff --git a/plc_src/POU/Filters/ExpRunAverage.st b/plc_src/POU/Filters/ExpRunAverage.st new file mode 100644 index 0000000..a3d9605 --- /dev/null +++ b/plc_src/POU/Filters/ExpRunAverage.st @@ -0,0 +1,21 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Filters + Name : ExpRunAverage + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION ExpRunAverage : REAL +(* Экспоненциальное скользящее среднее (экспоненциальное сглаживание)*) +VAR_INPUT + In : REAL; (* Входное значение *) + Old : REAL; (* Значение экспоненциального скользящего среднего на прошлом шаге *) + K : REAL; (* Весовой коэффициент в интервале от 0 до 1, отражающий скорость старения прошлых данных: чем выше его значение, тем больший удельный вес имеют новые наблюдения величины *) +END_VAR +(*----- IMPLEMENTATION -----*) +ExpRunAverage := (1.0 - K) * Old + K * In; +END_FUNCTION diff --git a/plc_src/POU/Filters/ExpRunAverageAdaptive.st b/plc_src/POU/Filters/ExpRunAverageAdaptive.st new file mode 100644 index 0000000..a11723d --- /dev/null +++ b/plc_src/POU/Filters/ExpRunAverageAdaptive.st @@ -0,0 +1,27 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Filters + Name : ExpRunAverageAdaptive + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION ExpRunAverageAdaptive : REAL +(* Экспоненциальное скользящее среднее c адаптивным коэффициентом*) +VAR_INPUT + In : REAL; (* Входное значение *) + Old : REAL; (* Значение экспоненциального скользящего среднего на прошлом шаге *) + K1 : REAL; (* Весовой коэффициент в интервале от 0 до 1, отражающий скорость старения прошлых данных: чем выше его значение, тем больший удельный вес имеют новые наблюдения величины *) + Delta : REAL; (* Разница для изменения коэффициента *) + K2 : REAL; (* Измененный весовой коэффициент в интервале от 0 до 1 (больший чем K1) *) +END_VAR +VAR + K: REAL; +END_VAR +(*----- IMPLEMENTATION -----*) +IF ABS(In - Old) > Delta THEN K := K2; ELSE K := K1; END_IF; +ExpRunAverageAdaptive := (1.0 - K) * Old + K * In; +END_FUNCTION diff --git a/plc_src/POU/Filters/MultipleClick.st b/plc_src/POU/Filters/MultipleClick.st new file mode 100644 index 0000000..6025162 --- /dev/null +++ b/plc_src/POU/Filters/MultipleClick.st @@ -0,0 +1,58 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Filters + Name : MultipleClick + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK MultipleClick +VAR_INPUT + In: BOOL;(* Вход кнопки *) + TimeToWait: TIME:=T#500MS; (* Максимальное время между нажатиями *) + TimeRelease: TIME:=T#0S; (* Время удержания выхода*) +END_VAR +VAR_OUTPUT + Click1: BOOL;(* Было сделано 1 нажатие *) + Click2: BOOL;(* Было сделано 2 нажатия *) + Click3: BOOL;(* Было сделано 3 нажатия *) + Click: WORD;(* Количество нажатий *) +END_VAR +VAR + R_TRIG_BTN: R_TRIG; + TOF_Click: TOF; + CurrentClick: WORD := 0; + TP_Release: TP; + F_TRIG_Click: F_TRIG; +END_VAR + +(*----- IMPLEMENTATION -----*) +R_TRIG_BTN(CLK:=In); +TOF_Click(IN:=R_TRIG_BTN.Q, PT:=TimeToWait); + +IF TOF_Click.Q AND R_TRIG_BTN.Q THEN + CurrentClick:=CurrentClick+1; +END_IF + +F_TRIG_Click(CLK:=TOF_Click.Q); +TP_Release(IN:=F_TRIG_Click.Q, PT:=TimeRelease); + +IF F_TRIG_Click.Q THEN + Click:=CurrentClick; + CurrentClick:=0; +END_IF + +IF TP_Release.Q THEN + Click1 := (Click = 1); + Click2 := (Click = 2); + Click3 := (Click = 3); +ELSE + Click:=0; + Click1 := 0; + Click2 := 0; + Click3 := 0; +END_IF +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Filters/RunMiddleArifm.st b/plc_src/POU/Filters/RunMiddleArifm.st new file mode 100644 index 0000000..0e74371 --- /dev/null +++ b/plc_src/POU/Filters/RunMiddleArifm.st @@ -0,0 +1,42 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Filters + Name : RunMiddleArifm + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK RunMiddleArifm +(* Скользящая средняя *) +VAR_INPUT + In : REAL; + N : INT:=32; (* Размер выборки 0...32 *) + RST : BOOL; +END_VAR +VAR_OUTPUT + Y : REAL; +END_VAR +VAR + init: BOOL; + buffer : ARRAY[0..31] OF REAL; + i: INT; + nEff: INT; +END_VAR +(*----- IMPLEMENTATION -----*) +nEff := LIMIT(0, N, 32); + +IF NOT init OR RST OR nEff = 0 THEN + init := TRUE; + FOR i := 0 TO nEff - 1 DO + buffer[i] := In; + END_FOR; + Y := In; +ELSE + IF i >= nEff - 1 THEN i := 0; ELSE i := i + 1; END_IF; + Y := Y + (In - buffer[i]) / INT_TO_REAL(nEff); + buffer[i] := In; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Иное/DFS.st b/plc_src/POU/Иное/DFS.st new file mode 100644 index 0000000..ab33010 --- /dev/null +++ b/plc_src/POU/Иное/DFS.st @@ -0,0 +1,49 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Иное + Name : DFS + Type : PROGRAM + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +PROGRAM DFS +(* Алгоритм поиска в глубину *) +(* Копировать в свою программу *) +VAR_INPUT + graph: POINTER TO ARRAY[1..n, 1..n] OF BOOL; // Граф связности (редактируется извне программы через DFS.graph[i,j]) + connected: POINTER TO ARRAY[1..n] OF INT; //подключенные вершины + Start: INT := 1; // Вершина из которой начинается поиск +END_VAR +VAR + STACK: STACK_I16; + v: INT; + i: INT; + visited: ARRAY[1..n] OF BOOL; // посещенные вершины + k: INT := 1; // номер посещенной вершины +END_VAR +VAR CONSTANT + n : INT := 12; // Количество вершин графа +END_VAR +(*----- IMPLEMENTATION -----*) +FOR i:=1 TO n DO + visited[i]:=0; + connected^[i]:=0; +END_FOR +k:=1; + +STACK(Din:= Start, PUSH:= 1); +visited[Start]:= 1; +WHILE NOT STACK.EMPTY DO + STACK(pop:= 1, Dout=>v); + FOR i:=1 TO n DO + IF (graph^[v,i] AND NOT(visited[i])) THEN + visited[i]:= 1; + STACK(Din:= i, PUSH:= 1); + connected^[k]:=i;k:=k+1; + END_IF + END_FOR +END_WHILE +END_PROGRAM diff --git a/plc_src/POU/Общие/ALARM_1.st b/plc_src/POU/Общие/ALARM_1.st new file mode 100644 index 0000000..5a601d6 --- /dev/null +++ b/plc_src/POU/Общие/ALARM_1.st @@ -0,0 +1,34 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : ALARM_1 + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK ALARM_1 +(* Функциональный блок проверяет входное значение X на принадлежность [LO_1…HI_1]*) +VAR_INPUT + X : REAL; (* Контролируемое значение. *) + LO_1 : REAL; (* Нижний предел 1. *) + HI_1 : REAL; (* Верхний предел 1. *) + HYS : REAL; (* Значение гистерезиса (берется как HYS/2 отностительно середины) *) +END_VAR +VAR_OUTPUT + Q1_LO : BOOL := FALSE; (* Флаг «значение меньше нижнего предела 1». *) + Q1_HI : BOOL := FALSE; (* Флаг «значение больше верхнего предела 1». *) +END_VAR +VAR + tmp: REAL; +END_VAR +(*----- IMPLEMENTATION -----*) +tmp := X - HYS * 0.5; +IF tmp > LO_1 THEN Q1_LO := FALSE; END_IF; +IF tmp > HI_1 THEN Q1_HI := TRUE; END_IF; +tmp := tmp + HYS; +IF tmp < LO_1 THEN Q1_LO := TRUE; END_IF; +IF tmp < HI_1 THEN Q1_HI := FALSE; END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/ALARM_2.st b/plc_src/POU/Общие/ALARM_2.st new file mode 100644 index 0000000..c7e70f4 --- /dev/null +++ b/plc_src/POU/Общие/ALARM_2.st @@ -0,0 +1,42 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : ALARM_2 + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK ALARM_2 +(* Функциональный блок проверяет входное значение X на принадлежность [LO_1…HI_1] [LO_2…HI_2] *) +VAR_INPUT + X : REAL; (* Контролируемое значение. *) + LO_1 : REAL; (* Нижний предел 1. *) + HI_1 : REAL; (* Верхний предел 1. *) + LO_2 : REAL; (* Нижний предел 2. *) + HI_2 : REAL; (* Верхний предел 2. *) + HYS : REAL; (* Значение гистерезиса (берется как HYS/2 отностительно середины) *) +END_VAR +VAR_OUTPUT + Q1_LO : BOOL := FALSE; (* Флаг «значение меньше нижнего предела 1». *) + Q1_HI : BOOL := FALSE; (* Флаг «значение больше верхнего предела 1». *) + Q2_LO : BOOL := FALSE; (* Флаг «значение меньше нижнего предела 2». *) + Q2_HI : BOOL := FALSE; (* Флаг «значение больше верхнего предела 2». *) +END_VAR +VAR + tmp: REAL; +END_VAR +(*----- IMPLEMENTATION -----*) +tmp := X - HYS * 0.5; +IF tmp > LO_1 THEN Q1_LO := FALSE; END_IF; +IF tmp > LO_2 THEN Q2_LO := FALSE; END_IF; +IF tmp > HI_1 THEN Q1_HI := TRUE; END_IF; +IF tmp > HI_2 THEN Q2_HI := TRUE; END_IF; +tmp := tmp + HYS; +IF tmp < LO_1 THEN Q1_LO := TRUE; END_IF; +IF tmp < LO_2 THEN Q2_LO := TRUE; END_IF; +IF tmp < HI_1 THEN Q1_HI := FALSE; END_IF; +IF tmp < HI_2 THEN Q2_HI := FALSE; END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/ALARM_3.st b/plc_src/POU/Общие/ALARM_3.st new file mode 100644 index 0000000..fbc1dd4 --- /dev/null +++ b/plc_src/POU/Общие/ALARM_3.st @@ -0,0 +1,50 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : ALARM_3 + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK ALARM_3 +(* Функциональный блок проверяет входное значение X на принадлежность [LO_1…HI_1] [LO_2…HI_2] [LO_3…HI_3] *) +VAR_INPUT + X : REAL; (* Контролируемое значение. *) + LO_1 : REAL; (* Нижний предел 1. *) + HI_1 : REAL; (* Верхний предел 1. *) + LO_2 : REAL; (* Нижний предел 2. *) + HI_2 : REAL; (* Верхний предел 2. *) + LO_3 : REAL; (* Нижний предел 3. *) + HI_3 : REAL; (* Верхний предел 3. *) + HYS : REAL; (* Значение гистерезиса (берется как HYS/2 отностительно середины) *) +END_VAR +VAR_OUTPUT + Q1_LO : BOOL := FALSE; (* Флаг «значение меньше нижнего предела 1». *) + Q1_HI : BOOL := FALSE; (* Флаг «значение больше верхнего предела 1». *) + Q2_LO : BOOL := FALSE; (* Флаг «значение меньше нижнего предела 2». *) + Q2_HI : BOOL := FALSE; (* Флаг «значение больше верхнего предела 2». *) + Q3_LO : BOOL := FALSE; (* Флаг «значение меньше нижнего предела 3». *) + Q3_HI : BOOL := FALSE; (* Флаг «значение больше верхнего предела 3». *) +END_VAR +VAR + tmp: REAL; +END_VAR +(*----- IMPLEMENTATION -----*) +tmp := X - HYS * 0.5; +IF tmp > LO_1 THEN Q1_LO := FALSE; END_IF; +IF tmp > LO_2 THEN Q2_LO := FALSE; END_IF; +IF tmp > LO_3 THEN Q3_LO := FALSE; END_IF; +IF tmp > HI_1 THEN Q1_HI := TRUE; END_IF; +IF tmp > HI_2 THEN Q2_HI := TRUE; END_IF; +IF tmp > HI_3 THEN Q3_HI := TRUE; END_IF; +tmp := tmp + HYS; +IF tmp < LO_1 THEN Q1_LO := TRUE; END_IF; +IF tmp < LO_2 THEN Q2_LO := TRUE; END_IF; +IF tmp < LO_3 THEN Q3_LO := TRUE; END_IF; +IF tmp < HI_1 THEN Q1_HI := FALSE; END_IF; +IF tmp < HI_2 THEN Q2_HI := FALSE; END_IF; +IF tmp < HI_3 THEN Q3_HI := FALSE; END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/BLINK_LMP.st b/plc_src/POU/Общие/BLINK_LMP.st new file mode 100644 index 0000000..fa6a7e2 --- /dev/null +++ b/plc_src/POU/Общие/BLINK_LMP.st @@ -0,0 +1,36 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : BLINK_LMP + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK BLINK_LMP +(* +Мигание лампами в завистмости от статуса +*) +VAR_INPUT + State: WORD:=0; (* 0 - выключено; 1 - включено; 2 - мигание с частотой 1; 3 - мигание с частотой 2; 4 - мигание с частотой 3; *) + Inp_BL_1: BOOL := FALSE; (* Пульсар миганич с частотой 1 *) + Inp_BL_2: BOOL := FALSE; (* Пульсар миганич с частотой 2 *) + Inp_BL_3: BOOL := FALSE; (* Пульсар миганич с частотой 3 *) +END_VAR +VAR_OUTPUT + LMP: BOOL; +END_VAR +VAR +END_VAR + +(*----- IMPLEMENTATION -----*) +CASE State OF +0: LMP:=FALSE; +1: LMP:=TRUE; +2: LMP:=Inp_BL_1; +3: LMP:=Inp_BL_2; +4: LMP:=Inp_BL_3; +END_CASE; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/BUZZER_CTRL.st b/plc_src/POU/Общие/BUZZER_CTRL.st new file mode 100644 index 0000000..373d017 --- /dev/null +++ b/plc_src/POU/Общие/BUZZER_CTRL.st @@ -0,0 +1,72 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : BUZZER_CTRL + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK BUZZER_CTRL +(* +Звучание звонком лампами в завистмости от статуса с возможностью отключения звука +*) +VAR_INPUT + State: WORD:=0; (* 0 - выключено; 1 - включено; 2 - мигание с частотой 1; 3 - мигание с частотой 2; 4 - мигание с частотой 3; *) + Sound_off: BOOL; (* Отключение звука *) + Inp_BL_1: BOOL := FALSE; (* Пульсар миганич с частотой 1 *) + Inp_BL_2: BOOL := FALSE; (* Пульсар миганич с частотой 2 *) + Inp_BL_3: BOOL := FALSE; (* Пульсар миганич с частотой 3 *) + PermanentOFF_Enable: BOOL := FALSE; (*Включение возможности отключить звук перманентно *) + T_PermanentOFF: TIME := T#10S; (* Время удержания кнопки отключения звука для перманентного отключения *) +END_VAR +VAR_OUTPUT + BUZZER: BOOL; +END_VAR +VAR + R_TRIG_0: R_TRIG; + R_TRIG_1: R_TRIG; + R_TRIG_2: R_TRIG; + R_TRIG_3: R_TRIG; + R_TRIG_4: R_TRIG; + B_ctrl: WORD; + + T_S_OFF: TON; + R_TRIG_S_OFF: R_TRIG; + AlltimeOff: BOOL; + TP1: TP:=(PT:=T#100MS); + R_TRIG1: R_TRIG; + F_TRIG1: F_TRIG; + F_TRIG2: F_TRIG; +END_VAR + +(*----- IMPLEMENTATION -----*) +R_TRIG_0(CLK:=(State=0)); IF R_TRIG_0.Q THEN B_ctrl:=0; END_IF; +R_TRIG_1(CLK:=(State=1)); IF R_TRIG_1.Q THEN B_ctrl:=1; END_IF; +R_TRIG_2(CLK:=(State=2)); IF R_TRIG_2.Q THEN B_ctrl:=2; END_IF; +R_TRIG_3(CLK:=(State=3)); IF R_TRIG_3.Q THEN B_ctrl:=3; END_IF; +R_TRIG_4(CLK:=(State=4)); IF R_TRIG_4.Q THEN B_ctrl:=4; END_IF; + +T_S_OFF(IN:=Sound_off, PT:=T_PermanentOFF); +R_TRIG_S_OFF(CLK:=Sound_off AND PermanentOFF_Enable); +R_TRIG1(CLK:=AlltimeOff); +F_TRIG1(CLK:=AlltimeOff); +TP1(IN:=R_TRIG1.Q OR F_TRIG1.Q); +F_TRIG2(CLK:=TP1.Q); +IF R_TRIG_S_OFF.Q THEN B_ctrl:=0; AlltimeOff:=0; END_IF +IF T_S_OFF.Q THEN AlltimeOff:=1; END_IF +IF AlltimeOff THEN B_ctrl:=0; END_IF; +IF TP1.Q THEN B_ctrl:=1; END_IF; +IF (F_TRIG2.Q OR F_TRIG1.Q) THEN B_ctrl:=0; END_IF; + + +CASE B_ctrl OF +0: BUZZER:=FALSE; +1: BUZZER:=TRUE; +2: BUZZER:=Inp_BL_1; +3: BUZZER:=Inp_BL_2; +4: BUZZER:=Inp_BL_3; +END_CASE; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/Button_acceleration.st b/plc_src/POU/Общие/Button_acceleration.st new file mode 100644 index 0000000..653dda1 --- /dev/null +++ b/plc_src/POU/Общие/Button_acceleration.st @@ -0,0 +1,59 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : Button_acceleration + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK Button_acceleration +(* Ускорение изменения значения при нажатой кнопке *) +VAR_INPUT + Button: BOOL; + D_0: REAL := 0; (* Прирост при одиночном нажатии *) + T_W_1: TIME := T#0s; (* Время кратковременного удержания *) + T_D_1: TIME := T#200ms; (* Шаг времени прироста при кратковременном удержании *) + D_1: REAL := 0; (* Прирост при кратковременном удержании *) + T_W_2: TIME := T#0S; (* Время длительного удержания *) + T_D_2: TIME := T#200MS; (* Шаг времени прироста при длительном удержании *) + D_2: REAL := 0; (* Прирост при длительном удержании *) + Zeroes : BOOL := false; (* При этом флаге происходит сброс в 0 при дадании -D_0 << D_0 *) +END_VAR +VAR_IN_OUT + Out: REAL; (* Вход-выход который изменяется *) +END_VAR +VAR + Ton_1: TON; + Ton_2: TON; + TOF_1: TOF; + TOF_2: TOF; + R_TRIG_0: R_TRIG; + R_TRIG_1: R_TRIG; + R_TRIG_2: R_TRIG; +END_VAR +VAR_OUTPUT +END_VAR +(*----- IMPLEMENTATION -----*) +Ton_1(PT := T_W_1,IN:=Button); +Ton_2(PT := T_W_2,IN:=Button); +TOF_1(PT := T_D_1,IN:=Ton_1.Q AND NOT(TOF_1.Q) AND NOT(Ton_2.Q)); +TOF_2(PT := T_D_2,IN:=Ton_2.Q AND NOT(TOF_2.Q)); +R_TRIG_0(CLK:=Button AND NOT(Ton_1.Q) AND NOT(Ton_2.Q)); +R_TRIG_1(CLK:=TOF_1.Q); +R_TRIG_2(CLK:=TOF_2.Q); +IF R_TRIG_0.Q THEN + Out:=Out+D_0; +END_IF +IF R_TRIG_1.Q THEN + Out:=Out+D_1; +END_IF +IF R_TRIG_2.Q THEN + Out:=Out+D_2; +END_IF +IF Zeroes AND ABS(Out) < ABS(D_0) THEN + Out:=0; +END_IF +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/CONFIRMATION.st b/plc_src/POU/Общие/CONFIRMATION.st new file mode 100644 index 0000000..aed234c --- /dev/null +++ b/plc_src/POU/Общие/CONFIRMATION.st @@ -0,0 +1,46 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : CONFIRMATION + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK CONFIRMATION +(* + Квитирование +*) +VAR_INPUT + Alarm: BOOL; // Авария + ControlState_On: BOOL; // Управление с текущего поста + Sound_Off: BOOL; // Сигнал снятия звука + Confirmation: BOOL; // Сигнал квитирования +END_VAR +VAR_OUTPUT + Lamp_Alarm_C: WORD; // Лампа аварии + Lamp_Confirmation_C: WORD; // Лампа квитирования + Buzzer_C: WORD; // Управление звонком +END_VAR +VAR + Alarm_Not_Sound_Off: BOOL; + Alarm_Not_Confirm: BOOL; + R_TR1: R_TRIG; + R_TR2: R_TRIG; + R_TR3: R_TRIG; +END_VAR + +(*----- IMPLEMENTATION -----*) +R_TR1(CLK:=Confirmation); IF R_TR1.Q THEN Alarm_Not_Confirm:=0; END_IF; +R_TR2(CLK:=Sound_Off); IF R_TR2.Q THEN Alarm_Not_Sound_Off:=0; END_IF; +R_TR3(CLK:=Alarm); IF R_TR3.Q AND ControlState_On THEN Alarm_Not_Confirm:=1; Alarm_Not_Sound_Off:=1; END_IF; +IF NOT ControlState_On THEN Alarm_Not_Confirm:=0; Alarm_Not_Sound_Off:=0; END_IF; + +IF NOT Alarm AND NOT Alarm_Not_Confirm THEN Lamp_Alarm_C:=0; Lamp_Confirmation_C:=0; END_IF; +IF Alarm AND NOT Alarm_Not_Confirm THEN Lamp_Alarm_C:=1; Lamp_Confirmation_C:=0; END_IF; +IF Alarm_Not_Confirm THEN Lamp_Alarm_C:=2; Lamp_Confirmation_C:=1; END_IF; + +IF Alarm_Not_Sound_Off THEN Buzzer_C:=2; ELSE Buzzer_C:=0; END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/HYST.st b/plc_src/POU/Общие/HYST.st new file mode 100644 index 0000000..e5eaca2 --- /dev/null +++ b/plc_src/POU/Общие/HYST.st @@ -0,0 +1,45 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : HYST + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK HYST +VAR_INPUT + In : REAL; + ON : REAL; + OFF : REAL; +END_VAR +VAR_OUTPUT + Q : BOOL; + win : BOOL; +END_VAR + +(*----- IMPLEMENTATION -----*) +IF ON >= OFF THEN + IF IN < OFF THEN + Q := FALSE; + WIN := FALSE; + ELSIF IN > ON THEN + Q := TRUE; + WIN := FALSE; + ELSE + WIN := TRUE; + END_IF; +ELSE + IF IN > OFF THEN + Q := FALSE; + WIN := FALSE; + ELSIF IN < ON THEN + Q := TRUE; + WIN := FALSE; + ELSE + WIN := TRUE; + END_IF; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/Norm_real.st b/plc_src/POU/Общие/Norm_real.st new file mode 100644 index 0000000..b623a64 --- /dev/null +++ b/plc_src/POU/Общие/Norm_real.st @@ -0,0 +1,27 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : Norm_real + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION Norm_real : REAL +(* + Нормирует значение аналогового входа X по двум заданным граничным точкам нормировки (X1,Y1) (X2,Y2) +*) +VAR_INPUT + X: REAL; + X1: REAL; + Y1: REAL; + X2: REAL; + Y2: REAL; +END_VAR + +(*----- IMPLEMENTATION -----*) +IF (X2-X1 = 0) THEN Norm_real:=Y1; RETURN; END_IF; +Norm_real:=(X-X1)*(Y2-Y1)/(X2-X1)+Y1; +END_FUNCTION diff --git a/plc_src/POU/Общие/PIDcontrol.st b/plc_src/POU/Общие/PIDcontrol.st new file mode 100644 index 0000000..d1dc81f --- /dev/null +++ b/plc_src/POU/Общие/PIDcontrol.st @@ -0,0 +1,125 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : PIDcontrol + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK PIDcontrol (* ПИД-регулятор. *) +VAR_INPUT + ProcessVariable: REAL := 0.0; (* Измеренное значение параметра *) + Setpoint: REAL := 0.0; (* Заданное значение *) + Kp: REAL := 0.001; (* Пропорциональный коэффициент *) + Ki: REAL := 0.001; (* Интегральный коэффициент *) + Kd: REAL := 0.0; (* Дифференциальный коэффициент *) + Kdf: REAL := 1.0; (* Коэффициент фильтрации (1/Tdf) *) + DBmax: REAL := 0.01; (* Верхняя граница зоны нечувствительности *) + DBmin: REAL := - 0.01; (* Нижняя граница зоны нечувствительности *) + OutMax: REAL := 100.0; (* Максимальный выход *) + OutMin: REAL := 0.0; (* Минимальный выход *) + Ts: REAL := 0.1; (* Время дискретизации [с] *) + Manual: REAL := 25.0; (* Значение в ручном режиме *) + ManOn: BOOL := FALSE; (* Флаг ручного режима *) + Reset: BOOL := FALSE; (* Сброс интегратора *) + AntiWindup: BOOL := TRUE; (* Включить антивиндап *) +END_VAR + +VAR_OUTPUT + Out: REAL := 0.0; (* Выходной сигнал *) + Err: REAL := 0.0; (* Ошибка регулирования *) + OutLimited: BOOL := FALSE; (* Флаг ограничения выхода *) +END_VAR + +VAR (* Внутренние переменные, сохраняемые. *) + Er: REAL := 0.0; (* Текущая ошибка *) + Ppart: REAL := 0.0; (* Пропорциональная часть *) + Ipart: REAL := 0.0; (* Интегральная часть *) + Dpart: REAL := 0.0; (* Дифференциальная часть *) + PrevEr: REAL := 0.0; (* Ошибка на предыдущем шаге (для дифференциатора). *) + FirstScan: BOOL := TRUE; (* Флаг первого сканирования. *) +END_VAR + +VAR_TEMP + Auto: REAL; (* Выход ПИД *) + Sw: REAL; (* Промежуточное значение *) +END_VAR +(*----- IMPLEMENTATION -----*) +(* === ИНИЦИАЛИЗАЦИЯ И СБРОС === *) +IF Reset OR FirstScan THEN + Ipart := 0.0; + Dpart := 0.0; + PrevEr := 0.0; + FirstScan := FALSE; +END_IF; + +(* === ПРОВЕРКА ВХОДНЫХ ПАРАМЕТРОВ === *) +IF (OutMin >= OutMax) OR (DBmin >= DBmax) OR (Ts <= 0) THEN + Out := 0.0; + OutLimited := TRUE; + RETURN; +END_IF; + +(* === РАСЧЕТ ОШИБКИ === *) +Er := Setpoint - ProcessVariable; +Err := Er; + +(* ЗОНА НЕЧУВСТВИТЕЛЬНОСТИ *) +IF (DBmin < Er) AND (Er < DBmax) THEN + Er := 0.0; +END_IF; + +(* === ПРОПОРЦИОНАЛЬНАЯ ЧАСТЬ === *) +Ppart := Kp * Er; + +(* === ИНТЕГРАЛЬНАЯ ЧАСТЬ === *) +IF Ki <> 0.0 THEN + Ipart := Ipart + (Ki * Er * Ts); + Ipart := LIMIT(OutMin, Ipart, OutMax); +ELSE + Ipart := 0.0; +END_IF; + +(* === ДИФФЕРЕНЦИАЛЬНАЯ ЧАСТЬ (фильтрованная) === *) +IF Kd <> 0.0 THEN + Dpart := Kd * Kdf * (Er - PrevEr) - Kdf * Dpart * Ts; + PrevEr := Er; (* Сохраняем ошибку для следующего шага *) +ELSE + Dpart := 0.0; +END_IF; + +(* === ВЫЧИСЛЕНИЕ ВЫХОДА === *) +Auto := Ppart + Ipart + Dpart; + +(* === РЕЖИМ РУЧНОЙ/АВТОМАТИЧЕСКИЙ === *) +IF ManOn THEN +Sw := Manual; + (* Сброс интегратора при переходе в ручной режим *) + IF AntiWindup THEN + Ipart := Sw - (Ppart + Dpart); + END_IF; +ELSE + Sw := Auto; +END_IF; + +(* === ОГРАНИЧЕНИЕ ВЫХОДА И АНТИВИНДАП === *) +IF Sw >= OutMax THEN + Out := OutMax; + OutLimited := TRUE; + IF AntiWindup AND NOT ManOn THEN + Ipart := OutMax - (Ppart + Dpart); + END_IF; +ELSIF Sw <= OutMin THEN + Out := OutMin; + OutLimited := TRUE; + IF AntiWindup AND NOT ManOn THEN + Ipart := OutMin - (Ppart + Dpart); + END_IF; +ELSE + Out := Sw; + OutLimited := FALSE; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/Pulse_Switch.st b/plc_src/POU/Общие/Pulse_Switch.st new file mode 100644 index 0000000..962040d --- /dev/null +++ b/plc_src/POU/Общие/Pulse_Switch.st @@ -0,0 +1,32 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : Pulse_Switch + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK Pulse_Switch +VAR + R_tr_1: R_TRIG; + F_tr_1: F_TRIG; + tp_on: TP:=(PT:=t#500ms); + tp_off: TP:=(PT:=t#500ms); +END_VAR +VAR_INPUT + Switch_ctrl: BOOL; +END_VAR +VAR_OUTPUT + Switch_on: BOOL; + Switch_off: BOOL; +END_VAR +(*----- IMPLEMENTATION -----*) +R_tr_1(CLK:=Switch_ctrl); +tp_on(IN:=R_tr_1.Q, Q=>Switch_on); + +F_tr_1(CLK:=Switch_ctrl); +tp_off(IN:=F_tr_1.Q, Q=>Switch_off); +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/STACK_I16.st b/plc_src/POU/Общие/STACK_I16.st new file mode 100644 index 0000000..e4e0f0e --- /dev/null +++ b/plc_src/POU/Общие/STACK_I16.st @@ -0,0 +1,55 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : STACK_I16 + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK STACK_I16 +(* стек на 16 элементов типа INT*) +VAR_INPUT + Din : INT; (* Переменная для записи в стек *) + E : BOOL := TRUE;(* Включение стека *) + POP : BOOL; (* Извлечение из стека *) + PUSH : BOOL; (* Запись в стек *) + RST : BOOL; (* Обнуление стека *) +END_VAR +VAR_OUTPUT + Dout : INT; + EMPTY : BOOL := TRUE; + FULL : BOOL; +END_VAR +VAR + stack: ARRAY[0..n] OF INT; + pt : INT; +END_VAR +VAR CONSTANT + n : INT := 15; (* changing this value will chage the number of stored elements in the fifo *) +END_VAR + +(*----- IMPLEMENTATION -----*) +IF RST THEN + (* asynchronous reset for the fifo *) + pt := 0; + EMPTY := TRUE; + FULL := FALSE; + Dout := 0; +ELSIF E THEN + IF NOT EMPTY AND POP THEN + pt := pt - 1; + Dout := stack[pt]; + EMPTY := pt = 0; + FULL := FALSE; + END_IF; + IF NOT FULL AND PUSH THEN + stack[pt] := Din; + pt := pt + 1; + FULL := pt > n; + EMPTY := FALSE; + END_IF; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/STATUS_ANALOG_M.st b/plc_src/POU/Общие/STATUS_ANALOG_M.st new file mode 100644 index 0000000..beaa0cd --- /dev/null +++ b/plc_src/POU/Общие/STATUS_ANALOG_M.st @@ -0,0 +1,99 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : STATUS_ANALOG_M + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK STATUS_ANALOG_M +(* + Модуль формирования статуса + Гистерезис по отключению и задержка по включению, вход типа REAL + + -1 - нет связи с датчиком + 0 - не определено + 1 - норма + 2 - предупредительный по нижнему уровню + 3 - аварийный по нижнему уровню + 4 - предупредительный по верхнему уровню, + 5 - аварийный по верхнему уровню +*) +VAR_INPUT + Input: REAL; + fail_hi: REAL:=3.402823466E+38; + delta_fail_hi: REAL := 0; + time_fail_hi: TIME :=t#0s; + warn_hi: REAL:=3.402823466E+38; + delta_warn_hi: REAL := 0; + time_warn_hi: TIME :=t#0s; + warn_low: REAL:=-3.402823466E+38; + delta_warn_low: REAL := 0; + time_warn_low: TIME :=t#0s; + fail_low: REAL:=-3.402823466E+38; + delta_fail_low: REAL := 0; + time_fail_low: TIME :=t#0s; + no_signal: REAL:=-3.402823466E+38; + delta_no_signal: REAL := 0; + time_no_signal: TIME :=T#0S; + + In_fail_hi: BOOL := FALSE; + In_warn_hi: BOOL := FALSE; + In_warn_low: BOOL := FALSE; + In_fail_low: BOOL := FALSE; + In_no_signal: BOOL := FALSE; +END_VAR +VAR_OUTPUT + Status: AnalogValueState; +END_VAR +VAR + fail_hi_st: BOOL; + warn_hi_st: BOOL; + normal_st: BOOL; + warn_low_st: BOOL; + fail_low_st: BOOL; + no_signal_st: BOOL; + TON_fail_hi: TON; + TON_warn_hi: TON; + TON_warn_low: TON; + TON_fail_low: TON; + TON_no_signal: TON; +END_VAR + +(*----- IMPLEMENTATION -----*) +TON_no_signal(IN := (Input <= no_signal) OR In_no_signal, PT := time_no_signal); +IF TON_no_signal.Q THEN no_signal_st := TRUE; ELSIF (Input > no_signal + delta_no_signal) THEN no_signal_st := FALSE; END_IF; + +TON_fail_low(IN := ((Input <= fail_low) AND NOT(no_signal_st)) OR In_fail_low, PT := time_fail_low); +IF TON_fail_low.Q THEN fail_low_st := TRUE; ELSIF (Input > fail_low + delta_fail_low) THEN fail_low_st := FALSE; END_IF; + +TON_warn_low(IN := ((Input <= warn_low) AND NOT(no_signal_st) AND NOT(TON_fail_low.Q)) OR In_warn_low, PT := time_warn_low); +IF TON_warn_low.Q THEN warn_low_st := TRUE; ELSIF (Input > warn_low + delta_warn_low) THEN warn_low_st := FALSE; END_IF; + +TON_fail_hi(IN := ((Input >= fail_hi) AND NOT(no_signal_st)) OR In_fail_hi, PT := time_fail_hi); +IF TON_fail_hi.Q THEN fail_hi_st := TRUE; ELSIF (Input < fail_hi - delta_fail_hi) THEN fail_hi_st := FALSE; END_IF; + +TON_warn_hi(IN := ((Input >= warn_hi) AND NOT(no_signal_st) AND NOT(TON_fail_hi.Q)) OR In_warn_hi, PT := time_warn_hi); +IF TON_warn_hi.Q THEN warn_hi_st := TRUE; ELSIF (Input < warn_hi - delta_warn_hi) THEN warn_hi_st := FALSE; END_IF; + +normal_st := (NOT(no_signal_st) AND NOT(fail_low_st) AND NOT(warn_low_st) AND NOT(warn_hi_st) AND NOT(fail_hi_st)); + +IF no_signal_st THEN + Status := AnalogValueState.no_signal; +ELSIF fail_hi_st THEN + Status := AnalogValueState.fail_hi; +ELSIF fail_low_st THEN + Status := AnalogValueState.fail_low; +ELSIF warn_hi_st THEN + Status := AnalogValueState.warn_hi; +ELSIF warn_low_st THEN + Status := AnalogValueState.warn_low; +ELSIF normal_st THEN + Status := AnalogValueState.normal; +ELSE + Status := AnalogValueState.undefined; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/STATUS_ANALOG_M_I.st b/plc_src/POU/Общие/STATUS_ANALOG_M_I.st new file mode 100644 index 0000000..d02d9a5 --- /dev/null +++ b/plc_src/POU/Общие/STATUS_ANALOG_M_I.st @@ -0,0 +1,102 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : STATUS_ANALOG_M_I + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK STATUS_ANALOG_M_I +(* + Модуль формирования статуса + Гистерезис по отключению и задержка по включению, вход типа INT + + -1 - нет связи с датчиком + 0 - не определено + 1 - норма + 2 - предупредительный по нижнему уровню + 3 - аварийный по нижнему уровню + 4 - предупредительный по верхнему уровню, + 5 - аварийный по верхнему уровню +*) +VAR_INPUT + Input: INT; + fail_hi: INT:=32767; + delta_fail_hi: INT := 0; + time_fail_hi: TIME :=t#0s; + warn_hi: INT:=32767; + delta_warn_hi: INT := 0; + time_warn_hi: TIME :=t#0s; + warn_low: INT:=-32768; + delta_warn_low: INT := 0; + time_warn_low: TIME :=t#0s; + fail_low: INT:=-32768; + delta_fail_low: INT := 0; + time_fail_low: TIME :=t#0s; + no_signal: INT:=-32768; + delta_no_signal: INT := 0; + time_no_signal: TIME :=T#0S; + + In_fail_hi: BOOL := FALSE; + In_warn_hi: BOOL := FALSE; + In_warn_low: BOOL := FALSE; + In_fail_low: BOOL := FALSE; + In_no_signal: BOOL := FALSE; +END_VAR +VAR_OUTPUT + Status: AnalogValueState; +END_VAR +VAR +(* !!! ВНУТРЕННИЕ ВЫХОДНЫЕ ПЕРЕМЕННЫЕ!!! *) + fail_hi_st: BOOL; + warn_hi_st: BOOL; + normal_st: BOOL; + warn_low_st: BOOL; + fail_low_st: BOOL; + no_signal_st: BOOL; + +(* !!! ВНУТРЕННИЕ НЕВЫХОДНЫЕ ПЕРЕМЕННЫЕ!!! *) + TON_fail_hi: TON; + TON_warn_hi: TON; + TON_warn_low: TON; + TON_fail_low: TON; + TON_no_signal: TON; +END_VAR + +(*----- IMPLEMENTATION -----*) +TON_no_signal(IN := (Input <= no_signal) OR In_no_signal, PT := time_no_signal); +IF TON_no_signal.Q THEN no_signal_st := TRUE; ELSIF (Input > no_signal + delta_no_signal) THEN no_signal_st := FALSE; END_IF; + +TON_fail_low(IN := ((Input <= fail_low) AND NOT(no_signal_st)) OR In_fail_low, PT := time_fail_low); +IF TON_fail_low.Q THEN fail_low_st := TRUE; ELSIF (Input > fail_low + delta_fail_low) THEN fail_low_st := FALSE; END_IF; + +TON_warn_low(IN := ((Input <= warn_low) AND NOT(no_signal_st) AND NOT(TON_fail_low.Q)) OR In_warn_low, PT := time_warn_low); +IF TON_warn_low.Q THEN warn_low_st := TRUE; ELSIF (Input > warn_low + delta_warn_low) THEN warn_low_st := FALSE; END_IF; + +TON_fail_hi(IN := ((Input >= fail_hi) AND NOT(no_signal_st)) OR In_fail_hi, PT := time_fail_hi); +IF TON_fail_hi.Q THEN fail_hi_st := TRUE; ELSIF (Input < fail_hi - delta_fail_hi) THEN fail_hi_st := FALSE; END_IF; + +TON_warn_hi(IN := ((Input >= warn_hi) AND NOT(no_signal_st) AND NOT(TON_fail_hi.Q)) OR In_warn_hi, PT := time_warn_hi); +IF TON_warn_hi.Q THEN warn_hi_st := TRUE; ELSIF (Input < warn_hi - delta_warn_hi) THEN warn_hi_st := FALSE; END_IF; + +normal_st := (NOT(no_signal_st) AND NOT(fail_low_st) AND NOT(warn_low_st) AND NOT(warn_hi_st) AND NOT(fail_hi_st)); + +IF no_signal_st THEN + Status := AnalogValueState.no_signal; +ELSIF fail_hi_st THEN + Status := AnalogValueState.fail_hi; +ELSIF fail_low_st THEN + Status := AnalogValueState.fail_low; +ELSIF warn_hi_st THEN + Status := AnalogValueState.warn_hi; +ELSIF warn_low_st THEN + Status := AnalogValueState.warn_low; +ELSIF normal_st THEN + Status := AnalogValueState.normal; +ELSE + Status := AnalogValueState.undefined; +END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/Status_Connect_Counter.st b/plc_src/POU/Общие/Status_Connect_Counter.st new file mode 100644 index 0000000..5792421 --- /dev/null +++ b/plc_src/POU/Общие/Status_Connect_Counter.st @@ -0,0 +1,39 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : Status_Connect_Counter + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK Status_Connect_Counter +(* Статус связи с СВУ по счетчику*) +VAR_INPUT + Counter: WORD; + T_Error: TIME; (* время для диагностирования ошибки*) +END_VAR +VAR_OUTPUT + CounterIsChanged: BOOL; + ERROR_CONNECT: BOOL; +END_VAR +VAR + CounterOld: WORD; + TON_SU: TON; +END_VAR + +(*----- IMPLEMENTATION -----*) +TON_SU(IN:=(Counter = CounterOld), PT:=T_Error); +ERROR_CONNECT:=TON_SU.Q; + +IF(Counter <> CounterOld) THEN + CounterIsChanged:=TRUE; +ELSE + CounterIsChanged:=FALSE; +END_IF +CounterOld:=Counter; + + +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/Status_SU_Connect.st b/plc_src/POU/Общие/Status_SU_Connect.st new file mode 100644 index 0000000..82670ab --- /dev/null +++ b/plc_src/POU/Общие/Status_SU_Connect.st @@ -0,0 +1,30 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : Status_SU_Connect + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK Status_SU_Connect +(* Статус связи с СВУ по серцебиению*) +VAR_INPUT + HeartBit: BOOL; + T_Pulse: TIME; +END_VAR +VAR_OUTPUT + ERROR_CONNECT: BOOL; +END_VAR +VAR + TON_SU_1: TON; + TON_SU_2: TON; +END_VAR + +(*----- IMPLEMENTATION -----*) +TON_SU_1(IN:=HeartBit, PT:=T_Pulse); +TON_SU_2(IN:=NOT HeartBit, PT:=T_Pulse); +ERROR_CONNECT:=TON_SU_1.Q OR TON_SU_2.Q; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/TOF_RST.st b/plc_src/POU/Общие/TOF_RST.st new file mode 100644 index 0000000..59cd0b2 --- /dev/null +++ b/plc_src/POU/Общие/TOF_RST.st @@ -0,0 +1,68 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : TOF_RST + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK TOF_RST +VAR_INPUT + IN: BOOL; // Управляющий сигнал + PT: TIME; // Время задержки выключения + RST: BOOL; // Принудительный сброс +END_VAR +VAR_OUTPUT + Q: BOOL; // Выход + ET: TIME; // Время работы таймера +END_VAR +VAR + _ton: TON; + prevIN: BOOL; // предыдущее состояние IN для детектирования фронта + timerRunning: BOOL; // TRUE — идёт отсчёт задержки +END_VAR +(*----- IMPLEMENTATION -----*) +// Принудительный сброс — наивысший приоритет +IF RST THEN + Q := FALSE; + timerRunning := FALSE; + prevIN := FALSE; // после RST не ловим задний фронт + _ton(IN := FALSE, PT := PT); + ET := _ton.ET; + RETURN; +END_IF + +// Передний фронт IN — отменяем задержку +IF IN AND NOT prevIN THEN + timerRunning := FALSE; +END_IF + +// Задний фронт IN — запускаем задержку +IF NOT IN AND prevIN THEN + timerRunning := TRUE; +END_IF + +prevIN := IN; + +// Запуск/работа таймера +_ton(IN := timerRunning, PT := PT); +ET := _ton.ET; + +// Логика выхода +IF IN THEN + Q := TRUE; + timerRunning := FALSE; +ELSIF timerRunning THEN + IF _ton.Q THEN + Q := FALSE; + timerRunning := FALSE; + ELSE + Q := TRUE; + END_IF +ELSE + Q := FALSE; +END_IF +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/TONOF.st b/plc_src/POU/Общие/TONOF.st new file mode 100644 index 0000000..ef73766 --- /dev/null +++ b/plc_src/POU/Общие/TONOF.st @@ -0,0 +1,32 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : TONOF + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK TONOF +(* +TONOF генерирует TON и TOF задержки для переменной IN +Задержка на вход T_ON, задержка на выход T_OFF +*) +VAR_INPUT + IN : BOOL; + T_ON : TIME := T#0s; (* Задержка на вход *) + T_OFF : TIME := T#0s; (* Задержка на выход *) +END_VAR +VAR_OUTPUT + Q : BOOL; +END_VAR +VAR + X : TON; +END_VAR + +(*----- IMPLEMENTATION -----*) +X(IN := IN XOR Q, PT := SEL(IN,T_OFF,T_ON)); +IF X.Q THEN Q := IN; END_IF; +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Общие/TP_RST.st b/plc_src/POU/Общие/TP_RST.st new file mode 100644 index 0000000..1153e43 --- /dev/null +++ b/plc_src/POU/Общие/TP_RST.st @@ -0,0 +1,60 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Общие + Name : TP_RST + Type : FUNCTION_BLOCK + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION_BLOCK TP_RST +VAR_INPUT + IN: BOOL; // Управляющий сигнал + PT: TIME; // Длительность импульса + RST: BOOL; // Принудительный сброс +END_VAR +VAR_OUTPUT + Q: BOOL; // Выход + ET: TIME; // Прошедшее время импульса +END_VAR +VAR + _ton: TON; + prevIN: BOOL; // Предыдущее состояние IN + timerRunning: BOOL; // TRUE — идёт отсчёт импульса +END_VAR +(*----- IMPLEMENTATION -----*) +// Принудительный сброс — наивысший приоритет +IF RST THEN + Q := FALSE; + timerRunning := FALSE; + prevIN := FALSE; + _ton(IN := FALSE, PT := PT); + ET := _ton.ET; + RETURN; +END_IF + +// Передний фронт IN — запускаем импульс (только если не идёт уже) +IF IN AND NOT prevIN AND NOT timerRunning THEN + timerRunning := TRUE; +END_IF + +prevIN := IN; + +// Таймер вызывается каждый цикл +_ton(IN := timerRunning, PT := PT); +ET := _ton.ET; + +// Логика выхода +IF timerRunning THEN + IF _ton.Q THEN + Q := FALSE; + timerRunning := FALSE; + ELSE + Q := TRUE; + END_IF +ELSE + Q := FALSE; +END_IF +END_FUNCTION_BLOCK diff --git a/plc_src/POU/Преобразование значений/DWORD_OF_2WORD.st b/plc_src/POU/Преобразование значений/DWORD_OF_2WORD.st new file mode 100644 index 0000000..70221f4 --- /dev/null +++ b/plc_src/POU/Преобразование значений/DWORD_OF_2WORD.st @@ -0,0 +1,20 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Преобразование значений + Name : DWORD_OF_2WORD + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION DWORD_OF_2WORD : DWORD +VAR_INPUT + W1 : WORD; + W0 : WORD; +END_VAR + +(*----- IMPLEMENTATION -----*) +DWORD_OF_2WORD := SHL(WORD_TO_DWORD(W1),16) OR WORD_TO_DWORD(W0); +END_FUNCTION diff --git a/plc_src/POU/Преобразование значений/DW_TO_REAL.st b/plc_src/POU/Преобразование значений/DW_TO_REAL.st new file mode 100644 index 0000000..f4c4004 --- /dev/null +++ b/plc_src/POU/Преобразование значений/DW_TO_REAL.st @@ -0,0 +1,23 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Преобразование значений + Name : DW_TO_REAL + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION DW_TO_REAL : REAL +VAR_INPUT + X : DWORD; +END_VAR +VAR + pt : POINTER TO REAL; +END_VAR + +(*----- IMPLEMENTATION -----*) +pt := ADR(X); +DW_TO_REAL := pt^; +END_FUNCTION diff --git a/plc_src/POU/Преобразование значений/NTC_termistor_T.st b/plc_src/POU/Преобразование значений/NTC_termistor_T.st new file mode 100644 index 0000000..85ca553 --- /dev/null +++ b/plc_src/POU/Преобразование значений/NTC_termistor_T.st @@ -0,0 +1,29 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Преобразование значений + Name : NTC_termistor_T + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION NTC_termistor_T : REAL +(* Перевод сопротивдления в температуру для NTC-type термистолров *) +VAR_INPUT + Res: REAL; (* Значение сопротивления *) + R0: REAL; (* Сопротивление при T0 *) + T0: REAL; (* T0 *) + b: REAL; (* Beta *) +END_VAR +VAR +END_VAR + +(*----- IMPLEMENTATION -----*) +IF (Res <= REAL#0.0) OR (R0 <= REAL#0.0) OR (b = REAL#0.0) THEN + NTC_termistor_T := REAL#0.0; + RETURN; +END_IF; +NTC_termistor_T := (1.0 / ((1.0 / (T0 + 273.15)) + (1.0 / b) * LN(Res / R0))) - 273.15; +END_FUNCTION diff --git a/plc_src/POU/Преобразование значений/REAL_TO_DW.st b/plc_src/POU/Преобразование значений/REAL_TO_DW.st new file mode 100644 index 0000000..8da5ae1 --- /dev/null +++ b/plc_src/POU/Преобразование значений/REAL_TO_DW.st @@ -0,0 +1,23 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Преобразование значений + Name : REAL_TO_DW + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION REAL_TO_DW : DWORD +VAR_INPUT + X : REAL; +END_VAR +VAR + pt : POINTER TO DWORD; +END_VAR + +(*----- IMPLEMENTATION -----*) +pt := ADR(X); +REAL_TO_DW := pt^; +END_FUNCTION diff --git a/plc_src/POU/Преобразование значений/WORD_OF_DWORD.st b/plc_src/POU/Преобразование значений/WORD_OF_DWORD.st new file mode 100644 index 0000000..b9bb732 --- /dev/null +++ b/plc_src/POU/Преобразование значений/WORD_OF_DWORD.st @@ -0,0 +1,20 @@ +(* +=============================================================================== + Project : PLC_Library + Device : - + Path : Преобразование значений + Name : WORD_OF_DWORD + Type : FUNCTION + Exported : 2026-07-31 11:16:16 +=============================================================================== +*) + +FUNCTION WORD_OF_DWORD : WORD +VAR_INPUT + in : DWORD; + N : BYTE; +END_VAR + +(*----- IMPLEMENTATION -----*) +WORD_OF_DWORD := DWORD_TO_WORD(SHR(in,SHL(n,4))); +END_FUNCTION diff --git a/Скрипты/export_plc_src.py b/Скрипты/export_plc_src.py index e62299b..6221104 100644 --- a/Скрипты/export_plc_src.py +++ b/Скрипты/export_plc_src.py @@ -1,7 +1,22 @@ # encoding:utf-8 # export_plc_src.py # Экспорт POU / GVL / DUT из открытого проекта CODESYS в текстовые файлы -# для хранения в git и работы с Claude Code. +# plc_src/ +# ├── PLC1/ +# │ ├── POU/... +# │ ├── GVL/... +# │ └── DUT/... +# ├── PLC2/ +# │ ├── POU/... +# │ ├── GVL/... +# │ └── DUT/... +# └── _common/ +# │ ├── POU/... +# │ ├── GVL/... +# │ └── DUT/.. +# +# Методы (METHOD) и действия (ACTION) не создают отдельных файлов -- +# они дописываются в конец файла того POU/FB, в котором были созданы # # Запуск: # 1) Внутри CODESYS: Tools -> Scripting -> Execute Script File -> выбрать этот файл @@ -10,8 +25,6 @@ # --Profile="CODESYS V3.5 SP17 Patch 3" --runscript="export_plc_src.py" ^ # --project="D:\path\to\ТвойПроект.project" # -# Проверено для установки: CODESYS V3.5 SP17 Patch 3 (32-bit) -# # По умолчанию папка plc_src создаётся автоматически рядом с файлом .project. # Если авто-определение не сработает, впиши путь вручную в MANUAL_OUTPUT_ROOT ниже. @@ -19,9 +32,31 @@ from __future__ import print_function import os import codecs import re +import datetime PRAGMA_RE = re.compile(r"^\s*\{[^}]*\}\s*") +# Единая метка времени на весь запуск экспорта (одинаковая во всех файлах). +EXPORT_TIMESTAMP = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") + +# Имена служебных узлов дерева, которые не нужно показывать в поле Path +# (сравнение без учёта регистра). Сами узлы всё равно обходятся -- пропускаются +# только их имена при формировании пути. +EXCLUDED_PATH_NAMES = set(["application", "plc logic"]) + +# Закрывающие ключевые слова, которые CODESYS не включает ни в textual_declaration, +# ни в textual_implementation -- их нужно дописывать вручную, чтобы файл был +# синтаксически полным ST-блоком. +END_KEYWORDS = { + "PROGRAM": "END_PROGRAM", + "FUNCTION_BLOCK": "END_FUNCTION_BLOCK", + "FUNCTION": "END_FUNCTION", + "INTERFACE": "END_INTERFACE", + "METHOD": "END_METHOD", + "PROPERTY": "END_PROPERTY", + "ACTION": "END_ACTION", +} + def strip_leading_pragmas(text): """Убирает ведущие блоки вида {attribute 'qualified_only'} перед проверкой ключевого слова.""" @@ -43,8 +78,6 @@ def get_project_file_path(proj): return val except Exception: pass - # Фолбэк: вытащить путь из текстового представления объекта, - # например "Project(Project=0, stPath=C:\...\*.project)" try: s = str(proj) m = re.search(r"stPath=(.*?)\)", s) @@ -71,37 +104,148 @@ def get_declaration_text(obj): return None -def get_text(obj, declaration_text): - """Собирает декларацию + реализацию (если есть) в единый текст.""" - text = declaration_text or "" +def has_implementation(obj): try: - if hasattr(obj, "has_textual_implementation") and obj.has_textual_implementation: - text += "\r\n(*----- IMPLEMENTATION -----*)\r\n" - text += obj.textual_implementation.text + return bool(getattr(obj, "has_textual_implementation", False)) + except Exception: + return False + + +def get_implementation_text(obj): + try: + return obj.textual_implementation.text except Exception as e: - text += "(* ошибка чтения implementation: %s *)\r\n" % e - return text + return "(* ошибка чтения implementation: %s *)\r\n" % e -def classify(declaration_text): - """Определяет тип объекта по первому ключевому слову декларации.""" - if not declaration_text: +def classify(obj, declaration_text): + """Определяет тип объекта. + Если есть декларация -- смотрим по ключевому слову (POU/GVL/DUT/METHOD/ACTION). + Если декларации нет, но есть implementation -- это ACTION (у него нет декларации в API).""" + if declaration_text: + stripped = strip_leading_pragmas(declaration_text.strip()).strip().upper() + if stripped.startswith(("PROGRAM", "FUNCTION_BLOCK", "FUNCTION", "INTERFACE")): + return "pou" + if stripped.startswith(("METHOD", "PROPERTY")): + return "method" + if stripped.startswith("VAR_GLOBAL"): + return "gvl" + if stripped.startswith("TYPE"): + return "dut" return None - stripped = strip_leading_pragmas(declaration_text.strip()).strip().upper() - if stripped.startswith(("PROGRAM", "FUNCTION_BLOCK", "FUNCTION", "INTERFACE", "METHOD", "ACTION", "PROPERTY")): - return "pou" - if stripped.startswith("VAR_GLOBAL"): - return "gvl" - if stripped.startswith("TYPE"): - return "dut" + + if has_implementation(obj): + return "action" + return None -def write_file(folder, name, text): - path = os.path.join(folder, name + ".st") +def get_type_label(kind, declaration_text): + """Возвращает читаемое название типа объекта, например 'FUNCTION_BLOCK', 'METHOD', 'ACTION'.""" + if kind == "action": + return "ACTION" + if declaration_text: + stripped = strip_leading_pragmas(declaration_text.strip()).strip().upper() + for kw in ("FUNCTION_BLOCK", "PROGRAM", "FUNCTION", "INTERFACE", "METHOD", "PROPERTY"): + if stripped.startswith(kw): + return kw + if stripped.startswith("VAR_GLOBAL"): + return "GVL" + if stripped.startswith("TYPE"): + return "DUT" + return kind.upper() if kind else "UNKNOWN" + + +def close_block(text, type_label): + """Дописывает END_PROGRAM / END_FUNCTION_BLOCK / END_METHOD / END_ACTION и т.п., + если это применимо к данному типу объекта.""" + kw = END_KEYWORDS.get(type_label) + if not kw: + return text + if not text.endswith("\n"): + text += "\r\n" + text += kw + "\r\n" + return text + + +def build_pou_text(obj, declaration_text, type_label): + """Декларация + implementation (если есть) + закрывающее END_*.""" + text = declaration_text or "" + if has_implementation(obj): + text += "\r\n(*----- IMPLEMENTATION -----*)\r\n" + text += get_implementation_text(obj) + text = close_block(text, type_label) + return text + + +def build_action_text(obj, name): + """У Action нет declaration в API -- собираем заголовок 'ACTION Имя :' вручную, + затем тело и закрывающее END_ACTION.""" + text = "ACTION %s :\r\n" % name + text += get_implementation_text(obj) + text = close_block(text, "ACTION") + return text + + +def build_header(project_name, device_name, path_parts, obj_name, type_label): + """Заголовок-паспорт файла: + + (* + =============================================================================== + Project : MyProject + Device : PLC + Path : Application/Logic/Motion + Name : FB_Axis + Type : FUNCTION_BLOCK + Exported : 2026-07-30 14:32:18 + =============================================================================== + *) + """ + rel_path = "/".join(path_parts) if path_parts else "-" + sep = "=" * 79 + lines = [ + "(*", + sep, + " Project : %s" % project_name, + " Device : %s" % device_name, + " Path : %s" % rel_path, + " Name : %s" % obj_name, + " Type : %s" % type_label, + " Exported : %s" % EXPORT_TIMESTAMP, + sep, + "*)", + "", + "", + ] + return "\r\n".join(lines) + + +def write_new_file(folder, path_parts, obj_name, text, project_name, device_name, type_label): + """path_parts -- путь БЕЗ имени самого объекта (папки родителей). + Создаёт вложенные папки и записывает файл с заголовком-паспортом.""" + sub_parts = [sanitize_folder_name(p) for p in path_parts] + sub_folder = os.path.join(folder, *sub_parts) if sub_parts else folder + if not os.path.exists(sub_folder): + os.makedirs(sub_folder) + file_name = sanitize_folder_name(obj_name) + path = os.path.join(sub_folder, file_name + ".st") with codecs.open(path, "w", encoding="utf-8") as f: + f.write(build_header(project_name, device_name, path_parts, obj_name, type_label)) f.write(text) print(" -> %s" % path) + return path + + +def append_to_file(path, path_parts, obj_name, text, project_name, device_name, type_label): + """path_parts здесь включает имя родительского POU (т.к. метод/action лежит "внутри" него).""" + if not path or not os.path.exists(path): + return False + with codecs.open(path, "a", encoding="utf-8") as f: + f.write("\r\n") + f.write(build_header(project_name, device_name, path_parts, obj_name, type_label)) + f.write(text) + print(" -> дописано в %s (%s: %s)" % (path, type_label, obj_name)) + return True def sanitize_folder_name(name): @@ -132,34 +276,78 @@ def is_device_node(obj): return False -def walk(node, dirs, exported): +def walk(node, dirs, exported, project_name, device_name, current_path=None, parent_pou_path=None): + """current_path -- список имён родителей от корня устройства (без учёта device_name самого), + накапливается по мере рекурсивного спуска по реальному дереву проекта -- поэтому Path + строится из фактической структуры, а не из строки, которую отдаёт CODESYS API.""" + if current_path is None: + current_path = [] + for child in node.get_children(False): name = safe_name(child) decl = get_declaration_text(child) - kind = classify(decl) + kind = classify(child, decl) + type_label = get_type_label(kind, decl) + + next_parent_path = parent_pou_path + if name.strip().lower() in EXCLUDED_PATH_NAMES: + child_path = current_path # не добавляем служебное имя в путь + else: + child_path = current_path + [name] # путь для рекурсии в детей ЭТОГО child'а if kind == "pou": - print("POU: %s" % name) - write_file(dirs["pou"], name, get_text(child, decl)) + print("POU: %s/%s" % ("/".join(current_path), name)) + text = build_pou_text(child, decl, type_label) + path = write_new_file(dirs["pou"], current_path, name, text, + project_name, device_name, type_label) exported["pou"] += 1 + next_parent_path = path + + elif kind == "method": + print("METHOD: %s/%s" % ("/".join(current_path), name)) + text = build_pou_text(child, decl, type_label) + if parent_pou_path and append_to_file(parent_pou_path, current_path, name, text, + project_name, device_name, type_label): + exported["method"] += 1 + else: + write_new_file(dirs["pou"], current_path, name, text, + project_name, device_name, type_label) + exported["method"] += 1 + + elif kind == "action": + print("ACTION: %s/%s" % ("/".join(current_path), name)) + text = build_action_text(child, name) + if parent_pou_path and append_to_file(parent_pou_path, current_path, name, text, + project_name, device_name, type_label): + exported["action"] += 1 + else: + write_new_file(dirs["pou"], current_path, name, text, + project_name, device_name, type_label) + exported["action"] += 1 + elif kind == "gvl": - print("GVL: %s" % name) - write_file(dirs["gvl"], name, get_text(child, decl)) + print("GVL: %s/%s" % ("/".join(current_path), name)) + write_new_file(dirs["gvl"], current_path, name, decl or "", + project_name, device_name, type_label) exported["gvl"] += 1 + elif kind == "dut": - print("DUT: %s" % name) - write_file(dirs["dut"], name, get_text(child, decl)) + print("DUT: %s/%s" % ("/".join(current_path), name)) + write_new_file(dirs["dut"], current_path, name, decl or "", + project_name, device_name, type_label) exported["dut"] += 1 + else: exported["skipped"] += 1 - # рекурсивно обходим детей (для FB с методами, папок и т.д.) - walk(child, dirs, exported) + # рекурсивно обходим детей (методы/actions FB, вложенные папки и т.д.), + # передавая накопленный путь и (если только что создан POU) новый parent_pou_path + walk(child, dirs, exported, project_name, device_name, child_path, next_parent_path) # ==== НАСТРОЙКА ==== -# Оставь пустой строкой "" чтобы папка plc_src создавалась автоматически -# рядом с файлом .project. Укажи путь вручную только если авто-определение +# Оставить пустой строкой "" чтобы папка plc_src создавалась автоматически +# рядом с файлом .project. Указать путь вручную только если авто-определение # не сработает (см. вывод скрипта при запуске). MANUAL_OUTPUT_ROOT = r"" # ===================================================== @@ -169,11 +357,17 @@ proj = projects.primary if proj is None: print("ОШИБКА: нет открытого проекта. Открой .project перед запуском скрипта.") else: + project_file = get_project_file_path(proj) + + if project_file: + project_name = os.path.splitext(os.path.basename(project_file))[0] + else: + project_name = "UnknownProject" + if MANUAL_OUTPUT_ROOT: OUTPUT_ROOT = MANUAL_OUTPUT_ROOT print("Папка вывода задана вручную: %s" % OUTPUT_ROOT) else: - project_file = get_project_file_path(proj) if project_file: project_dir = os.path.dirname(project_file) OUTPUT_ROOT = os.path.join(project_dir, "plc_src") @@ -185,31 +379,31 @@ else: print("Задай путь вручную в переменной MANUAL_OUTPUT_ROOT и запусти снова.") if OUTPUT_ROOT: - exported = {"pou": 0, "gvl": 0, "dut": 0, "skipped": 0} + exported = {"pou": 0, "gvl": 0, "dut": 0, "method": 0, "action": 0, "skipped": 0} - print("--- Экспорт в %s ---" % OUTPUT_ROOT) + print("--- Экспорт проекта '%s' в %s ---" % (project_name, OUTPUT_ROOT)) top_children = proj.get_children(False) device_nodes = [c for c in top_children if is_device_node(c)] if device_nodes: for device in device_nodes: - dev_name = sanitize_folder_name(safe_name(device)) - print("=== Устройство: %s ===" % dev_name) - dev_dirs = make_dirs(os.path.join(OUTPUT_ROOT, dev_name)) - walk(device, dev_dirs, exported) + device_name = safe_name(device) + dev_folder = sanitize_folder_name(device_name) + print("=== Устройство: %s ===" % device_name) + dev_dirs = make_dirs(os.path.join(OUTPUT_ROOT, dev_folder)) + walk(device, dev_dirs, exported, project_name, device_name) - # на случай текстовых объектов вне дерева устройств (редко, но на всякий случай) other_dirs = make_dirs(os.path.join(OUTPUT_ROOT, "_common")) for child in top_children: if not is_device_node(child): - walk(child, other_dirs, exported) + walk(child, other_dirs, exported, project_name, "-") else: - # устройств не нашлось (или is_device не сработал) -- работаем как раньше, без разбивки print("Устройства верхнего уровня не найдены, экспорт без разбивки по папкам.") dirs = make_dirs(OUTPUT_ROOT) - walk(proj, dirs, exported) + walk(proj, dirs, exported, project_name, "-") - print("--- Готово: POU=%d, GVL=%d, DUT=%d, пропущено=%d ---" % ( - exported["pou"], exported["gvl"], exported["dut"], exported["skipped"] + print("--- Готово: POU=%d, METHOD=%d, ACTION=%d, GVL=%d, DUT=%d, пропущено=%d ---" % ( + exported["pou"], exported["method"], exported["action"], + exported["gvl"], exported["dut"], exported["skipped"] ))