☰
S7-1200/1500结构化编程:白班夜班流量累计与掉电保持
2026/11/4 10:47:29 网站建设 项目流程

1. 现场一对账,问题往往不在流量计

做过工厂计量的人大概都遇到过这种场面:月底盘点,生产部说这个月处理量应该是多少,财务按照流量计累计的数值一算,差了百分之七八,双方坐下来一查,流量计本身标定没问题,脉冲输出也正常,问题出在“累计”这两个字上——谁在什么时间点清零、清零前有没有把数存下来、白班夜班交接那几分钟的数据到底算谁的,全是糊涂账。标题里说的西门子PLC结构化编程和白班夜班流量累计,本质上解决的就是这件事:让每一班用了多少、总共用了多少,都有明确出处,交接班不再靠人嘴报数。

这个内容适合谁看?我把它分成三类。第一类是刚接手S7-1200/1500项目、还在用一堆M点写程序的电气工程师,你需要一套能复用的块结构;第二类是负责能源计量、水平衡、蒸汽平衡的仪表或工艺人员,你关心的是数据准不准、能不能追溯;第三类是做设备改造的集成商,甲方要求加一个班次报表功能,你需要在不停产的前提下把逻辑塞进去。不管哪一类,只要你的现场有脉冲流量计、有模拟量瞬时流量、有班次交接的需求,这套思路都能直接搬。

我先把结论摆出来:班次累计的难点从来不是“加一个数”,而是时间基准、数据保持、切换时序这三件事的耦合。很多程序写成一个大FC,几百行网络连在一起,时间判断、脉冲计数、清零、显示全搅在一块,出问题根本没法定位。结构化编程的价值就在这里——它逼着你把职责切开,切开了,问题自然就浮出来了。下面我按照从设计到落地的顺序,把这块内容完整讲一遍,中间会给出可以直接抄的SCL代码和DB结构,也会讲清楚每一个参数为什么这么选。

1.1 一个水处理站的典型场景

我拿一个虚构的场景来说,方便后面举例。某水处理站有两路进水,一路是脉冲输出的电磁流量计,每100个脉冲代表1立方米;另一路是4-20mA输出的瞬时流量,量程0到50立方米每小时。站里实行两班倒,白班早八点到晚八点,夜班晚八点到次日早八点。要求是:HMI上能看当前班次累计、上一班累计、当日总累计,并且每天早八点交接班时,上一班的数值要被锁定,不能被新数据覆盖,同时要保留最近七天的历史。

这个需求听起来简单,但只要你动手写,马上会撞上几个问题:夜班跨零点怎么算?PLC断电重启后,正在累计的这半小时数据怎么办?交接班那一瞬间正好来了一个脉冲,算白班还是夜班?HMI上按了清零,是清本班还是清总累计?这些问题不提前想清楚,程序写出来一定是补丁摞补丁。

1.2 三种常见的“土办法”及其代价

我见过最多的写法是:在OB1里用系统时钟存储器的一个位做触发,比如M0.5是1Hz脉冲,然后IF M0.5 THEN里面读时间、判断小时数、累加。这种写法能跑,但问题在于所有逻辑挤在一个网络里,时间读取、班次判断、流量累加、清零判断互相依赖,改一处动全身。更麻烦的是,一旦现场要加第二路流量,你得把整段复制一遍,两个流量计的清零时序还可能对不齐。

第二种写法是把班次判断做成比较指令,直接比较RD_LOC_T返回的HOUR是不是大于8小于20。这个写法本身没错,但它忽略了一个坑:RD_LOC_T在S7-1200上是异步执行的,每次调用都会占用扫描时间,如果放在高频扫描的OB里反复调用,会拖慢整个程序。正确做法是用一个低频触发(比如1Hz)去刷新时间缓存,其他地方读缓存。

第三种是清零逻辑写成IF 时间 = 8:00:00 THEN 清零。这种写法几乎必翻车,因为PLC的扫描周期和时钟秒的变化不可能精确对齐,8:00:00这个状态可能被扫描到两次,也可能一次都扫不到。正确思路是用“班次编号变化”作为切换条件,而不是用“时间等于某个值”。

1.3 结构化能带来的实际收益

