ST状态机编程模板:用 CASE 语句实现标准的顺序控制流程
在电气自动化系统中,顺序控制是最常见、最核心的控制类型之一。它广泛应用于包装机械、装配线、灌装设备、电梯逻辑、锅炉启停等场景。这类任务的共性是:动作严格按阶段推进,每个阶段有明确的启动条件、执行动作、完成判据和转移路径。若用传统梯形图(LAD)实现复杂顺序,容易陷入“线网缠绕、跳转难查、调试费时”的困境;而结构化文本(ST)语言凭借其接近自然语言的逻辑表达能力、清晰的分支结构与变量管理机制,成为构建高可靠性顺序控制的理想载体。
本文提供一套经工业现场验证的 ST 状态机编程模板,完全基于 IEC 61131-3 标准的 CASE 语句实现,不依赖厂商扩展指令,可直接用于 Codesys、TIA Portal(SCL 模式)、Unity Pro、GX Works3(ST 编辑器)等主流 PLC 平台。全文聚焦“可读性、可维护性、抗干扰性”三大工程刚需,所有代码片段均可复制粘贴后编译运行。
一、为什么必须用状态机?——顺序控制的本质矛盾
顺序控制不是“一步到位”,而是“分步裁决”。典型问题包括:
- 同一物理输出(如电机启停)在不同阶段含义不同(阶段1启动→阶段2保持→阶段3停止);
- 某一条件(如“门已关”)在阶段A是启动前提,在阶段B却是故障禁止条件;
- 手动干预(如急停、暂停)需中断当前流程,但不能丢失上下文。
若用 IF-ELSE 嵌套实现,代码将迅速退化为“意大利面条式逻辑”:
IF (Step1_Start AND NOT Step1_Done) THEN
Motor := TRUE;
IF Sensor_A THEN Step1_Done := TRUE; END_IF;
ELSIF (Step1_Done AND Step2_Start AND NOT Step2_Done) THEN
Valve := TRUE;
IF Timer2.Q THEN Step2_Done := TRUE; END_IF;
// …… 几十层嵌套后,谁还记得 Step17 的退出条件?
END_IF;
而状态机将控制逻辑与状态数据分离:
State变量唯一标识当前所处阶段(如STATE_IDLE,STATE_FILL,STATE_SEAL);- 每个
CASE分支只专注本阶段的三件事:执行动作、判断转移条件、设置下一状态; - 所有状态转移由显式
State := NEXT_STATE控制,无隐式跳转,逻辑流向一目了然。
二、标准状态机四要素(缺一不可)
一个工业级状态机必须包含以下四个组成部分,否则无法应对真实产线需求:
-
状态枚举定义(Type Declaration)
使用TYPE ... END_TYPE明确定义所有合法状态,禁止用整数或字符串硬编码。TYPE T_State : ( STATE_IDLE, // 待机:等待启动命令 STATE_STARTUP, // 启动:上电自检、安全复位 STATE_FILL, // 填充:打开进料阀,启动输送带 STATE_SEAL, // 封口:下降压头,加热定时 STATE_EJECT, // 推出:打开出料气缸,延时返回 STATE_ERROR // 故障:所有输出置0,等待人工确认 ); END_TYPE -
状态变量与持久化存储
State必须声明为RETAIN(保持型),确保 PLC 断电重启后不丢失当前阶段:VAR State : T_State := STATE_IDLE; // 初始值设为待机 State_Prev : T_State; // 记录上一状态(用于边缘触发) END_VAR -
主状态机循环(Main CASE Block)
在主程序组织块(POU)中,用CASE State OF包裹全部逻辑:CASE State OF STATE_IDLE: // 待机阶段动作 Motor := FALSE; Valve := FALSE; Heater := FALSE; // 转移条件:收到启动信号且无故障 IF Start_Button AND NOT Fault_Flag THEN State := STATE_STARTUP; END_IF; STATE_STARTUP: // 启动阶段动作:执行自检序列 SelfTest_InProgress := TRUE; // 完成判据:所有传感器反馈正常 + 安全门关闭 IF SelfTest_Done AND Safety_Door_Closed THEN State := STATE_FILL; ELSIF SelfTest_Failed THEN State := STATE_ERROR; END_IF; STATE_FILL: // 填充阶段动作 Valve := TRUE; // 打开进料阀 Conveyor_Belt := TRUE; // 启动输送带 // 完成判据:料位传感器检测满料 OR 时间超限(防堵料) IF Level_Sensor_High OR Fill_Timer.Q THEN Valve := FALSE; // 关阀 Conveyor_Belt := FALSE; // 停带 State := STATE_SEAL; END_IF; STATE_SEAL: // 封口阶段动作 Press_Down := TRUE; // 下降压头 Heater := TRUE; // 启动加热 // 完成判据:压头到位 + 加热时间到 IF Press_Down_OK AND Heater_Timer.Q THEN Press_Up := TRUE; // 抬起压头 Heater := FALSE; State := STATE_EJECT; END_IF; STATE_EJECT: // 推出阶段动作 Eject_Cylinder := TRUE; // 推出气缸伸出 // 完成判据:推出到位 + 延时确保工件落槽 IF Eject_Ext_OK AND Eject_Delay.Q THEN Eject_Cylinder := FALSE; // 气缸缩回 State := STATE_IDLE; END_IF; STATE_ERROR: // 故障阶段动作:强制安全停机 Motor := FALSE; Valve := FALSE; Heater := FALSE; Press_Down := FALSE; Press_Up := FALSE; Eject_Cylinder := FALSE; // 转移条件:故障复位按钮按下 + 故障已清除 IF Reset_Button AND NOT Fault_Flag THEN State := STATE_IDLE; END_IF; END_CASE; -
状态变迁记录(可选但强烈推荐)
为调试与追溯增加状态切换日志:// 在主 CASE 块外添加 IF State <> State_Prev THEN State_Change_Time := T#NOW; // 记录切换时刻 State_Change_Count := State_Change_Count + 1; END_IF; State_Prev := State;
三、关键工程实践(避坑指南)
▶ 条件判断必须“去抖+闭锁”,严禁裸信号
传感器信号存在机械抖动、电磁干扰,直接使用 IF Sensor THEN 会导致状态反复跳变。正确做法:
-
对所有输入信号(尤其是按钮、限位开关)加
TP(脉冲定时器)或R_TRIG(上升沿触发器)滤波; -
对关键完成条件(如
Fill_Timer.Q)增加“确认延迟”:// 错误示例:Timer.Q 一变TRUE立即转移 IF Fill_Timer.Q THEN State := STATE_SEAL; END_IF; // 正确示例:需连续200ms为TRUE才认可 FILL_CONFIRM: TP(T#200MS); FILL_CONFIRM(IN := Fill_Timer.Q); IF FILL_CONFIRM.Q THEN State := STATE_SEAL; END_IF;
▶ 输出必须“双重约束”:状态驱动 + 安全使能
即使处于 STATE_FILL,若安全门意外打开,进料阀也必须立即关闭。因此所有输出需满足:
Valve := (State = STATE_FILL) AND Safety_Enable;
Conveyor_Belt := (State = STATE_FILL) AND Safety_Enable AND No_Jam;
其中 Safety_Enable 是全局安全使能信号(由急停、门锁、光幕等串联生成)。
▶ 支持暂停/恢复的增强设计
在 STATE_FILL 和 STATE_SEAL 等长周期阶段,增加暂停功能:
// 在 STATE_FILL 分支内插入
IF Pause_Button THEN
Conveyor_Belt := FALSE;
Valve := FALSE;
Fill_Pause_Timer(IN := TRUE, PT := T#5S); // 暂停超时自动转入ERROR
IF Fill_Pause_Timer.Q THEN State := STATE_ERROR; END_IF;
ELSIF Resume_Button THEN
Conveyor_Belt := TRUE;
Valve := TRUE;
END_IF;
▶ 故障处理必须分级响应
- 一级故障(如温度超限):暂停当前阶段,尝试自动恢复;
- 二级故障(如安全门开):立即转入
STATE_ERROR,切断所有输出; - 故障代码必须写入
Fault_Code全局变量,并通过 HMI 显示具体原因(如Fault_Code := 102; // "Sealing temp too high")。
四、完整可运行模板(含初始化与安全兜底)
以下为可直接部署的最小可行模板(适用于任何支持 ST 的 PLC):
// ====== 声明区 ======
TYPE T_State : (STATE_IDLE, STATE_STARTUP, STATE_RUN, STATE_STOP, STATE_ERROR);
END_TYPE
VAR
State : T_State := STATE_IDLE;
State_Prev : T_State;
// 输入信号(根据实际IO映射)
Start_Button : BOOL;
Stop_Button : BOOL;
Safety_Enable : BOOL; // 急停、门锁、光幕串联
Sensor_Full : BOOL;
// 输出信号
Motor : BOOL;
Valve : BOOL;
// 内部定时器(需在PLC资源中配置)
Startup_Timer : TON;
Run_Timer : TON;
Stop_Timer : TON;
// 故障管理
Fault_Flag : BOOL;
Fault_Code : INT := 0;
END_VAR
// ====== 主逻辑区 ======
// 安全兜底:任何时刻 Safety_Enable 失效,强制进入 ERROR
IF NOT Safety_Enable THEN
State := STATE_ERROR;
Fault_Code := 1;
END_IF;
// 主状态机
CASE State OF
STATE_IDLE:
Motor := FALSE;
Valve := FALSE;
IF Start_Button AND NOT Fault_Flag THEN
State := STATE_STARTUP;
END_IF;
STATE_STARTUP:
Startup_Timer(IN := TRUE, PT := T#3S);
IF Startup_Timer.Q THEN
State := STATE_RUN;
END_IF;
STATE_RUN:
Motor := TRUE;
Valve := TRUE;
Run_Timer(IN := TRUE, PT := T#10S);
IF Sensor_Full OR Run_Timer.Q THEN
State := STATE_STOP;
END_IF;
STATE_STOP:
Motor := FALSE;
Valve := FALSE;
Stop_Timer(IN := TRUE, PT := T#2S);
IF Stop_Timer.Q THEN
State := STATE_IDLE;
END_IF;
STATE_ERROR:
Motor := FALSE;
Valve := FALSE;
IF Stop_Button THEN // 仅允许通过停止按钮复位
Fault_Flag := FALSE;
Fault_Code := 0;
State := STATE_IDLE;
END_IF;
END_CASE;
// 状态变迁记录(用于HMI诊断)
IF State <> State_Prev THEN
State_Prev := State;
END_IF;
五、调试与验证清单
| 验证项 | 操作方法 | 预期结果 |
|---|---|---|
| 单步模拟 | 在仿真环境手动置位 Start_Button,观察 State 变量变化 |
STATE_IDLE → STATE_STARTUP → STATE_RUN → STATE_STOP → STATE_IDLE 循环准确 |
| 急停测试 | 在 STATE_RUN 时断开 Safety_Enable |
State 立即变为 STATE_ERROR,Motor 和 Valve 瞬间为 FALSE |
| 故障注入 | 强制 Fault_Flag := TRUE |
当前阶段立即终止,转入 STATE_ERROR;Stop_Button 后清除故障并返回 STATE_IDLE |
| 边界压力 | 连续快速点按 Start_Button(<100ms间隔) |
State 不发生重复跳变,仅响应首个有效边沿 |
六、进阶提示:从 CASE 到模块化
当设备包含多个独立工序(如“主输送线+分拣臂+打标机”),应将每个工序封装为独立的 FB(功能块):
FUNCTION_BLOCK FB_Filling_Station
VAR_INPUT
Start_Cmd : BOOL;
Safety_OK : BOOL;
Full_Sensor : BOOL;
END_VAR
VAR_OUTPUT
Is_Running : BOOL;
Error_Code : INT;
END_VAR
VAR
State : T_State := STATE_IDLE;
Timer : TON;
END_VAR
// 内部使用完全相同的 CASE 结构
主程序调用:
Filling_Station(Start_Cmd := Main_Start, Safety_OK := Safety_Main, Full_Sensor := Level_High);
Packing_Arm(Start_Cmd := Filling_Station.Is_Running, ...);
此设计实现高内聚、低耦合,任一单元故障不影响其他单元,且支持热插拔替换。
状态机不是编程技巧,而是对控制本质的敬畏。每一个 CASE 分支都是对物理世界一个确定阶段的忠实建模,每一次 State := ... 都是对系统演进路径的主动掌控。坚持使用本模板,你交付的将不再是“能跑的程序”,而是“可读、可测、可扩展、可传承”的自动化资产。

暂无评论,快来抢沙发吧!