Structured Text (ST): Почему инженеры ПЛК всё чаще выбирают «программистский» язык
2026-02-24 11:24
Типичная картина на пусконаладке насосной станции. Опытный автоматчик полтора часа реализует на Ladder Diagram алгоритм чередования насосов с учётом наработки. Контакты, катушки, блоки сравнения - экран CODESYS превращается в схему метро крупного города. Всё работает. Почти. Один граничный случай - когда оба насоса набирают одинаковую наработку с точностью до минуты - даёт непредсказуемое поведение. Поиск бага в «лесенке» из 200 ступеней занимает ещё три часа.
Тот же алгоритм на Structured Text - 60 строк. Читаемо, как псевдокод. Граничный случай виден при первом же код-ревью, потому что логика описана явно, а не нарисована проводами.
Это не агитация против Ladder. Это разговор о том, что ST - не замена остальным языкам МЭК, а мощный инструмент, который большинство инженеров АСУ ТП до сих пор недооценивают или просто не используют там, где он реально спасает.
Что такое ST и откуда он взялся
Structured Text - текстовый язык высокого уровня из стандарта IEC 61131-3. Стандарт вышел в 1993 году и описал пять языков программирования ПЛК: LD (Ladder Diagram), FBD (Function Block Diagram), IL (Instruction List), SFC (Sequential Function Chart) и ST. Сегодня в CODESYS 3.5 добавлен ещё и CFC (Continuous Function Chart), но это уже расширение экосистемы, а не часть базового стандарта.
ST по синтаксису похож на Pascal и Ada - с присваиванием через :=, блоками IF...THEN...ELSE...END_IF, циклами FOR, WHILE, REPEAT. Если раньше приходилось писать на Pascal или хотя бы читать чужой код на C - ST освоите за день. За второй день уже будет что-то работающее. За неделю станет понятно, почему опытные разработчики ПЛК держат ST как основной инструмент для сложной логики.
Синтаксис на практике: не теория, а реальный код
Начнём с самого базового - типов данных и объявления переменных. В ST всё строго типизировано, как и полагается промышленному языку, где ошибка типа может стоить дороже, чем отладочная сессия.
VAR
rTemperature : REAL; (* Температура теплоносителя, °C *)
ePumpState : E_PumpState; (* Перечисление: состояние насоса *)
tStartDelay : TON; (* Таймер задержки пуска *)
END_VAR
Уже здесь видна разница с Ladder: переменные именованные, типы явные, комментарии прямо в коде. Не надо лезть в таблицу символов, чтобы понять, что %MW42 - это температура теплоносителя.
IF-THEN-ELSE: не просто ветвление
Конструкция условного перехода в ST позволяет строить сложные условия без нагромождения параллельных ветвей:
IF rTemperature > 85.0 THEN
bPumpEnabled := FALSE;
bAlarmOverheat := TRUE;
ELSIF rTemperature > 75.0 THEN
(* Предупреждение, насос продолжает работать *)
bAlarmOverheat := FALSE;
bWarningHighTemp := TRUE;
ELSE
bAlarmOverheat := FALSE;
bWarningHighTemp := FALSE;
END_IF
На Ladder это три параллельные ветки с контактами сравнения и катушками. Работает одинаково. Читается и отлаживается - совсем не одинаково.
Циклы: то, что Ladder не умеет нормально
Вот где ST действительно берёт реванш. Цикл FOR - это естественная конструкция. На Ladder циклическую обработку массива - например, поиск максимума среди показаний 16 термопар - надо городить через указатели или вообще отказываться от идеи делать это компактно.
(* Поиск максимальной температуры среди 16 датчиков *)
rMaxTemp := arTemperature[0];
FOR i := 1 TO 15 DO
IF arTemperature[i] > rMaxTemp THEN
rMaxTemp := arTemperature[i];
nHotSensorIdx := i;
END_IF
END_FOR
16 строк кода. На FBD это нагромождение блоков MAX, соединённых проводами. На Ladder - отдельный разговор с психотерапевтом.
CASE: конечные автоматы без боли
Управление технологическим процессом почти всегда можно и нужно описывать через конечный автомат. ST для этого просто создан:
CASE eSystemState OF
STATE_IDLE:
IF bStartCommand THEN
eSystemState := STATE_PRIMING;
tPrimingTimer(IN := TRUE, PT := T#30S);
END_IF
STATE_PRIMING:
bPumpPriming := TRUE;
tPrimingTimer(IN := TRUE);
IF tPrimingTimer.Q THEN
tPrimingTimer(IN := FALSE);
eSystemState := STATE_RUNNING;
END_IF
IF NOT bPressureOK THEN
eSystemState := STATE_FAULT;
END_IF
STATE_RUNNING:
bPumpRun := TRUE;
IF bStopCommand OR bEmergencyStop THEN
eSystemState := STATE_STOPPING;
END_IF
STATE_FAULT:
bPumpRun := FALSE;
bFaultOutput := TRUE;
IF bFaultReset AND NOT bEmergencyStop THEN
bFaultOutput := FALSE;
eSystemState := STATE_IDLE;
END_IF
END_CASE
Структура читается как техническое задание. Каждое состояние изолировано. Добавить новое состояние - просто дописать ещё один CASE. Это и есть то, о чём говорят в книгах про читаемый промышленный код.
Функции и функциональные блоки: архитектура, которая работает
Одна из сильнейших сторон ST - возможность писать собственные функциональные блоки (FB) с чистым интерфейсом. Это объектно-ориентированный подход в мире ПЛК, пусть и без полноценного наследования (хотя CODESYS 3.5 поддерживает расширение FB).
Классический пример - блок управления задвижкой. Таких задвижек на производстве может быть 50 штук. Если у каждой своя программа, написанная копипастом - это катастрофа обслуживания. Правильный подход: один FB FB_Valve, инстанцированный 50 раз.
FUNCTION_BLOCK FB_Valve
VAR_INPUT
bOpen : BOOL; (* Команда открыть *)
bClose : BOOL; (* Команда закрыть *)
bOpenedFB : BOOL; (* Концевик "открыто" от поля *)
bClosedFB : BOOL; (* Концевик "закрыто" от поля *)
tTimeout : TIME := T#30S; (* Таймаут хода задвижки *)
END_VAR
VAR_OUTPUT
bOpenCmd : BOOL; (* Команда на привод - открыть *)
bCloseCmd : BOOL; (* Команда на привод - закрыть *)
bFault : BOOL; (* Авария: таймаут или рассогласование *)
eState : E_ValveState; (* Текущее состояние *)
END_VAR
VAR
tMovingTimer : TON;
bMoving : BOOL;
END_VAR
Тело блока - те же CASE и IF, но логика написана один раз и верифицирована. При изменении логики управления задвижкой - правишь в одном месте, перекомпилируешь, и изменение применяется ко всем 50 экземплярам. Это не фантастика, это нормальная инженерная практика.
Где ST работает лучше всех, а где стоит взять другой язык
Честный разговор невозможен без признания: ST не универсален. У каждого языка МЭК своя ниша.
ST лучше всего подходит для сложной математики и вычислений - ПИД-регуляторы, пересчёт физических величин, цифровая фильтрация. Для алгоритмов с массивами и циклами - обработка данных от множества датчиков, поиск, сортировка. Для конечных автоматов с большим числом состояний. Для написания библиотечных функциональных блоков. Для строковых операций при формировании сообщений через MQTT или OPC UA. И для ручной реализации протоколов обмена, когда стандартная библиотека не закрывает кейс.
Ladder Diagram незаменим там, где программу должен читать электрик или технолог без знания программирования, где логика битовая и простая - реле, контакторы, блокировки, или где заказчик явно требует LD по нормам безопасности.
FBD выигрывает на аналоговых цепях обработки сигналов и при быстром прототипировании небольших регуляторов, где важна визуальная связь блоков.
На практике хороший проект ПЛК - это микс. Верхний уровень диспетчеризации - SFC. Подпрограммы состояний - ST. Простые блокировки и сигнализация - Ladder. Обработка аналогов - FBD или ST по ситуации.
Типовые ошибки при работе со ST: грабли, которые уже кто-то наступил
Забытые вызовы функциональных блоков. FB в ST надо вызывать явно в каждом цикле. В отличие от FBD, где экземпляр нарисован на схеме и всегда выполняется, в ST легко написать вызов таймера внутри ветки IF и потом несколько часов искать, почему таймер работает не так.
(* НЕПРАВИЛЬНО - таймер вызывается только при bCondition = TRUE *)
IF bCondition THEN
tMyTimer(IN := TRUE, PT := T#5S);
END_IF
(* ПРАВИЛЬНО - таймер вызывается каждый цикл *)
tMyTimer(IN := bCondition, PT := T#5S);
Деление на ноль с вещественными числами. REAL в ПЛК - это IEEE 754 float. Деление на ноль не вызовет исключения, но переменная примет значение +Inf или NaN. Это потом красиво "плывёт" по Modbus в SCADA, и диспетчер видит на мнемосхеме температуру 1.#INF градусов.
IF ABS(rDivisor) > 1.0E-6 THEN
rResult := rNumerator / rDivisor;
ELSE
rResult := 0.0;
bDivisionError := TRUE;
END_IF
Путаница с типами при работе с Modbus-регистрами. Реальное поле боли. Данные из Modbus приходят как WORD или INT. Температура 23.5°C передаётся как 235 (один знак после запятой). Забыть масштабирование при конвертации - стандартная ошибка пусконаладки.
(* Правильное масштабирование: регистр Modbus -> физическая величина *)
rTemperature := INT_TO_REAL(nModbusReg) / 10.0;
Переменные RETAIN без понимания последствий. RETAIN-переменные сохраняются при перезапуске ПЛК, но не при обновлении прошивки. Если в RETAIN попала переменная состояния конечного автомата, после аварийного перезапуска автомат стартует не из STATE_IDLE, а из STATE_FAULT или STATE_RUNNING. Итог - оборудование включается само по себе после подачи питания.
ST и архитектура реального проекта
Хороший проект для среднего объекта (10-50 аналоговых входов, 30-100 дискретных каналов, несколько приводов) в CODESYS 3.5 обычно строится так: PRG_Main на SFC последовательно вызывает задачи; PRG_IO занимается сбором и предобработкой сигналов - масштабирование, фильтрация - на ST; PRG_Alarms обрабатывает аварии; PRG_Control реализует основную логику через конечные автоматы на ST с вызовами FB; PRG_Comms управляет коммуникациями - Modbus RTU/TCP, OPC UA, MQTT. Библиотека типовых блоков FB_Pump, FB_Valve, FB_Drive написана на ST и переиспользуется от проекта к проекту.
Это не единственный правильный способ, но он даёт понятную структуру и легко масштабируется.
Отдельно про коммуникации. Работа с Modbus RTU/TCP на уровне ST - регулярная необходимость. Особенно когда нужно опрашивать нестандартные устройства или реализовывать логику условного опроса (читать дополнительные регистры только при определённом состоянии). Здесь ST с его условиями и циклами на голову выше, чем попытки сделать то же самое в FBD.
Отладка ST: жизнь без осциллографа
В CODESYS встроен полноценный отладчик: точки останова (Breakpoints), пошаговое выполнение, мониторинг переменных в реальном времени. Можно поставить брейкпоинт в нужном месте и наблюдать значения переменных в момент срабатывания условия - это уже другой уровень отладки по сравнению с миганием индикаторов на шкафу.
Для поиска редких ошибок, возникающих раз в несколько часов, в ST удобно вести журналирование через внутренний буфер событий:
IF bFaultCondition AND (nLogIndex < MAX_LOG_SIZE) THEN
arEventLog[nLogIndex].dtTimestamp := NOW();
arEventLog[nLogIndex].eState := eCurrentState;
arEventLog[nLogIndex].rTemp := rTemperature;
nLogIndex := nLogIndex + 1;
END_IF
Этот массив затем читается через OPC UA или MQTT в SCADA - и есть полная картина событий, предшествовавших аварии. Никакого внешнего регистратора, всё в памяти ПЛК.
Производительность: ST не медленнее, чем кажется
Распространённый миф: "Ladder быстрее, потому что это нативный язык ПЛК". На современных контроллерах это не так. Компилятор CODESYS транслирует ST в тот же машинный код, что и Ladder. Производительность практически идентична, а в ряде случаев ST даже выигрывает - компилятор лучше оптимизирует текстовый код, чем интерпретирует граф.
Реальное время цикла зависит от архитектуры задач (Task Configuration) и объёма коммуникаций, но не от выбора языка внутри POU. На контроллерах с процессорами ARM Cortex-A класса под управлением Linux RT - как, например, в линейке ПЛК СТАБУР со средой исполнения CODESYS 3.5.16 - цикл задачи 1 мс при среднем объёме проекта является рабочей нормой, а не предельным режимом.
FAQ: коротко о главном
Что такое Structured Text (ST) в программировании ПЛК? ST - текстовый язык программирования ПЛК стандарта IEC 61131-3, синтаксически похожий на Pascal. Позволяет реализовывать сложные алгоритмы с циклами, условиями, функциями и типизированными переменными - то, что крайне неудобно делать на графических языках Ladder или FBD.
Чем ST лучше Ladder Diagram? ST лучше для сложных вычислений, обработки массивов, реализации конечных автоматов и написания библиотечных блоков. Ladder удобнее для простой битовой логики и там, где программу должен читать персонал без знания программирования.
В каких средах программирования ПЛК поддерживается ST? ST поддерживается во всех основных средах: CODESYS 3.5, MasterSCADA 4D, TIA Portal (язык SCL - это и есть ST), Unity Pro. Любая среда, совместимая со стандартом IEC 61131-3, обязана поддерживать ST.
Сложно ли перейти на ST инженеру, знающему Ladder? Нет. Базовый синтаксис осваивается за 1-2 дня. Главный барьер - психологический: переход от визуального мышления к текстовому. Через месяц практики большинство инженеров отмечают, что ST для сложных задач удобнее и быстрее в разработке.
Насколько ST распространён в российской практике АСУ ТП? ST активно используется в проектах на базе CODESYS и MasterSCADA 4D. Российские производители ПЛК, включая отечественные решения для импортозамещения, например СТБАУР, полностью поддерживают IEC 61131-3 со всеми пятью языками, включая ST.
Вместо итогов
ST - это не "язык программистов для программистов". Это инструмент инженера-автоматчика, который понял: хороший код - тот, который работает через 5 лет после пусконаладки и который можно поддерживать без автора. Ни одна схема Ladder из 500 ступеней этому критерию не отвечает.
Начните с малого: вынесите следующий расчётный блок - ПИД, пересчёт единиц, логику выбора режима - в ST. Посмотрите, как изменится читаемость проекта. Скорее всего, возвращаться не захочется.
А граничные случаи - они всегда найдут способ удивить, если логика не описана явно. ST хотя бы даёт шанс поймать их раньше, чем это сделает производство.