PDA

Просмотр полной версии : Библиотека Util.lib - ошибка в коде ФБ частотомера FREQ_MEASURE



ленивый
09.05.2020, 22:07
В проекте необходимо измерить частоту импульсных сигналов на дискретных входах ПЛК.
Частоты 10-50 Гц (период 20-100 мс) и 0.04-0.5 Гц (2-25 с).
Использовал простой ФБ FREQ_MEASURE (да, в курсе, что есть решения гораздо лучше, но в первом приближении и это устраивало).

Описание ФБ в мануале "Руководство пользователя по программированию ПЛК в Codesys 2.3", Приложение D --> Библиотека UTIL.LIB --> Генераторы сигналов --> FREQ_MEASURE.

Обратил внимание, что выходной флаг VALID, отвечающий за "достоверность" измеренной частоты, для сигнала 10-50 Гц ведет себя примерно так, как описано: при наличии сигнала поднимается после заполнения массива усреднения, а при при исчезновении сигнала - сбрасывается.
А вот для низкочастотных сигналов этот флаг практически всегда сброшен, VALID = FALSE.

Полез в код ФБ (несущественные фрагменты опущены).


FUNCTION_BLOCK FREQ_MEASURE
(* FB to measure the frequency of a signal *)
VAR_INPUT
IN:BOOL; (* input signal *)
PERIODS: INT (1..10) :=1; (* out is the average frequency during PERIODS (number of periods) *)
RESET: BOOL; (* reset measurement *)
END_VAR
VAR_OUTPUT
OUT:REAL; (* frequency [Hz]*)
VALID:BOOL; (* FALSE: not yet PERIODS measurements done OR time distance between two rising edges > 3*OUT *)
END_VAR

(* ====== исполняемый код =========*)

IF RESET THEN
(* код инициализации *)
END_IF

IF IN AND NOT OLDIN THEN (*rising edge *)

(* код вычисления усредненной частоты*)

ELSIF INIT AND VALID AND TIME_TO_DWORD(TIME()-OLDT) > 3000*OUT THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF

OLDIN:=IN;
По смыслу флаг VALID должен сбрасываться, когда на входном сигнале нет импульсов в течение 3 последних измеренных периодов.
Но OUT - это измеренная ЧАСТОТА, поэтому 3 периода (в миллисекундах) есть 3000/OUT, а не 3000*OUT, как в коде.

Это явный баг. Чем выше частота, тем дольше будет интервал ожидание сигнала, хотя должно быть ровно наоборот.
Причем даже описание флага неверно ни в комментарии ФБ, ни в мануале по Codesys.

Исправленный код (без декларации функции и описания перемененных)


IF RESET THEN
(* код инициализации *)
END_IF

IF IN AND NOT OLDIN THEN (*rising edge *)

(* код вычисления усредненной частоты*)

ELSIF INIT AND VALID THEN
IF OUT <> 0 THEN
IF TIME_TO_DWORD(TIME()-OLDT) > 3000/OUT THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF
END_IF
END_IF

OLDIN:=IN;


Если IF работает в ST, как в паскале или си (и я не знаю, так ли это), то проверку частоты на 0 можно не выносить в отдельный блок, а сразу написать


IF OUT <> 0 AND TIME_TO_DWORD(TIME()-OLDT) > 3000/OUT THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF

Ну и еще. При резком уменьшении частоты даже исправленный ФБ будет работать не очень хорошо: флаг VALID будет сбрасываться, пока не придет новый импульс. Например, частота изменяется скачком с 0.5 Гц (2 с) до 0.1 Гц (10 с) - после 6 с ожидания флаг сбросится и 4 с будет в таком состоянии ждать нового импульса. На высоких частотах лаг, естественно, будет меньше. Так вот, для себя сделал еще одну вещь: завел в ФБ дополнительный параметр - интервал ожидания импульса, не зависящий от частоты. Если я знаю примерный диапазон частот, то задав этот интервал, равный, скажем, 2 или 3 максимальным периодам, получу, что флаг VALID не упадет при резком изменении частоты входного сигнала, а свалится только когда импульса не будет в течении заданного интервала. Если же взять интервал равный нулю, то ФБ будет работать как обычно - по тройному интервалу ожидания, зависящему от частоты. Если нужно, выложу код.

