ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

PLC定时器提前动作的幽灵调用——累加型FB双调用导致延时减半排查实录

PLC定时器提前动作的幽灵调用——累加型FB双调用导致延时减半排查实录 设备停止延时逻辑在PLC里写得清清楚楚PT设的T#5SHMI上显示也是5秒可现场偏偏2.5秒就动作了。气缸提前换向顶了一下工装好在没伤到人。我把这段ST程序翻来覆去看了好几遍FB调用语句确实只写了一次。但实际表现就像程序里藏了另一个幽灵调用。这篇就把整个排查过程、根因和以后怎么防完整拆开写清楚。1. 故障实录设定5秒的延时停止2.5秒就触发了1.1 现场现象那条小装配线停线停了两小时。操作工描述得挺玄乎我按停止按钮设备应该5秒后关气缸结果2秒多就关了。而且不是偶发是每次都能复现。我跟工程师到现场先把程序在线调出来看功能块实例的背景数据块里PT确实是5000msEnable信号也正常。逻辑写得很简单// OB1 主程序中的调用 IF NOT Cyl_Work THEN FB_DelayStop(Enable : TRUE, Reset : FALSE, Preset : T#5S); END_IF; IF FB_DelayStop.Done THEN Cyl_Close : TRUE; // 气缸关闭 END_IF;这段代码正常到不能再正常。按钮一按Cyl_Work变FALSEFB的Enable被置TRUE开始计时。5秒后Done置TRUE关闭气缸。可现场就是2.5秒关。1.2 我一开始的三步常规排查遇到定时不准我习惯先查三个点。第一查PT参数有没有被HMI或触摸屏改掉。有些设备支持在面板上调整延时时间操作工或者上一个调试工程师可能改过。我打开HMI画面延时设定值显示5秒。又去PLC监控表里看背景DB里的PT也是5000ms。这个排除。第二查扫描周期是不是被拉长了。假如某一小段程序执行时间特别长扫描周期到了几十毫秒甚至上百毫秒那定时器累加可能会出现非线性。我用监控表抓了CPU的扫描周期稳定在10ms左右没有异常峰值。这个也排除。第三查FB内部是不是有别的复位或清零逻辑。我把FB_DelayStop内部代码打开看逻辑很规整Enable为TRUE时累加Reset为TRUE时清零没有多余的旁路。这个也排除。按理说这三步查完问题就该暴露了。但时间就是不对。后来我回过头去看监控表里的ET变化才发现真正的线索其实早就在眼前只是我一开始没往那个方向想。2. 关键突破监控表里的ET增长速度不对2.1 从时间不对到调用次数不对我用监控表同时监控FB_DelayStop的ET、Enable、Done三个变量每隔一秒记录一次数值。这里要说明一下FB_DelayStop并不是直接用系统TON而是自己写的一个累加型定时器FB。为啥不用系统TON因为现场有个特殊需求延时过程中允许操作工随时修改目标时间并且要立即生效基于扫描周期累加的自定义FB改起来更顺手。这也是很多老工程师的习惯尤其在设备改造项目里。监控数据如下真实时间ET显示值ET增量对应扫描周期数0s0ms001s约2020ms约2020ms约202次2s约4050ms约2030ms约203次2.5s约5000ms触发Done约253次问题一下就出来了。真实时间走了1秒ET却涨了差不多2020ms。按10ms的扫描周期算1秒内顶多累加100次每次10ms应该只有1000ms才对。现在等于1秒内累加了200次。换句话说每个扫描周期内累加动作被执行了2次。FB_DelayStop内部用的是这种累加逻辑FUNCTION_BLOCK FB_DelayStop VAR_INPUT Enable: BOOL; Reset: BOOL; Preset: TIME; END_VAR VAR_OUTPUT Done: BOOL; ET: TIME; END_VAR VAR accTime: TIME; END_VAR // 每次被调用就累加一个扫描周期时间SCAN_CYCLE_TIME由全局变量提供 IF Reset THEN accTime : T#0MS; Done : FALSE; ELSIF Enable THEN accTime : accTime SCAN_CYCLE_TIME; IF accTime Preset THEN Done : TRUE; accTime : Preset; END_IF; ELSE accTime : T#0MS; Done : FALSE; END_IF; ET : accTime; END_FUNCTION_BLOCKSCAN_CYCLE_TIME来自全局数据块每个OB1扫描周期开始时由系统更新时间值大约10ms。因为FB_DelayStop每个扫描周期被调用了一次所以理论上accTime每个周期只加一次10ms。但监控结果显示它加了两次。这就指向一个结论这个FB在一个扫描周期里被调用了2次。2.2 在FB里埋一个调用计数器一锤定音为了把这件事坐实我没有继续猜直接在FB_DelayStop里面加了一个静态变量CallCount每次调用自增1VAR accTime: TIME; CallCount: DINT; // 调试用 END_VAR CallCount : CallCount 1;然后把CallCount也拖到监控表里。结果非常清楚1秒内CallCount涨了约200次换算到每个扫描周期约10ms就是2次。代码里明明只写了一次FB_DelayStop调用为什么实际跑起来是每个周期两次这说明调用源不止一个。我开始全局搜索这个FB实例名结果在OB30里找到了一模一样的调用语句。3. 根因定位代码里只写一次的FB实际上有两个调用源3.1 OB1主程序和OB30循环中断里各有一处调用项目里有一个OB30循环中断设置的是每10ms触发一次原本是给一个高速计数的功能用的。但之前的工程师觉得反正OB30每个10ms跑一趟OB1扫描周期也差不多10ms把延时停止逻辑放到OB30里响应不是更快吗于是在OB30里也复制了一份// OB30 循环中断里的调用 FB_DelayStop(Enable : TRUE, Reset : FALSE, Preset : T#5S);问题就从这里炸出来的。OB1扫描周期大约10msOB30也是10ms中断一次。两个程序块都在访问同一个FB实例的背景数据区。OB1里调一次累加10ms然后中断来了OB30里又调一次又累加10ms。结果这一个真实扫描周期里accTime被加了两次实际时间过去10ms定时器却以为过去了20ms。5秒的定时按正常逻辑应该在第500个扫描周期约5秒真实时间触发。现在每个周期记20ms250个周期就满了250个周期大约2.5秒——和现场现象完全吻合。3.2 ST代码中隐形调用更容易被忽略的原因以前用LAD/FBD编程的时候FB是以块的形式画在程序段里的。你只要扫一眼程序段就能看到这个FB在几个地方被调用了几个网络里各摆着一个块清清楚楚。但换成ST语言之后同样的调用变成了一行一行的赋值表达式散落在不同的POU里。人眼审查的时候注意力自然会集中在OB1主程序上很难想到去OB30、OB35这些中断块里再翻一遍。这个案例里搜一下FB实例名在项目里出现几次这个动作其实只要花30秒。但人在现场拿着监控表的时候第一反应永远是我的逻辑哪里写错了而不是这段逻辑是不是被别的地方也在调用。这是ST编程时代一个特别典型的思维盲区。FC、FB越拆越细POU数量越来越多调用关系如果不靠交叉引用工具去查光靠眼睛看一定会漏。3.3 另一种常见多点调用多个IF分支的条件重叠这里再提一种同样常见、但更隐蔽的情况。有的同学写ST喜欢这样组织逻辑// 手动模式允许时调用 IF ManualMode THEN FB_DeviceControl(Enable : TRUE); END_IF; // 自动模式允许时调用 IF AutoMode THEN FB_DeviceControl(Enable : TRUE); END_IF;从表面上看这段代码把控制逻辑复制到了两种模式下好像没什么问题。但如果ManualMode和AutoMode在某一个扫描周期里同时为TRUE——比如模式切换的瞬间、或者某个信号抖动了一下——同一个扫描周期里FB_DeviceControl就被执行了2次。对于普通逻辑FB执行2次可能结果一样顶多多写几次输出。但对于定时器、计数器这类有记忆的功能块执行2次就是2倍累加。这个在审查代码时非常容易被忽略因为IF分支里每个调用单独看都没问题条件重叠造成的双调用不实际跑一遍根本看不出来。4. 定时器FB为什么怕重复调用累加型定时器的伤害模型4.1 累加型定时器的工作原理先把定时器的本质说清楚。无论是系统自带的TON还是自己写的累加型定时器定时器FB的共同特点是它必须依赖调用来推进时间。系统TON在TIA Portal等现代PLC里内部是基于实时时钟的。每次调用时它取当前系统时间减去上次调用时记录的时间把差值加到ET上。这种实现的精度高而且即使两个调用点之间间隔很不均匀也不影响累计时间。累加型定时器则完全不一样。它没有内部的时间戳它的时间基准是扫描周期。每被调用一次它就把一个扫描周期的时间累加到accTime里。这种实现的好处是简单、可控、改预设时间可以立即生效坏处就是它完全依赖调用次数。调用1次累加1份调用2次累加2份调用0次它就永远不走。4.2 一次扫描周期累加两次时间直接减半拿这个项目的参数具体算一笔账扫描周期约10msPT设定5000ms正常情况需要5000 / 10 500个扫描周期触发实际异常每个扫描周期累加2次即每个周期增加20ms异常后触发条件5000 / 20 250个扫描周期触发500个扫描周期对应真实时间约5秒250个扫描周期对应真实时间约2.5秒。时间刚好减半。这不是个例。任何基于计数累加的定时器遇到双调用都会时间减半。如果遇到三调用时间就变成三分之一。而且这个问题在双调用源稳定存在的时候表现得很稳定不会时好时坏所以特别容易让人误以为是PT参数设置错误或者HMI被误改。4.3 系统TON与累加型定时器的不同表现有同学会问那如果用的是系统TON是不是就没这个问题了要分情况说。如果系统TON的实现是基于实时时间戳那么即使一个扫描周期里被调用2次第二次调用时它读取系统时间发现距离第一次调用只过去了微秒级的时间ET累加量几乎为0。这种情况下时间不会翻倍。这是很多现代PLC的做法。但问题并没有完全消失。同一个实例在两个地方被调用意味着它内部的输入变量IN、PT可能在两次调用之间被不同的代码路径修改。比如OB1先把Reset置TRUE清掉了定时器几毫秒后OB30又来置Enable继续运行两个调用源交替控制同一个定时器最终的状态取决于谁最后执行而不是谁逻辑更正确。这种竞争条件在程序复杂时非常难查因为每一次扫描周期的执行顺序都取决于中断优先级和时序抖动。我在排查这个故障时的态度是不管系统TON会不会时间翻倍同一个FB实例被两个执行源调用本身就是一个结构性的错误。这个错误不修掉今天不炸改天换一个CPU或者调整一下中断配置迟早会炸。5. 从这次坑里总结的防范套路5.1 交叉引用检查调用源的最快方式故障定位之后我把排查方法沉淀成了一个固定动作凡是遇到定时器、计数器类FB行为异常第一件事不是盯监控表而是先做交叉引用查询。在TIA Portal里可以右键点击FB或者它的背景数据块选择交叉引用然后看它被哪些程序块引用。施耐德的Unity Pro、汇川的Autoshop这些软件也都有类似功能只是菜单位置不同。这个操作能一次性列出项目中所有调用该FB实例的位置比人工翻代码靠谱得多。后来我给自己定了一条规矩新写的每个FB尤其是带内部状态的功能块写完第一版必须做一次交叉引用检查确认调用源只有预期的那几个。等到现场出问题再查成本就完全不一样了。5.2 实例单一调用源原则这是一个工程上很朴素的原则一个FB实例最好只有一个稳定的、唯一的调用源。如果需要在一个设备的多个控制模式手动/自动/调试里都用同一段控制逻辑正确做法不是在一个扫描周期里用多个IF分支分别调用同一个FB实例而是用一个统一的中间变量把模式判断合并起来只调用一次// 推荐写法不管什么模式最终只调用一次 IF ManualMode OR AutoMode THEN FB_DeviceControl(Enable : TRUE); END_IF;如果需要同时在一个主OB里运行、又希望周期中断OB里也关照它那就更应该扪心自问到底需要在哪个OB里运行还是把这个FB变成由统一的调度OB来负责其他OB一概不碰定时器类FB尤其要遵守这个原则。它的状态完全靠调用历史维系你多碰它一次它的历史就多一个完全不该出现的片段。5.3 在中断OB里调用FB前的自查清单经过这次事件我给团队成员整理了一个自查清单凡是打算在OB30、OB35这类循环中断里挪程序逻辑先确认三件事这个FB实例在主程序OB里有没有已经被调用如果有不要在中断OB里再调用一次。如果必须在中断OB里频繁采样应该拆一个独立的输入采样FB用专门的实例不要和主程序共用。中断OB的执行周期和OB1扫描周期如果不能形成明确的倍数关系尽量把慢逻辑放在OB1快逻辑放在中断OB两者不要交叉引用同一段带状态的功能块。这个清单就是几条大白话但能挡住绝大多数时间减半和状态跳变的坑。5.4 调试技巧给FB加调用计数器最后把我用的那个调试技巧再单独说一遍。遇到疑似被多调用的FB直接在FB内部塞一个静态计数器VAR CallCount : DINT; // 每次调用自增监控用 END_VAR // 放在FB内部逻辑的最前面 CallCount : CallCount 1;然后在线监控这个CallCount。如果它每秒增长次数大约等于1000ms / 扫描周期ms那就是正常的一次调用如果翻倍了说明有第二个调用源如果增长极其不均匀说明调用点在多个优先级不同的OB里。这个技巧唯一的成本就是FB里多个变量占一点内存但它能让你在5分钟内确认调用次数这个维度是否正常比反复看监控表的ET推断要强得多。排查完之后把这段调试代码删掉或者注释掉都行。根据我个人的经验很多类似定时器莫名其妙快一倍的报障最后的原因都落在某个FB被多个地方同时调用上。这个坑本身不难理解难的是第一时间想到去查调用关系。希望这篇实录能帮你少走两小时的弯路。
返回列表