ST语言(Structured Text)是IEC 61131-3标准定义的五大PLC编程语言之一,广泛用于工业自动化控制系统中。其语法接近Pascal,支持变量声明、条件判断、循环、函数调用和结构化表达式,适用于复杂逻辑与数学运算。但正因其表达力强,也隐藏着一类隐蔽却致命的风险:除零错误未捕获导致的程序停机。
这类异常不触发常规报警,不写入诊断缓冲区,不进入错误处理分支,而是直接中断当前任务周期,造成主循环“卡死”——表现为:输出冻结、定时器停摆、HMI刷新停滞、通讯心跳超时,最终引发设备急停、产线中断或安全回路动作。更棘手的是,它常在低概率工况下复现(如传感器信号突变、参数配置遗漏、手动调试误输),难以通过常规测试覆盖。
以下为一套完整、可立即落地的异常处理机制,覆盖预防、检测、隔离、恢复四层防线,全部基于标准ST语言实现,无需专用库或厂商扩展指令。
一、理解ST语言中除零错误的本质行为
在绝大多数主流PLC平台(如西门子S7-1500、倍福TwinCAT、罗克韦尔ControlLogix、施耐德Modicon M340/M580)中,ST语言对整数(INT、DINT)和实数(REAL、LREAL)的除法运算,遵循如下默认行为:
- 对
INT/DINT类型:执行整数除法。当除数为0时,结果值不确定(UNDEFINED),且不抛出运行时异常;后续若对该结果进行赋值、比较或参与计算,多数平台会将该变量标记为“无效值”,并在后续周期中导致整个任务(Task)被系统挂起。 - 对
REAL/LREAL类型:执行浮点除法。IEEE 754标准规定x / 0.0应返回±INF或NaN。但PLC固件通常不启用浮点异常中断,也不自动将INF/NaN转为错误状态;若程序后续对INF做> 100.0判断,或对NaN做= 0.0比较,结果恒为FALSE,逻辑悄然失效;若传入函数块(如MOVE、SCALE),则可能触发内部校验失败,导致函数块输出锁定或任务跳过。
关键结论:
ST语言本身不提供类似高级语言的 try...catch 机制;除零不是“报错”,而是“埋雷”。
因此,防御必须前置——在除法发生前,强制校验除数有效性。
二、四级防御机制:从编码规范到运行时兜底
防御层级1:编码强制规范(开发阶段)
所有涉及除法的ST代码,必须采用统一模板,禁止裸写 / 运算符。
// ✅ 正确模板:带零值防护的除法封装
FUNCTION_DIV_SAFE : REAL
VAR_INPUT
pNumerator : REAL; // 被除数
pDenominator : REAL; // 除数
pDefault : REAL := 0.0; // 除数为零时返回值
pTolerance : REAL := 1.0E-6; // 浮点零判定阈值
END_VAR
VAR
bIsZero : BOOL;
END_VAR
bIsZero := ABS(pDenominator) < pTolerance;
IF bIsZero THEN
FUNCTION_DIV_SAFE := pDefault;
ELSE
FUNCTION_DIV_SAFE := pNumerator / pDenominator;
END_IF
使用方式:
// 替换原有:SpeedRef := SetPoint / GearRatio;
SpeedRef := DIV_SAFE(SetPoint, GearRatio, 0.0, 1.0E-6);
⚠️ 注意:
pTolerance不可设为0.0(浮点比较不可靠),典型值1.0E-6适用于大多数工艺量程(如转速0–3000 rpm,位置0–10 m)。
对整数运算,同样需封装:
FUNCTION_DIV_INT_SAFE : DINT
VAR_INPUT
pNumerator : DINT;
pDenominator : DINT;
pDefault : DINT := 0;
END_VAR
IF pDenominator = 0 THEN
FUNCTION_DIV_INT_SAFE := pDefault;
ELSE
FUNCTION_DIV_INT_SAFE := pNumerator / pDenominator; // PLC整数除法自动截断
END_IF
防御层级2:运行时动态监控(调试与维护阶段)
在主程序循环中嵌入全局除数监控模块,实时捕获“高风险除数”变化趋势:
// 全局变量声明(在全局DB或全局变量表中)
g_DivMonitor : STRUCT
LastDenominator : REAL; // 上周期除数
CurrentDenominator : REAL; // 当前除数
IsDenominatorStable : BOOL; // 连续3周期变化 < 0.1%
StabilityCounter : UINT; // 稳定计数器
ZeroCrossingDetected : BOOL; // 本次检测到跨零
END_STRUCT
// 主程序中调用(例如在MAIN组织块顶部)
g_DivMonitor.CurrentDenominator := GearRatio; // 示例:监控齿轮比变量
g_DivMonitor.ZeroCrossingDetected :=
(g_DivMonitor.LastDenominator >= 0.0) AND
(g_DivMonitor.CurrentDenominator < 0.0) OR
(g_DivMonitor.LastDenominator <= 0.0) AND
(g_DivMonitor.CurrentDenominator > 0.0);
// 更新稳定性判断(变化率 < 0.1% 视为稳定)
IF ABS(g_DivMonitor.CurrentDenominator - g_DivMonitor.LastDenominator)
< (0.001 * MAX(ABS(g_DivMonitor.CurrentDenominator), ABS(g_DivMonitor.LastDenominator), 1.0)) THEN
g_DivMonitor.StabilityCounter := g_DivMonitor.StabilityCounter + 1;
IF g_DivMonitor.StabilityCounter > 2 THEN
g_DivMonitor.IsDenominatorStable := TRUE;
END_IF;
ELSE
g_DivMonitor.StabilityCounter := 0;
g_DivMonitor.IsDenominatorStable := FALSE;
END_IF;
g_DivMonitor.LastDenominator := g_DivMonitor.CurrentDenominator;
此模块不干预逻辑,仅生成诊断信号:
g_DivMonitor.ZeroCrossingDetected = TRUE→ 触发HMI弹窗:“齿轮比变量跨越零点,请检查传感器接线或参数配置”g_DivMonitor.IsDenominatorStable = FALSE持续5秒 → 启动慢速采样模式,降低控制频率至1 Hz,避免高频除零冲击
防御层级3:任务级隔离(系统架构阶段)
将高风险计算(如PID参数在线整定、流量补偿、电机滑差计算)部署在独立的低优先级任务中,与主控任务(如轴同步、安全逻辑)物理隔离。
| 任务名称 | 优先级 | 周期 | 承载内容 | 异常影响 |
|---|---|---|---|---|
MAIN_CTRL |
高(10) | 2 ms | 安全I/O扫描、轴位置环、急停链 | 任一周期失败 → 系统停机 |
CALC_SAFETY |
中(5) | 100 ms | 温度补偿、负载估算、除法密集型算法 | 单次失败 → 本任务跳过,MAIN_CTRL 不受影响 |
DIAG_MONITOR |
低(1) | 1 s | 除数趋势分析、历史极值记录、日志写入 | 失败无影响 |
配置方法(以TIA Portal为例):
- 在项目树中右键“Tasks” → “Add new task”
- 名称填
CALC_SAFETY,类型选Cyclic,周期设100 ms - 将所有含
/运算的FB/FC拖入该任务组织块(OB) - 关键操作:在
CALC_SAFETYOB顶部插入以下代码:
// 任务内看门狗:若单次执行超时,则强制复位本任务上下文
IF ExecutionTime_ms > 80.0 THEN // 预留20%余量
// 清空所有中间计算变量(如临时数组、累加器)
FOR i := 0 TO 99 DO
TempArray[i] := 0.0;
END_FOR;
IntegralTerm := 0.0;
// 记录超时事件到诊断缓冲区
g_DiagLog[LogIndex].Code := 1024;
g_DiagLog[LogIndex].Time := T#0s;
LogIndex := (LogIndex + 1) MOD 100;
END_IF
防御层级4:硬件级兜底(工程交付阶段)
当软件层全部失效时,依赖PLC硬件机制实现“最后屏障”:
-
启用看门狗定时器(Watchdog Timer)
所有主流PLC均支持任务级看门狗。以西门子S7-1500为例:- 在设备配置中打开“Properties” → “Cycle/Clock Memory”
- 勾选
Enable watchdog for this task - 设置超时时间 ≥ 1.5 × 任务周期(如100 ms任务设为150 ms)
- 关键设置:选择
Reaction: Restart task(而非Stop CPU)
效果:一旦
CALC_SAFETY因除零卡死,150 ms后系统自动重启该任务,清除所有寄存器状态,主任务MAIN_CTRL持续运行。 -
配置故障安全输入(F-DI)作为除数硬限位
若除数来自外部模拟量(如压力变送器4–20 mA),可将电流信号同时接入普通AI通道与F-DI通道:- 普通AI通道用于计算
- F-DI通道配置为“电流低于3.5 mA时置位FQ(Fault Quality)标志”
- 在除法前插入硬约束:
IF NOT FQ_PressureSensor THEN PressureComp := DIV_SAFE(MeasuredPressure, BasePressure, 1.0, 1.0E-3); ELSE PressureComp := 1.0; // 安全默认值 // 同时触发F-Alarm:要求人工确认传感器状态 END_IF
三、真实故障复现与验证方法
为确保机制有效,必须进行三类验证:
-
主动注入测试
在HMI上添加调试按钮,点击后向GearRatio变量写入0.0,观察:DIV_SAFE是否返回0.0(非崩溃)g_DivMonitor.ZeroCrossingDetected是否在下一个周期置位CALC_SAFETY任务是否在150 ms后自动恢复(用TIA Portal的“Cycle Time Monitor”查看)
-
边界值压力测试
使用脚本连续写入序列:1E-7,1E-8,0.0,-1E-8,-1E-7,验证:pTolerance = 1.0E-6能正确捕获所有|x| < 1E-6的“准零”值ABS()函数对负数零判定无偏差
-
长期老化测试
运行72小时,每10分钟自动记录:g_DivMonitor.CurrentDenominator极小值DIV_SAFE调用次数中返回默认值的占比CALC_SAFETY任务重启次数
若占比 > 0.1%,说明现场存在持续性传感器漂移,需校准硬件。
四、常见误区与纠正
| 误区描述 | 错误示例 | 正确做法 |
|---|---|---|
| 认为REAL除零会自动报错 | IF MyRealVar / 0.0 > 10.0 THEN ... |
必须先 IF ABS(0.0) > 1E-6 THEN ... |
| 用整数比较替代浮点容差 | IF GearRatio = 0 THEN ... |
改为 IF ABS(GearRatio) < 1E-6 THEN ... |
| 在FB内部隐藏除法不暴露 | FB内直接写 Output := A / B; |
将B作为IN接口,调用方负责预检;或FB内部用DIV_SAFE |
| 依赖HMI显示判断异常 | HMI显示“--”就认为正常 | HMI必须绑定g_DivMonitor.ZeroCrossingDetected,显示红色告警图标 |
五、附录:可直接导入的标准化函数块(ST代码)
// 文件名:DIV_SAFE.FB (符合IEC 61131-3标准)
FUNCTION_BLOCK DIV_SAFE
VAR_INPUT
Numerator : REAL;
Denominator : REAL;
Default : REAL := 0.0;
Tolerance : REAL := 1.0E-6;
END_VAR
VAR_OUTPUT
Result : REAL;
IsValid : BOOL; // TRUE=除法成功,FALSE=使用默认值
END_VAR
VAR
bIsZero : BOOL;
END_VAR
bIsZero := ABS(Denominator) < Tolerance;
IF bIsZero THEN
Result := Default;
IsValid := FALSE;
ELSE
Result := Numerator / Denominator;
IsValid := TRUE;
END_IF
使用时,在调用处声明实例:
fbDiv1 : DIV_SAFE;
...
fbDiv1(Numerator:=SetPoint, Denominator:=GearRatio);
SpeedRef := fbDiv1.Result;
IF NOT fbDiv1.IsValid THEN
// 触发降级模式:切换至开环控制
ControlMode := OPEN_LOOP;
END_IF
暂无评论,快来抢沙发吧!