Вообще, прошу проверить все эти выкладки, а то может и накосячил где. :confused:

ленивый
10.05.2020, 17:30
Вообще лишнее
Ух ты! Что, прямо так в рантайме делить на 0 можно?? Без последствий?
Если да, то что получится: 3000 или 0xFFFFFFFF?

И да, как к специалисту, вопрос: IF будет вычислять все свои условия или до результата (на который остальные уже не влияют)?



Как раз хорошо.
Это кому как.
Если на какой-нибудь панели или в Скаде на флажок повешена проверка частоты с генерацией аларма, то будет не гут: это же не сигнал исчез, а просто частота изменилась, а у нас аларм: "хватай мешки, вокзал отходит".

ленивый
10.05.2020, 18:29
Спасательный круг посреди пустыни самое оно.
Мда. "Все люди делятся на два сорта: у одних есть заряженный револьвер, а другие копают."
ОК, буду копать.

PS. Я понял про проверку OUT на 0. Действительно, не нужна. Заходят в ветку, когда уже не 0.



А откуда Вы узнали что частота изменилась ДО того как пришел сигнал ? Вы руками щупаете/глазами видите/??
Да нет же!
Речь шла про еще одну доработку ФБ.
Когда известен диапазон частот входного сигнала.
И я знаю максимально возможный период.
И буду ждать этот сигнал не больше чем этот период. Или немного больше. Сколько задам.
И флаг не будет падать.

А если частота сигнала заранее неизвестна, то поставлю в этом параметре ФБ 0 и он будет работать как обычно.


N. Турбина улетела к еб..ям.
Это уже фэйл на этапе проектирования - вешать такой критичный параметр на низкочастотный сигнал.


Лучше я выложу уже код, а Вы его раскритикуете. :)



FUNCTION_BLOCK FREQ_MEASURE_EX
(* FB to measure the frequency of a signal *)
VAR_INPUT
IN:BOOL; (* input signal *)
PERIODS: INT (1..10) :=1; (* out is the average frequency during PERIODS (number of periods) *)
RESET: BOOL; (* reset measurement *)
INTERVAL: TIME := T#0s; (* absolute interval of signal inactivity *)
END_VAR
VAR_OUTPUT
OUT:REAL; (* frequency [Hz]*)
VALID:BOOL; (* FALSE: not yet PERIODS measurements done OR time distance between two rising edges > 3*OUT *)
END_VAR
VAR
OLDIN: BOOL;
INIT: BOOL;
OLDT: TIME;
DIFF: DWORD;
ADIFF: ARRAY[0..9] OF DWORD;
V: INT;
B:INT;
I: INT;
INACTIVEINTERVAL: DWORD;
END_VAR


IF RESET THEN
B:=0;
V:=0;
INIT:=FALSE;
VALID:=FALSE;
OUT:=0;
INACTIVEINTERVAL := TIME_TO_DWORD(INTERVAL);
RETURN;
END_IF

IF IN AND NOT OLDIN THEN (*rising edge *)
IF INIT THEN
DIFF := TIME_TO_DWORD(TIME()-OLDT);
IF Diff > 0 THEN
ADIFF[B] := DIFF;
B := (B+1) MOD PERIODS;
V:= MIN(V+1, PERIODS);
IF V=PERIODS THEN
OUT := 0;
FOR I:=0 TO PERIODS-1 DO
OUT := OUT + ADIFF[I];
END_FOR
OUT := 1000.0 * PERIODS / OUT;
VALID:=TRUE;
ELSE
VALID:=FALSE;
END_IF
END_IF
END_IF
INIT := TRUE;
OLDT := TIME();
ELSIF INIT AND VALID THEN
IF INACTIVEINTERVAL <> 0 THEN
IF TIME_TO_DWORD(TIME()-OLDT) > INACTIVEINTERVAL THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF
ELSIF TIME_TO_DWORD(TIME()-OLDT) > 3000 / OUT THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF
END_IF

