十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

PLC定时器双执行之谜:FB只调用一次,为何定时器却跑了两遍?

PLC定时器双执行之谜:FB只调用一次,为何定时器却跑了两遍? 做PLC编程这行你迟早会遇到一个让你怀疑人生的bug。我这次的项目用的是西门子S7-1200和博途TIA Portal V17程序主要用SCL写也就是IEC 61131-3标准里的ST结构化文本语言。设备是一台小型包装机我封装了一个FB用来控制皮带启动时序内部放了一个TON延时定时器相当于按下启动按钮之后先延时3秒再把电机带起来。OB1里非常干净只有一个FB调用点背景数据块名为FB_Packaging_DB。在线监控的时候发现一个怪象启动信号明明只是轻轻点了一下程序里的定时器却像是被人连续触发了两回输出Q到了3秒时会自己先通一下、断一下再通一下。用示波器抓DO点电机接触器的吸合也有一个非常短的重跳。整个动作不是正常的一次启停而是跳了一次电。最初我以为是按钮信号抖动用得不好把Start信号加了滤波问题依旧。后来我把程序从头到尾又看了一遍FB内部逻辑非常简单不太像逻辑写错。最后用交叉引用查定时器的背景DB才看到问题核心定时器使用的不是一个局部TON变量而是一个全局的IEC定时器背景DB而这个全局DB除了FB内部在用还有一个上位机数据记录任务在做周期读写。也就是说代码里FB只有一个调用点但定时器数据背后有两条独立驱动路径一条来自OB1里的FB调用一条来自上位机周期任务。定时器相当于被两个人同时拽着跑不跑乱才怪。1. 现象复盘一个FB却像被调用了两次1.1 我遇到的实际故障现象这个故障最折磨人的地方是“看起来一切正常”。程序逻辑监控里FB确实只在OB1里出现一次启动信号也是干净的没有乱七八糟的重复边沿。但设备动作就是不对定时器延时结果比设定值明显偏短而且每次启动时输出都会跳两下。因为是在包装线这种连续运行的设备上现场没法停太久我只能一边在线监控一边等下一个启动周期反复观察TON的ET和Q状态。最明显的一次我看ET到差不多3秒时Q已经置1但没过多久Q又回零然后又置1。等于在极短时间里完成了“到点-复位-再到点”整个过程。我最初怀疑是不是TON的PT被改写于是把PT放到了交叉引用里查只找到FB内部一处赋值。又怀疑Start输入有硬件干扰后来用仿真表强制Start信号强制到true后问题照样出现。这时候我才意识到问题可能不在输入信号而在定时器本体。用交叉引用查TON背景DB的引用列表时我才看到除了OB1调用路径会写这个DB之外还有一个来自上位机数据记录任务的写访问。一个定时器被两套系统刷新自然会出现上面那种“双执行”的诡异动作。1.2 先分清“真调用两次”和“假性双执行”在展开分析之前我们要把“FB调用1次却执行2次”这句话拆成两种情况。第一种叫做“真调用两次”也就是同一个FB实例确实有多个调用点可能是OB1里调了一次、OB30循环中断里又调了一次也可能是在某个条件分支里调了一次、块结尾处又调了一次。这种情况通过调用结构树一眼就能看出来属于调用关系设计问题。第二种叫做“假性双执行”代码里确实只写了这一个调用点但FB内部操作的定时器实例或全局变量是共享的其他程序块、上位机、通信指令也在动同一个数据区域导致FB执行一次但定时器状态被人为刷新了两次。我重点想聊第二种因为第一种在网上资料很多而第二种很容易让人抱着代码找半天找不到。很多从S7-300/400时代过来的工程师都习惯用S5定时器也就是T1、T2这种绝对地址定时器。在S7-300/400里FB内部可以直接操作T1T1就是全局资源。后来到了S7-1200/1500和TIA PortalS5定时器被IEC定时器替代新的TON/TOF/TP指令自带背景DB。一些人为了图方便在做ST编程时仍然把TON的背景DB做成全局DB然后所有FB共用这一个DB这就埋下了定时器被多路径驱动的隐患。2. 为什么FB明明调用一次定时器却执行两次2.1 FB和FC的区别决定定时器数据的存放位置要理解这个问题先得把PLC里两大程序块的区别讲明白。FBFunction Block和FCFunction都是IEC 61131-3里的标准POU区别在于FB自带背景数据块FC没有。你可以简单地把FB理解成一个带私人抽屉的盒子每次调用时你给它一个抽屉编号它把中间状态都存在抽屉里FC则是一个只干活不留痕的临时工所有内部变量都放在临时区调用完就清空。定时器需要记住当前计时到什么位置所以它必须在FB里用静态变量或者背景DB保存状态。如果定时器数据被放在全局环境里等于这个私人抽屉变成了公共信箱任何程序都能往里塞东西读取的时候自然就可能乱套。这里有个很容易被忽略的细节TON本身也是一个系统功能块它有自己的一套内部状态比如当前计时值、输出Q、内部使用的上次扫描时间标记。你把TON放在FB的局部变量区和放到一个全局DB里对TON自身来说没有区别它都只是存数据。区别在于谁有权访问这个数据区域。局部实例化之后TON的数据跟着外层FB的背景DB走不同FB实例天然隔离。全局DB则意味着任何逻辑、任何任务、甚至通信服务都能访问它。所以问题的本质不是FB调用了几次而是定时器实例的数据作用域被扩大到了全局。2.2 定时器实例化方式局部实例 vs 全局背景DB在ST/SCL中调用一个TON定时器有几种不同的姿势。第一种是在FB的VAR区里声明一个TON类型的变量比如TimerInstance : TON;这种方式最推荐因为这个TimerInstance随着FB实例的背景DB一起保存每个FB实例都有一份自己的定时器。第二种是直接在调用TON时给它分配一个独立的背景DB比如在OB1里添加TON_DB_1然后把TON_DB_1(IN:Start, PT:Delay, QDone)写进程序这种方式适合在OB1等组织块里直接使用定时器不涉及多实例问题。第三种是项目里先建好一个全局TON背景DB然后在多个FB里直接调用它比如Global_Timer_DB_1.Timer1(IN:Start, PT:Delay, QDone)这就触发了共享风险。问题集中在第三种。当一个全局TON背景DB同时被多个FB、多个调用点或上位机任务使用时它就不只是某个FB的私有状态了。你在FB_A里把它当自己的定时器用在FB_B里又把它当另一个定时器用每次调用都会根据当时的IN、PT、R输入刷新它的内部状态。即使FB_A的调用频率和FB_B完全不同最终表现就是定时器被加速、被复位、被异常触发。在交叉引用里查这个全局DB会发现一长串读写位置这时候再回头看“FB调用1次”就很讽刺了——FB确实只调用1次但定时器是按多个写者的频率在执行。2.3 最常见的三种“双执行”路径路径触发方式典型特征常见平台双组织块调用同一个FB实例在OB1和OB30等不同任务中调用定时器ET增长速度异常甚至一个周期跳变两次TIA Portal、CODESYS、TwinCAT全局定时器DB共享多个FB/多个调用点共用同一个TON背景DB定时器输出随机触发或互相干扰TIA Portal、CODESYS使能信号周期内被改写ST代码在定时器调用前/后多次修改IN输入定时器经常提前复位或永远到不了PT所有支持ST的平台第一种路径在TIA Portal里特别容易遇到。很多人习惯把设备动作写在OB1里然后又在OB30或OB32里做高速检测图省事就直接复制调用同一个FB实例。OB30是循环中断默认100ms执行一次如果FB内部有一个5秒的TON定时器OB1每个扫描周期更新一次ETOB30每个100ms又更新一次ET同一个定时器ET的增长速度就变成了双份。从OB1监控窗口看程序明明只写了1次调用定时器却走得像被调用了2次。第二种路径就是我开头说的全局背景DB共享。第三种路径则属于ST代码写法问题比如在TON调用之后又对Start变量做了复位或者TON的IN不是来自输入端子而是来自一个在同一扫描周期里被多次赋值的中间变量。这些情况看着像定时器执行了两次实质上是定时器被同一个输入信号反复“重新触发”。后面我会把每种路径的排查方法都过一遍。3. 定位问题用调用结构和交叉引用一层层查3.1 第一步确认FB真实调用次数遇到异常行为第一件事不是改代码而是先用工具确认FB到底被调用了多少次。在TIA Portal里右键点击FB选择“调用结构”可以看到这个FB被谁调用、调用了多少次还能看到它内部又调用了哪些块。CODESYS和TwinCAT里也有类似视图CODESYS叫“调用树”TwinCAT在POU的References里也能看到。如果调用结构里只显示了OB1一个调用点而FB内部又被其他FB嵌套调用那就要把子层级也展开看它是否通过多重背景被多个上层块间接调用。注意一个细节TIA Portal的调用结构默认可能只显示主程序调用链看不到OB100、OB30这些组织块对同一个FB的调用因为组织块之间没有直接调用关系。所以检查完调用结构之后我还建议直接在程序块窗口使用“交叉引用”搜索这个FB的名字所有相关引用会以列表形式列出来包含引用类型、所在块、网络号、地址信息。这一步能把隐藏在不同任务里的调用点全揪出来。我见过太多人只看OB1里没第二次调用就直接排除重复调用结果坑在OB30里。3.2 第二步查定时器背景DB被谁读写确认完FB调用点之后下一个目标就是定时器背景DB。如果FB内部用的是局部TON变量它的数据不在独立DB里而是嵌在FB背景DB中。如果用的是全局TON背景DB项目树里能直接看到这个DB。选中这个DB右键选择“交叉引用”所有读写位置都会列出来。读位置多还好说写位置一旦超过一个就要认真对待了。在这个列表里你可能会看到OB1、OB30、甚至HMI连接里的数据记录任务、Profinet通信指令都在写同一个DB区域。这里需要提醒一下当我们说“上位机在读写定时器背景DB”时并不意味着上位机真的直接调用了TON指令它可能只是在读写DB里的某个变量但由于TON的背景DB被当作全局数据区使用上位机写入了某个参数比如把PT预设时间改了或者把IN状态改了都会间接影响定时器状态。这种跨程序、跨通信路径的干扰排查起来最隐蔽。我的习惯是一旦发现一个背景DB被写位置超过一个先停下来画一条数据流图搞清楚每条写路径会在什么时机改变哪些字段。3.3 第三步用自增计数器做二分定位纯靠肉眼观察在线监控有时候很难判断FB到底执行了几次。这里推荐一个我非常常用的绝招在FB内部加一个静态自增计数器。具体做法是在VAR区加一个DINT变量比如ScanCount : DINT;然后在FB代码第一行写ScanCount : ScanCount 1;。在线监控时如果ScanCount每个扫描周期固定加1说明这个FB本身只在这条路径上执行一次如果每个扫描周期加2或者数值跳变不稳定那必然存在第二条调用路径。这个方法相当于在程序里安了一个转速表FB执行频率一变就能看出来。那怎么判断是不是定时器本体被外部共享了呢你可以在定时器调用前后分别记录EN端状态和ET值。比如在定时器调用之前把ET_Before : TimerInstance.ET;调用之后再读一次。如果在一个扫描周期内ET的增量明显大于当前任务周期应有的增量那说明除了当前调用还有别的程序路径也在刷新这个定时器。如果ET增量看起来正常但Q输出却异常翻转那就更可能是IN信号或定时器DB被其他地方改写。用自增计数器和ET增量两个维度夹击基本能在一两个小时内定位到问题层。3.4 不同ST平台的排查工具差异TIA Portal、CODESYS、TwinCAT这三个主流平台在排查方法上大同小异。TIA Portal的交叉引用功能最直观右键点一下就能看到完整的引用列表调用结构也很清楚。CODESYS里要在“视图”菜单里打开“调用树”交叉引用在POU上右键也有但需要手动刷新。TwinCAT在PLC环境下有“参考”功能不过它对结构化文本的支持和CODESYS一脉相承。国产PLC大部分都基于CODESYS比如汇川、信捷、和利时它们的ST语言和调试风格基本一致学会了CODESYS就等于同时学会了多个平台。解决的问题方法都一样先看调用结构再看交叉引用最后用自增计数器确认执行频次。4. 实操复现从错误写法到正确写法4.1 错误写法全局定时器DB 多任务调用为了让你直观看到问题我用SCL写一个复现例子。先看错误写法。假设我有一个FB名字叫FB_FanControl负责风机延时启动内部用了一个全局TON背景DB代码大概长这样FUNCTION_BLOCK FB_FanControl VAR_INPUT Start : BOOL; PT : TIME; END_VAR VAR_OUTPUT Done : BOOL; END_VAR // 示意调用项目里已经存在的全局TON背景DB Global_Timer_DB_1.Timer1(IN : Start, PT : PT, Q Done); END_FUNCTION_BLOCK看代码本身FB内部没有任何问题。问题在于这个Global_Timer_DB_1不是一个只属于FB_FanControl的定时器实例它是一个全局DB。假设项目中OB1调用了FB_FanControl的一个实例同时OB30循环中断里也调用了同一个实例那么两个任务会在各自的执行周期里对Global_Timer_DB_1做读写。每个任务写一次定时器执行次数自然就翻倍了。还有人会问如果我在OB30里不调用FB_FanControl而是直接调用Global_Timer_DB_1里那个Timer1呢结果一样。Global_Timer_DB_1里面的TON对象被两个不同的逻辑位置调用只要其中一个调用时IN true就会影响定时器状态。所以错误的核心不在代码看起来正不正确而在于定时器实例的作用域被设计成了全局共享。这种写法在小型项目里可能运行几个月都不暴露问题一旦出现第二个调用点定时器立刻开始抽风。我把错误写法的现象总结成三个征兆一是定时器ET增长速度不是自己设定的时间基准二是Done输出带重复脉冲或提前翻转三是多个互不相关的功能块表现出的定时器行为完全一致。如果你在现场看到这三个征兆优先怀疑全局定时器DB共享。4.2 正确写法FB内部使用局部TON实例正确写法其实特别简单就是让每个FB实例拥有一份独立的TON状态。做法是在FB的VAR区里声明TON类型变量然后直接调用它。还是上面那个风机启动FB正确写法这样写FUNCTION_BLOCK FB_FanControl VAR_INPUT Start : BOOL; PT : TIME; END_VAR VAR_OUTPUT Done : BOOL; END_VAR VAR TimerInstance : TON; END_VAR TimerInstance(IN : Start, PT : PT, Q Done); END_FUNCTION_BLOCK这段代码的关键区别在VAR区的TimerInstance : TON;。这个TON实例不需要单独的背景DB它会被放入FB_FanControl自身实例的背景DB中。换句话说如果你在OB1里为FB_FanControl创建了两个实例DB分别叫FB_FanControl_DB_1和FB_FanControl_DB_2那么每个实例都携带着各自独立的TimerInstance。FB实例之间互不干扰这才是FB封装定时器应该有的样子。当你在OB1里调用完这个正确写法的FB实例即使OB30里再用另一个FB实例也完全没有问题因为每个实例都有独立的定时器状态。同一个FB类型可以被任意多个实例化实例之间通过背景DB隔离。这条规则是所有支持ST语言的PLC平台通用的CODESYS、TwinCAT里也一样。虽然细节上不同IDE的显示效果不一样但原理完全相同。4.3 复杂场景多重背景嵌套调用的隔离方式接下来是很多进阶用户踩过坑的多重背景嵌套。什么叫多重背景简单说就是一个FB内部把另一个FB的类型声明成自己的静态变量。比如父FB_FB_MotorGroup需要两组风机控制它可以在VAR区写FanA : FB_FanControl; FanB : FB_FanControl;这样FanA和FanB就是两个内嵌的子实例。由于FB_FanControl内部用了局部TONFanA的定时器和FanB的定时器天然隔离完全不需要手动管理背景DB代码也会变得非常干净。如果把子FB内部的定时器改成全局TON DB情况就不一样了。父FB就算声明了FanA和FanB两个子实例它们内部调用的仍然是同一个全局定时器DB。FanA启动会让定时器计时FanB启动会同时触发同一个定时器最后两个风机动作完全绑在一起。这种问题光看父FB代码根本看不出来必须进到子FB内部去做交叉引用才能发现全局定时器DB被FanA和FanB共用。所以嵌套调用的隔离前提是每个子FB内部必须使用局部实例化方式。我建议团队里统一一个编码规范凡是FB内部要使用定时器、计数器、上升沿检测这类带记忆功能的标准块一律在FB的VAR区声明局部实例禁止在FB内部直接调用全局TON/CTU背景DB。这条规范不需要太复杂执行起来收益却特别大可以省掉无数个“为什么定时器自己乱走”的加班夜。5. 避坑清单与常见问题速查5.1 ST定时器编程的六条避坑原则不要在FB内部使用全局TON/TOF/TP背景DB哪怕是项目里只有一个FB实例也要避免。一个FB实例只在一个任务/组织块中调用不要让OB1和中断OB共用同一个实例。定时器的IN输入不要在一个扫描周期里被多次赋值如有多个触发源先用中间变量聚合。使用TON时必须在每个扫描周期持续调用不要用IF条件把调用包得时断时续。在多实例场景下优先用FB局部变量声明定时器让每个实例自然隔离。定期用交叉引用扫描定时器背景DB发现写位置超过一个就立即重构。这里特别说一下第四条。TON这类IEC定时器不是硬件定时器它靠每个扫描周期被调用来更新内部计时状态。如果你把TON调用写在一个IF条件里条件为false的那个扫描周期定时器就没有执行状态会保持在上次调用的结果。很多人以为这没问题实际上如果条件在临界状态抖动定时器就会被间歇性驱动表现为计时速度不稳定。正确做法是让TON无条件调用再用IN输入控制启停用R输入做复位。5.2 常见问题与解决方案速查表现象可能原因排查方法解决方案定时器计时速度翻倍同一定时器被两个任务同时驱动交叉引用查背景DB拆分实例或只在一个任务中调用定时器输出反复跳动全局TON DB被多个FB共享查全局DB的写位置改为FB局部TON实例定时器永远到不了PTTON调用被包进IF条件且不持续执行检查ST代码结构性保证定时器每个扫描周期都被调用按一次启动却触发两次输出IN信号在扫描周期内被多次改写在线监控IN信号用置位/复位或中间变量统一触发一个FB多个实例互相影响子FB内部用了全局定时器DB查看子FB交叉引用子FB内部改用局部定时器实例上位机周期性干扰定时器定时器背景DB被通信任务读写查看HMI/上位机数据访问定时器状态不放在上位机可写区域5.3 一次现场排查记录启动延时为什么时好时坏分享一个现场排查案例。去年朋友工厂一条包装线出现一个诡异问题输送带启动延时3秒有时正常有时刚启动就立刻动作有时干脆不启动。现场工程师查了两个小时没头绪把程序截图发给我。程序里有一个FB_CVPack内部封装了TON定时器OB1调用点只有一次。看起来一切都对。我让他打开交叉引用查询TON背景DB发现除了OB1的调用外还有一个HMI脚本通过数据记录功能每隔500ms写一次这个DB里的IN参数。虽然HMI写的是IN参数表面上看与TON状态无关但实际上TON背景DB被两个系统同时访问内部状态变量和Q/ET标志在异常写入时会出现瞬态错乱。后来把定时器改成了FB局部实例HMI的写入点指向一个新的中间变量问题立刻消失。这个案例说明一个道理排查定时器异常不能只看PLC程序本身还要看通信和上位机是否染指了定时器背景DB。PLC程序是封闭的但DB可以被外部系统访问一旦开放了这个口子定时器就不再是FB独享的私有财产了。现场来了问题先做好边界隔离再谈程序逻辑。6. 个人一点心得我自己的项目经验里ST/SCL最容易出问题的地方永远不在语法而在变量实例的作用域理解。定时器看似是一个指令底层其实是一个带记忆状态的功能块它比普通函数复杂得多。FB调用一次不代表定时器执行一次因为你可能把一个全局定时器背景DB当成了私有资源。刚学ST语言时我也觉得局部变量和全局变量只是声明位置不同后来被这种双执行问题折磨过一遍才彻底明白实例隔离才是结构化文本的重心。最后再分享一个小技巧写新的FB时只要看到内部要出现TON、TOF、TP、CTU这类带记忆的标准功能块第一反应就应该是“我要一个局部实例”而不是“我要一个全局DB”。把这个习惯刻进脑子里至少能帮你避开一半的定时器坑。希望这篇踩坑实录能让你少熬几个夜。
返回列表