在结构化文本(Structured Text,ST)编程中,枚举变量(Enumerated Type)是提升代码可读性、可维护性和安全性的核心手段。尤其在电气自动化项目(如基于IEC 61131-3标准的PLC程序)中,状态机(State Machine)几乎无处不在——设备启停、工艺步进、故障处理、模式切换等逻辑,都依赖清晰、自解释的状态定义。而直接用整数(如 State := 2;)或字符串(如 State := 'RUN';)赋值,极易导致“魔法数字”问题:后续维护者无法直观判断 2 代表什么,拼写错误难被编译器捕获,重构时易遗漏所有隐式引用。
本指南不讲抽象理论,只聚焦一个具体动作:如何正确声明枚举类型、定义其成员、并在ST中完成类型安全的赋值(如 State := StateType.Run;)。每一步均可在主流PLC开发环境(如 CODESYS、TIA Portal、Unity Pro)中直接验证,无需额外工具。
一、为什么必须用枚举?两个真实痛点
先看一段典型“反模式”代码:
VAR
State : INT;
END_VAR
// 程序段内
IF StartButton THEN
State := 1; // 启动中?
ELSIF MotorReady THEN
State := 2; // 运行中?还是就绪?
ELSIF FaultDetected THEN
State := 3; // 故障态?哪个故障?
END_IF
问题立即暴露:
- 不可读:
State := 2不说明业务含义,阅读者需反复查注释或上下文。 - 不安全:
State := 999编译通过,但该值未定义,可能触发未处理分支,引发静默故障。
而使用枚举后:
TYPE StateType : (Idle, Starting, Running, Stopping, Faulted);
END_TYPE
VAR
State : StateType;
END_VAR
// 赋值即自解释
IF StartButton THEN
State := StateType.Starting; // 明确:进入启动流程
ELSIF MotorReady THEN
State := StateType.Running; // 明确:主电机已运行
ELSIF FaultDetected THEN
State := StateType.Faulted; // 明确:进入故障态
END_IF
此时,StateType.Starting 不仅语义清晰,且编译器会强制校验:
✅ 允许:State := StateType.Starting;
❌ 报错:State := StateType.NonExistent;(成员不存在)
❌ 报错:State := 42;(类型不匹配,INT不能赋给StateType)
这才是电气自动化对可靠性的基本要求:让错误在编译期暴露,而非在现场停机时爆发。
二、四步写出可运行的枚举赋值(CODESYS/TIA实操)
以下步骤在 CODESYS V3.5+ 和 TIA Portal V17+ 中完全一致。语法符合 IEC 61131-3 第3版标准。
1. 声明枚举类型:用 TYPE ... : (...) 语法
在全局变量表(Global Variable List)或程序组织单元(POU)的 TYPE 区域声明:
TYPE StateType : (
Idle, // 空闲态:设备待命,无动作
Starting, // 启动中:接触器吸合,软启运行
Running, // 运行中:主电机全速,工艺执行
Stopping, // 停止中:减速制动,抱闸动作
Faulted // 故障态:急停触发、过流、通讯中断
);
END_TYPE
⚠️ 关键细节:
- 枚举成员名必须是合法标识符(字母/下划线开头,不含空格、特殊符号);
- 成员间用英文逗号
,分隔,末尾不加逗号; - 每个成员自动分配序号:
Idle = 0,Starting = 1,Running = 2,Stopping = 3,Faulted = 4; - 不建议手动指定值(如
Idle := 100;),除非有硬件协议强制要求——这会破坏可移植性。
2. 定义变量:声明为该枚举类型
在 VAR 或 VAR_GLOBAL 区声明变量:
VAR
State : StateType; // 主状态变量
LastState : StateType; // 上一状态,用于边沿检测
CmdState : StateType; // HMI下发的目标状态(带校验)
END_VAR
✅ 正确:State : StateType; —— 变量类型与枚举类型名严格一致。
❌ 错误:State : StateType(); 或 State : ENUM StateType; —— ST中无括号调用语法,也不用ENUM关键字。
3. 赋值操作:始终使用 TypeName.MemberName 格式
在程序逻辑中(如 PROGRAM Main 的 BODY 内),必须通过类型名限定成员:
// ✅ 正确:类型安全、可读性强
State := StateType.Idle;
State := StateType.Running;
State := StateType.Faulted;
// ❌ 错误:编译失败(未限定类型)
State := Idle; // Error: 'Idle' is not declared in this scope
// ❌ 错误:类型不匹配(整数不能赋枚举)
State := 2; // Error: Cannot convert INT to StateType
// ❌ 错误:字符串不能赋枚举(即使内容匹配)
State := 'Running'; // Error: Cannot convert STRING to StateType
💡 提示:在 CODESYS 中,输入
StateType.后按Ctrl + Space可弹出成员智能提示;TIA Portal 中输入StateType.后按Ctrl + Shift + Space同样生效。这是验证枚举是否正确定义的最快方式。
4. 状态比较:同样需类型限定
判断当前状态时,比较操作符右侧也必须使用限定格式:
// ✅ 正确:类型一致,编译通过
IF State = StateType.Running THEN
EnableProcessValves := TRUE;
END_IF
IF State = StateType.Faulted THEN
SoundAlarm := TRUE;
END_IF
// ❌ 错误:'Faulted' 未限定,编译报错
IF State = Faulted THEN // Error: 'Faulted' undefined
// ✅ 替代方案:用 CASE 语句,更清晰表达多分支
CASE State OF
StateType.Idle:
ResetAllTimers();
StateType.Running:
RunPIDControl();
StateType.Faulted:
LogFaultCode(ErrorCode.Last);
END_CASE
三、进阶技巧:让枚举真正服务于工程实践
▶ 技巧1:为枚举成员添加描述性注释(CODESYS专属)
CODESYS 支持在枚举声明中为每个成员添加 // 注释,这些注释会显示在在线诊断窗口和变量监视表中:
TYPE StateType : (
Idle, // 设备上电后初始态,等待启动命令
Starting, // 接触器闭合,软启斜坡上升中
Running, // 主电机达额定转速,工艺阀组受控
Stopping, // 接收停机指令,执行减速+抱闸
Faulted // 任一安全条件不满足,进入锁存态
);
END_TYPE
在线调试时,鼠标悬停在 State 变量上,即可看到 Running → 主电机达额定转速,工艺阀组受控,大幅降低现场排查时间。
▶ 技巧2:用 TO_STRING() 实现状态文本化(HMI/日志友好)
枚举本身是整数,但常需转换为字符串用于HMI显示或日志记录。IEC 61131-3 标准库提供 TO_STRING() 函数(CODESYS 需启用 STRING 库):
VAR
StateStr : STRING(32);
END_VAR
// 将当前状态转为字符串(自动映射成员名)
StateStr := TO_STRING(State); // 若 State = StateType.Running,则 StateStr = 'Running'
// 在HMI文本框绑定 StateStr,或写入日志
WriteLog('Current state: ' + StateStr);
✅ 优势:无需 CASE 手动映射,新增枚举成员后,TO_STRING() 自动支持。
⚠️ 注意:TO_STRING() 返回的是成员标识符名(Running),非注释内容(主电机达额定转速...)。
▶ 技巧3:状态转换校验——防止非法跳转
电气安全标准(如 IEC 61508)要求状态迁移必须可控。用枚举可轻松实现白名单校验:
FUNCTION IsValidTransition : BOOL
VAR_INPUT
From : StateType;
To : StateType;
END_VAR
CASE From OF
StateType.Idle:
IsValidTransition := (To = StateType.Starting) OR (To = StateType.Faulted);
StateType.Starting:
IsValidTransition := (To = StateType.Running) OR (To = StateType.Faulted) OR (To = StateType.Stopping);
StateType.Running:
IsValidTransition := (To = StateType.Stopping) OR (To = StateType.Faulted);
StateType.Stopping:
IsValidTransition := (To = StateType.Idle) OR (To = StateType.Faulted);
StateType.Faulted:
IsValidTransition := (To = StateType.Idle); // 仅允许复位回空闲
END_CASE
END_FUNCTION
// 使用示例:仅当转换合法时才更新状态
IF IsValidTransition(State, CmdState) THEN
State := CmdState;
ELSE
TriggerWarning('Illegal state transition blocked');
END_IF
此函数将状态逻辑显式编码,杜绝 Idle → Running(跳过启动过程)等危险路径。
四、常见错误及修正对照表
| 错误现象 | 错误代码示例 | 根本原因 | 正确写法 |
|---|---|---|---|
编译报错:Identifier not found |
State := Starting; |
未用 StateType. 限定成员 |
State := StateType.Starting; |
编译报错:Type mismatch |
State := 'Running'; |
字符串不能隐式转枚举 | State := StateType.Running; |
| 运行异常:状态不更新 | State := StateType.Idle; 但监视值不变 |
变量 State 声明为 INT 而非 StateType |
检查 VAR 区:State : StateType; |
| 下载失败:枚举名重复 | TYPE StateType : (...); TYPE StateType : (...); |
同一作用域重复定义类型 | 删除重复声明,或重命名(如 MotorStateType) |
| HMI显示乱码 | TO_STRING(State) 返回空字符串 |
未启用 STRING 标准库 |
在项目设置中勾选 String Library |
五、扩展思考:枚举不是万能的——何时该换方案?
枚举适用于状态有限、静态、业务含义明确的场景。遇到以下情况,应切换策略:
- 状态动态生成(如配方编号由HMI录入):改用
ARRAY[0..999] OF STRING+ 查找逻辑; - 状态需携带参数(如
Faulted(ErrorCode.OverTemp, 120.5)):改用结构体TYPE FaultInfo : STRUCT Code : INT; Temp : REAL; END_STRUCT; - 跨设备统一状态码(如PROFINET IO设备规范):采用预定义的
UINT常量集,并配CASE映射,确保与设备手册一致。
记住:可读性源于语义匹配,而非语法炫技。枚举的价值,在于让 StateType.Running 这七个字符,比一行注释更准确地传达意图。
State := StateType.Run;
这不是一句代码,是自动化工程师写给未来自己的承诺:
这里没有猜测,只有定义;
没有遗忘,只有引用;
没有停机,只有确定。

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