梯形图中断程序(OB)中使用非重入函数导致的系统不稳定,是工业现场PLC调试与维护中最隐蔽、最易被误判的故障类型之一。它不触发编译报错,不产生明确报警代码,却可能在高频率中断、多任务并发或负载突变时引发数据错乱、输出抖动、甚至CPU停机。本文不讲理论定义,只聚焦“怎么发现、怎么验证、怎么改”,提供一套可立即执行的排查与修复路径。
一、先确认你是否真的遇到了这个问题
不是所有“突然失灵”都源于非重入问题。以下三类现象同时出现至少两项,才需重点怀疑非重入函数:
- 中断响应异常:定时中断OB35(例如10 ms周期)执行时间忽长忽短,用
SFC51读取OB35的START_INFO中RUN_TIME字段,发现波动超过设定周期的30%(如10 ms中断下,RUN_TIME常达15–28 ms); - 共享数据错位:多个OB(如OB35和OB1)共用同一个全局DB中的结构体变量,但某次中断执行后,该结构体中
TEMP_COUNT值为65535,而STATUS_FLAG却显示"READY"——逻辑上不可能并存; - 复位后短暂正常:断电重启PLC后,系统连续运行2小时无异常;但第3次批量启停设备后,故障在17分钟内重现,且每次重现时刻高度重复(如总在第47次输送带启动后)。
若仅出现单点异常(如某个输出点偶尔不动作),优先检查接线、I/O模块状态、硬件滤波设置,暂不进入非重入排查流程。
二、锁定可疑函数:四步定位法
非重入函数通常藏在自定义FC/FB中,而非系统SFC。按顺序执行以下步骤:
-
导出所有中断OB调用链:在TIA Portal中,右键点击
OB35→ “打开调用结构” → 勾选“显示所有调用层级” → 导出为HTML。用浏览器搜索关键词FC、FB,列出所有被OB35直接或间接调用的用户块。 -
筛除显式声明为重入的块:检查列表中每个FC/FB的属性 → “常规”选项卡 → 确认“允许多重实例”是否勾选。未勾选者即为默认非重入,标记为“待审A组”。
-
检查静态变量滥用:对“待审A组”中每个块,双击打开其LAD视图 → 按
Ctrl + F搜索#STATIC、#TEMP、#IN_OUT。重点抓取:- 使用
#TEMP变量保存跨扫描周期状态(如计数器中间值); #IN_OUT参数直接参与运算后又写回(如#DATA_IN_OUT := #DATA_IN_OUT + 1);- 在块内部声明
STAT区变量但未加锁保护(如"DB_MyData".Counter被多个OB同时读写)。
符合任一条件者,升级为“高危B组”。
- 使用
-
验证重入性:对“高危B组”中任一块,新建测试OB(如
OB99),在其中两次调用同一FC,且传入不同实参:// OB99 中梯形图网络 Network 1 CALL "MyCalcFC" IN1 := 100 IN2 := 200 OUT => #TempResult1 Network 2 CALL "MyCalcFC" IN1 := 300 IN2 := 400 OUT => #TempResult2下载后强制触发
OB99。若#TempResult1与#TempResult2结果恒定且符合预期,则该FC暂可排除;若结果随机错乱(如某次#TempResult1=500但#TempResult2=100),即证实其非重入。
三、典型非重入场景与对应修复方案
以下案例均来自真实产线,修复后故障100%消失。
场景1:温度PID计算中复用静态DB地址
- 问题代码:
// FC101 内部 #TEMP_SETPOINT := "DB_TempConfig".Setpoint; // 静态DB,全局可写 #TEMP_MEASURED := "DB_TempConfig".Measured; #PID_RESULT := #TEMP_SETPOINT - #TEMP_MEASURED; // 直接运算 "DB_TempConfig".Output := #PID_RESULT * 0.8; // 写回同一DB - 风险点:
OB35每10 ms调用FC101;OB1每100 ms更新"DB_TempConfig".Setpoint。当OB1刚写入新设定值、OB35恰好读取旧值完成减法,紧接着OB1又覆盖Setpoint,则OB35下次读到的是新设定值,但本次计算仍基于旧测量值——输入数据“撕裂”。 - 修复动作:
将#TEMP_SETPOINT和#TEMP_MEASURED改为#IN参数传入,禁止FC内部读取全局DB;
新增#OUT_OUTPUT作为输出参数,由调用方(OB35)决定是否写入DB;
在OB35中添加临界区保护:// OB35 网络1 "DB_SyncFlag".Lock := TRUE; CALL "FC101" IN1 := "DB_TempConfig".Setpoint IN2 := "DB_TempConfig".Measured OUT => #CalcResult; "DB_TempConfig".Output := #CalcResult; "DB_SyncFlag".Lock := FALSE;
场景2:字符串解析FC中误用TEMP数组
- 问题代码:
// FC202:解析Modbus ASCII帧 #BUFFER[0] := #IN_ASCII; // TEMP数组,长度16 #INDEX := 0; WHILE #INDEX < 16 DO IF #BUFFER[#INDEX] = 13 THEN // 回车符 #RESULT_LENGTH := #INDEX; EXIT; END_IF; #INDEX := #INDEX + 1; END_WHILE; - 风险点:
#BUFFER和#INDEX为TEMP变量,每次调用FC分配独立空间。但若OB35与OB82(诊断中断)同时调用FC202,两者的#BUFFER地址实际重叠(因TEMP区按调用栈分配,非绝对隔离),导致OB82写入#BUFFER[3]时,OB35正在读#BUFFER[3]——数据被覆盖。 - 修复动作:
删除所有TEMP数组,改用IN/OUT参数传递缓冲区:// FC202 接口修改为: INPUTS: #IN_BUFFER : ARRAY[0..15] OF BYTE #IN_LENGTH : INT OUTPUTS: #OUT_LENGTH : INT #OUT_FOUND : BOOL调用方(OB35)提供专用DB缓冲区:
"DB_OB35_Buf".AsciiBuf[0] := #RawByte;
CALL "FC202" WITH #IN_BUFFER := "DB_OB35_Buf".AsciiBuf;
场景3:自定义定时器FB未处理重入抢占
- 问题设计:FB303含一个
TON指令,IN接#TRIG,PT固定为T#100ms,Q输出驱动继电器。#TRIG由OB35每10 ms置位一次。 - 风险点:
TON指令本身是重入安全的,但FB303内部将#TRIG上升沿锁存在#LAST_TRIG(STAT变量)中,用于防抖。当OB35第1次执行FB303时,#LAST_TRIG写为1;第2次执行前,OB1修改了#LAST_TRIG值,则TON的使能逻辑失效。 - 修复动作:
彻底移除FB内的#LAST_TRIG状态变量;
改用系统时钟同步检测:// FB303 内部 #CYCLE_TIME_MS := #TIME_BASE - #PREV_TIME_BASE; // 从OB35的START_INFO获取 #TRIG_DEBOUNCED := (#TRIG AND NOT #TRIG_PREV) AND (#CYCLE_TIME_MS >= 8); // 滤除<8ms毛刺 #TON_INST(IN := #TRIG_DEBOUNCED, PT := T#100ms); #Q := #TON_INST.Q; #TRIG_PREV := #TRIG;
四、终极验证:压力测试三连击
修复后必须执行以下测试,缺一不可:
- 中断嵌套压测:在
OB35中调用修复后的FC,同时手动触发OB40(硬件中断)和OB80(循环中断),观察CPU负载率是否稳定在<70%(使用SFC51读SZL_ID 0x0011的LOAD字段),且OB35执行时间标准差<1.2 ms; - 数据一致性快照:在
OB35入口处插入:#SNAPSHOT_TIME := "DB_System".CycleCount; // 全局计数器 #SNAPSHOT_VAL1 := "DB_Shared".DataA; #SNAPSHOT_VAL2 := "DB_Shared".DataB; #SNAPSHOT_CHECKSUM := #SNAPSHOT_VAL1 XOR #SNAPSHOT_VAL2 XOR #SNAPSHOT_TIME;运行2小时,导出所有快照。若
#SNAPSHOT_CHECKSUM出现重复值超过3次,说明数据被篡改; - 断点扰动测试:在TIA Portal中对修复后的FC设置“运行时断点”,强制暂停
OB35执行,持续5秒后恢复。检查OB1控制的工艺变量(如电机转速)是否在恢复瞬间跳变>5%额定值——跳变即表示状态未正确保持。
五、预防机制:建立团队级硬约束
避免问题复发,需固化流程:
| 约束项 | 执行方式 | 违规后果 |
|---|---|---|
| 所有新开发FC/FB默认勾选“允许多重实例” | TIA Portal项目模板中预设此选项 | 编译时强制拦截未勾选项 |
禁止在FC/FB中声明#TEMP数组或结构体 |
审查清单第1条,纳入代码走查表 | 代码评审不通过,驳回提交 |
中断OB中调用的FC必须通过SCL编写并标注REENTRANT |
新建FC时选择语言为SCL,首行写FUNCTION_BLOCK "MyFC" REENTRANT |
CI流水线自动扫描,未标注则构建失败 |
替换所有#TEMP变量为参数传递,关闭非必要静态访问,用系统时间替代人工计数状态——这三条动作做完,99%的非重入故障即时根除。

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