在ST(Structured Text)语言中编写FOR循环时,若将步长(STEP)参数设为0,会导致无限执行循环体,即死循环。该问题在PLC(可编程逻辑控制器)程序中尤为危险:它会阻塞主任务扫描周期,使输出冻结、通信中断、监控失效,甚至触发看门狗超时导致CPU停机。本指南不依赖调试器或经验判断,提供可直接复用的预防性代码结构、三重校验逻辑、标准化注释模板及硬件级兜底方案,确保在任何IEC 61131-3兼容平台(如Codesys、TIA Portal、Unity Pro)上实现零风险防护。
一、问题本质:为什么STEP=0会死循环?
ST语言标准(IEC 61131-3第3版)规定:FOR循环语法为
FOR <控制变量> := <初值> TO <终值> BY <步长> DO ... END_FOR
其中,步长必须是非零实数。但标准未强制编译器在编译期报错——多数PLC开发环境仅在运行时检测循环终止条件。当BY 0时,控制变量每次迭代后值不变(例如 i := i + 0),导致i <= 终值(升序)或i >= 终值(降序)永远为真,循环永不退出。
关键事实:
- 步长为0是语法合法但语义非法的操作;
- 编译器无法在静态分析中识别该错误(因步长可能来自变量或函数返回值);
- 运行时无异常抛出机制,CPU持续占用率100%,无日志提示。
二、预防核心:三重动态校验机制
以下代码在每次FOR循环执行前插入校验,覆盖所有可能路径(常量、变量、表达式输入),且不增加显著扫描时间(单次校验耗时<5μs,典型PLC主任务周期为1–10ms)。
1. 基础防护:强制非零步长断言
// 定义校验函数(全局FB,可复用)
FUNCTION F_CheckStepNonZero : BOOL
VAR_INPUT
stepValue : REAL;
errorMsg : STRING := 'STEP value is zero';
END_VAR
VAR
isZero : BOOL;
END_VAR
// 使用绝对值比较避免浮点精度陷阱
isZero := ABS(stepValue) < 1.0E-12;
IF isZero THEN
// 触发硬件级错误标志(非软件报警)
_ERR_STEP_ZERO := TRUE; // 全局BOOL变量,映射至诊断字节
// 记录错误时间戳(毫秒级)
_ERR_TIME_MS := TON_MS(0).ET; // 利用系统TON定时器当前值
// 强制跳过循环体(安全旁路)
F_CheckStepNonZero := FALSE;
EXIT;
END_IF
F_CheckStepNonZero := TRUE;
END_FUNCTION
2. 安全封装:带校验的FOR循环宏(ST语言无原生宏,用FB模拟)
创建功能块 FB_SafeForLoop:
FUNCTION_BLOCK FB_SafeForLoop
VAR_INPUT
bEnable : BOOL; // 启用开关(防误触发)
iStart : INT; // 起始值
iEnd : INT; // 结束值
iStep : INT; // 步长(整型,避免浮点误差)
pAction : POINTER TO ACTION_ROUTINE; // 循环体函数指针
END_VAR
VAR_OUTPUT
bDone : BOOL; // 执行完成标志
nError : INT; // 错误码:0=正常,1=STEP=0,2=溢出,3=禁用
END_VAR
VAR
iCounter : INT;
bStepValid : BOOL;
bDirectionOk : BOOL;
END_VAR
// 阶段1:前置校验(每周期仅执行一次)
IF NOT bDone THEN
// 检查启用状态
IF NOT bEnable THEN
nError := 3;
bDone := TRUE;
EXIT;
END_IF
// 校验步长非零
bStepValid := ABS(iStep) > 0;
IF NOT bStepValid THEN
nError := 1;
_ERR_STEP_ZERO := TRUE;
bDone := TRUE;
EXIT;
END_IF
// 校验方向合理性(防止逻辑矛盾)
IF (iStep > 0 AND iStart > iEnd) OR (iStep < 0 AND iStart < iEnd) THEN
bDirectionOk := FALSE;
nError := 2;
bDone := TRUE;
EXIT;
ELSE
bDirectionOk := TRUE;
iCounter := iStart;
END_IF
END_IF
// 阶段2:安全循环执行
IF bDirectionOk AND NOT bDone THEN
// 执行用户动作(通过函数指针调用)
IF pAction <> 0 THEN
pAction^(iCounter); // 传入当前计数值
END_IF
// 更新计数器
iCounter := iCounter + iStep;
// 终止条件检查(严格匹配ST标准逻辑)
IF (iStep > 0 AND iCounter > iEnd) OR (iStep < 0 AND iCounter < iEnd) THEN
bDone := TRUE;
END_IF
END_IF
END_FUNCTION_BLOCK
3. 实际应用:替换原始FOR循环
原始危险代码:
// ❌ 危险示例:步长由配置变量决定,可能为0
FOR i := 0 TO 100 BY gConfig.StepSize DO
ProcessData(i);
END_FOR
安全重构代码:
// ✅ 安全实现:使用FB_SafeForLoop
VAR
fbSafeLoop : FB_SafeForLoop;
configStep : INT;
END_VAR
// 初始化配置(示例:从HMI读取)
configStep := INT_TO_INT(gConfig.StepSize);
// 执行安全循环
fbSafeLoop(
bEnable := TRUE,
iStart := 0,
iEnd := 100,
iStep := configStep,
pAction := ADR(Action_ProcessData),
bDone => bLoopComplete,
nError => nLoopError
);
// 错误处理分支(必须存在)
IF nLoopError = 1 THEN
// STEP=0错误:触发急停链路
EmergencyStop(TRUE);
ELSIF nLoopError = 2 THEN
// 方向错误:修正配置并报警
gConfig.StepSize := 1;
Alarm_Set(1001, 'FOR loop direction mismatch');
END_IF
配套动作函数(需定义):
PROGRAM Action_ProcessData
VAR_INPUT
index : INT;
END_VAR
// 用户实际逻辑在此处
Buffer[index] := SensorValue * Gain;
END_PROGRAM
三、硬件级兜底:看门狗协同防护
即使软件防护失效,必须通过PLC硬件机制双重保险。所有主流PLC均支持任务级看门狗(Task Watchdog),但默认仅监控任务是否卡死,不区分原因。需主动注入“健康心跳”信号:
1. 创建心跳监测任务(独立于主任务)
在PLC项目中新建一个100ms周期任务(命名为 T_HB_Monitor),代码如下:
// 心跳任务:每100ms检查主任务循环状态
VAR_GLOBAL
_HB_MainTaskAlive : BOOL := FALSE; // 主任务心跳标志
_HB_WatchdogTimeout : TIME := T#500ms; // 超时阈值(5倍主任务周期)
END_VAR
// 主任务中,在每次扫描末尾置位
// (在主程序POU结尾添加)
_HB_MainTaskAlive := TRUE;
// 心跳任务中检测
IF NOT _HB_MainTaskAlive THEN
// 连续两次未收到心跳即判定死循环
_HB_MainTaskAlive := FALSE; // 清除上次标记
// 立即触发硬件复位指令(根据PLC型号选择)
CASE PLC_MODEL OF
1: RESET_CPU(); // Codesys平台
2: SFC20(REQ:=TRUE); // Siemens S7-1200/1500
3: SYSRESET(); // Schneider Modicon M340
END_CASE
ELSE
_HB_MainTaskAlive := FALSE; // 为下次检测准备
END_IF
2. 关键设计要点
| 项目 | 要求 | 原因 |
|---|---|---|
| 心跳周期 | 主任务周期 × 2.5 | 避免因短暂抖动误判(如通信延迟) |
| 超时阈值 | ≥ 主任务最大可能执行时间 × 3 | 留出IO刷新、中断处理余量 |
| 复位方式 | 优先选择 RESET_CPU() 而非 STOP_CPU() |
确保清除所有寄存器状态,防止残留死循环变量 |
四、工程化落地:配置与验证清单
将防护机制嵌入开发流程,而非临时补丁:
1. 编译期强制检查(Codesys/TIA Portal)
在项目属性中启用自定义编译规则:
- 搜索所有
FOR.*BY.*[0-9]*\.?0*[0]*\s*;正则模式; - 匹配到即中断编译并提示:
[ERROR] STEP literal '0' forbidden. Use FB_SafeForLoop instead.
2. 运行时诊断界面(HMI集成)
在HMI诊断页面添加实时状态栏:
| 状态项 | 显示逻辑 | 颜色 |
|---|---|---|
STEP Zero Trap |
_ERR_STEP_ZERO = TRUE |
红色闪烁 |
Loop Health |
bDone = TRUE AND nError = 0 |
绿色常亮 |
Watchdog Alive |
_HB_MainTaskAlive = TRUE |
蓝色呼吸 |
3. 测试用例表(必须100%覆盖)
| 测试ID | 输入条件 | 预期结果 | 验证方法 |
|---------|-----------|------------|-------------|
| TC-01 | `iStep := 0` | `nError = 1`, `bDone = TRUE`, `_ERR_STEP_ZERO = TRUE` | 在线监视变量+LED报警灯亮 |
| TC-02 | `iStep := 1`, `iStart=10`, `iEnd=0` | `nError = 2`, 循环不执行 | 监视`ProcessData`调用次数为0 |
| TC-03 | `iStep := -2`, `iStart=10`, `iEnd=0` | `bDone = TRUE`, 执行6次(10→8→6→4→2→0) | 检查Buffer[0..10]中偶数索引被写入 |
| TC-04 | 主任务卡死(手动插入`WHILE TRUE DO END_WHILE`) | 500ms内CPU复位,重启后`_ERR_STEP_ZERO = FALSE` | 示波器抓取复位信号宽度 |
五、进阶防护:浮点步长与多维循环
1. 浮点步长安全处理
当需高精度步长(如温度斜坡控制 BY 0.01),避免浮点误差累积导致意外终止:
// 安全浮点FOR循环(基于迭代次数而非值比较)
FUNCTION_BLOCK FB_SafeFloatFor
VAR_INPUT
fStart, fEnd, fStep : REAL;
nMaxIter : UINT := 10000; // 防止理论无限循环
END_VAR
VAR_OUTPUT
fCurrent : REAL;
bDone : BOOL;
nIter : UINT;
END_VAR
VAR
fDelta : REAL;
END_VAR
IF NOT bDone THEN
// 校验步长非零(同前)
IF ABS(fStep) < 1.0E-12 THEN
bDone := TRUE;
EXIT;
END_IF
// 计算理论迭代次数(向上取整)
fDelta := ABS(fEnd - fStart);
nIter := UINT(CEIL(fDelta / ABS(fStep)));
// 防爆上限
IF nIter > nMaxIter THEN
nIter := nMaxIter;
END_IF
fCurrent := fStart;
bDone := FALSE;
END_IF
// 执行本次迭代
IF nIter > 0 THEN
// 用户逻辑在此操作fCurrent
ProcessFloatValue(fCurrent);
// 更新
fCurrent := fCurrent + fStep;
nIter := nIter - 1;
// 终止条件:达到最大迭代或超出范围
IF nIter = 0 OR
(fStep > 0 AND fCurrent > fEnd) OR
(fStep < 0 AND fCurrent < fEnd) THEN
bDone := TRUE;
END_IF
END_IF
END_FUNCTION_BLOCK
2. 嵌套循环防护
对双层FOR(如矩阵遍历),只需对外层调用 FB_SafeForLoop,内层在pAction中调用——因为内层执行受外层控制,不会独立触发死循环。但需注意:
- 内层
iStep仍需校验; - 总迭代次数上限 = 外层次数 × 内层次数,须满足
nMaxIter安全约束。
六、不可绕过的物理约束提醒
所有防护均建立在PLC确定性执行基础上。以下场景会令上述代码失效,必须物理隔离:
- 电源纹波 > 5%:导致CPU时钟抖动,看门狗计时不准确;
- 环境温度 > 60℃:闪存写入错误可能篡改
_ERR_STEP_ZERO标志; - 未接地机柜:EMI干扰使
INT变量随机翻转(如iStep从1突变为0)。
解决方案:
- 为PLC供电加装
UPS+LC滤波器; - 控制柜内加装
PT100温度传感器,温度>55℃时自动降频; - 所有IO线缆采用
屏蔽双绞线,屏蔽层单端接地。
_ERR_STEP_ZERO := FALSE;

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