В симуляторе CODESYS последовательность «Idle → Fill → Heat → Run» отрабатывает идеально: CASE по переменной типа E_Phase переключает шаги, HMI показывает правильные подписи. После Download на контроллер установка застревает в Heat, хотя температура уже давно выше порога. В online watch Phase показывает число 3, а не Run. Коллега говорит: «ENUM сломался». На деле CASE сравнивает не то, что вы думали: HMI шлёт INT, порядок элементов ENUM в проекте не совпал с сохранённым retain, или в одной ветке сравнение идёт с «голым» числом.
Перечисления в Structured Text по IEC 61131-3 удобны для state machine, но требуют дисциплины: объявление, неявные значения 0,1,2…, приведение типов, согласование с панелью. Статья разбирает, почему симулятор «прощает», а target после загрузки - нет. Это не справочник ST с нуля и не полный список ошибок компилятора - только ENUM, CASE и диагностика до пуска.
ENUM в ST - именованный целочисленный тип с фиксированным набором значений. CASE по ENUM должен использовать элементы перечисления, не «магические» INT с HMI. После загрузки на ПЛК расхождение с симулятором чаще всего из-за: (1) HMI или Modbus передаёт число вместо символического ENUM; (2) изменили порядок элементов ENUM в проекте - старые retain/PERSISTENT числа указывают на другой шаг; (3) неявное приведение INT к ENUM без валидации; (4) в CASE нет ветки ELSE - попадаете в «дыру» между значениями; (5) симулятор и target разные по размеру базового INT (редко, но проверять). Диагностика: online watch в десятичном и символьном виде, принудительная запись граничных значений, таблица соответствия число ↔︎ элемент до FAT.
Если не задавать :=, компилятор присвоит 0, 1, 2… по порядку объявления. Добавили новый элемент в середину списка - все последующие числа сдвинулись. Retain-переменная Phase с числом 3 после обновления проекта означает уже не Run, а следующий элемент.
На симуляторе вы каждый раз стартуете с cold start - Phase=0 (Idle). На объекте retain тащит старое число - CASE попадает в «чужую» ветку или в ELSE.
Правило для проекта: новые состояния добавляют в конец ENUM или явно задают числовые значения с «дырами» (10, 20, 30) для расширения. Изменение числа существующего элемента - миграция с планом сброса retain.
Без ELSE при Phase = INT#5 (мусор с HMI) ни одна ветка не выполняется - «залипание» последнего выхода. Симулятор вы могли не гонять через 5, поле - гоняет.
Сравнение CASE Phase OF 2: вместо E_Phase.Heat допустимо компилятором (неявное приведение), но ломает читаемость и рефакторинг: сменили числа в TYPE - CASE молчит с не теми константами.
Вложенный CASE и несколько ENUM в одном POU - следите, чтобы не затенять имена. E_Phase.Run и переменная Run - разные сущности; путаница в условиях перехода между фазами.
Панель оператора часто не знает про ваш E_Phase. В выпадающем списке - индексы 0…4 или произвольные «коды рецепта». PLC получает INT на %MW и присваивает:
Phase := INT_TO_ENUM(PhaseRaw, E_Phase);Если PhaseRaw = 99, результат implementation-defined - может стать первым элементом, последним или остаться прежним. На симуляторе тестировали 0…4, оператор выбрал «6» - поле.
Правильно: сначала проверка диапазона:
IF PhaseRaw >= 0 AND PhaseRaw <= INT(E_Phase.Stop) THEN Phase := INT_TO_ENUM(PhaseRaw, E_Phase); ELSE Fault_BadPhaseFromHMI := TRUE; END_IFИли таблица соответствия через массив valid codes. На HMI подписи синхронизируют с документом «код - имя фазы», не с «как нарисовалось».
Modbus и SCADA: если тег Phase - 16-bit integer, убедитесь, что word не приходит signed отрицательным при значении >32767 на расширенном ENUM (редко для фаз, чаще для статусов).
В watch CODESYS ENUM показывают символически и как число. При расхождении CASE смотрите оба. Типичная картина: символ Heat, число 3 - а в TYPE Run := 3. Кто-то правил ENUM, retain не сбросили.
Force ENUM на объекте осторожно - interlock может открыть клапан на «Idle». Force только на стенде или с механическим снятием.
Breakpoint на переход Phase := … и лог в CSV (PhaseNum, PhaseName, Time) на первом пуске после upgrade - дешёвый post-mortem.
Сравнение Phase = E_Phase.Run при невалидном ENUM может быть FALSE даже когда «кажется Run» - используйте validated INT для HMI-диагностики параллельно.
Симуляция на PC часто стартует без retain-памяти контроллera. Cold download на железо обнуляет не-retain переменные, но VAR RETAIN и PERSISTENT сохраняют старые числа фаз. Warm restart - ещё один сценарий: логика перезапускается, Phase retain = 3 (старое Run), условия перехода из Run в Idle не выполнены - «зависание».
Online change с изменением TYPE ENUM: компилятор может предупредить о несовместимости; если проигнорировать - числа в памяти не соответствуют новым символам.
Размер enum базируется на INT; если target и симулятор одна версия CODESYS 3.5 - расхождение редко. Зато библиотека с ENUM из другого package на симуляторе подтянулась, на target старая - разные numeric values при одинаковых именах.
Первый scan после RUN: инициализация IF NOT Initialized THEN Phase := E_Phase.Idle должна учитывать, нужно ли перезаписывать retain при версии рецепта (RecipeVer в retain сравнили с константой проекта).
Для HMI с выпадающим списком фаз синхронизируйте не только числа, но и отображаемые строки с элементами ENUM в PLC. Иначе оператор выбирает «Разогрев», а в PLC приходит индекс 2, который после правки TYPE стал «Останов» - визуально всё верно, логика ушла не туда.
ENUM часто живут в ST, а переходы рисуют в SFC. Согласуйте: SFC step active vs ENUM Phase - два источника правды ломают CASE в ST.
В LD/FBD переменная ENUM на контакте - неявное «<> 0». Для фазы Idle=0 контакт «Phase» ложно трактуют как «не Idle».
Отладочный вывод через SysMsg или запись в ring buffer с (ORD(Phase), TO_STRING(Phase)) на каждом переходе помогает на первом пуске после upgrade: в журнале видно, пришло ли 3 как число или как Run после смены TYPE.
Экспорт XML PLCopen и импорт в другой проект иногда пересортировывает элементы ENUM - после миграции прогоните таблицу чисел.
Подробнее про ST на объекте - Structured Text: почему инженеры ПЛК; синтаксис и ошибки компилятора - справочник IEC 61131-3.
Если рецепт хранит не только числа, но и фазу Phase : E_Phase в STRUCT RETAIN, смена ENUM в проекте без bump RecipeVer - классическая ловушка. При старте приложения сравнивайте StoredRecipeVer с CURRENT_RECIPE_VER из константы; при несовпадии - сброс фазы в Idle и запрос оператору выбрать рецепт заново, а не молчаливый CASE по старому числу.
Экспорт рецептов на USB или через OPC как набор INT-кодов без документации «код 3 = Run» через полгода превращается в лотерею. Таблица соответствия в паспорте HMI обязательна.
При импорте проекта из другой версии CODESYS (upgrade 3.5.16 → новый patch) прогоните diff по всем TYPE ENUM в библиотеках. Vendor library могла переопределить базовые значения статусов связи Modbus - ваш CASE по E_LinkState внезапно показывает Connected вместо Timeout.
Подготовьте таблицу из 10–15 строк: принудительно задайте PhaseRaw с HMI или через write Modbus 0…N и max+1, отключение связи HMI на минуту, power cycle PLC с retain, online change с добавлением одного элемента ENUM в конец списка. Для каждого шага - ожидаемая фаза, флаг Fault, положение клапанов. Симулятор прогоняет только happy path; стенд - границы.
В online mode включите запись журнала переходов OldPhase, NewPhase, Trigger в CSV на SD или буфер ring buffer на 1000 записей - на FAT покажете технологу, почему установка «зависла в Heat», не гадая по одному скрину watch.
| Значение / симптом | Ошибка типа | Как поймать до пуска |
|---|---|---|
| Watch: число 3, символ не Run | Сдвиг ENUM после правки TYPE | Таблица ENUM до/после, сброс retain |
| CASE не входит ни в одну ветку | INT вне списка, нет ELSE | Тест PhaseRaw 0…255 на стенде |
| Симулятор OK, поле Heat | Retain + HMI код не совпадает | Сверка списка HMI с TYPE |
| После Online Change «левый» шаг | Несовместимость retain | Cold start с документированным сбросом |
| `INT_TO_ENUM` без проверки | Мусор с панели | Range check перед присвоением |
| Run на экране, Idle в логике | Два тега: display vs Phase | Один источник правды в PLC |
| Первый scan сбрасывает не то | Init перезаписывает retain | Условие по RecipeVer / FirstScan |
| Modbus Phase скачет | Word swap / signed | Тип UINT vs INT в карте |
Можно ли сравнивать ENUM с INT в IF?
Компилятор часто разрешает - это ловушка. Сравнивайте с элементами ENUM или validated INT.
Сбросить все ENUM retain после изменения TYPE?
Да, если менялись числовые значения. Зафиксируйте в журнале версий проекта.
Почему симулятор показывал Run?
Cold start каждый Run, HMI в sim не подключена, Phase шёл только из логики переходов.
CASE vs IF-ELSIF для фаз?
CASE чище при полном покрытии ENUM; IF гибче для диапазонов - не смешивайте стили в одном POU без нужды.
Компилятор предупреждает о ENUM - игнорировать?
Нет. Warnings про неявное приведение и необработанные элементы CASE - в FAT checklist.
Можно ли хранить ENUM в VAR RETAIN?
Да, но помните про сдвиг чисел при изменении TYPE - см. материал про retain и миграцию версий рецепта.
CASE OF нескольких переменных в одном выражении?
В IEC 61131-3 CASE только по одному selector. Вложенный CASE или структура состояний - явно и с ELSE на каждом уровне.
В CODESYS 3.5 на ПЛК СТАБУР state machine на ENUM - нормальная практика для рецептов и пусковых последовательностей. Перед Download на объект после правки TYPE выгрузите таблицу фаз в PDF для наладчика и проверьте retain-политику: что сбрасывается при обновлении версии рецепта, что должно сохраниться намеренно.