ST(Structured Text)是IEC 61131-3标准定义的高级文本编程语言,广泛用于PLC(可编程逻辑控制器)开发。它语法接近Pascal,支持条件判断、循环、函数调用和结构化数据操作,是实现复杂控制逻辑的首选。但ST代码的可读性不等于可维护性——一段能正确运行的代码,可能因缩进混乱、换行随意、命名模糊,在三个月后连原作者都难以快速定位问题。本文聚焦三个可立即落地、零成本、却长期被忽视的规范化动作:缩进、换行与命名风格,并用真实工程案例说明它们如何直接决定故障排查耗时、协作效率与系统寿命。
一、缩进:不是美观问题,是逻辑分层的视觉锚点
ST代码没有强制缩进语法(不像Python),但PLC编辑器(如TIA Portal、Codesys、Unity Pro)普遍支持自动缩进。若关闭或忽略缩进,嵌套层级将完全依赖括号和关键字判断,极易误判作用域。
错误示范(无缩进或缩进不一致):
IF MotorStart = TRUE THEN
MotorRunning := TRUE;
IF TempSensor > 85 THEN
Alarm := TRUE;
FanSpeed := 100;
ELSE
FanSpeed := 50;
END_IF;
ELSE
MotorRunning := FALSE;
END_IF;
这段代码逻辑上正确,但存在两个致命隐患:
FanSpeed := 50;看似属于ELSE分支,实则属于外层IF MotorStart = TRUE的ELSE;Alarm := TRUE;和FanSpeed := 100;同级缩进,却分属不同嵌套深度,人为制造认知负荷。
正确做法:
- 统一使用4个空格缩进(禁用Tab键)。Tab在不同编辑器中宽度不一致(Windows默认4,Linux常为8),易导致协作时格式错乱;
- 每层嵌套严格增加一级缩进,包括
IF/ELSIF/ELSE、CASE、FOR、WHILE、REPEAT及其对应END_结构; BEGIN/END块必须缩进(即使单行语句),因为ST允许省略BEGIN/END,但显式写出并缩进可杜绝歧义。
修正后:
IF MotorStart = TRUE THEN
MotorRunning := TRUE;
IF TempSensor > 85 THEN
Alarm := TRUE;
FanSpeed := 100;
ELSE
FanSpeed := 50;
END_IF;
ELSE
MotorRunning := FALSE;
END_IF;
关键变化:
- 外层
IF下所有语句缩进4空格; - 内层
IF在外层基础上再缩进4空格(共8空格); END_IF与对应IF对齐(即缩进量相同),形成“门框式”视觉闭环。
✅ 实测数据:某汽车焊装线PLC项目,将32台设备的ST程序统一缩进后,新人熟悉单个控制单元平均耗时从47分钟降至19分钟;售后工程师处理“误触发报警”类故障,平均定位时间缩短63%(从22分钟→8.2分钟)。
二、换行:控制语句长度,避免水平滚动灾难
ST代码常需处理长表达式(如多传感器加权计算)、复杂条件组合(如安全联锁链)、结构体字段链式访问。强行写在一行会导致:
- 编辑器水平滚动条激活,被迫左右拖拽,丢失上下文;
- 打印调试时自动折行,折断位置不可控,日志难解析;
- 版本对比工具(如Git)将整行标为变更,掩盖真实修改点。
核心原则:单行代码≤80字符(经典终端宽度,兼顾IDE默认设置与打印需求)。
2.1 条件表达式换行规范
禁止将多个 AND/OR 连接的条件挤在一行:
IF (StartButton = TRUE) AND (SafetyDoorClosed = TRUE) AND (NoOverloadAlarm = TRUE) AND (CoolantLevelOK = TRUE) THEN ...
正确换行方式(运算符前置,每条件独占一行):
IF (StartButton = TRUE)
AND (SafetyDoorClosed = TRUE)
AND (NoOverloadAlarm = TRUE)
AND (CoolantLevelOK = TRUE) THEN
理由:
AND/OR是逻辑连接词,置于行首清晰表明“这是前一行条件的延续”;- 每个括号内条件独立、完整,便于逐项注释或临时注释某一项;
- Git diff仅标记被修改的那行,而非整块条件。
2.2 赋值语句换行规范
长计算表达式优先在运算符后换行:
// 错误:运算符后无空格,且换行位置割裂语义
PositionError := ABS(ActualPos - TargetPos) * GainKp
+ (ActualVel - TargetVel) * GainKd
+ IntegralSum * GainKi;
// 正确:运算符前置,对齐,保留空格
PositionError := ABS(ActualPos - TargetPos) * GainKp
+ (ActualVel - TargetVel) * GainKd
+ IntegralSum * GainKi;
2.3 结构体与数组访问换行
深层嵌套访问必须分行:
// 错误:一行到底,无法快速识别层级
MachineState.Conveyor[3].Motor.Temperature.SensorValue := TempRead;
// 正确:每级访问独占一行,`.` 对齐
MachineState
.Conveyor[3]
.Motor
.Temperature
.SensorValue := TempRead;
✅ 工程验证:某包装机械OEM厂商对12个ST函数块实施换行规范后,代码审查中发现的逻辑错误率下降41%,其中73%为因长行误读导致的括号配对错误或字段名拼写混淆。
三、命名风格:用名字讲清“谁、做什么、何时用”
ST变量、函数、FB(功能块)的命名不是个人喜好,而是接口契约。模糊命名(如 Data1, Flag, Temp)迫使读者反复跳转查定义;过长命名(如 TheCurrentActualTemperatureValueOfTheMainHeatingElementInDegreeCelsius)反而降低扫描效率。
3.1 基础命名规则(必须遵守)
| 类型 | 前缀 | 示例 | 说明 |
|---|---|---|---|
| 输入变量 | i_ |
i_StartButton |
i_ = input,明确该变量只读,来自HMI或外部信号 |
| 输出变量 | o_ |
o_MotorPower |
o_ = output,明确该变量只写,驱动执行器 |
| 内部变量 | m_ |
m_TimerCount |
m_ = memory,程序内部暂存、中间计算结果 |
| 全局变量 | g_ |
g_SystemMode |
g_ = global,跨POU(程序组织单元)共享,需谨慎使用 |
| 函数/方法 | F_ |
F_CalculatePID |
F_ = function,返回单一值 |
| 功能块 | FB_ |
FB_ValveControl |
FB_ = function block,含内部状态(如定时器、计数器) |
严禁使用下划线开头(如 _Temp)或纯数字开头(如 1stRun),因部分PLC平台不支持。
3.2 名称主体:名词+动词短语,拒绝缩写黑话
- ✅ 推荐:
i_SafetyDoorOpen,o_ConveyorBeltRun,m_AxisPositionError - ❌ 禁止:
i_DOOR,o_BELT,m_ERRPOS(缩写无上下文,ERRPOS是位置误差?还是错误位置?)
特例:行业通用缩写可接受,但需全局统一并在项目规范文档中明确定义:
PWM(脉宽调制)、PID(比例积分微分)、HMI(人机界面)、IO(输入输出)- 禁止自创缩写:
Mtr(Motor)、Tmp(Temperature)、Cnt(Count)
3.3 布尔变量:用肯定式动词,避免双重否定
- ✅
i_EmergencyStopPressed,o_ValveOpenCommand,m_DriveReady - ❌
i_EmergencyStopNotPressed,o_ValveNotClosed,m_DriveNotReady
理由:布尔值天然具象,“Not”增加大脑解码步骤,且易在条件中引发NOT NOT嵌套错误。
✅ 案例:某半导体晶圆搬运设备项目,将原有27个FB中模糊命名变量全部重构。上线后首次重大升级(增加真空检测逻辑),开发人员新增代码耗时减少55%,因无需花时间反向推导
VarX实际含义。
四、三者协同:一个真实故障修复场景
某灌装线PLC出现偶发性“灌装量偏差±5ml”问题。原始代码片段(已脱敏):
IF i_FlowMeterPulse > 0 AND i_FlowMeterPulse < 10000 THEN
FlowRate := REAL_TO_INT(i_FlowMeterPulse * 0.025);
IF FlowRate > o_TargetFlowRate * 1.1 OR FlowRate < o_TargetFlowRate * 0.9 THEN
o_AlarmFlowDeviation := TRUE;
ELSE
o_AlarmFlowDeviation := FALSE;
END_IF;
END_IF;
问题定位耗时:3天
- 缩进缺失:
o_AlarmFlowDeviation := FALSE;看似属内层IF,实为外层IF的ELSE,导致报警逻辑失效; - 换行不当:
o_TargetFlowRate * 1.1未加括号,实际执行o_TargetFlowRate * (1.1 OR FlowRate)(因运算符优先级误读); - 命名模糊:
i_FlowMeterPulse未体现单位(是脉冲数?还是已转换的流量?),o_TargetFlowRate未说明是设定值还是反馈值。
规范化后:
IF (i_FlowMeterPulseCount > 0)
AND (i_FlowMeterPulseCount < 10000) THEN
m_FlowRateLitersPerMin := REAL_TO_INT(
i_FlowMeterPulseCount * 0.025
);
IF (m_FlowRateLitersPerMin > (o_SetpointFlowRate_LPM * 1.1))
OR (m_FlowRateLitersPerMin < (o_SetpointFlowRate_LPM * 0.9)) THEN
o_Alarm_FlowDeviation := TRUE;
ELSE
o_Alarm_FlowDeviation := FALSE;
END_IF;
END_IF;
效果:
- 新工程师首次查看即指出:
o_SetpointFlowRate_LPM是设定值,但灌装阀实际开度由m_ActualValvePosition控制,偏差根源在PID参数整定不足; - 故障复现与修复总耗时:4小时。
五、落地执行清单(可直接嵌入团队规范)
| 项目 | 执行动作 | 工具建议 |
|---|---|---|
| 缩进 | 在PLC编辑器中启用“4空格缩进”,关闭Tab插入 | TIA Portal:Options → Settings → Editor → Indentation |
| 换行 | 将 IF/FOR/WHILE 条件行长度上限设为80字符,超长必拆 |
Codesys:Tools → Options → Editor → Line wrapping off |
| 命名 | 创建《项目命名词典》Excel,列出所有前缀、物理量标准缩写、单位后缀(如_LPM) |
使用Excel数据验证功能强制选择列表 |
| 自动检查 | 集成静态代码分析工具(如Codesys自带Code Analysis,或定制Python脚本) | 检查缩进空格数、行长度、命名前缀合规性 |
| 代码审查 | 将三项规范列为PR(Pull Request)必检项,任一不达标即驳回 | GitLab CI配置检查脚本 |
六、为什么这些“小习惯”决定自动化系统的生死?
电气自动化系统生命周期常达15年以上。最初编写代码的工程师可能早已离职,而设备仍在产线日夜运行。此时,代码不是艺术品,而是维修手册、是交接文档、是故障时唯一的真相来源。
- 缩进 是代码的骨骼,确保逻辑层级不坍塌;
- 换行 是代码的呼吸,让长表达式可分解、可追踪;
- 命名 是代码的语言,让变量名自己说出用途、范围与约束。
它们不提升单次运行性能,但直接决定:
- 下一位工程师读懂你意图所需的时间;
- 安全联锁逻辑被意外修改的概率;
- 紧急停机指令是否真能切断动力源——而不是因
o_MotorStop被误写成o_MotorStart的倒置逻辑而失效。
规范化不是束缚创造力,而是把有限的认知资源,从“猜代码意思”转移到“解决真正的问题”。

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