把上面这些坑绕开之后,程序会长成什么样子?我的做法是:一个FB负责“流量累计”,一个FB负责“班次管理”,两者通过一个共享的DB交换数据,OB1里只做调用和参数传递。这样一来,第二路流量只需要再拖一个背景DB,第三路、第四路同理;班次规则要改(比如改成三班倒),只动班次FB,流量FB一行不改;现场要查问题,在线监控直接看DB里的中间变量,哪个环节断了肉眼可见。

2. 整体设计:把“班次”和“流量”拆成两件事

结构化编程这四个字,很多人理解成“多建几个FC”,其实不是。真正的结构化是按变化频率和职责边界来切分。流量累计的算法基本不变,班次规则却经常变(两班倒、三班倒、节假日不停机),这两者的变化频率完全不同,就应该分开。这就是单一职责原则在PLC里的落地方式。

2.1 块结构怎么划分

我的划分方式是这样的:

块类型名称职责调用位置
OBOB1 主循环只做调用,不放逻辑系统
OBOB100 启动初始化班次状态、恢复累计值系统
FBFB_FlowTotal流量累计算法,含脉冲与积分两种模式OB1
FBFB_ShiftManager班次判定、切换、清零、历史保存OB1
FCFC_TimeRead读时间并缓存到全局DBOB1(1Hz触发)
DBDB_ShiftCfg班次配置参数全局
DBDB_FlowData各路流量数据、累计值全局
DBDB_History七天历史归档全局

这张表里最关键的是FC_TimeRead这个块。它做的事情只有一件:把PLC的本地时间读出来,拆成年月日时分秒,存到全局DB里。所有需要时间的地方都读这个DB,而不是自己调RD_LOC_T。这么做的好处是,全站只有一个时间源,不会出现A块读到8:00:00、B块读到8:00:01的错位。

2.2 为什么用FB加多重背景,而不是复制粘贴

S7-1200/1500的FB支持多重背景,意思是你可以在一个FB里声明另一个FB的静态实例,或者给同一个FB分配多个背景DB。对流量累计来说,这个特性太有用了。假设站里有四路流量,你写一个FB_FlowTotal,然后在DB_FlowData里声明四个静态实例:

// DB_FlowData 内部结构示意 Flow1 : FB_FlowTotal; Flow2 : FB_FlowTotal; Flow3 : FB_FlowTotal; Flow4 : FB_FlowTotal;

调用的时候写四行,参数各传各的。这样做的直接好处是:算法只有一份,改一处全生效;背景数据各自独立,互不干扰;在线监控时,四个实例的中间变量清清楚楚,不会像复制粘贴那样出现“改了第一路忘了改第三路”的经典事故。

注意:多重背景的FB内部不能再嵌套太深,S7-1200对嵌套深度有限制(一般是8层),嵌套过深编译会报错。我的经验是控制在三层以内,流量FB里不要再套通讯FB。

2.3 数据流向要提前画清楚

在动手建块之前,我习惯先在纸上画一遍数据流,这一步花十分钟,能省后面十个小时。这个项目的数据流大致是:

脉冲信号或模拟量 → 流量FB内部累加 → 本班累计值 → 班次切换时搬移到“上一班累计” → 总累计同步更新 → HMI读取 → 上位机归档。

这里有个容易忽略的点:总累计和班次累计必须是两套独立的累加,不能用“本班+上一班”去反推总累计。因为如果某天中途手动清了一次本班累计,反推出来的总累计就错了。总累计只增不减,除非运维人员走专门的清零流程。

3. 时间基准:班次判定的三个关键技术点

时间这块是整个功能的地基。地基没打好,后面累计算得再准,班次归属也是错的。我见过一个项目,PLC用的是S7-1200,没有配电池,现场停电两小时,上电后系统时间回到2000年1月1日,程序照样按小时数判断班次,结果当班的累计数全算到了别的班头上,最后是靠流量计本身的累计字对账才对上。所以时间这块必须认真对待。

3.1 读取PLC本地时间

S7-1200/1500读时间有两个常用指令:RD_SYS_T读系统时间(UTC),RD_LOC_T读本地时间。国内项目基本都是用本地时间,因为操作工看的是墙上挂钟。RD_LOC_T返回一个DTL结构体,里面有YEAR、MONTH、DAY、WEEKDAY、HOUR、MINUTE、SECOND、NANOSECOND这些成员。

