☰
PLC跨小时统计选型:T#5S轮询比R_TRIG更稳
2026/10/19 1:05:33 网站建设 项目流程

做产线自动化的人,迟早都会碰到跨小时统计这个活儿。设备上有个累计值,产量、流量、能耗、废水排放量都算,上位机想看“每个小时实际发生了多少”,就得让PLC在每个自然小时切换那一刻把数据快照拍下来。标题里“T#5S相比R_TRIG哪种稳”这个问题,本质上是两种技术路线之争:一边是每5秒定时看一眼时间,另一边是拿R_TRIG上升沿去抓小时变化的边沿。我两种写法都试过、都上过现场,这些年做下来,结论很明确:在跨小时统计这个场景里,T#5S轮询方案比R_TRIG稳得多。下面我把原理、代码、极端场景对照全部展开,适合正在写报表统计逻辑的PLC工程师、做能源管理系统的同行,也适合刚开始接触ST语言、想搞清楚定时器和上升沿怎么选的新手。

1. 跨小时统计到底卡在哪里:先搞清需求再谈方案

先说业务侧。所谓跨小时统计,通常不是要PLC自己在整点“报一声”,而是要在整点切换的瞬间完成三件事:把当前累计值快照存为上一小时的结束值、把本小时的起始累计值更新为同一个快照、让上位机随时能读到“当前小时已经累计了多少”。常见的对象包括流量计累计量、电度表读数、产量计数器、设备运行时长。

这个需求听起来不难,但PLC本身并没有“自然小时”这个概念。它只知道扫描周期和系统时钟。跨小时统计的本质,就成了“在正确的时间点做一次正确的采样”。难点全在边界条件:23:59:59到00:00:00那一瞬间怎么处理、程序重启会不会误统计、手动校时之后会不会漏统计。这些情况平时遇不到,一旦遇到就是报表数据对不上,而且事后完全没法追溯。

1.1 动手前先选对统计模型:累计值差,不要清零

我见过不少现场,小时统计做出来之后偶尔丢数据,查到最后根本不是定时方案的问题,而是统计模型选错了。最忌讳的是“跨小时后把计数器清零”这种思路。清零模型的隐患在于:清零指令和计数更新如果落在同一个扫描周期,边界上的脉冲可能被吞掉,或者被算进下一个小时。设备一快,这种错误会随机出现,极难复现。

我现在的标准做法是累计值差模型:保留一个总累计值,它本身单调上升,任何时刻的小时用量都等于“当前累计值减小时起始快照值”。跨小时时只更新快照,不碰累计值本体。这样无论检测跨小时的手段是哪一种,统计逻辑自身都是安全的。这也是后面对比方案的前提。

1.2 怎么评判“稳”:四个维度

方案好不好,不能只看正常整点切换跑没跑通,还得看极端情况。我通常用四个维度打分:

  • 漏检率:是否存在某次跨小时完全检测不到的情况;
  • 重复触发概率:同一次跨小时会不会被统计两次;
  • 异常容忍度:程序重启、时间回拨、校时跳跃之后还能不能安全恢复;
  • 可维护性:三个月后换个人来看代码,能不能一眼读懂。

后文所有对比都围绕这四个维度展开。把标准先定清楚,再谈技术选型,才不会变成无意义的“哪个写法更高级”之争。

2. T#5S定时轮询的真实运行逻辑:越简单越不容易出错

2.1 T#5S是什么:一张5秒看一次时间的节奏表

IEC 61131-3里,T#5S是时间字面量,表示5秒。它在ST代码里最常见的用法是配合TON定时器生成一个固定周期脉冲。典型的写法是:

tonTick(IN := NOT tonTick.Q, PT := T#5S);

这一行的行为逻辑是:TON的Q在定时到达后变TRUE,我们把Q取反接到IN上,下一周期IN变FALSE,TON复位,Q变回FALSE,于是IN再次变TRUE,又开始定时。结果就是每5秒,Q会拉高一个扫描周期。这个“每5秒冒出来一个脉冲”的节奏,就是T#5S方案的地基。

对比一下R_TRIG方案中“每个扫描周期都去读时间、比较时间”的做法,T#5S方案明显对CPU更友好,更重要的是它的检测逻辑和扫描周期解耦了一部分,不会因为扫描周期的微小抖动而反复触发。

2.2 核心比较逻辑:用不等于,别用大于

T#5S方案最关键的细节是:检测跨小时时用不等于比较,而不是上升沿或大于比较。原因后面会展开,先看代码:

dtNow := GETDATE(); iNowHour := HOUR_OF_DAY(dtNow); IF iNowHour <> iLastHour THEN // 跨小时了,执行快照和统计 rLastHourValue := rTotal - rHourStartTotal; rHourStartTotal := rTotal; iLastHour := iNowHour; bHourChanged := TRUE; ELSE bHourChanged := FALSE; END_IF

这里用<>(不等于),等于把判断变成了“只要小时值变了就认账”。23点变0点是变了,0点变1点也是变了,程序不需要关心变化方向,也不需要理解小时值本身是循环的。这套比较逻辑能在任何方向的时间变化下都触发统计。

2.3 为什么间隔选5秒,而不是1秒或1分钟

定时间隔是一个工程权衡。我用过1秒,也用过1分钟,最后稳定在5秒:

  • 1秒轮询:每个小时要读3600次时间,PLC的CPU和通信都被频繁打扰,而且并不会让统计更精确——它只是把最大检测延迟从5秒压缩到1秒,对报表用户来说几乎无感;
  • 1分钟轮询:最大检测延迟变成60秒,整点过了1分钟报表还没刷新,上位机那边就会催了;
  • 5秒轮询:最大延迟5秒,绝大多数报表页面都接受,CPU开销比1秒低了整整一个数量级,典型的中小型PLC完全跑得动。

如果现场还带着零点校准、班次切换、定时推送这些联动逻辑,5秒的节奏通常也不会和它们冲突。这是一个相当安全的默认值。

3. R_TRIG上升沿方案的隐性盲区:23点跨0点那一下

3.1 R_TRIG的直观做法,为什么会掉进循环时间的陷阱

R_TRIG是IEC 61131-3里的标准功能块,作用是检测BOOL信号的上升沿,Q在CLK从FALSE变为TRUE时输出一个扫描周期的TRUE。很多工程师第一次做跨小时统计时,会自然想到“小时值从8变成9不就是上升沿吗”,于是把它接给R_TRIG。思路看着顺,实际跑起来就出事。

问题在于:小时值本身不是单调递增的信号。它按0到23循环,23之后跳回0。从23切换到0,在数值上看是下降沿,R_TRIG对下降沿完全无感。于是每天零点那次统计,R_TRIG方案会安静地跳票,而零点恰恰是报表里最显眼的那一次。

另一种常见做法是检测“分钟=0”的信号:

bAtHour := (MINUTE_OF_DAY(dtNow) = 0); rtrigMinute(CLK := bAtHour); IF rtrigMinute.Q THEN // 整点触发 END_IF

这个方案的问题是它对扫描时刻极其挑剔。程序必须恰好在“分钟值从非0变成0”之后紧跟着的那个扫描周期执行到这段逻辑,不能早也不能晚。正常循环运行没问题,但一旦程序在00:00:30才重启,第一次读取分钟就是0,CLK没有可观测的上升沿,R_TRIG便不会触发。更麻烦的是,这类故障只在特定时间点重启才会出现,极难定位。

3.2 跨天特判能解围,但补丁会越打越多

有人给R_TRIG打补丁:检测iNowHour = 0 AND iLastHour = 23,把跨天那次补上。这样做确实能让零点统计恢复,但代码已经比T#5S的不等于比较复杂了。更糟的是,这个补丁只覆盖23到0这一种跳变。手动校时从10点回拨到8点、跨月切换、闰秒、甚至时间连续跳了两个小时,这些场景都需要继续加判断分支。补丁一多,后面接手的人看着一坨条件语句,头都大了。

3.3 更隐蔽的问题:重复统计与状态维持

再补一刀。R_TRIG对持续为TRUE的信号只响应第一次变化,这在某些场景反而是隐患。比如时间源发生一次向前跳变,程序在旧时间下已经触发过一次统计,跳到新时间后,小时值如果并没有再次上升,R_TRIG不会再次触发。结果就是数据被“卡住”,报表里那一小时永远是旧值。

这些还只是正常校时场景。如果现场手动把时间从23:58改到00:03,小时值从23变成0,依然是下降沿,R_TRIG连“补一次”的机会都没有。说到底,R_TRIG是为“BOOL信号从0翻转到1”这种物理事件设计的,而“小时值变化”是循环取值的状态变化,两者根本不是同一个物种。

4. 三组实测场景对比:整点切换、程序重启、时间校时

4.1 仿真环境怎么搭

纸上谈兵没意思,我把两种方案各写成一个功能块,在通用软PLC仿真环境里跑了几组对照。循环周期设为10ms,用一个变量模拟每秒递增1的累计值,系统时间分别预设为午夜前、程序启动、时间回拨三种情况,连续观察统计记录是否正确生成。

4.2 场景一:23:59:50开始的整点切换

把系统时间停在23:59:50,跑3分钟。T#5S方案在23:59:55读取小时值23,无动作;到00:00:05的定时脉冲到来,小时值变成0,不等于比较成立,上一小时统计输出,新小时快照建立。统计延迟约5秒,完全可接受。

R_TRIG基础版全程没有触发,零点数据直接丢失。加了跨天特判的R_TRIG版在预设条件下能触发,但要写分支判断、记录上一小时值,代码量和维护点明显更多。

4.3 场景二:00:00:20程序重启

在00:00:20重启程序。T#5S版功能块首次扫描就把当前小时值设为基准,同时记录当前累计值为本小时初始快照,不会产生任何误统计。

R_TRIG版这里埋着一个很深的初始化坑:如果忘了把“上一次小时值”初始化为当前小时值,CLK第一次从初始FALSE变为TRUE时就会误触发。实测中我把上次小时值初始化成-1,程序启动后立刻输出了一个假的小时统计。这个坑不仔细踩一次,很难想起来。

4.4 场景三:手动校时,方向不同结果天差地别

把系统时间从15:00手动改到16:30。两个方案都能检测到变化并统计一次,区别不大。

把系统时间从16:30改回15:00。T#5S的不等于比较照常触发,逻辑与之前完全一致,没有任何额外成本。R_TRIG看不到任何上升沿,直接漏检。而且运维人员改完时间后通常不会意识到小时统计已经悄悄漏了一次,等发现报表数据对不上时,已经没有任何日志能还原当时的真实累计值了。

三组对照下来,R_TRIG比T#5S多出的两个边界成本:跨天特判和重启初始化,恰好都是现场最容易出问题的时段。

5. 我的选型结论与落地代码模板

5.1 结论:跨小时统计用T#5S,R_TRIG留给物理信号

把结论说透一点。在跨小时统计这个具体场景里,T#5S轮询+不等于比较是我会毫不犹豫选择的方案。R_TRIG不是不能用,但它要求你额外维护跨天特判和重启初始化两个易错点,还得祈祷时间源永远不反向跳动。把一种需要大量补丁才能可靠工作的方案用在日常报表统计上,不划算。

R_TRIG的真正用武之地是检测物理上升动作:按钮按下、传感器到位、报警出现、气缸伸出。这些信号的语义是事件,边沿检测天然契合。拿它去处理小时值这种循环数值的状态变化,等于用错了工具。

5.2 可复制的ST功能块骨架

下面这份是以累计值差模型为基础的跨小时统计功能块。输入当前累计值,输出当前小时增量、上一小时完整增量和跨小时标志。定时部分用TON产生5秒脉冲,时间比较用不等于。

FUNCTION_BLOCK FB_HourlyStat VAR_INPUT rTotal : REAL; // 累计值输入(来自仪表/计数器) END_VAR VAR_OUTPUT rCurrentHour : REAL; // 当前小时累计增量 rLastHour : REAL; // 上一个完整小时增量 bHourChanged : BOOL; // 本次扫描发生跨小时 END_VAR VAR tonTick : TON; iLastHour : INT; // 上次记录的“小时序号” rStart : REAL; // 本小时起始累计值快照 bInit : BOOL := FALSE; iNowHour : INT; dtNow : DT; END_VAR (* 5秒定时脉冲 *) tonTick(IN := NOT tonTick.Q, PT := T#5S); IF tonTick.Q THEN dtNow := GETDATE(); iNowHour := DT_TO_HOUR_KEY(dtNow); (* 自某个基准起的小时序号,跨天时自然递增 *) IF NOT bInit THEN bInit := TRUE; iLastHour := iNowHour; rStart := rTotal; bHourChanged := FALSE; ELSIF iNowHour <> iLastHour THEN rLastHour := rTotal - rStart; rStart := rTotal; iLastHour := iNowHour; bHourChanged := TRUE; ELSE bHourChanged := FALSE; END_IF END_IF (* 当前小时增量持续刷新,上位机随时读都是准的 *) rCurrentHour := rTotal - rStart;

注释里那个DT_TO_HOUR_KEY是我打的示意函数:在实际PLC里,这个位置通常用“日期差乘以24加小时”或者“距离某个固定起点的总小时数”来实现,也可以直接比较“日期+小时”的组合值。核心是让时间序号跨天时保持连续递增,23点到0点时序号从某个值变成下一个值,不等于比较自然成立。

使用时有三个工程要点:

  • 功能块放在固定的任务里调用,不要在多个循环任务或中断任务里同时调用,否则静态变量会互相踩踏;
  • 统计结果建议直接写到全局变量或DB块,坚持每5秒刷新一次当前小时增量的输出模式,避免上位机读到中间态的垃圾值;
  • 首次扫描必须先初始化,把当前小时序号的变量和累计值快照都设好,再谈检测。

5.3 工程里还要盯住三个细节

第一,时间同步。跨小时统计的精度完全依赖PLC本地时钟。现场多台PLC如果各自走各自的时钟,统计边界会差出几十秒。我习惯以一台主PLC或者上位机为基准统一校时,并且定期做一次时间同步巡检。

第二,掉电恢复。累计值差模型天然抗掉电:掉电期间累计值不增加,恢复后rStart和当前累计值都还在,跨小时快照基本不会错乱。如果断电恰好横跨整点,T#5S恢复后第一个5秒脉冲就会检测到小时变化并补上统计,最多差5秒数据。

第三,时间跳变异常报警。我在实际项目里会再加一个计数器:如果检测到跨小时变化的次数拆开看间隔异常,就置一个报警位,提示运维“本地时钟可能被修改过”。这种问题平时没人能想到,只有程序自己盯着才靠谱。

5.4 这套骨架还能往哪些方向扩展

T#5S轮询思路很容易推广到跨分钟、跨班次、跨日统计,只需要把时间比较的粒度从“小时”换成“分钟”或“日期序号”,代码骨架完全不动。如果需要做整点报表推送,在bHourChanged为TRUE的分支里追加历史数组写入或消息推送就行。想做一个多设备小时能耗对比平台,也只需要把每个设备各放一个这样的FB,结果统一汇总到上位机。

我在好几个产线项目里给小时统计换过方案,早期也用过R_TRIG,后来陆续被零点漏数、校时漏数折腾过几回,才定下来用T#5S轮询加不等于比较这套。说句实在话,统计逻辑本身不复杂,最难的是把各种意外时刻都照顾到。T#5S方案之所以稳,不是因为它有什么高深算法,而是它把边界条件放到了明面上:随时看一眼,变了就记录,就这么简单。如果你手头也有类似的跨小时统计需求,建议先按这个骨架搭一版,再故意调几个极端时间跑跑仿真,比争论哪种写法更高级实在得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询