PDA

Просмотр полной версии : Алгоритм поддержания уровня количеством включённых насосов



FPavel
01.02.2026, 09:59
Доброго дня

Передо мной стоит задача управления несколькими насосами для поддержания уровня в резервуаре.
Уровень измеряется аналоговым датчиком.
Расход из резервуара может меняться в широких пределах.
Приток в резервуар регулируется количеством одновременно работающих насосов.
Насосы включаются контакторами (не ПЧВ и не мягкими пускателями).

Для регулирования уровня хочу выбрать аналоговый ПИД с разделением всего диапазона на участки, соответствующие количеству включённых насосов.
Например, 4 насоса, 4 участка (0-25, 25-50, 50-75, 75-100) - выход 1% и включить 1 насос, выход 76% включить 4 насоса. Но тут, мне кажется, прячется неравномерность на краях всего диапазона - для включения одного насоса достаточно немного увеличить выход (чуть больше 0%), а чтобы выключить 4-й насос и оставить в работе 3 насоса нужно снижать от 100% до 75%.
Или же лучше использовать позиционно-независимый, хотя и отдалённо похожий интегральный критерий (как в котловом каскаде КТР-121 (https://docs.owen.ru/product/ktr121/633/82270#topic-82275))?

Поэтому у меня два вопроса?
1. какой алгоритм управления количеством лучше выбрать? Если ПИД, то как лучше разбить участки для адекватной работы на краях диапазона? Или интегральный критерий?
2. каким алгоритмом предотвратить одновременное включение и выключение нескольких насосов - как организовать приоритет?

Достаточно описать алгоритмы, реализовать смогу самостоятельно. И хотелось бы получить советы на основе опыта реализаций.

capzap
01.02.2026, 10:42
Методом наименьших квадратов вычислять какой уровень будет через н-ый промежуток времени и исходить из этого, если достигнет уровня для подключения/выключения ещё одного насоса то готовится к включению/выключению, если нет то оставаться при своих, через тот же н-й промежуток времени проверять ещё раз и так по циклу

IVM
01.02.2026, 10:55
Зачем мудрить. Сделай двухпозиционый регулятор с широким гестирезисом. Врубай сразу все насосы. Износ у всех будет одинаковый.

Валенок
01.02.2026, 12:21
Если ПИД, то как лучше разбить участки для адекватной работы на краях диапазона? Или интегральный критерий?
2. каким алгоритмом предотвратить одновременное включение и выключение нескольких насосов - как организовать приоритет?
.


var input
нельзя1,
нельзя2,
нельзя3,
нельзя4: bool; //например авария
ПИД_выход: real; //0.0..1.0, снаружи определяется пид. И он не знает для чего. Да и не нужно ему
end_var

var output
пашет1,
пашет2,
пашет3,
пашет4: bool;
end_var

var
нельзя,
пашет,
пахал: array[1..4] of bool;
скока: array[1..4] of udint;
i: udint;
сейчас_пашет: udint;
нужно_пахателей: udint;
гонишь: tof;
дольше_всех: udint;
нашелся: udint;
-----------
нельзя[1] := нельзя1;
нельзя[2] := нельзя2;
нельзя[3] := нельзя3;
нельзя[4] := нельзя4;

for i := 1 to 4 do
пашет[i] := not нельзя[i] and пашет[i];
скока[i] := скока[i] + 1;
if пашет[i] <> пахал[i] then
пахал[i] := пашет[i];
скока[i] := 0;
end_if
end_for

сейчас_пашет :=
bool_to_udint(пашет[1]) +
bool_to_udint(пашет[2]) +
bool_to_udint(пашет[3]) +
bool_to_udint(пашет[4]);

case real_to_udint(ПИД_выход * 100.0) of
//например
0..5: нужно_пахателей := 0;
10..20: нужно_пахателей := 1;
30..45: нужно_пахателей := 2;
55..70: нужно_пахателей := 3;
80..100: нужно_пахателей := 4;
end_case
//А есля пид на стартя войдет с 73? - да пофиг. "И" есть

нашелся := 0;
if not гонишь.Q and сейчас_пашет <> нужно_пахателей then
дольше_всех := 0;
for i := 1 to 4 do
if сейчас_пашет > нужно_пахателей and пашет[i] and скока[i] >= дольше_всех then
нашелся := i;
дольше_всех := скока[i];
elsif сейчас_пашет < нужно_пахателей and not пашет[i] and not нельзя[i] and скока[i] >= дольше_всех then
нашелся := i;
дольше_всех := скока[i];
end_if
end_for
if нашелся <> 0 then
пашет[нашелся] := сейчас_пашет < нужно_пахателей;
end_if
end_if
гонишь(in := нашелся <> 0, pt := ...);

пашет1 := пашет[1];
пашет2 := пашет[2];
пашет3 := пашет[3];
пашет4 := пашет[4];
Для простого масштабирования
В битовые маски можно, но смысла не вижу. Да и логические для целочисленных не завезли
При массивах из фб - изящнее. Не завезли.

EFrol
01.02.2026, 13:11
Можно и ПИД с интегрированием - опробовано уже.
Только не приоритет, а раздельный старт с интервалом. Т.к. выходная может прыгать 75% <-> 76% (пересекать границу несколько раз).
Два циклических счётчика до числа насосов.
- кандидат на запуск (после запуска +1), если надо больше, чем работает
- кандидат на останов (после останова +1), если надо меньше, чем работает
т.е. FIFO-стек из насосов
если будет нужна ротация, через интервал, то следующий на запуск - пуск, следующий на останов - стоп и работаем дальше

FPavel
01.02.2026, 13:24
Для простого масштабирования
В битовые маски можно, но смысла не вижу. Да и логические для целочисленных не завезли
При массивах из фб - изящнее. Не завезли.
:)

Я наверное, разделю функционал на несколько ФБ:
- каскад (количество)
- приоритет включения - выключения насоса по критерию (общей наработке или продолжительности простоя или придумает заказчик)
- предотвращение одновременной коммутации нескольких насосов

Но основную идею из кода увидел - спасибо. Соответствие диапазона требуемой мощности количеству требуемых насосов. Да и по паузам и по критерию - тоже (однопроходный алгоритм выбора по заданному критерию - без сортировки)

kondor3000
01.02.2026, 13:34
Не нужен тут ПИД, ротация 4 насосов и включение по уровню давно выложены тут, последний вариант.
https://owen.ru/forum/showthread.php?t=38920&page=2#13

FPavel
01.02.2026, 13:39
Можно и ПИД с интегрированием - опробовано уже.
Только не приоритет, а раздельный старт с интервалом. Т.к. выходная может прыгать 75% <-> 76% (пересекать границу несколько раз).
Два циклических счётчика до числа насосов.
- кандидат на запуск (после запуска +1), если надо больше, чем работает
- кандидат на останов (после останова +1), если надо меньше, чем работает
т.е. FIFO-стек из насосов
если будет нужна ротация, через интервал, то следующий на запуск - пуск, следующий на останов - стоп и работаем дальше

Заказчик уточнил по ротации - ни в коем случае, т.к. это чем-то плохо для системы или насосов - я не понял, но принял к исполнению.
Но, заказчик согласился, что можно выполнять ротацию в ходе работы - добавлять наименее работавший, отключать наиболее проработавший (пока критерий такой - может быть позже поменяет, и буду исправлять).

Спасибо - о колебаниях не подумал.
Тогда, раз возможны колебания выхода ПИД на границе переключения, то склоняюсь к отказу от ПИД и выбору интегрального критерия - он воздействует на изменение количества всего на 1 насос.
А там в составе критерия для КТР есть и время стабилизации, и разные значения критерия для +1 и для -1 и всякие другие параметры.

Валенок
01.02.2026, 13:51
раз возможны колебания выхода ПИД на границе переключения,
Учтено. Если внимательно смотрели

Валенок
01.02.2026, 13:56
Не нужен тут ПИД,..
Извините, но и ПИД'ом можно сделать. И чем угодно.
Вопрос был

... хочу выбрать аналоговый ПИД ...Если ПИД, ..

FPavel
01.02.2026, 14:02
Не нужен тут ПИД, ротация 4 насосов и включение по уровню давно выложены тут, последний вариант.
https://owen.ru/forum/showthread.php?t=38920&page=2#13

Спасибо

Не очень люблю ответы в виде кода в файлах, лучше бы добавлять пояснения в сообщении. Особенно принятые алгоритмические решения.
Потому, что идею беру с благодарностью, а реализацию буду дополнять защитами (например, останов работы по причине неправильной последовательности электродов)

Если не сложно, дополните примеры очень краткими пояснениями на пару тройку строк.
Как понимаю, количество насосов определяется по 4 дискретным уровням (аналоговым уставкам) и на каком-то из них и уравняется баланс между притоком и оттоком воды в резервуар (+/-1 насос).

Тоже решение. Предполагаю, будет работать, только уровни подобрать с учётом максимальной скорости заполнения.

Реализуется просто. Могу сделать два варианта - Ваш и по интегральному критерию - и предоставить заказчику выбирать. Интегральный критерий сложен для восприятия, а тут достаточно сказать, что баланс наступит около одной из 4 уставок.

kondor3000
01.02.2026, 14:11
Реализуется просто. Могу сделать два варианта - Ваш и по интегральному критерию - и предоставить заказчику выбирать. Интегральный критерий сложен для восприятия, а тут достаточно сказать, что баланс наступит около одной из 4 уставок.

Каскад с интегралом выложен тут, переключение тем быстрее, чем больше разница температур ( у вас от уровня) https://owen.ru/forum/showthread.php?t=38453&page=10#93
Ротация с уровнем и переключением по времени, это упрощённая версия, но смысл тот же. В примерах всё подписано, разобраться не сложно.
Уровень можно сделать как по аналогу, так и по дискретным сигналам, там всё просто.

FPavel
01.02.2026, 14:14
Учтено. Если внимательно смотрели

Спасибо, да, внимательно прочитал и разобрался перед ответом - увидел диапазон 0-5 для отключённых состояний.

У меня до сих пор были задания на одновременную работу не более 2 насосов, а там выбор делал просто по двухпозиционному алгоритму с гистерезисом и задержками (правда, сравнивал не отклонения, а токи и частоту - чтобы работа была не на максимуме мощности).

Теперь же разрешено задействовать все имеющиеся насосы. Своего опыта и мнения нет, изучаю опыт других. Даже на алгоритме пока остановиться не могу.

Поэтому благодарен всем за советы и мнения о достоинствах и недостатках алгоритмов.

Сергей0308
01.02.2026, 17:51
Вот здесь выкладывал проекты с ротацией насосов и каскад:
https://owen.ru/forum/showthread.php?t=38619&page=8&p=420896&viewfull=1#post420896
https://owen.ru/forum/showthread.php?t=38619&page=4&p=420791&viewfull=1#post420791
https://owen.ru/forum/showthread.php?t=32428&page=4&p=322722&viewfull=1#post322722
Кстати, много раз это обсуждалось в разный темах!
Если у Вас значение регулируемого параметра резко не может меняться всё будет работать 100 лет, в смысле, лучше не мудрить, а то и перемудрить можно!

FPavel
01.02.2026, 18:36
Вот здесь выкладывал проекты с ротацией насосов и каскад:
Спасибо

Посмотрю.
Хочу сделать несколько вариантов и на практике проверить - какой подойдёт, тот и оставлю.
Сейчас пробую собрать каскад по КТР-121
https://docs.owen.ru/product/ktr121/633/82270#topic-82275
Чуть позже сделаю по предложению kondor3000 - поиск баланса притока и оттока по нескольким уровням, отстоящим от задания - дискретным уровням
https://owen.ru/forum/showthread.php?t=42441&p=479184&viewfull=1#post479184
И Ваши посмотрю и реализую.

Получится модульная система:
- каскадный регулятор (вход уровень, выход количество насосов)
- блок приоритетного включения и выключения (на входе количество требуемых насосов, на выходе запросы включения конкретных насосов)
- передача запросов на пускатели с разнесением коммутации во времени (не одновременный пуск-останов)
Т.е. смогу экспериментировать с каскадным регулятором простой заменой ФБ.
Нужно только набрать в редакторе.

С ротацией вопросов в целом нет, вдобавок, заказчик настаивает на отказе от ротации в работе - только при останове или при переключениях мощности под управлением каскада.

За объект регулирования ничего не знаю - в ТЗ только схема автоматизации и электрическая функциональная, краткое описание функционала алгоритмов.

Сергей0308
01.02.2026, 22:53
Спасибо

Посмотрю.
Хочу сделать несколько вариантов и на практике проверить - какой подойдёт, тот и оставлю.
Сейчас пробую собрать каскад по КТР-121
https://docs.owen.ru/product/ktr121/633/82270#topic-82275
Чуть позже сделаю по предложению kondor3000 - поиск баланса притока и оттока по нескольким уровням, отстоящим от задания - дискретным уровням
https://owen.ru/forum/showthread.php?t=42441&p=479184&viewfull=1#post479184
И Ваши посмотрю и реализую.

Получится модульная система:
- каскадный регулятор (вход уровень, выход количество насосов)
- блок приоритетного включения и выключения (на входе количество требуемых насосов, на выходе запросы включения конкретных насосов)
- передача запросов на пускатели с разнесением коммутации во времени (не одновременный пуск-останов)
Т.е. смогу экспериментировать с каскадным регулятором простой заменой ФБ.
Нужно только набрать в редакторе.

С ротацией вопросов в целом нет, вдобавок, заказчик настаивает на отказе от ротации в работе - только при останове или при переключениях мощности под управлением каскада.

За объект регулирования ничего не знаю - в ТЗ только схема автоматизации и электрическая функциональная, краткое описание функционала алгоритмов.

Вот здесь разруливание одновременного запуска делал:
https://owen.ru/forum/showthread.php?t=26216&page=353&p=432040&viewfull=1#post432040

FPavel
01.02.2026, 23:50
Вот здесь разруливание одновременного запуска делал:
https://owen.ru/forum/showthread.php?t=26216&page=353&p=432040&viewfull=1#post432040
Классно - выделение номера изменившегося бита при помощи CD32 - я и не знал, когда бы она пригодилась.

Похоже, 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


/// <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

И пример использования
Там же упрощённый каскадный регулятор - по сетке уставок включает всё меньше насосов последовательно с каждым увеличивающимся диапазоном.


Проверял только в эмуляции. Может ещё и не все ошибки выловил.

1exan
02.02.2026, 04:37
[QUOTE=FPavel;479218...

И пример использования
Там же упрощённый каскадный регулятор - по сетке уставок включает всё меньше насосов последовательно с каждым увеличивающимся диапазоном.


Проверял только в эмуляции. Может ещё и не все ошибки выловил.[/QUOTE]

Это нормальное поведение?
87821

FPavel
02.02.2026, 06:18
Это нормальное поведение?
Распределитель насосов по заданным количеству и приоритету наименьшей наработки ещё предстоит сделать, он не готов, на его месте ФБ-заглушка - поэтому такое поведение - включение заведомо неисправного насоса.
Реально готовы здесь только регулятор каскада и ограничитель одновременных переключений.

Так что в целом - не нормально. Но для проверки и отладки регулятора каскада - сойдёт.

melky
02.02.2026, 09:25
Найдите макрос от AI! по насосам до 8-ми штук, там легко реализовать и ошибки и управление через биты. Какие в работе, а время он сам считает.

FPavel
02.02.2026, 19:17
Найдите макрос от AI! по насосам до 8-ми штук, там легко реализовать и ошибки и управление через биты. Какие в работе, а время он сам считает.
Речь о
https://owen.ru/forum/showthread.php?t=40597&p=452976&viewfull=1#post452976
и
https://owen.ru/forum/showthread.php?t=40597&p=452994&viewfull=1#post452994
?

Ведь страшно же! Мне кажется, что быстрее велосипед соберу, чем чужой код на трюках с логическими операциями разберу - он же близок к регулярным выражениям

melky
02.02.2026, 19:29
Да. У вас всего там 3 входа, битовыми масками управлять не сложно.
Задали количество и маску насосов.
По аварии меняется маска. Все остальное делает макрос.

kondor3000
02.02.2026, 21:01
Ведь страшно же! Мне кажется, что быстрее велосипед соберу, чем чужой код на трюках с логическими операциями разберу - он же близок к регулярным выражениям

87852 87853
Сразу видно, мои проекты даже не смотрели, всё подписано как на схеме, так и в ФБ.

FPavel
02.02.2026, 23:42
Ещё раз благодарю всех, откликнувшихся на просьбу о помощи

Да. У вас всего там 3 входа, битовыми масками управлять не сложно.
Задали количество и маску насосов.
По аварии меняется маска. Все остальное делает макрос.
Я - пошутил, рассыпухой, а не битовыми масками уже решал задачу ротации с приоритетным выбором по наработке, а не просто по очереди, поэтому хоть и не все детали, но общий смысл понимаю в схеме AI!
Если серьёзно, именно эта схема получается нерасширяемой - к ней нет пояснений с выводом формул (можно вывести самостоятельно - но терять время), также холст плотно заполнен, из-за этого встраивать приоритеты ротации будет труднее, чем создание с нуля - сначала вывести формулы, расчистить холст, добавить расширение функционала.
Правда, сделать самому, хоть и не с таким мастерством битовых трюков - быстрее. А в моём случае - просто достать из предыдущей работы.

Как урок мастерства - интересно. Запомню приём и при необходимости его применю. Пример уже будет.

Спасибо.

87852 87853
Сразу видно, мои проекты даже не смотрели, всё подписано как на схеме, так и в ФБ.
Внимательно читал и именно этот пример. В сообщении выше разбирал его работу.
В собственном тестовом примере реализовал его каскадный регулятор с учётом аналогового датчика и количества насосов. Не совсем разобрался с дискретными уровнями и непривычными битовыми трюками, но, показалось, что идею понял - по мере роста уровня на заданных отметках последовательно отключаются насосы.
Достоинства - простота в понимании принципа работы для наладчика и эксплуатации. Главное объяснить, что баланс наступит на одном из назначенных уровней переключения, а не на каком-то конкретном.
Недостаток - при включении на пустой резервуар сразу включает все доступные насосы, что можно компенсировать следующим ФБ, ограничивающим одновременную коммутацию. А также требуется отдельная задержка при смене количества насосов - чтобы избежать возможного частого "тактирования".

Вот блок ротации просмотрел несколько бегло. Он сделан универсально, но циклов много - ощущение, что часть можно заменить однопроходными алгоритмами, усложняя условия проверки. А для ничтожного количества - (до 5) циклы можно развернуть и решение получится довольно простым и наглядным даже на FBD. Это причины беглого осмотра макроса, хотя просмотрел его полностью и комментарии прочитал.

Недавно готовил документацию на библиотеку коллег в CODESYS 3.5 - там был подобный блок ротации и АВР на открытых массивах array для безграничного числа насосов - мне кажется, он содержал всего 3 цикла.

Наверное, не буду жадничать и на выходных подготовлю и опубликую свою ротацию на небольшое число насосов - не универсальный, а под конкретное количество. Заказчик потребовал отказа от ротации в работе (только при пуске учитывать наработку) и проверить макрос на практике я не смог, только в эмуляции, но думаю, он рабочий.

----------------------------------
От себя добавлю, делая небольшие утилиты в Pascal (FreePascal), использовал автоформаттер исходного кода JCF. Поэтому, после начала работы с ST начал искать подобный инструмент - он существует, но только в виде двух вариантов расширений к VSCode - после установки VSCode нужно зайти в магазин расширений и найти искомое. Ему во всём далеко до AStyle и JCF, есть глюки - комментарии автодокументирования "///" разделяет пробелом "/ //", но код читать легче.
Коллеги, делясь кодом с другими - прогоняйте его через автоформаттер! да и просто придерживайтесь аккуратного оформления.

Мне нравится один из форумов по программированию в рунете - если кто-то "приволакивает" интересную задачку, то люди решают её и объективно критикуют чужие решения (иной раз и излишне резко), все учатся.
Здесь из-за профессиональной среды, нет разборов решений, только предложения собственных выстраданных решений.
Надеюсь, что попытками взвесить достоинства и недостатки предложенных решений никого не обидел.

kondor3000
03.02.2026, 08:02
Недавно готовил документацию на библиотеку коллег в CODESYS 3.5 - там был подобный блок ротации и АВР на открытых массивах array для безграничного числа насосов - мне кажется, он содержал всего 3 цикла.

Хорошее сравнение
У меня 3 цикла и кол-во насосов тоже любое. Но у них всего 3 цикла (но это не точно), а тут целых 3 цикла. У них массивы +, а у других минус.
Не считая того, что в Cоdesys в 100 раз больше возможностей, а тут надо выносить всё наружу для сохранения, границы массивов переменной не задать.
И сравнивать Cоdesys с Лоджиком как минимум некорректно.

По поводу разборов решений, если есть интересный вопрос, народ включается, только таких вопросов всё меньше с каждым годом.
А больше достают пустомели, которые даже не вникнув (прочитав часть последнего поста или вырвав из контекста кусок), пишут всякую муть.

melky
03.02.2026, 09:07
kondor3000 а где можно задать границы массивов? в смысле вы про на лету поменять границы или о чем тут ?

kondor3000
03.02.2026, 09:15
kondor3000 а где можно задать границы массивов? в смысле вы про на лету поменять границы или о чем тут ?

В Cоdesys можно написать nasos : ARRAY[1..nam] OF BOOL; // кол-во насосов , где nam задать равным 2,3,4,5 или 8 или 12
а в Ложике только nasos : ARRAY[0..4] OF BOOL; // кол-во насосов и менять всё вручную, например у меня надо исправить в 5 местах.

Чтобы сохранить наработку в Лоджике, её надо вывести на выход и на вход ФБ, а в Cоdesys только присвоить в глобальные и RETAIN.
Это усложняет все ФБ наработки, основной ФБ и кол-во входов-выходов, кратно от кол-ва насосов.

FPavel
03.02.2026, 10:41
kondor3000 а где можно задать границы массивов? в смысле вы про на лету поменять границы или о чем тут ?
В CODESYS 3.5 можно сделать открытый массив (в терминах привычного мне Pascal)

var_input или var_in_out
abMyArray: array of bool;
А при обращении к ФБ дать ему массив с твёрдо заданными границами.

Внутри ФБ диапазон индексов для цикла уточняется при помощи функций - LOWER_BOUND(abMyArray, 1) и UPPER_BOUND(abMyArray, 1), где 1 - это номер интересующей размерности массива (от 1), для матрицы нужно было бы использовать и 1 и 2.

Таким образом получается достаточно универсальный ФБ.

melky
03.02.2026, 11:27
ARRAY[1..nam] но я так понимаю, что вы даете ему границы в рамках запуска программы? То есть при инициализации этого массива, так ведь?

Если проще - можно сделать так: array[5] а потом записать в него 6-ю переменную и он сам увеличится? или все же надо объявить новый, скопировать из старого и добавить эту 6-ю переменную?

просто сам увеличивается насколько понял только в javascript, везде нужно делать resize

kondor3000
04.02.2026, 09:41
ARRAY[1..nam] но я так понимаю, что вы даете ему границы в рамках запуска программы? То есть при инициализации этого массива, так ведь?

Если проще - можно сделать так: array[5] а потом записать в него 6-ю переменную и он сам увеличится? или все же надо объявить новый, скопировать из старого и добавить эту 6-ю переменную?

просто сам увеличивается насколько понял только в javascript, везде нужно делать resize

nam, можно поменять и в работе, главное, что бы не получилось переполнения в циклах. Меньше можно, больше только если предусмотрено программой,
иначе ПЛК зависнет. Поэтому у меня nam поменять в работе нельзя, он просто помещён в константы и на ходу не меняется.

melky
04.02.2026, 09:48
kondor3000 спасибо, принцип понятен. жалко, что в ST нет понятий List и Dyctionary :)

FPavel
05.02.2026, 02:23
Покажу моделирование работы каскадного регулятора с интегральным критерием

Возможно, я немного устал и не могу сосредоточиться, но толком настроить его не получилось - колеблется число работающих насосов от 0 до 5.
Не получается от того, что при накоплении интегрального критерия не учитывается наступивший перегиб на графике уровня - уровень уже снижается, но насосы выключаются (или наоборот), ощущение, что не хватает дифференциальной или пропорциональной составляющей.
Или прямых рук.
У меня в модели потребитель из резервуара с постоянной нагрузкой, а в жизни нагрузка будет меняться...

Проверял на моделях насосов и резервуара.

Модель насоса - на него подаётся питание и с небольшой задержкой он включается - замыкается допконтакт bRun. Немного времени на разгон и появляется проток bStream. Сигнал протока подаётся на модель резервуара.

Модель резервуара - не стал заморачиваться с геометрией и производительностями насосов - просто обозначил, что один работающий насос в секунду добавляет в резервуар несколько миллиметров уровня, и чем больше насосов, тем выше скорость наполнения. Насос отбора из резервуара тоже отбирает с заданной скоростью rSub.

С экрана нужно задать исправность насосов и включить алгоритм - работа начнётся.

Регулятор состоит из трёх частей:
- сам регулятор, определяющий необходимое число насосов
- диспетчер, распределяющий включения между насосами
- коммутатор, исключающий одновременную коммутацию пускателей

Сейчас уже немного устал и проверять не буду. Чуть позже займусь.
Хочу довести до ума макрос с алгоритмом https://owen.ru/forum/showthread.php?t=42441&p=479184&viewfull=1#post479184
количество насосов определяется по 4 дискретным уровням (аналоговым уставкам) и на каком-то из них и уравняется баланс между притоком и оттоком воды в резервуар (+/-1 насос).
Проверю и его на модели.

Т.е. на промежуточном этапе у меня нет умиротворения от качества работы каскадного регулятора.

1exan
05.02.2026, 07:21
...

Т.е. на промежуточном этапе у меня нет умиротворения от качества работы каскадного регулятора.

Валенок же предложил вполне умиротворяющий вариант - в паре со стандартным регулятором чем не подошёл?

melky
05.02.2026, 08:39
з.ы. мне кажется надо измерять скорость расхода воды, и исходя из этого включать необходимое количество насосов. Нет расхода, не полная емкость - оставляем один насос для докачки.

FPavel
05.02.2026, 09:15
Валенок же предложил вполне умиротворяющий вариант - в паре со стандартным регулятором чем не подошёл?


з.ы. мне кажется надо измерять скорость расхода воды, и исходя из этого включать необходимое количество насосов. Нет расхода, не полная емкость - оставляем один насос для докачки.

Спасибо

Сейчас, когда уже готова модель для проверки - смогу проверить разные варианты, и от kondor3000 и от Валенок.
Сейчас занят другими заданиями, сегодня вечером займусь - что-то возьму готовым, что-то создам.
Может, доработаю существующую - при росте уровня не добавлять новый насос или что-то в этом духе. Но это выглядит, как костыль, усложняющий понимание принципов работы системы.
Может и вправду, вариант kondor3000 несмотря на кажущуюся простоту - самый оптимальный - ведь точное до миллиметра поддержание на требуется, а разброс в 300 мм при уставке 8000 мм - не сильно критичен.
---------------
Я ничего не игнорировал, просто модель для проверки была готова только вчера и проверять было не на чем.
Теперь начну проверять разные варианты.
-----------------
Для каскада на основе ПИД регулятора и сетки выбора количества насосов, как понимаю пропуски в интервалах выбора case - это реализация гистерезиса для борьбы с частыми переключениями

case real_to_udint(rPID) of
0..13: need := 0;
18..31: need := 1;
36..49: need := 2;
54..67: need := 3;
72..84: need := 4;
88..100: need := 5;
end_case
------------------
Проверил вариант, предложенный kondor3000 - в части оценки количества требуемых насосов. Получилось довольно хорошо.
Задал сетку уставок. Переключение (+1 или -1) числа требуемых насосов выполняется на следующей уставке, а не на той, которая вызвала крайнее переключение - подобие гистерезиса.
В моей модели повезло с подбором сетки - баланс наступает точно на задании регулятора, но это случайность - в общем случае ожидал где-то внутри сетки :)
Гистерезис организовал по следующему алгоритму:
- пусть в диапазоне между двумя уставками переключения возможно два значения требуемых насосов, в зависимости от того, снижается или повышается уровень - (nMin) и (nMin+1)
- для измеренного уровня и заданной сетки определяю минимальное значение возможных насосов (nMin)
- если на предыдущем цикле запрашивалось меньше nMin, то настал час увеличить до этого минимума
- если на предыдущем цикле запрашивалось больше (nMin+1), то снижаем аппетиты до этого (nMin+1)

