Введение
Сегодня пойдёт речь о программировании контроллеров — другими словами, об автоматике и КИПиА. Постараюсь рассказать просто о сложном.
«Проблема? Какая проблема?» — скажете вы, если вы профессиональный программист АСУ. А я вот нет. Я не программист АСУ. И что такое гистерезис, я узнал только когда столкнулся c дребезгом. Но об этом как-нибудь отдельно.
Итак, мы в программе говорим: включи вентилятор, когда температура будет 21 градус. Всё работает, все счастливы. Пока матушка-природа не преподнесёт нам погоду в 20.9.
Все мы знаем, что у датчика есть погрешность. К ней добавляется любое дуновение ветерка, которое может чуть опустить или чуть приподнять температуру на десятые доли. Проблема в том, что аналоговые датчики присылают сырой сигнал — напряжение, которое меняется в зависимости от температуры. Контроллер принимает этот сигнал через АЦП (аналого-цифровой преобразователь) и оцифровывает его. Но сигнал не идеален — он может меняться десятки раз в секунду. А программируемое реле (у меня это ПР205) будет на каждое изменение реагировать и дёргать выходные сигналы так же десятки раз в секунду.
Теперь представьте оборудование и пускатели, которые подключены к этому выходу. Пускатели начинают щёлкать по несколько раз в секунду. Моторы стартуют и останавливаются без паузы. Контакты греются, обмотки не успевают остыть. Это реальная опасность: от выхода оборудования из строя до пожара.
И это очень известная и распространённая проблема. У программистов АСУ уже есть несколько способов с ней бороться.
Почему готовые решения не подходят
В среде разработки для ПР205 есть готовые блоки гистерезиса. Но у них выходные пины булевые — TRUE или FALSE. Другими словами они отвечают на вопрос «пора включать?» — да или нет.
А нам нужно передавать числовое значение температуры дальше в систему. Например, в блок климат-контроля, который сам решает, что включать в зависимости от температуры. Просто «да/нет» тут не подходит.
Сидеть и разбираться в чужом коде, пытаться адаптировать его под свои нужды — тоже задача не из лёгких. Шансы высоки не получить то, что нужно именно для твоей задачи.
Поэтому пишем свой блок. Такой, какой нужен нам.
Что будем делать
Нам нужен гистерезис для аналогового датчика температуры с тремя особенностями:
- Плавающая зона. Зона не привязана к фиксированному порогу, а всегда отсчитывается от последнего зафиксированного значения.
- Выход REAL. Блок отдаёт отфильтрованное число, а не просто логическую 1 или 0.
- Ручной режим. Возможность принудительно задать значение, но это так бонусом.
Как это работает.
Представьте: датчик показывает температуру. Мы запоминаем первое значение — например, 25.0 °C. И строим вокруг него зону ±1.0 °C (это значение можно настроить). Получаем коридор от 24.0 до 26.0 °C.
Дальше что бы ни происходило:
- Пришло 25.3 — внутри зоны, выход не меняем.
- Пришло 25.8 — всё ещё внутри, выход держим.
- Пришло 26.2 — вышли за верхнюю границу. Вот теперь обновляем выход до 26.2 и строим новую зону: от 25.2 до 27.2.
- Температура упала до 24.0 — вышли за нижнюю границу. Обновляем выход до 24.0. Новая зона: от 23.0 до 25.0.
Это не просто отсечка шума. Мелкие колебания гасятся, но реальное изменение температуры отслеживается честно.
Пишем функциональный блок на ST
// ==================== TempHysteresis ====================
// Гистерезис с плавающей зоной и выходом REAL
FUNCTION_BLOCK TempHysteresis
VAR_INPUT
RawValue: REAL; // Входная температура с датчика
Enable: BOOL; // Включение блока
ManualOverride: BOOL; // Ручной режим
ManualValue: REAL := 25.0; // Ручное значение
Hysteresis: REAL := 1.0; // Зона гистерезиса (по умолчанию ±1.0)
Reset: BOOL; // Сброс
END_VAR
VAR_OUTPUT
FilteredValue: REAL; // Отфильтрованное значение
State: UDINT; // 0=инит, 1=авто, 2=ручной
END_VAR
VAR
lastOutput: REAL := 0.0;
highLimit: REAL;
lowLimit: REAL;
initialized: BOOL := FALSE;
END_VAR
IF Reset OR NOT Enable THEN
FilteredValue := RawValue;
lastOutput := RawValue;
initialized := FALSE;
State := 0;
RETURN;
END_IF;
IF ManualOverride THEN
FilteredValue := ManualValue;
lastOutput := ManualValue;
State := 2;
RETURN;
END_IF;
IF NOT initialized THEN
lastOutput := RawValue;
FilteredValue := RawValue;
initialized := TRUE;
END_IF;
highLimit := lastOutput + Hysteresis;
lowLimit := lastOutput - Hysteresis;
IF RawValue > highLimit THEN
lastOutput := RawValue;
FilteredValue := RawValue;
ELSIF RawValue < lowLimit THEN
lastOutput := RawValue;
FilteredValue := RawValue;
ELSE
FilteredValue := lastOutput;
END_IF;
State := 1;
END_FUNCTION_BLOCK
Код написан специально для ПР205. На других платформах синтаксис может отличаться — проверяйте перед использованием.
Знакомимся с блоком TempHysteresis
Вытаскиваем его на рабочий стол и подключаем
Давайте разберём, что куда подавать и что снимать.
Входные пины (INPUT):
Пин Тип Назначение
RawValue REAL Сырое значение с аналогового датчика температуры.
Enable BOOL Включение блока. Подаём TRUE для работы.
ManualOverride BOOL Принудительный ручной режим.
ManualValue REAL Ручное значение температуры.
Hysteresis REAL Ширина зоны гистерезиса. По умолчанию ±1.0 °C.
Reset BOOL Сброс блока в начальное состояние.
Выходные пины (OUTPUT):
Пин Тип Назначение
FilteredValue REAL Отфильтрованное стабильное значение температуры. Именно его подаём дальше в систему климат-контроля.
State UDINT Текущее состояние блока: 0 — инициализация, 1 — автоматический режим (работа с датчиком), 2 — ручной режим.
Логика работы.
Блок работает по принципу плавающей зоны нечувствительности:
- При первом запуске (или после сброса) запоминает текущее значение с датчика и отдаёт его на выход.
Инициализация прошла на 21,3 °C. ( это вот это значение на выходе 2,13Е+01 ) Дальше вступает в работу наш гистерезис ±1.0 °C. он строит коридор вокруг запомненного значения:
[lastOutput - Hysteresis] ... [lastOutput + Hysteresis].
Пока новые показания с датчика находятся внутри этого коридора — выход не меняется. Дребезг и мелкие колебания в этом диапазоне игнорируются.
Как только датчик показывает значение за пределами коридора — блок признаёт это реальным изменением температуры, обновляет выход и строит новый коридор вокруг нового значения.
Ручной режим - если ManualOverride = TRUE то выход всегда равен ManualValue. Автоматика отключена. Даже не знаю кому на гистерезисе может понадобится ручной режим, но как говорится запас карман не тянет :))
Как подобрать Hysteresis под свою задачу.
- Если датчик шумит слабо — ставьте 0.5. Реакция на изменение температуры будет быстрой.
- Если датчик шумит сильно или вам нужна максимальная стабильность — ставьте 1.5 или 2.0. Реакция будет с небольшой задержкой, зато без ложных срабатываний.
- Не ставьте 0 — это отключает гистерезис, и блок превращается в простой повторитель.
Куда подавать выход.
Выход FilteredValue идёт туда, куда раньше вы подавали сырое значение с датчика. Например у меня это в блок климат-контроля, или на экран оператора, или в систему регистрации данных.
Как нейросеть помогла в этой истории
Есть важный нюанс, который вылез в процессе работы. Язык ST для контроллеров автоматики на разных платформах может отличаться. Хоть везде он называется одинаково — ST. Пришлось потратить достаточно много времени на то, чтобы разобраться в этих отличиях: типы данных, массивы и так далее — всё работает не так, как ожидаешь. Информацию пришлось собирать из разных источников: справочники, книги, форумы. Потом структурировать в компактный вид — чтобы скормить и научить нейросеть писать код без ошибок.
И это сработало. По мере получения опыта процесс пошёл веселее и — самое главное — всё быстрее. Сразу скажу: идея, задача, логика работы, тестирование на реальном железе — всё это вам придётся делать самостоятельно. Нейросеть — это инструмент. В умелых руках он превращается из хайповой игрушки в реальный рабочий инструмент в хозяйстве, который ускоряет работу в разы. Сама она физически не может написать ни строчки кода, если вы не поставите ей правильную задачу.
Заключение
Дребезг аналогового датчика — это серьёзная проблема, если её игнорировать. Но задача вполне решаемая. Правильная постановка задачи, чёткое описание логики — и мы получаем стабильные показания с датчика температуры и влажности. Чем ниже значение гистерезиса вы укажете — тем чаще будет меняться значение на выходном сигнале FilteredValue. Чем выше — тем спокойнее и стабильнее показания, но с небольшой задержкой на реальные изменения.
Не благодарите, пользуйтесь :)) Всего хорошего вам!