Блог

Типичные ошибки при программировании ПЛК и их решение

2025-12-26 14:25
Объект сдан, акты подписаны. Но через неделю звонит заказчик: «ПЛК ушел в стоп», «Значения уставок слетели после перезагрузки», «Насос дергается». Вы открываете проект и видите не инженерную логику, а набор «костылей».
В программировании промышленных контроллеров действуют жесткие законы реального времени. Здесь нельзя просто «подождать». Здесь каждый байт памяти на счету.
Разберем типичные архитектурные ошибки, которые допускают даже опытные специалисты при переходе с релейной логики на ST.

1. Попытка организовать задержку через циклы (Убийство Watchdog)

Самая частая ошибка при миграции программистов с C++ или Python. В IT, чтобы подождать 5 секунд, пишут паузу. В ПЛК новички пытаются сделать это через цикл WHILE или REPEAT, ожидая, пока переменная изменит состояние.
Почему это фатально: ПЛК работает циклически. Чтение входов -> Выполнение программы -> Запись выходов. Если программа «застрянет» в цикле WHILE внутри одного скана дольше, чем разрешено (обычно 100-500 мс), сработает аппаратный сторожевой таймер (Watchdog Timer). Контроллер уйдет в ошибку EXCEPTION и остановит всё производство.
Как правильно (ST): Для задержек используются только таймеры (TON, TOF, TP). Они не останавливают выполнение кода, а работают параллельно с ним, считая время между вызовами.
Неправильно (Блокировка цикла):
WHILE (bSensor = FALSE) DO
(* Ждем, пока датчик сработает — ПЛК уйдет в STOP *)
iCounter := iCounter + 1;
END_WHILE
Правильно (Таймер):
fbTimer(IN := NOT bSensor, PT := T#5S);
IF fbTimer.Q THEN
(* Действие спустя 5 секунд *)
bAlarm := TRUE;
END_IF

2. Сравнение REAL «в лоб»

Вы пишете условие: IF rTemperature = 50.0 THEN .... На панели оператора вы видите ровно 50.0. Но условие не срабатывает.
В чем проблема: Числа с плавающей точкой (REAL/LREAL) хранятся в формате IEEE 754. Из-за особенностей двоичной математики число 50.0 в памяти может выглядеть как 49.9999999 или 50.0000001. Оператор равенства = вернет FALSE.
Как правильно: Всегда используйте «зону нечувствительности» (Deadband) или функцию ABS (модуль числа).
Правильно (ST):
IF ABS(rTemperature - 50.0) < 0.1 THEN
(* Считаем, что температура равна 50 с допуском 0.1 *)
bHeaterOn := FALSE;
END_IF

3. «Спагетти-код» на флагах вместо CASE

Когда нужно реализовать последовательность (Шаг 1: Налить воду -> Шаг 2: Нагреть -> Шаг 3: Слить), новички часто плодят десятки булевых переменных: bStep1_Done, bStep2_Active.
Через месяц в таком коде невозможно разобраться. Условия конфликтуют, появляются «гонки сигналов» (Race Condition), когда два шага включаются одновременно.
Как правильно: Используйте конструкцию CASE (Машина состояний / State Machine). Это стандарт де-факто для реализации алгоритмов управления.
Пример (ST):
CASE iStep OF
0: (* Ожидание *)
IF bStart THEN iStep := 10; END_IF
10: (* Налив воды *)
doValveIn := TRUE;
IF bLevelReached THEN
doValveIn := FALSE;
iStep := 20;
END_IF
20: (* Нагрев *)
(* Логика нагрева *)
END_CASE

4. Выход за границы массива (Array Out of Bounds)

В CODESYS (и в ПЛК СТАБУР на его базе) объявление массива выглядит так: arrValues : ARRAY [1..10] OF INT;. Если в цикле FOR вы по ошибке обратитесь к arrValues[11], компилятор может пропустить это, но во время работы произойдет запись в чужую область памяти. Это может перезаписать соседние переменные, вызвать сбой ядра или непредсказуемое поведение выходов.
Решение:
  1. Внимательно следите за индексами циклов.
  2. Используйте функцию CheckBounds. В CODESYS можно добавить специальный POU CheckBounds, который будет перехватывать любые обращения за пределы массивов и ограничивать их. Это немного увеличивает время цикла, но спасает от критических сбоев.

5. Потеря данных при рестарте (Retain vs Persistent)

Частая боль: оператор настроил уставки ПИД-регулятора, ночью на заводе моргнул свет, ПЛК перезагрузился — все настройки сбросились в ноль.
В чем ошибка: Программист объявил переменные просто как VAR. При перезагрузке (Reset Warm/Cold) они инициализируются дефолтными значениями.
Как правильно:
  • VAR_RETAIN: Данные сохраняются при выключении питания (хранятся в NVRAM или на батарейке). Но сбрасываются при «Заводском сбросе» (Reset Origin).
  • VAR_PERSISTENT: Данные сохраняются даже при загрузке новой программы (если структура переменных не изменилась).
Для ПЛК СТАБУР, работающих в ответственных узлах (ТП, котельные), критические уставки всегда должны быть в области RETAIN.
VAR_RETAIN
rSetPoint_Pressure : REAL; (* Сохранится после откл. питания *)
END_VAR

Итог

Качественный код для АСУ ТП — это не тот, который «просто работает», а тот, который:
  1. Не блокирует цикл (использует таймеры и CASE).
  2. Предсказуем (учитывает погрешности REAL).
  3. Безопасен (проверяет границы массивов).
  4. Живуч (помнит уставки после аварии питания).
Используйте возможности среды разработки (Statemachine diagram, CheckBounds), а не изобретайте велосипеды, которые потом ломаются на реальном объекте.