// число агрегатов в диапазоне нахождения измеренного значения уровня
// равно nMin или nMin+1 в зависимости от того снижается уровень или растёт
// Поэтому, если уровень снижается и теперь нужно больше агрегатов (nAmount < nMin),
// то повышаем их запрашиваемое количество.
// Если уровень вырос и агрегатов требуется меньше (nAmount > nMin + 1),
// то понижаем их запрашиваемое количество
IF nAmount < nMin THEN
// если реально включено меньше, то требуем не меньше минимального
nAmount := nMin;
ELSIF nAmount > nMin + 1 THEN
// если уровень поднялся и стало требоваться меньше агрегатов, то на один снижаем
nAmount := nMin + 1;
END_IF
Наверное, можно получить гибрид таких подходов к гистерезису при использованию ПИД - при изменении диапазона переключать или не переключать количество в зависимости от направления изменения выхода ПИД.
А может и не стОит... :)

Склоняюсь остановить выбор на "сетке уставок", как на близком к ПИД (вернее, П без ИД - отсюда и статическая ошибка регулирования), но простом в пояснении для эксплуатации.

Спасибо большое за помощь в выборе алгоритма!

FPavel
05.02.2026, 23:44
з.ы. мне кажется надо измерять скорость расхода воды, и исходя из этого включать необходимое количество насосов. Нет расхода, не полная емкость - оставляем один насос для докачки.

Да, это подошло бы, был бы дополнительный датчик протока. Но схема автоматизации утверждена. И, скажем так, моё мнение мало интересует заказчика.

FPavel
06.02.2026, 08:15
Нашёл ошибку - не обрабатывалась ситуация отключения всех насосов при превышении верхнего рабочего уровня.

Теперь уже точно всё

FPavel
03.03.2026, 21:30
Во время ПНР обнаружилась ошибка при формировании сигналов включения насосов - реальная программа была сложнее, позволяла включать часть насосов вручную, а часть в автоматическом режиме, и из-за этого в ФБ Dispatcher_ формировалась неправильная последовательность включений и выключений.
Изначальная задумка была в том, чтобы при таким смешанном режиме за счёт управляемых насосов поддерживать требуемое количество, независимо от количества насосов, включённых вручную.
Но где-то ошибся в условии выбора и при недостатке времени принял решение отказаться от этого улучшения.

Исправленный вариант прикрепляю к сообщению