Классно - выделение номера изменившегося бита при помощи CD32 - я и не знал, когда бы она пригодилась.Вот здесь разруливание одновременного запуска делал:
https://owen.ru/forum/showthread.php...l=1#post432040
Похоже, CD32 - ещё и выделяет приоритет номера насоса (номер бита), чтобы можно было менять состояние только у одного насоса.
Я полностью не разобрался с CD32 и парным к нему DC32 и поэтому сделал рассыпухой для каждого отдельного насоса и приоритет обеспечивается жёстко определённым порядком строк.
У меня на 5 насосов на ST - из-за необходимости уверенности в порядке выполнения
исключение одновременных переключений:Код:///<Description>ФБ предотвращает одновременный пуск или останов нескольких (пяти) агрегатов, разделяя изменения состояний заданной паузой</Description> ///<Author>!!FPA!!</Author> ///<GroupName>Насосы</GroupName> FUNCTION_BLOCK NotAllAtOnce5_ VAR_INPUT ///<Description>Разрешение работы. При FALSE все выходы одновременно выключаются.</Description> bEnable: BOOL; ///<Description>Запрос включения агрегата 1</Description> bRequest1: BOOL; ///<Description>Запрос включения агрегата 2</Description> bRequest2: BOOL; ///<Description>Запрос включения агрегата 3</Description> bRequest3: BOOL; ///<Description>Запрос включения агрегата 4</Description> bRequest4: BOOL; ///<Description>Запрос включения агрегата 5</Description> bRequest5: BOOL; ///<Description>Задержка между переключениями</Description> nDelay_s: UDINT; END_VAR VAR_OUTPUT ///<Description>Команда включения агрегата 1</Description> bStart1: BOOL; ///<Description>Команда включения агрегата 2</Description> bStart2: BOOL; ///<Description>Команда включения агрегата 3</Description> bStart3: BOOL; ///<Description>Команда включения агрегата 4</Description> bStart4: BOOL; ///<Description>Команда включения агрегата 5</Description> bStart5: BOOL; END_VAR VAR tDelay: TIME; bStartPrevious1: BOOL; bStartPrevious2: BOOL; bStartPrevious3: BOOL; bStartPrevious4: BOOL; bStartPrevious5: BOOL; tpPause: SYS.TP; bSwitchEn: BOOL; // разрешение изменить состояние END_VAR // преобразование типа и пересчёт значения в [с] tDelay := UDINT_TO_TIME(nDelay_s * 1000); // ограничение минимального значения IF tDelay < T#1s THEN tDelay := T#1s; END_IF // присвоение таймеру для уменьшения параметров в следующих вызовах tpPause(T := tDelay); IF bEnable THEN // обработка для агрегата 1 bSwitchEn := NOT(tpPause.Q) AND (bRequest1 <> bStart1); IF bSwitchEn THEN bStart1 := bRequest1; END_IF tpPause(I := bSwitchEn); // обновление значения выхода таймера для следующего обработчика // обработка для агрегата 2 bSwitchEn := NOT(tpPause.Q) AND (bRequest2 <> bStart2); IF bSwitchEn THEN bStart2 := bRequest2; END_IF tpPause(I := bSwitchEn); // обновление значения выхода таймера для следующего обработчика // обработка для агрегата 3 bSwitchEn := NOT(tpPause.Q) AND (bRequest3 <> bStart3); IF bSwitchEn THEN bStart3 := bRequest3; END_IF tpPause(I := bSwitchEn); // обновление значения выхода таймера для следующего обработчика // обработка для агрегата 4 bSwitchEn := NOT(tpPause.Q) AND (bRequest4 <> bStart4); IF bSwitchEn THEN bStart4 := bRequest4; END_IF tpPause(I := bSwitchEn); // обновление значения выхода таймера для следующего обработчика // обработка для агрегата 5 bSwitchEn := NOT(tpPause.Q) AND (bRequest5 <> bStart5); IF bSwitchEn THEN bStart5 := bRequest5; END_IF tpPause(I := bSwitchEn); // обновление значения выхода таймера для следующего обработчика ELSE // отсчёт от момента выключения, для паузы при быстром переключении bEnable tpPause(I := bStart1 OR bStart2 OR bStart3 OR bStart4 OR bStart5); // выключить все агрегаты bStart1 := FALSE; bStart2 := FALSE; bStart3 := FALSE; bStart4 := FALSE; bStart5 := FALSE; END_IF bStartPrevious1 := bStart1; bStartPrevious2 := bStart2; bStartPrevious3 := bStart3; bStartPrevious4 := bStart4; bStartPrevious5 := bStart5; END_FUNCTION_BLOCK
каскад с интегральным критерием (a-la КТР-121):Код:/// <Description>ФБ каскадного регулирования инерционной нагрузкой - позволяет увеличивать и уменьшать количество одновременно работающих агрегатов</Description> /// <Author>!!FPA!!</Author> /// <GroupName>Регуляторы</GroupName> FUNCTION_BLOCK Cascade5_ VAR_INPUT /// <Description>Разрешение работы. При FALSE выход рабен нулю - все агрегаты одновременно выключаются.</Description> bEnable: BOOL; /// <Description>Запрос включения регулятора</Description> bRequest: BOOL; /// <Description>Исправность агрегата 1 к работе</Description> bReady1: BOOL; /// <Description>Исправность агрегата 2 к работе</Description> bReady2: BOOL; /// <Description>Исправность агрегата 3 к работе</Description> bReady3: BOOL; /// <Description>Исправность агрегата 4 к работе</Description> bReady4: BOOL; /// <Description>Исправность агрегата 5 к работе</Description> bReady5: BOOL; /// <Description>Измеренное значение регулируемой величины</Description> rPV: REAL; /// <Description>Задание регулятора</Description> rSP: REAL; /// <Description>Зона нечувствительности (симметричная в каждую сторону)</Description> rDB: REAL; /// <Description>Значение параметро-временного интеграла, по достижении которого ступень включается</Description> rQSub: REAL; /// <Description>Значение параметро-временного интеграла, по достижении которого ступень отключаются</Description> rQAdd: REAL; /// <Description>Задержка начала расчета интеграла на подключение ступени, c</Description> nDelayAdd: UDINT; /// <Description>Задержка начала расчета интеграла на отключение ступени, c</Description> nDelaySub: UDINT; /// <Description>Снижение измеренного значения от задания для принудительного запуска агрегата</Description> rSP_DeltaOn: REAL; /// <Description>Максимальное измеренное значения для принудительного отключения всех агрегатов</Description> rSP_Off: REAL; END_VAR VAR_OUTPUT nAmount: UDINT; nMax: UDINT; bAddRequest: BOOL; bSubRequest: BOOL; bCalcEn: BOOL; nTimeCount: UDINT; rQCalcAdd: REAL; rQCalcSub: REAL; END_VAR VAR bInitDone: BOOL; nAmountPrevious: UDINT; nTimeNow: UDINT; nTimePrevious: UDINT; nTimeDelta: UDINT; rTimeDelta: REAL; END_VAR // подсчёт готовых агрегатов nMax := BOOL_TO_UDINT(bReady1) + BOOL_TO_UDINT(bReady2) + BOOL_TO_UDINT(bReady3) + BOOL_TO_UDINT(bReady4) + BOOL_TO_UDINT(bReady5); // вычисление длительности предыдущего цикла nTimeNow := TIME_TO_UDINT(get_time()); IF NOT bInitDone THEN nTimePrevious := nTimeNow; nTimeCount := 0; nAmount := 0; nAmountPrevious := 0; bInitDone := TRUE; END_IF nTimeDelta := nTimeNow - nTimePrevious; rTimeDelta := UDINT_TO_REAL(nTimeDelta) * 0.001; nTimePrevious := nTimeNow; IF bEnable AND bRequest THEN // если значение опустилось слишком низко - включаем первый агрегат // и не ждём значительного снижения до появления запроса от интегрального критерия IF (nAmount = 0) AND (rPV <= rSP - rSP_DeltaOn) THEN nAmount := 1; END_IF // если значение поднялось слишком высоко - отключаем все агрегаты // и не ждём значительного повышения до появления запроса от интенрального критерия IF (nAmount > 0) AND (rPV > rSP_Off) THEN nAmount := 0; END_IF // при входе в зону нечувствительности обнуляются интегральные критерии IF (rPV < rSP + rDB) AND (rPV > rSP - rDB) THEN rQCalcSub := 0; bSubRequest := FALSE; rQCalcAdd := 0; bAddRequest := FALSE; bCalcEn := TRUE; END_IF // вычисление интегрального критерия IF bCalcEn THEN // запрос - по возможности добавить количество работающих агрегатов IF (rPV <= rSP - rDB) THEN // обнуление интеграла противоположного направления rQCalcSub := 0; bSubRequest := FALSE; // вычисление интегрального критерия rQCalcAdd := rQCalcAdd + (rSP - rDB - rPV) * rTimeDelta; bAddRequest := (rQCalcAdd >= rQAdd); // ограничение вычислений от переполнения IF bAddRequest THEN rQCalcAdd := rQAdd; END_IF END_IF // запрос - по возможности сократить количество работающих агрегатов IF (rPV >= rSP + rDB) THEN // обнуление интеграла противоположного направления rQCalcAdd := 0; bAddRequest := FALSE; // вычисление интегрального критерия rQCalcSub := rQCalcSub + (rPV - rSP - rDB) * rTimeDelta; bSubRequest := (rQCalcSub >= rQSub); // ограничение вычислений от переполнения IF bSubRequest THEN rQCalcSub := rQSub; END_IF END_IF END_IF IF nAmount > nMax THEN nAmount := nMax; nAmountPrevious := nMax; END_IF // если есть запрос и возможность увеличения количества - увеличиваем IF (nAmount < nMax) AND bAddRequest THEN bAddRequest := FALSE; rQCalcAdd := 0; bCalcEn := FALSE; nAmount := nAmount + 1; END_IF // если есть запрос и возможность уменьшения количества - уменьшаем IF (nAmount > 0) AND bSubRequest THEN bSubRequest := FALSE; rQCalcSub := 0; bCalcEn := FALSE; nAmount := nAmount - 1; END_IF // если интегрирование запрещено - значит выполняется какой-то отсчёт // поверяем его завершение IF NOT bCalcEn THEN nTimeCount := nTimeCount + nTimeDelta; IF (rPV <= rSP - rDB) AND (nTimeCount >= nDelayAdd * 1000) THEN bCalcEn := TRUE; END_IF IF (rPv >= rSP + rDB) AND (nTimeCount >= nDelaySub * 1000) THEN bCalcEn := TRUE; END_IF ELSE // если количество агрегатов изменилось, то делаем паузу в вычислениях bCalcEn := (nAmount = nAmountPrevious); nTimeCount := 0; END_IF ELSE // при запрете работы или отключении регулирования nAmount := 0; bCalcEn := FALSE; END_IF nAmountPrevious := nAmount; END_FUNCTION_BLOCK
И пример использования
Там же упрощённый каскадный регулятор - по сетке уставок включает всё меньше насосов последовательно с каждым увеличивающимся диапазоном.
Проверял только в эмуляции. Может ещё и не все ошибки выловил.





Ответить с цитированием