梯形图子程序返回后局部数据未保持,是电气自动化系统调试与维护中高频出现、却常被误判为“硬件故障”或“PLC死机”的隐蔽性问题。它不触发报警,不中断扫描周期,但会导致逻辑跳变、输出异常、连锁失效——比如:某输送线在子程序调用后突然停止,复位按钮失灵;某温控段在子程序退出后加热输出持续为0,即使设定值已回升;某安全门联锁在子程序执行完毕后失去互锁响应。这些现象的根源,往往不是IO模块损坏或接线松动,而是局部变量(Local Data)生命周期管理不当引发的逻辑断层。
一、先明确:什么是“局部数据”,它为什么“不保持”
在主流PLC平台(如西门子S7-1200/1500、罗克韦尔Logix 5000、三菱Q系列)中,子程序(Function Block / FC/FB 或 AOI)的变量分为三类:
- 输入(IN):调用时传入的值,子程序内部可读可写(取决于是否声明为
IN_OUT),但退出后不保存; - 输出(OUT):子程序内部计算结果,返回给主程序,仅在本次调用有效;
- 静态(STATIC)或背景数据块(Instance DB)变量:唯一能跨扫描周期保持的数据区,其值在子程序退出后仍驻留在内存中,下次调用时继续使用。
而“局部数据”(Local Data),特指在子程序内部未显式声明为STATIC、也未映射到背景DB的临时变量(例如STEP 7中FB块内未勾选“静态”属性的TEMP变量;Logix中未定义为.MEM或未绑定UDT实例的本地标签)。这类变量的存储空间由PLC操作系统在每次子程序调用时动态分配,在子程序执行结束瞬间立即释放。其内容不会自动保留到下一次调用。
这就导致一个确定性后果:
若子程序中用TEMP变量记录中间状态(如计时器当前值、步进序列号、上一次采样差值),而该变量未升级为静态存储,则每次调用开始时,该变量被重置为初始值(通常是0或FALSE),造成状态链断裂。
例如,一段用于检测“连续3次按下启动按钮才动作”的子程序逻辑:
// 错误写法:使用TEMP变量计数
IF Start_Button THEN
Count := Count + 1; // Count 是 TEMP 变量
ELSE
Count := 0;
END_IF;
IF Count >= 3 THEN
Output_Enable := TRUE;
END_IF;
第一次调用子程序:按钮按下 → Count = 1;
第二次调用:按钮再次按下 → Count = 2;
第三次调用:按钮再按 → Count = 3 → 输出激活;
第四次调用(无论按钮是否按下):子程序重新进入,Count被初始化为0 → 状态清零 → 逻辑重置。
这就是典型的“返回后局部数据未保持”导致的逻辑断层——不是程序写错了,而是变量生存期设计错了。
二、四步定位法:快速识别是否为局部数据丢失问题
当现场出现“子程序返回后行为异常”时,按以下顺序排查,可90%以上排除硬件干扰,直击变量本质:
-
确认异常发生时机是否严格绑定子程序调用周期
观察PLC在线监控:在子程序调用前一周期(Call指令前的扫描周期)、调用中(Call指令执行期间)、调用后一周期(Call指令后的首个扫描周期)三个时间点,对比关键中间变量的值。若发现:- 调用中变量有非零值(如
Timer_ACC = 2500), - 调用后首周期同一变量变为
0(且该变量未被其他逻辑清零),
则高度怀疑为TEMP变量丢失。
- 调用中变量有非零值(如
-
检查变量声明属性(无需反编译,看编程软件界面)
- 在TIA Portal中:双击FB块 → 左侧“接口”选项卡 → 查看该变量所在列是否为
Static(是)或Temp(否); - 在Studio 5000中:打开AOI → “Data Files”页签 → 查看变量是否位于
.MEM区域(是)或仅在Local Tags下无存储类型标识(否); - 在GX Works3中:打开FB → “变量”窗口 → 查看“变量类型”列是否为
Static(是)或Temporary(否)。
- 在TIA Portal中:双击FB块 → 左侧“接口”选项卡 → 查看该变量所在列是否为
-
验证是否所有依赖路径均通过同一实例调用
子程序若被多个主程序(OB1、OB35等)或不同网络重复调用,且共用同一组TEMP变量,将引发变量覆盖冲突。例如:
OB1调用FB1 →Temp_Count = 2;
同一扫描周期内OB35也调用FB1 →Temp_Count被重置为0并开始新计数;
OB1后续读取时得到错误值。
此时需检查调用源是否唯一,或是否误用全局变量替代实例化变量。 -
做最小化复现测试
新建一个空主程序,仅包含:- 一个置位按钮(
M0.0); - 一次子程序调用(带必要输入);
- 一个输出指示灯(监控子程序内部TEMP变量对应输出);
手动单步执行两次调用(使用PLC仿真或强制扫描模式),直接观测TEMP变量值是否跨调用保持。若不保持,即确诊。
- 一个置位按钮(
三、五种可靠解决方案(按推荐优先级排序)
方案1:将TEMP变量升级为STATIC(首选,零成本,最彻底)
适用于西门子S7-1200/1500 FB块、三菱Q系列FB、欧姆龙NJ/NX系列。
操作步骤:
- 在FB块编辑界面,打开“接口”(Interface)视图;
- 找到需保持的变量(如
Count、Last_Value、Step_No); - 将其“类型”(Type)列从
Temp改为Static; - 保存并重新生成背景数据块(DB)(TIA Portal中右键FB → “生成背景数据块”);
- 下载FB及对应DB到PLC。
✅ 优势:变量自动绑定至该FB实例的背景DB,全生命周期保持,无需修改调用逻辑。
⚠️ 注意:STATIC变量在FB首次调用时初始化为默认值(可在声明时指定,如Count : Int := 0;),后续永不重置。
方案2:显式使用背景数据块(DB)字段(兼容性强,适合老旧系统)
适用于所有支持DB的PLC(包括S7-300/400、早期Logix版本)。
操作步骤:
- 创建一个全局DB或专用实例DB;
- 在DB中声明所需保持变量(如
DB_Count : INT); - 在子程序中,不再声明TEMP变量,改为直接读写该DB字段;
- 调用子程序时,确保DB已加载且地址正确(如
DB1.DBW2)。
✅ 优势:完全规避TEMP生命周期限制,DB变量天然保持;
⚠️ 注意:需手动管理DB地址与变量映射,增加工程配置量。
方案3:改用全局标记(Memory Area)变量(慎用,仅限简单场景)
适用于变量极少、无多实例需求的情况(如单台设备只有一个主控逻辑)。
操作步骤:
- 在全局DB或M区(如
M100.0、MW200)中分配变量; - 子程序中直接访问(如
M100.0作为标志位,MW200作为计数值); - 主程序调用前后无需传递,变量始终存在。
⚠️ 风险:破坏模块化设计,易引发多处逻辑耦合;调试时难以追溯变量归属;不适用于多设备复用逻辑。
✅ 适用场景:紧急抢修、演示原型、或变量仅为布尔标志且无并发调用。
方案4:在调用端完成状态传递(主动防御型)
当无法修改子程序源码(如第三方封装FB)时,用主程序接管状态维持。
操作步骤:
- 在主程序中声明一个与子程序TEMP变量同类型的全局变量(如
Global_Count : INT); - 每次调用子程序前,将
Global_Count值通过IN_OUT参数传入子程序; - 子程序内部对该
IN_OUT变量运算; - 调用返回后,立即将该
IN_OUT参数的返回值回写到Global_Count。
示例(TIA Portal风格):
// 主程序中
Global_Count := Global_Count; // 初始化(可选)
// 调用子程序,传入并接收更新值
My_FB(
Input := Sensor_Signal,
Counter_IO := Global_Count, // IN_OUT 参数
Output := Motor_Start
);
Global_Count := My_FB.Counter_IO; // 强制回写,确保保持
✅ 优势:不改动子程序,兼容黑盒模块;
⚠️ 注意:必须保证每次调用后都执行回写,漏写即失效;IN_OUT参数不能是常量或字面量(如10),必须是可写变量。
方案5:用定时器/计数器指令内置存储(硬件级绕过)
适用于仅需保持“时间”或“次数”的简单状态。
PLC内置定时器(TON、TOF)和计数器(CTU、CTD)指令,其ET(已耗时间)、PV(预设值)、Q(完成位)等成员本身就是静态存储,不受调用周期影响。
操作步骤:
- 删除原TEMP变量实现的软计时/计数逻辑;
- 改用标准TON指令(如
TON_DB1),其ET值在指令未复位时持续累加; - 用
TON_DB1.Q作为动作使能条件,而非判断TEMP变量是否≥3。
✅ 优势:利用PLC固有机制,100%可靠,无需额外变量;
⚠️ 注意:需确保定时器IN输入在需要计时期间持续为TRUE,且TON实例DB不被意外删除或覆盖。
四、预防清单:杜绝此类问题再次发生
| 类别 | 检查项 | 执行方式 |
|---|---|---|
| 编码规范 | 所有需跨调用保持的状态变量,声明时必须标注Static(FB)或绑定至.MEM(AOI) |
在代码审查清单中加入此项 |
| 模板强制 | 新建FB/AOI时,默认启用“生成背景DB”并预置Static区段 |
在公司工程模板中固化 |
| 变量命名 | 对TEMP变量统一加前缀_TMP_(如_TMP_Counter),对STATIC变量加前缀_STA_(如_STA_Counter) |
编程规范文档明文规定 |
| 仿真验证 | 在PLCSIM Advanced中,对子程序执行至少3次连续调用,监控所有中间变量 | 写入调试SOP |
| 交付物归档 | 提交程序包时,同步提供《变量生命周期说明表》,列明每个关键变量的类型、保持性、初始化值 | 作为验收交付物之一 |
五、真实案例还原:包装机急停连锁失效
现象:某国产包装机运行中,按下急停按钮后,主电机停止,但热封压辊继续缓慢下压,3秒后才完全停止,违反安全等级SIL2要求。
排查过程:
- 检查急停回路:触点动作正常,DI模块LED亮起;
- 监控急停信号
E_Stop_OK:OB1中为FALSE,正确; - 发现热封控制子程序
FB_HeatSeal中有一段“软延时释放”逻辑:IF NOT E_Stop_OK THEN Delay_Counter := Delay_Counter + 1; // TEMP变量! END_IF; IF Delay_Counter > 300 THEN // 300×10ms=3s Seal_Release := TRUE; END_IF; - 在线监控:急停触发后,
Delay_Counter在第一个扫描周期达1,第二周期达2……但第301周期调用时,Delay_Counter突然变回0,导致释放延迟被重置。
根因:Delay_Counter声明为TEMP,每次FB_HeatSeal被主循环调用时重初始化。
解决:
- 将
Delay_Counter改为Static; - 在FB声明区添加初始化:
Delay_Counter : Int := 0;; - 重新生成并下载背景DB;
- 测试:急停后第301周期
Delay_Counter = 301,Seal_Release准时置位。
效果:热封压辊在3秒整点释放,符合安全时序,且连续运行72小时无异常。
六、延伸提醒:勿混淆“保持性”与“初始化”
初学者常误认为:“只要变量是STATIC,就永远不初始化”。这是错误认知。
STATIC变量仅保证调用间保持,但其初始化行为仍受PLC上电/冷启动策略控制:
- 暖启动(Warm Restart):STATIC变量保持断电前最后值;
- 冷启动(Cold Restart):STATIC变量恢复为声明时的初始值(如
:= 0); - 下载程序(Download Block):若勾选“初始化静态变量”,则覆盖为初始值;未勾选则保持当前值。
因此,对安全关键变量(如急停计数器、安全门锁存标志),应在声明时赋予有意义的默认值,而非依赖上电状态。例如:
Safe_Door_Locked : Bool := FALSE; // 上电默认解锁,符合安全原则
Emergency_Count : Int := 0; // 计数器归零,避免误触发
直接修改变量声明类型,是最小侵入、最高可靠性的修复路径。

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