ST扫描周期超标:复杂循环导致看门狗超时的排查方法
在基于IEC 61131-3标准的PLC编程中,结构化文本(Structured Text,ST)因其接近高级语言的表达能力,常被用于实现复杂逻辑、数学运算、状态机和循环控制。但正因灵活性高,不当使用循环结构极易引发扫描周期(Scan Cycle Time)严重超标,最终触发硬件看门狗(Watchdog Timer)复位——表现为PLC无预警停机、输出突变、HMI通信中断,且故障日志仅显示“Watchdog timeout”或“Cycle time exceeded”,不指向具体代码位置。本文聚焦此类典型故障,提供一套无需依赖专用分析工具、纯靠PLC原生功能与系统性推理即可定位并解决的实操排查流程。
一、理解问题本质:为什么ST循环会拖垮扫描周期?
PLC执行遵循“循环扫描”机制:每个扫描周期内,CPU依次完成输入采样 → 程序执行 → 输出刷新。扫描周期时间 = 输入处理时间 + 用户程序执行时间 + 输出处理时间 + 系统开销。其中,用户程序执行时间完全由用户代码决定。
ST语言支持 FOR、WHILE、REPEAT 循环,其执行耗时与循环次数 × 单次迭代耗时成正比。问题常出现在以下场景:
- 隐式无限循环:
WHILE TRUE DO ... END_WHILE未设可靠退出条件,或退出判断依赖未更新的静态变量; - 动态大循环:循环上限由外部变量(如
FOR i := 0 TO n DO)控制,而n因传感器误读、通信异常或配置错误被赋值为极大数(如65535); - 嵌套循环叠加:外层循环
100次,内层每次执行200次操作,实际迭代达20,000次,远超毫秒级扫描窗口; - 循环内调用高开销函数:如浮点三角函数
SIN()、字符串分割FIND()、数组拷贝MEMCPY(),单次调用耗时达数十微秒,累积效应显著。
看门狗定时器通常设定为 100 ms 至 500 ms(依PLC型号而异),若单次扫描耗时超过该阈值,CPU强制复位。此时,问题根源不在硬件老化或电源波动,而在代码逻辑本身存在不可控的时间复杂度。
二、快速确认:验证是否为扫描周期超限
在开始深度排查前,先用PLC内置诊断功能做三步确认:
-
启用扫描周期监控:
- 在TIA Portal中,打开 “在线与诊断” → “循环时间” → 勾选 “记录循环时间”;
- 在Codesys中,调用
GET_CYCLE_TIME()函数块,将返回值写入REAL类型变量CycleTime_ms; - 在GX Works3中,进入 “诊断” → “PLC监视” → “扫描时间监视”,点击 “开始监视”。
-
观察实时数值:
- 正常工况下,扫描周期应稳定在
5 ms至30 ms(视CPU性能与程序规模而定); - 若出现
>100 ms的尖峰,且与某设备动作(如电机启动、配方切换)同步,则高度怀疑该动作触发了问题循环。
- 正常工况下,扫描周期应稳定在
-
检查看门狗事件日志:
- 在PLC信息界面查找 “Last reset reason” 或 “Reset source”;
- 明确显示
Watchdog timeout或Cycle time exceeded即可锁定方向。
若以上任一条件不满足,则问题可能源于I/O模块故障、背板总线干扰或电源噪声,需转入硬件排查流程——本文后续步骤不再适用。
三、分层隔离:定位问题代码段的四步法
核心原则:不逐行审阅全部ST代码,而是通过“功能块禁用→周期测量→对比分析”快速缩小范围。
步骤1:按功能模块注释全部POU(Program Organization Unit)
- 打开项目,找到所有
.ST文件(如MAIN.ST,MOTOR_CTRL.ST,DATA_PROCESS.ST); - 在每个POU开头添加唯一标识注释,例如:
// === MODULE: MOTOR_CTRL.ST (ID: M1) === - 对每个POU的调用语句临时注释(非删除),例如:
// MOTOR_CTRL(); // ← 注释此行 // DATA_PROCESS(); // ← 注释此行 - 仅保留
MAIN程序体中最简骨架(如仅Q0.0 := I0.0;),确保PLC能以≤2 ms周期运行; - 下载程序并监视扫描周期,确认基线正常(如
1.8 ms)。
步骤2:逐个解禁POU,捕获周期跳变点
- 取消注释第一个POU调用(如
MOTOR_CTRL();),下载并运行; - 监视扫描周期连续30秒,记录最大值;
- 若周期仍
<10 ms,继续解禁下一个; - 一旦某次解禁后周期跃升至
>80 ms,立即停止——该POU即为嫌疑对象。
✦ 关键技巧:若POU较多,可采用“二分法”加速。例如有8个POU,先解禁前4个,若周期正常,再解禁后4个中的前2个,依此类推。
步骤3:在嫌疑POU内定位问题循环
假设 DATA_PROCESS.ST 被锁定,打开其代码:
-
搜索所有循环关键字:
- 在编辑器中按
Ctrl + F,依次搜索FOR,WHILE,REPEAT; - 记录每处循环的起始行号及循环变量声明(如
FOR i := 0 TO MaxCount DO)。
- 在编辑器中按
-
对每个循环添加临时计时戳:
- 在循环前插入:
StartTime_us := GET_MS_TIMER(); // 使用PLC内置毫秒计时器 - 在循环后插入:
LoopTime_us := GET_MS_TIMER() - StartTime_us; IF LoopTime_us > 5000 THEN // 超过5ms即告警 AlarmFlag := TRUE; AlarmCode := 101; // 自定义错误码 END_IF - 将
AlarmFlag和AlarmCode连接至HMI报警画面,或写入数据日志区。
- 在循环前插入:
-
运行并触发工况:
- 当报警激活时,
AlarmCode直接对应循环编号(如101表示第一个循环),精准定位。
- 当报警激活时,
步骤4:分析循环参数与数据流
定位到具体循环后(例如 FOR i := 0 TO SensorDataLen DO),检查:
SensorDataLen来源:是否来自模拟量模块的原始寄存器?是否未做范围校验?- 添加防护:
IF SensorDataLen > 1000 THEN SensorDataLen := 1000; END_IF;
- 添加防护:
- 循环内操作类型:
- 若含
SQRT(),LN(),EXP()等浮点函数,改用查表法或预计算; - 若含
ARRAY[0..1000] OF REAL遍历,确认是否真正需要全量处理——能否改为增量更新?
- 若含
- 是否存在隐式阻塞:如循环内调用
WAIT指令(虽ST中不常见,但自定义函数可能封装),必须替换为状态机轮询。
四、根治方案:五种安全替代模式
发现问题是起点,重构才是终点。以下为经工业现场验证的安全替代方案,兼顾性能与可维护性:
替代1:大数组处理 → 分片执行(Slice Execution)
❌ 危险写法:
FOR i := 0 TO 5000 DO
Result[i] := ProcessData(RawData[i]);
END_FOR
✅ 安全写法(每次扫描只处理100项):
VAR
SliceStart : INT := 0;
SliceSize : INT := 100;
TotalItems : INT := 5000;
END_VAR
// 每次扫描处理一片
FOR i := SliceStart TO MIN(SliceStart + SliceSize - 1, TotalItems - 1) DO
Result[i] := ProcessData(RawData[i]);
END_FOR
// 更新下一片起始位置
SliceStart := SliceStart + SliceSize;
IF SliceStart >= TotalItems THEN
SliceStart := 0; // 循环回起始
FullProcessDone := TRUE;
END_IF
替代2:条件循环 → 状态机驱动
❌ 危险写法:
WHILE NOT Converged DO
NewValue := CalcNext(NewValue);
Converged := ABS(NewValue - OldValue) < Tolerance;
OldValue := NewValue;
END_WHILE
✅ 安全写法(最多执行20次,超限强制退出):
CASE ConvState OF
0: // 初始化
IterCount := 0;
OldValue := InitialValue;
ConvState := 1;
1: // 迭代计算
NewValue := CalcNext(OldValue);
IterCount := IterCount + 1;
IF ABS(NewValue - OldValue) < Tolerance THEN
FinalResult := NewValue;
ConvState := 3; // 成功
ELSIF IterCount >= 20 THEN
FinalResult := NewValue; // 取当前最优值
ConvState := 2; // 超限警告
ELSE
OldValue := NewValue;
END_IF
2,3: // 终止态,等待复位
// 不再执行计算
END_CASE
替代3:字符串解析 → 预分配缓冲区+索引遍历
❌ 危险写法(FIND 在长字符串中多次调用):
Pos1 := FIND(InputStr, ',');
Pos2 := FIND(COPY(InputStr, Pos1+1, LEN(InputStr)), ',');
✅ 安全写法(单次遍历提取所有字段):
VAR
i, StartPos : INT := 0;
FieldCount : INT := 0;
Fields : ARRAY[0..9] OF STRING[32];
END_VAR
FOR i := 0 TO LEN(InputStr) - 1 DO
IF InputStr[i] = ',' OR i = LEN(InputStr) - 1 THEN
IF i = LEN(InputStr) - 1 THEN
Fields[FieldCount] := COPY(InputStr, StartPos, i - StartPos + 1);
ELSE
Fields[FieldCount] := COPY(InputStr, StartPos, i - StartPos);
END_IF
FieldCount := FieldCount + 1;
StartPos := i + 1;
IF FieldCount >= 10 THEN EXIT; END_IF // 防溢出
END_IF
END_FOR
替代4:实时性要求高的循环 → 移至FB内部,用EXECUTE控制
将循环封装为功能块,通过使能信号控制执行时机:
FUNCTION_BLOCK DataAnalyzer
VAR_INPUT
Enable : BOOL;
RawData : ARRAY[0..999] OF REAL;
DataLen : INT;
END_VAR
VAR_OUTPUT
Result : REAL;
Done : BOOL;
Error : BOOL;
END_VAR
VAR
State : (stIdle, stProcess, stComplete);
i : INT;
END_VAR
CASE State OF
stIdle:
IF Enable THEN
i := 0;
State := stProcess;
END_IF
stProcess:
Result := Result + RawData[i]; // 示例累加
i := i + 1;
IF i >= DataLen THEN
State := stComplete;
END_IF
stComplete:
Done := TRUE;
State := stIdle;
END_CASE
调用时:Analyzer(EXECUTE := TriggerSignal); —— 仅在 TriggerSignal 上升沿执行一次完整分析,避免扫描周期污染。
替代5:绝对避免的写法(黑名单)
以下代码模式必须从项目中彻底清除:
| 危险模式 | 说明 | 替代方案 |
|---|---|---|
WHILE TRUE DO ... END_WHILE |
无退出条件,等同死循环 | 改为 IF 判断 + 状态机 |
FOR i := 0 TO 65535 DO |
上限硬编码为最大值 | 上限必须关联有效数据长度变量,并加 MAX 限制 |
REPEAT ... UNTIL (A AND B AND C) |
多条件组合退出,易因信号抖动失效 | 拆分为独立状态判断,加入防抖计时器 |
循环内调用 DELAY 或 TON |
阻塞整个扫描周期 | 改用 TP(脉冲定时器)配合状态转移 |
五、预防机制:构建代码准入防线
排查是救火,预防才是治本。建议在团队开发规范中强制执行:
-
ST代码审查清单(Checklist):
- 所有循环必须声明明确的上界变量,且该变量必须经过
MIN(..., MAX_ALLOWED)校验; - 所有
WHILE循环必须包含至少一个在循环体内更新的退出条件变量; - 禁止在循环内调用任何未标注“实时安全”的自定义函数(需函数文档明确声明最坏执行时间 ≤ 100 μs)。
- 所有循环必须声明明确的上界变量,且该变量必须经过
-
编译期强制约束(以Codesys为例):
- 在
Project → Options → Code Generation中启用 “Runtime error checking” → 勾选 “Array bounds check”; - 在
Tools → Scripting中部署脚本,自动扫描项目内所有FOR/WHILE/REPEAT,输出报告含:循环位置、变量名、疑似风险等级(如i TO 65535标为“高危”)。
- 在
-
运行期熔断保护(推荐):
在MAIN程序开头插入全局防护:VAR_GLOBAL GlobalWatchdogTimer : TON; ScanOverThreshold : BOOL; END_VAR GlobalWatchdogTimer(IN := TRUE, PT := T#50ms); // 50ms熔断阈值 ScanOverThreshold := GlobalWatchdogTimer.Q; IF ScanOverThreshold THEN // 强制进入安全状态:清零输出、置位急停标志 SAFE_SHUTDOWN(); END_IF
六、典型案例:某灌装线流量积分超时故障
现象:灌装启停瞬间,PLC每3–5小时随机复位,HMI显示“PLC stopped”。
排查过程:
- 步骤1:启用循环时间监视,复位前3秒捕捉到
427 ms峰值; - 步骤2:隔离发现
FLOW_INTEGRATOR.ST模块解禁后周期飙升; - 步骤3:定位循环
FOR i := 0 TO FlowSamples DO,FlowSamples来自高速计数器HC0寄存器; - 步骤4:发现
HC0在电机启停瞬间受电磁干扰,偶发读取到65535(计数器溢出值),导致循环执行65536次; - 步骤5:增加防护
FlowSamples := MIN(FlowSamples, 1000);并添加HC0读取有效性校验(比对前后两次读数差值是否合理),问题彻底消失。
根本原因:未对高速硬件寄存器做工程化滤波,将电气噪声直接映射为逻辑灾难。
将循环控制权交还给确定性,而非交给不可预测的输入或硬件噪声。每一次 FOR 的边界校验,都是对产线连续性的无声承诺。

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