OLDIN:=IN;

capzap
10.05.2020, 21:10
PS
Напоминаю : За "/OUT" - устная благодарность. И у немцев бывает октоберфест. Да и май сложный.

стоп стоп, это если код менять а пока что условие начнет выполнятся если есть приращение Diff а оно в свою очередь присвоится ADIFF[B] которая в свою очередь суммируется в OUT и оно никогда не будет равно нулю

ленивый
10.05.2020, 21:15
Хорошо.

Переименовал ФБ в FREQ_MEASURE_EX.
Кто захочет использовать - добавит его в Util.lib сам.


По ключевым вопросам расхождений нет.
Благодарю за разъяснения и вообще.



Финальные варианты кодов.

Исправленный код ФБ FREQ_MEASURE.
Для использования открыть библиотеку Util.lib, внести исправления в код ФБ FREQ_MEASURE, перекомпилировать библиотеку (F11).


FUNCTION_BLOCK FREQ_MEASURE
(* FB to measure the frequency of a signal *)
VAR_INPUT
IN:BOOL; (* input signal *)
PERIODS: INT (1..10) :=1; (* out is the average frequency during PERIODS (number of periods) *)
RESET: BOOL; (* reset measurement *)
END_VAR
VAR_OUTPUT
OUT:REAL; (* frequency [Hz]*)
VALID:BOOL; (* FALSE: not yet PERIODS measurements done OR time distance between two rising edges > 3/OUT *)
END_VAR
VAR
OLDIN: BOOL;
INIT: BOOL;
OLDT: TIME;
DIFF: DWORD;
ADIFF: ARRAY[0..9] OF DWORD;
V: INT;
B:INT;
I: INT;
END_VAR


IF RESET THEN
B:=0;
V:=0;
INIT:=FALSE;
VALID:=FALSE;
OUT:=0;
RETURN;
END_IF

IF IN AND NOT OLDIN THEN (*rising edge *)
IF INIT THEN
DIFF := TIME_TO_DWORD(TIME()-OLDT);
IF Diff > 0 THEN
ADIFF[B] := DIFF;
B := (B+1) MOD PERIODS;
V:= MIN(V+1, PERIODS);
IF V=PERIODS THEN
OUT := 0;
FOR I:=0 TO PERIODS-1 DO
OUT := OUT + ADIFF[I];
END_FOR
OUT := 1000.0 * PERIODS / OUT;
VALID:=TRUE;
ELSE
VALID:=FALSE;
END_IF
END_IF
END_IF
INIT := TRUE;
OLDT := TIME();
ELSIF INIT AND VALID AND TIME_TO_DWORD(TIME()-OLDT) > 3000 / OUT THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF

OLDIN:=IN;



Код ФБ FREQ_MEASURE_EX с расширенными настройками.
Для использования открыть библиотеку Util.lib, добавить в POU (в папку Signals) новый ФБ FREQ_MEASURE_EX, внести в окно объявления ФБ переменные, внести в окно кода ФБ текст исполняемого кода, перекомпилировать библиотеку (F11).


(* ===== объявление переменных ФБ =====*)

FUNCTION_BLOCK FREQ_MEASURE_EX
(* FB to measure the frequency of a signal *)
VAR_INPUT
IN:BOOL; (* input signal *)
PERIODS: INT (1..10) :=1; (* out is the average frequency during PERIODS (number of periods) *)
RESET: BOOL; (* reset measurement *)
INTERVAL: TIME := T#0s; (* absolute interval of signal inactivity *)
END_VAR
VAR_OUTPUT
OUT:REAL; (* frequency [Hz]*)
VALID:BOOL; (* FALSE: not yet PERIODS measurements done *)
(* OR time distance between two rising edges > INTERVAL if INTERVAL > 0*)
(* OR time distance between two rising edges > 3s / OUT if INTERVAL = 0*)
END_VAR
VAR
OLDIN: BOOL;
INIT: BOOL;
OLDT: TIME;
DIFF: DWORD;
ADIFF: ARRAY[0..9] OF DWORD;
V: INT;
B:INT;
I: INT;
INACTIVEINTERVAL: DWORD;
END_VAR