需要注意的是,这两个指令在S7-1200上是异步执行的,调用后会先返回一个初始值,真正的结果要等下一次扫描才有效。所以正确用法是:用1Hz的时钟位做触发,每秒读一次,读到的结果放进全局DB缓存。千万不要在高速循环里反复调用,也不要拿指令的返回值直接做实时判断。

// FC_TimeRead 内部逻辑(1Hz触发调用一次) IF #Trig THEN #tmpDTL := RD_LOC_T(); "DB_Global".Year := #tmpDTL.YEAR; "DB_Global".Month := #tmpDTL.MONTH; "DB_Global".Day := #tmpDTL.DAY; "DB_Global".Hour := #tmpDTL.HOUR; "DB_Global".Minute := #tmpDTL.MINUTE; "DB_Global".Second := #tmpDTL.SECOND; "DB_Global".TimeValid := (#tmpDTL.YEAR >= 2020); END_IF;

最后那行TimeValid是我加的一个保险。如果年份小于2020,说明时钟没设置(通常是掉电丢了),这时候程序应该报警并暂停班次切换,而不是拿错时间继续算。

3.2 班次边界的判断逻辑与防抖

判断当前属于哪个班,最直接的方式是比较小时数。假设白班是8点到20点,夜班是20点到次日8点:

IF ("DB_Global".Hour >= #DayStartHour) AND ("DB_Global".Hour < #NightStartHour) THEN #ShiftNow := 0; // 白班 ELSE #ShiftNow := 1; // 夜班 END_IF;

这个逻辑本身没问题,问题在于调用频率。如果每个扫描周期都算一次,在8:00:00前后可能会因为时间缓存的刷新时机出现抖动。解决办法是只在时间缓存更新之后判断一次,也就是跟在FC_TimeRead后面,用同一个1Hz触发位。这样每秒只判断一次,切换动作每秒最多发生一次,不会出现反复横跳。

再往深一层,即使每秒判断一次,跨界那一秒也可能因为秒数的读取造成两次切换。所以更稳妥的做法是用班次编号的变化沿作为切换条件,而不是用当前班次去驱动清零。具体就是:保存一个ShiftLast变量,当ShiftNow <> ShiftLast时,执行一次切换动作,然后把ShiftLast := ShiftNow。这样不管判断多少次,切换动作只执行一次。

3.3 跨零点夜班的处理

夜班从晚八点到早八点,中间跨过零点,这是最容易写错的地方。如果你用“日期变化”来判定班次切换,就会在零点误触发一次清零,把夜班前半段的数据白白清掉。正确的方式是完全不看日期,只看小时数。夜班的定义是“小时数大于等于20或小于8”,这个条件在零点前后是连续的,不会中断。

还有一个细节:交接班的时刻到底是20:00:00还是20:00:01?我的做法是以整点为准,也就是小时数一跳到20就切换。这意味着19:59:59到20:00:00之间那一秒的数据算白班,从20:00:00开始算夜班。如果你对边界有更严格的要求,可以精确到分钟甚至秒,但我个人经验是整点足够用了,现场没人会因为差一秒跟你较真。

提示:夜班跨零点还牵扯到“当日总累计”的定义。如果你的报表按自然日统计,那夜班就被拆成了两半;如果按班次统计,那就不该有“当日”这个概念。这一点必须提前跟甲方确认清楚,否则做完再改非常麻烦。

4. 流量累计:三种信号源对应三种算法

流量信号进PLC,常见就三种形式:脉冲、模拟量瞬时值、通讯读取累计字。三种形式的累计算法完全不同,坑也不一样。很多人图省事,全都用模拟量积分的方式处理,结果脉冲流量计的分辨率优势完全浪费;也有人把通讯读来的累计字直接当本班累计用,结果换班时忘了做差值,数据全乱。

4.1 脉冲型流量计:最容易被低估的细节

脉冲流量计的原理是每流过一定体积发出一串脉冲,比如每升一个脉冲,或者每100升一个脉冲。PLC这边用数字量输入点采集,检测上升沿计数,乘以每脉冲对应的体积就得到流量。

听起来简单,但这里有两个大坑。第一个坑是脉冲频率。假设流量计满量程时输出频率是2kHz,而你的DI点走的是普通输入通道,受扫描周期影响,高速脉冲很可能被漏掉。S7-1200的普通DI通道在程序扫描周期内只采样一次,一般扫描周期几毫秒到几十毫秒,能可靠捕捉的脉冲频率也就几十赫兹。超过这个频率就必须用高速计数器HSC。选型时先算一下:最大流量 × 每立方米脉冲数 = 最大脉冲频率,如果超过500Hz,就别犹豫,直接上HSC。

第二个坑是脉冲当量的换算。很多流量计铭牌上写的是“脉冲当量0.1L/P”,意思是每个脉冲0.1升,也就是10000个脉冲一立方米。程序里如果按每脉冲1升算,累计值直接差一万倍。这个参数一定要在调试时用实际体积核对一遍,方法是:接一桶水,记录脉冲数,反算当量,和铭牌对比。

脉冲累计的代码不复杂,关键是上升沿检测要放在FB内部,用静态变量保存上一次的状态:

// 脉冲累计(FB内部) IF #PulseInput AND NOT #statPulseLast THEN #statPulseCount := #statPulseCount + 1; END_IF; #statPulseLast := #PulseInput; // 折算成体积,单位:立方米 #FlowThisScan := DINT_TO_REAL(#statPulseCount) / #PulsePerCubic;

这里的PulsePerCubic就是每立方米多少个脉冲,做成输入参数,方便不同流量计复用。

4.2 模拟量瞬时流量:积分算法与采样周期

模拟量流量计输出的是瞬时流量,比如4-20mA对应0-50立方米每小时。PLC通过AI模块读回来的是0-27648的整数,需要先做线性变换,再对时间做积分才能得到累计体积。

线性变换的公式是:

#FlowInst := (DINT_TO_REAL(#RawValue) - 0.0) / 27648.0 * (#RangeHigh - #RangeLow) + #RangeLow;

这里RangeLow和RangeHigh就是量程下限和上限,比如0和50。注意,如果流量计有反向流或者量程不是从零开始,RangeLow要填实际值,不能想当然填0。

积分这一步最容易出错。累计体积等于瞬时流量乘以时间间隔,单位要统一。如果瞬时流量单位是立方米每小时,采样周期是毫秒,那么:

#VolumeAcc := #VolumeAcc + #FlowInst * DINT_TO_REAL(#CycleTimeMs) / 3600000.0;

3600000这个数是怎么来的?一小时是3600秒,一秒是1000毫秒,所以小时到毫秒的换算是3600×1000=3600000。这个数千万不能写错,写错一位,累计值就差几个数量级。

采样周期CycleTimeMs怎么拿?最准的方式是用RD_SYS_T读两次系统时间做差,但那样开销大。实用做法是用OB1的循环时间,或者干脆用一个固定的定时中断OB(比如OB30,设成100ms)来调用这个FB,采样周期就是100毫秒,稳定可靠。我推荐用定时中断,因为循环时间会随程序负载波动,积分误差会累积。

4.3 通讯读取累计字:最省事也最容易出错

有些智能流量计本身带累计功能,通过Modbus RTU或者Profinet把累计值直接送过来。这种方式看起来最省事,PLC不用自己算,但坑也最深。

首先,送过来的累计值通常是流量计上电以来的总量,是一个只增不减的大数。你要得到“本班用了多少”,必须做差值运算:切换班次时记下当前值作为基准,本班累计 = 当前值 - 基准值。如果忘了做基准,直接把累计字当本班累计显示,那数字永远是几十万,毫无意义。

其次,通讯可能中断。中断期间累计字不更新,恢复后突然跳变,差值就会算出一个巨大的数。解决办法是加通讯质量判断:如果通讯故障,暂停本班的差值运算,等通讯恢复后重新取基准,并把这段数据标记为“不完整”。

再次,累计字的单位可能是升、加仑、标准立方英尺,不同的表不一样。这个必须在点表里写清楚,别到时候对着数字猜。

4.4 精度、量程与溢出问题

REAL类型在西门子里是32位浮点,尾数24位,能精确表示的整数上限是16777216。什么意思?如果你的累计值超过一千六百万,再加一个很小的数,那个小数会被舍掉,累计算不动了。

这个坑在小流量长期累计的场景特别明显。比如一路流量单位用升,累计到两千万升(两万立方米)之后就不动了。解决办法有两个:一是控制单位,尽量用立方米而不是升,把数值压小;二是在累计值接近上限时做一次“进位”,比如超过一百万就把它除以一千并记录倍数,显示时再乘回来。第二种麻烦点,一般项目用第一种就够。

5. 完整实操:从建块到下载验证

前面讲的是设计和原理,这一节讲怎么落地。我以TIA Portal V16以上的版本为例,S7-1200和S7-1500的操作基本一致,差异我会单独标出来。

5.1 数据块与变量规划

先建全局DB,我习惯分两个:DB_ShiftCfg放配置,DB_FlowData放运行数据。配置和数据分开,好处是下载程序时可以单独保留运行数据,不会因为改了个配置参数把累计值刷掉。

// DB_ShiftCfg(非优化访问,便于上位机按偏移读) DayStartHour : Int := 8; // 白班开始小时 NightStartHour : Int := 20; // 夜班开始小时 AutoReset : Bool := TRUE; // 交接班自动清零

要特别说明一点:如果你需要上位机(比如某些组态软件、报表系统)按绝对地址读这个DB,那DB要设成“非优化访问”。如果是纯HMI走符号访问,优化访问性能更好。这个选择在新建DB时就要定下来,后期改起来要做数据搬迁。

运行数据DB里放各路流量的实例、当前班次、上一班累计、历史数组:

// DB_FlowData Flow1 : FB_FlowTotal; // 多重背景 Flow2 : FB_FlowTotal; ShiftNow : Int; ShiftLast : Int; LastShiftVol : Real; DayTotalVol : Real; History : Array[1..7] of Real;

5.2 FB接口定义

FB_FlowTotal的接口我设计成这样:

参数区名称类型说明
InputEnableBool使能
InputModeInt0=脉冲 1=模拟量 2=通讯
InputPulseInputBool脉冲输入
InputPulsePerCubicReal每立方米脉冲数
InputFlowInstReal瞬时流量 m³/h
InputCycleTimeMsDInt采样周期毫秒
InputResetBool手动清零
OutputVolumeThisShiftReal本班累计
OutputVolumeTotalReal总累计
InOutVolumeLastShiftReal上一班累计
StaticstatPulseLastBool边沿检测
StaticstatPulseCountDInt脉冲计数

Mode做成输入参数,是为了让同一个FB能适配不同类型的表。现场如果一路脉冲一路模拟量,两个背景DB传不同的Mode就行。

5.3 核心SCL代码实现

下面是FB的主体逻辑,我加了详细注释,可以直接用:

// ===== 第一部分:按模式计算本次扫描的增量 ===== CASE #Mode OF 0: // 脉冲模式 IF #PulseInput AND NOT #statPulseLast THEN #statPulseCount := #statPulseCount + 1; END_IF; #statPulseLast := #PulseInput; #VolumeThisShift := DINT_TO_REAL(#statPulseCount) / #PulsePerCubic; 1: // 模拟量积分模式 #VolumeThisShift := #VolumeThisShift + #FlowInst * DINT_TO_REAL(#CycleTimeMs) / 3600000.0; 2: // 通讯模式,由外部写入 ; // 空操作,数值由通讯块直接写 END_CASE; // ===== 第二部分:总累计 ===== #VolumeTotal := #VolumeTotal + (#VolumeThisShift - #statLastScan); #statLastScan := #VolumeThisShift; // ===== 第三部分:手动清零 ===== IF #Reset THEN #VolumeThisShift := 0.0; #statPulseCount := 0; #statLastScan := 0.0; END_IF;

这里有个细节值得说:总累计用“增量累加”的方式,也就是每次算出差值再加到总量上,而不是用VolumeTotal := VolumeTotal + 本次量。为什么?因为班长累计在某些模式下会被清零(比如脉冲计数归零),如果不做差值,总累计会被重复加或者漏加。用差值法,班长怎么清零都不影响总量。

5.4 在OB1中调用与时钟配置

OB1里的事情要少,我的写法是:

// 1Hz 时钟触发读时间 "Clock_1Hz"(ENO => ); // 读时间(每秒一次) "FC_TimeRead"(Trig := "Clock_1Hz".Q); // 班次管理(每秒一次) "FB_ShiftManager"( TimeValid := "DB_Global".TimeValid, Hour := "DB_Global".Hour, DayStart := "DB_ShiftCfg".DayStartHour, NightStart:= "DB_ShiftCfg".NightStartHour, Flow1Vol := "DB_FlowData".Flow1.VolumeThisShift, Flow2Vol := "DB_FlowData".Flow2.VolumeThisShift ); // 流量累计(每个扫描周期或定时中断) "DB_FlowData".Flow1( Enable := TRUE, Mode := 0, PulseInput := "DI_Flow1Pulse", PulsePerCubic := 100.0 );

时钟存储器在CPU属性里使能,字节地址设成0,这样M0.5就是1Hz。如果你的项目里M区已经被占用,换一个空闲字节,别硬挤。

S7-1200和S7-1500在这里有个差异:S7-1500的定时中断OB可以设到1ms,S7-1200最低是1ms但实际精度差一些。做流量积分的话,100ms的定时中断对大多数工况够用了,没必要追求1ms,反而增加CPU负担。

5.5 在线调试与数据验证方法

程序下进去之后,别急着看HMI。先在线监控DB,做三个验证:

第一,验证时间缓存。看DB_Global.Hour是不是和电脑时间一致,差几个小时说明时区没设对。

第二,验证脉冲计数。手动触发几次输入点(可以在线强制,或者拿根线短接),看statPulseCount是不是每次加一,VolumeThisShift是不是按当量变化。

第三,验证班次切换。最快的办法是临时把DayStartHour改成当前小时的下一小时,等一分钟看切换动作是否触发,LastShiftVol有没有被正确搬移。验证完记得改回来。

注意:在线强制输入点时,如果这个点同时接了实际设备,可能会造成误动作。调试前确认现场处于安全状态,或者干脆在实验室先跑通。

6. 掉电保持与数据可靠性

这一节单独拎出来讲,因为它是“数据能不能信”的关键。我见过太多项目,逻辑写得漂漂亮亮,一停电全归零,操作工第二天上班发现昨天的班产没了,直接投诉到设备部。

6.1 保持性存储区的设置

S7-1200/1500的DB可以设置“保持性”,勾上之后,这个DB里的变量在掉电时会被写入保持存储器,上电后恢复。设置位置在DB属性里,或者变量表里逐行勾选。我的建议是只把需要保持的变量勾上,不要整个DB全勾,因为保持存储器容量有限,S7-1200最多好像也就10KB,勾多了编译直接报错。

必须保持的变量有:总累计、上一班累计、当前班次编号、脉冲计数值。本班累计其实也应该保持,否则停电重启后本班数据从零开始,和总累计对不上。至于临时变量、边沿检测标志,不用保持,丢了重新初始化就行。

6.2 班次切换瞬间掉电的处理

这是个极端但真实存在的场景:19:59:59.9,切换动作执行到一半,LastShiftVol刚写完,VolumeThisShift还没清零,停电了。上电后会怎样?上一班的数据是对的,本班的数据还挂着上一班的尾巴。等下次切换时,这个尾巴会被算进新的一班。

严格来说这属于极小概率事件,但如果你做的是贸易结算级的计量,就得考虑。我的处理办法是切换动作分两步走,中间加一个状态标志:

IF #ShiftChangePending THEN #VolumeLastShift := #VolumeThisShift; #ShiftChangePending := FALSE; #VolumeThisShift := 0.0; #statPulseCount := 0; END_IF;

第一步先搬移并置标志,第二步再清零。上电时检查这个标志,如果为TRUE,说明上次切换没走完,把清零动作补上。这样即使中途掉电,数据也不会重复或遗漏。这个标志本身也要设成保持性。

6.3 定时归档到HMI或上位机

PLC的保持存储器不是万能的,它也有寿命和容量限制。重要数据还是应该定期归档到HMI或者上位机数据库。归档的触发点就是班次切换的那一刻,把上一班的数据写进一个环形数组,数组长度设7,覆盖一周。

HMI这边做一个历史数据画面,按日期和班次显示。这样即使PLC换了、程序重下了,历史数据还在HMI里存着。如果HMI容量也有限,那就往上位机或者数据库走,这个就看项目预算了。

7. 常见问题排查速查

现场调试遇到的问题,八成集中在这几类。我把它们整理成问答形式,方便对着查。

7.1 累计值偏大或偏小

这是最高频的问题。排查顺序我一般是这样:

先看脉冲当量。拿一个已知体积(比如一吨水)过表,记录脉冲数,反算每立方米多少脉冲,和程序里的PulsePerCubic对比。差十倍、百倍的情况太常见了,基本是单位换算没统一(升和立方米、加仑和升)。

再看积分周期。模拟量模式下,如果累计值系统性偏大百分之几,多半是CycleTimeMs填的值和实际调用周期不符。比如实际是100ms调用一次,程序里填了80,那每次积分就少算了20%的时间,累计值偏小。反过来填大了就偏大。

最后看量程。4-20mA对应的量程上下限如果填错,比如实际是0-50填成了0-100,瞬时流量直接翻倍,累计当然也翻倍。

7.2 班次不切换或反复切换

不切换,先查时间。看DB_Global.Hour在线值是不是在正常范围,如果显示的是0或者几百,说明时钟没读上来。再查DayStartHour和NightStartHour的配置,是不是两个值填反了。

反复切换,基本就是防抖没做好。检查切换条件是不是直接用了“当前班次”驱动清零,而不是用“班次变化沿”。如果代码里写成IF ShiftNow = 1 THEN 清零,那整个夜班期间每个扫描周期都在清零,数据永远是0。这个错误我见过不止一次,写的时候一定要注意。

7.3 清零后数值又恢复

按了HMI上的清零,数值归零,过两秒又变成原来的数。这是因为清零只清了显示值,没清累加的源数据。比如脉冲模式下,你清了VolumeThisShift,但statPulseCount没清,下一个扫描周期它又被算回来了。正确的清零要连源数据一起清,前面给的代码里那三行就是干这个的。

7.4 速查表

现象最可能原因排查动作
累计值恒为0使能未接通或脉冲无输入在线看输入点与使能位
累计值明显偏大脉冲当量或量程填错实测反算校对参数
累计值爬升缓慢积分周期填写大于实际对比定时中断设置
班次判断全错时钟未设置或时区错误检查TimeValid与HOUR
交接班数据丢失清零顺序错误检查搬移与清零先后
重启后数据归零DB未设保持性检查DB属性与容量
累计到某值后不动REAL精度溢出换算单位降低数值

8. 几条踩过坑之后的实操心得

最后说几条不上台面但很管用的经验。

第一条,永远给累计值留一个“原始脉冲数”或者“原始累计字”的备份。不管上层怎么换算、怎么清零,这两个原始数不要动。对账对不上时,拿原始数一算就清楚问题出在换算还是出在清零。我在一个项目里就是靠这个备份,五分钟定位出是通讯中断导致的基准漂移,不然得查一整天。

第二条,班次配置参数做成HMI可改,但加权限。现场调整班次时间是很常见的需求(比如临时改成三班倒),如果写死在程序里,每次都要改程序重新下载,很麻烦。但如果不加权限,操作工随手一改,数据全乱。我的做法是HMI上做成密码保护,或者放在工程师菜单里。

第三条,调试阶段在FB里加几个调试计数器,比如记录切换次数、通讯中断次数、脉冲溢出次数。正常运行时这些数应该稳定或者缓慢增长,一旦某个数突然暴涨,说明有问题。这些计数器平时不显示,排查时调出来看一眼,比翻程序快得多。

第四条,不要把流量累计和报表逻辑混在一个块里。报表是上位机的事,PLC只管把准确的数据送出去。PLC里做太多字符串拼接、日期格式化,既占资源又难维护,而且一旦报表格式要改,PLC程序也得跟着改,本末倒置。

第五条,也是我认为最重要的一条:先用手算验证,再相信程序。程序跑起来显示一个数,这个数对不对,不能凭感觉。拿秒表、拿量筒、拿流量计本身的机械字轮,做一次人工比对。比对通过一次,后面才有信心。我见过太多人程序一跑就交付,结果三个月后甲方发现数据差了一大截,回头查,连从哪天开始错的都不知道。

这套结构在我手上复用过的项目,从水处理到蒸汽计量到压缩空气分摊,逻辑基本没变过,变的无非是信号类型和单位换算。你要是刚开始接触这块,建议先在一个小项目上把这套块结构跑通,把坑踩一遍,后面再遇到类似需求,直接复制工程模板就行。

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

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

立即咨询