По мере роста программы ПЛК часто появляется что-то похожее:
IF xStart AND NOT xStep1 THEN
...
END_IF;
IF xStep1 AND xSensor THEN
...
END_IF;
IF xStep2 AND fbTimer.Q THEN
...
END_IF;
Сначала всё выглядит нормально.
Но потом появляются:
• дополнительные условия
• блокировки
• таймеры
• аварийные переходы
• ручной режим
• повторный запуск
И через некоторое время становится трудно ответить на простой вопрос:
«На каком этапе сейчас находится алгоритм?»
Для последовательных процессов часто удобнее использовать
CASE и явно хранить текущее состояние.━━━━━━━━━━━━━━━━━━
🏭 Пример задачи
Пусть после команды «Пуск» механизм должен:
1️⃣ включить двигатель
2️⃣ дождаться срабатывания датчика
3️⃣ работать ещё 5 секунд
4️⃣ остановиться
У процесса можно выделить четыре состояния:
IDLE
STARTING
WORKING
STOPPING
Создадим перечисление:
TYPE E_State :
(
IDLE,
STARTING,
WORKING,
STOPPING
);
END_TYPE
И переменную текущего состояния:
VAR
eState : E_State := IDLE;
END_VAR
Теперь вместо нескольких BOOL-переменных вида:
xStep1
xStep2
xStep3
xStep4
мы имеем одну:
eStateкоторая однозначно показывает текущий этап.
━━━━━━━━━━━━━━━━━━
⏱ Сначала подготовим таймер
Пусть в состоянии
WORKING механизм должен работать 5 секунд.Объявим:
VAR
eState : E_State := IDLE;
fbWorkTimer : TON;
xStart : BOOL;
xSensor : BOOL;
xMotor : BOOL;
END_VAR
Таймер удобно вызывать циклически:
fbWorkTimer(
IN := eState = WORKING,
PT := T#5s
);
Когда состояние становится
WORKING, таймер начинает отсчёт.При выходе из этого состояния:
IN = FALSE
и
TON сбрасывается.━━━━━━━━━━━━━━━━━━
🔧 Теперь сам CASE
CASE eState OF
IDLE:
xMotor := FALSE;
IF xStart THEN
eState := STARTING;
END_IF;
STARTING:
xMotor := TRUE;
IF xSensor THEN
eState := WORKING;
END_IF;
WORKING:
xMotor := TRUE;
IF fbWorkTimer.Q THEN
eState := STOPPING;
END_IF;
STOPPING:
xMotor := FALSE;
eState := IDLE;
END_CASE
Теперь структура алгоритма читается почти как описание технологического процесса.
━━━━━━━━━━━━━━━━━━
🔎 Разберём по шагам
1. IDLE
Механизм находится в ожидании:
xMotor := FALSE;
При появлении команды:
xStart = TRUE
выполняется:
eState := STARTING;
━━━━━━━━━━━━━━━━━━
2. STARTING
Включаем двигатель:
xMotor := TRUE;
И ждём подтверждающий сигнал от датчика:
IF xSensor THEN
eState := WORKING;
END_IF;
Пока датчик не сработал, алгоритм остаётся в
STARTING.━━━━━━━━━━━━━━━━━━
3. WORKING
Двигатель продолжает работать:
xMotor := TRUE;
А таймер:
fbWorkTimer.Q
станет
TRUE после пяти секунд пребывания в состоянии WORKING.После этого:
eState := STOPPING;
━━━━━━━━━━━━━━━━━━
4. STOPPING
Останавливаем двигатель:
xMotor := FALSE;
и возвращаем автомат в исходное состояние:
eState := IDLE;
Цикл завершён.
━━━━━━━━━━━━━━━━━━
🧠 Что здесь даёт CASE?
Представим:
eState = STARTING
Тогда выполняется только логика ветки:
STARTING:
Код
IDLE, WORKING и STOPPING в этом выполнении CASE не выполняется.Поэтому состояния процесса хорошо отделены друг от друга.
━━━━━━━━━━━━━━━━━━
⚠️ Важная особенность переходов
Допустим, внутри
STARTING выполняется:eState := WORKING;
Это не означает, что ветка
WORKING немедленно начнёт выполняться ниже в том же CASE.Текущая ветка уже была выбрана.