(* ===== исполняемый код ФБ =====*)

IF RESET THEN
B:=0;
V:=0;
INIT:=FALSE;
VALID:=FALSE;
OUT:=0;
INACTIVEINTERVAL := TIME_TO_DWORD(INTERVAL);
RETURN;
END_IF

IF IN AND NOT OLDIN THEN (*rising edge *)
IF INIT THEN
DIFF := TIME_TO_DWORD(TIME()-OLDT);
IF Diff > 0 THEN
ADIFF[B] := DIFF;
B := (B+1) MOD PERIODS;
V:= MIN(V+1, PERIODS);
IF V=PERIODS THEN
OUT := 0;
FOR I:=0 TO PERIODS-1 DO
OUT := OUT + ADIFF[I];
END_FOR
OUT := 1000.0 * PERIODS / OUT;
VALID:=TRUE;
ELSE
VALID:=FALSE;
END_IF
END_IF
END_IF
INIT := TRUE;
OLDT := TIME();
ELSIF INIT AND VALID THEN
IF INACTIVEINTERVAL <> 0 THEN
IF TIME_TO_DWORD(TIME()-OLDT) > INACTIVEINTERVAL THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF
ELSIF TIME_TO_DWORD(TIME()-OLDT) > 3000 / OUT THEN
VALID:=FALSE;
V:=0;
B:=0;
END_IF
END_IF

OLDIN:=IN;


Использование ФБ FREQ_MEASURE_EX


(* объявление глобальных переменных *)
(* дополнительно дискретный вход 1 в Конфигурации ПЛК должен быть именован как InputSignal *)
VAR_GLOBAL
FreqMeasure1 : FREQ_MEASURE_EX;
FreqMeasure2 : FREQ_MEASURE_EX;
Frequency1 : REAL;
Frequency2 : REAL;
Valid1 : BOOL;
Valid2 : BOOL;
INIT : BOOL := FALSE;
END_VAR




(* основная программа *)

PROGRAM PLC_PRG
VAR
END_VAR

(* инициализация *)
IF NOT(INIT) THEN
FreqMeasure1(PERIODS:=3, RESET := TRUE, INTERVAL := T#12s); (* усреднение по 3 периодам, ожидание сигнала не более 12с *)
FreqMeasure2(PERIODS:=3, RESET := TRUE); (* усреднение по 3 периодам, ожидание сигнала 3 последних измеренных периода, как ФБ FREQ_MEASUE *)
INIT := TRUE;
END_IF

(* измерение частоты *)

FreqMeasure1.IN := InputSignal;
Frequency1 := FreqMeasure1.OUT;
Valid1 := FreqMeasure1.VALID;


FreqMeasure2.IN := InputSignal;
Frequency2 := FreqMeasure2.OUT;
Valid2 := FreqMeasure2.VALID;

(* подаем на дискретный вход 1 импульсный сигнал с частотой 1 Гц *)
(* после выхода измерителей на режим Vald1 = Valid2 = TRUE резко уменьшаем частоту до 0.1 Гц*)
(* флаг Valid1 сбросится в FALSE примерно через 3с и восстановится через еще через 7с с новым значением частоты*)
(* флаг Valid2 будет удерживаться 12с и дождется нового значения частоты*)
(* новое значение частоты будет усредненным и выйдет на 0.1Гц примерно через 30с (3 периода) после изменения*)

ленивый
10.05.2020, 21:36
стоп стоп, это если код менять а пока что условие начнет выполнятся если есть приращение Diff а оно в свою очередь присвоится ADIFF[B] которая в свою очередь суммируется в OUT и оно никогда не будет равно нулю
Проверка на таймаут не производится, пока VALID = FALSE. Оно станет TRUE только после PERIODS сигналов на входе. А тогда частота OUT уже будет не ноль. Нулем она может стать только после ресета. Но тогда VALID опять FALSE и все снова повторится.