简介:西门子S7-1500 PLC流量累积数据处理案例基于博途STEP7环境编写,面向自动化、电气工程及计算机等相关专业学生、教师与企业工程师,适用于课程设计、毕业设计及项目初期验证等场景。压缩包共9个文件,主要包含ap13项目文件、xml配置文件、plf与idx组态索引文件以及一个项目备份压缩包,整体仅4.61MB,便于直接导入TIA Portal查看程序结构和在线仿真调试。案例代码已经过完整运行测试,功能确认无误,重点展示了流量信号的采集转换、累积计算、数据块存储与处理流程,有助于理解西门子PLC中模拟量处理、定时中断和DB块应用等关键知识点。读者既可对照案例学习工程化编程思路,也能在此基础上修改参数、添加功能,满足不同控制需求。该资源已有213人学习浏览,对PLC入门者与进阶开发者均具参考价值。
1. 为什么说 S7-1500 流量累积不只是“流量乘以时间”
做水处理或能源管理的同行,应该都遇到过这个场景:现场有台西门子S7-1500,博途TIA Portal里已经做好了模拟量采集,上位机要的是每天的流量累计值,而不是瞬时流量。你一边翻STEP7帮助,一边想着是不是用TON累加,结果发现精度和掉电保持都处理不干净。流量累积数据处理的核心就三件事:用循环中断固定采样周期、把瞬时流量按时间积分、把累积结果放到带掉电保持的DB里。下面直接讲博途STEP7里一套可复现的S7-1500流量累积方案,适合刚入门西门子PLC编程的工程师,也给正在调流量计积算的老手一些参数参考。
2. 流量累积的原理与博途STEP7选型:从瞬时流量到累积量
2.1 流量累积的基本公式:为什么离散采样必须考虑周期抖动
流量累积在数学上就是对瞬时流量做时间积分。设瞬时流量为 Q(t),单位是 m3/h,从 T0 到 T1 的累积体积 V = ∫ Q(t) dt。PLC 只能做离散采样,所以实际实现变成 V = Σ Q_i * Δt_i,其中 Q_i 是第 i 次采样的瞬时流量,Δt_i 是这次采样距上次采样的真实时间间隔。如果每次都按固定的 100ms 中断周期计算,公式可以简化为 V = T * Σ Q_i,但这里有个隐患:中断周期不是绝对准确,相邻两次启动之间的间隔可能有零点几毫秒到几毫秒的抖动,更糟的是某个周期被高优先级中断挤掉。按固定 T 计算累加,时间越长误差越明显。
所以我在写这一步时会刻意把 Δt_i 也给算进去。SCL 里可以直接读取系统时间戳,或者更简单的方法是:在循环中断 OB 里用一个静态变量记录上次扫描的系统时钟,然后算真实间隔。这样即使中断有抖动,累计结果也不会跟着漂。下面这段是 S7-1500 中常见的积分核心逻辑:
// 循环中断 OB30 中调用,每 100ms 一次 // 输入: gFlowInstant 瞬时流量 REAL 单位 m3/h // 静态变量: prevTime DTL 上次时间戳, total LREAL 累积量 IF firstRun THEN prevTime := GET_SYSTEM_TIME(); firstRun := FALSE; RETURN; END_IF; nowTime := GET_SYSTEM_TIME(); // TIME_BETWEEN 返回 TIME 类型,单位 ms,转小时处理 deltaT := TIME_TO_REAL(TIME_BETWEEN(prevTime, nowTime)) / 3600000.0; total := total + gFlowInstant * deltaT; prevTime := nowTime;这里把时间戳和间隔计算放到了中断里,而不是在 OB1 里用 TON 凑周期,原因后面细说。参数上注意gFlowInstant必须已经做过量程转换,单位保持一致,否则累积结果会直接放大或缩小 60 倍甚至 3600 倍。
2.2 为什么用循环中断 OB 而不是定时器 TON
很多刚开始做西门子PLC编程的朋友习惯用 TON 做定时触发。TON 本身没问题,问题在于它放在 OB1 里时,启动和停止的时机依赖扫描周期。当 OB1 里面有 PID、通信或大量位运算时,扫描周期可能从 5ms 跳到 20ms,用 TON 叠加出来的“周期”根本不稳定。而且 TON 只负责逻辑延时,不会自动给你一个均匀的采样时钟,你需要自己判断 started 到 done 的时刻,代码写起来绕。
S7-1500 的循环中断 OB(如 OB30)是由系统时钟触发的硬件级定时中断,不受 OB1 扫描时间影响,默认时间单位是 ms,可以在属性里设置 10ms、100ms、1s 等。我用得最多的是 100ms 和 1s:瞬时流量变化快的场合用 100ms,平稳的管道流量用 1s 也够,CPU 负载还会低一点。下面这张表是我在项目中常用的对比,供选型时参考。
| 触发方式 | 稳定性 | 最小周期 | 精度风险 | 典型用途 |
|---|---|---|---|---|
| OB1 + TON | 受扫描周期波动影响 | 受扫描周期限制 | 高 | 简单逻辑延时,不适合流量累积 |
| OB30 循环中断 | 硬件定时,可靠 | 可设 10ms | 低 | 流量累积、PID 采样 |
| OB10 时间中断 | 按日历触发 | 1 秒 | 中 | 日报表、整点记录 |
需要提醒的是,循环中断的周期设得太短会占用大量 CPU 时间片。如果 S7-1500 同时在跑运动控制或 PROFINET 实时通信,把 OB30 周期从 100ms 改到 10ms 之前,最好先看一下 CPU 的诊断缓冲区里是否出现“循环中断超时”的提示。
2.3 数据类型与 DB 设计:REAL 还是 LREAL?累积值存哪里
流量瞬时值从模拟量模块过来后,一般是 INT 或 REAL。S7-1500 原生支持 REAL,我建议统一用 REAL 存瞬时流量,累积量则要看总量级。举个例子:管道流量 100 m3/h,连续运行一年,累积量是 876000 m3。REAL 的精度大约是 7 位有效数字,到 876000 时有效分辨率只剩 0.1 m3 左右,看起来还行;但如果累积好几年,总量到 500 万 m3 之后,REAL 的分辨率可能已经超过 0.5 m3,这对贸易结算来说是不可接受的。
这时候两个选择:一个是累积量用 LREAL(64 位浮点),S7-1500 支持,但注意 LREAL 的运算在低端 CPU 上会稍微慢一点;另一个是把累积量拆成两个 REAL,一个存整数部分,一个存小数部分,每次累加后做进位。我一般优先用 LREAL,因为代码简单,而且 S7-1500 的算力足够。DB 里还有一个点不能忘:累积量对应的 DB 变量要勾选 Remanence(保持性),否则 PLC 掉电后总量归零,现场要骂人的。博途里右键 DB 变量,在属性里可以找到 Remanence 设置,选择 “Retain” 即可。除了累积量本身,上次时间戳和初始化标志也要设为保持,不然重启后时间基准会断掉。
3. 在博途TIA Portal中编写S7-1500流量累积程序的落地步骤
3.1 创建项目与硬件组态,建立 PLC 变量表
在博途 V16 或更高版本(V17/V18 也行)里新建项目后,添加 S7-1500 CPU,再把 AI 模块插入机架。模拟量模块的通道地址不是手动填的,TIA Portal 会自动分配,比如 IW64。随后在 PLC 变量表里把会用到的点建好,命名尽量见名知意。最基础的一套变量在下面表里,后面写的 SCL 程序就基于这张表。
| 符号名 | 地址/数据块 | 数据类型 | 含义 |
|---|---|---|---|
| AI_Flow_Raw | IW64 | INT | 流量变送器经模拟量模块输入的原始值 |
| HMI_ResetTotal | DB1.HMI_ResetTotal | BOOL | 上位机或触摸屏触发清零 |
| Flow_Instant | DB1.Flow_Instant | REAL | 量程转换后的瞬时流量 m3/h |
| Total_Accum | DB1.Total_Accum | LREAL | 累积流量 m3 |
不建议直接拿 IW64 参与累积,如果以后仪表量程或偏置变了,你得改好几处程序。先把原始值转换成一个独立的 Flow_Instant,再交给累积函数,这是模块化编程的基本习惯。
3.2 用 SCL 写一个可复用的流量累积 FB
在博途中插入一个 FB,语言选择 SCL,名字可以叫FlowTotalizer。这个 FB 要能复用,所以输入输出都用形参,不直接访问全局变量。完整的核心代码如下:
FUNCTION_BLOCK "FlowTotalizer" { S7_Optimized_Access := 'TRUE' } VERSION : 0.1 NON_RETAIN VAR_INPUT InstantFlow : REAL; // 瞬时流量,单位 m3/h Reset : BOOL; // 复位累积量 END_VAR VAR_OUTPUT Total : LREAL; // 累积量,单位 m3 END_VAR VAR init : BOOL := FALSE; // 第一次扫描标志 prevTime : DTL; // 上次采样的系统时间 nowTime : DTL; // 本次采样的系统时间 deltaT : REAL; // 实际时间间隔,单位小时 END_VAR // 复位:清空累计值并重新建立时间基准 IF Reset THEN Total := 0.0; init := FALSE; END_IF; // 第一次扫描只记录时间,不累加,避免从电源接通瞬间开始误算 IF NOT init THEN prevTime := GET_SYSTEM_TIME(); init := TRUE; RETURN; END_IF; nowTime := GET_SYSTEM_TIME(); // TIME_BETWEEN(prevTime, nowTime) 返回 TIME 类型,单位 ms deltaT := TIME_TO_REAL(TIME_BETWEEN(prevTime, nowTime)) / 3600000.0; Total := Total + LREAL_TO_REAL(InstantFlow) * deltaT; prevTime := nowTime;这段代码有几个地方要在参数层面确认:Reset是电平型,不是边沿型。如果 HMI 上一个复位按钮按下再松开,必须用R_TRIG生成一个脉冲,或者干脆让上位机保持 100ms 以上的高电平。GET_SYSTEM_TIME返回的是 CPU 系统时间,函数名在博途不同版本中基本一致,我用 V16 和 V20 都没有问题。TIME_TO_REAL把毫秒数转成 REAL,再除以 3600000 就是小时。
提示:如果库版本里找不到
TIME_BETWEEN,可以改用T_DIFF,效果一样;或者把 DTL 先转成 LDT 再手动做减法。
如果不想用GET_SYSTEM_TIME,也可以直接利用 OB30 的启动信息,它在 OB 内部可用,但复用性差,所以我更推荐上面的写法。
3.3 在主程序里调用 FB,并配置循环中断 OB30
FB 写好后,在主 OB1 里或者直接在 OB30 里调用。因为流量累积只关心时间间隔,不需要在 OB1 里扫描,所以最常见的做法是把 FB 放在 OB30 中,让累积动作被周期中断驱动。OB30 默认就是 100ms 周期,如果你要改,双击 OB30,在属性里找到“循环时间”设置。调用代码写在一段 SCL 里即可:
"FlowTotalizer_Instance"( InstantFlow := "Flow_Instant", Reset := "HMI_ResetTotal", Total := "Total_Accum" );如果你把Total定义成 FB 的输出,那么它不会自动保持。要保证掉电不丢,建议把Total改成一个VAR_IN_OUT,并传入 DB 里的保持性变量。比如先在 DB1 里建一个Total_Accum,属性改成Retain,再把这个变量挂到Total接口上。这样掉电后 CPU 重新启动,整个累积过程从掉电前的数值继续。
3.4 连接模拟量输入与量程转换,确认瞬时流量单位
流量累积的入口是Flow_Instant,它必须是从模拟量原始值转换出来的标准工程量。如果变送器输出 4-20mA,对应 0-100 m3/h,原始值AI_Flow_Raw的范围是 0~27648(单极性)。量程转换可以直接在 OB1 里用NORM_X和SCALE_X,也可以手动算,我一般写一行 SCL:
"Flow_Instant" := ("AI_Flow_Raw" / 27648.0) * 100.0;注意这里的前提是变送器已经做了零点和满度校准,4mA 对应 0,20mA 对应 100 m3/h。如果你的仪表用的是 0-10V 但把 0V 对应 0,也是一样的公式。唯一要小心的是有的变送器把 4mA 对应某个底值而不是 0,比如自带显示的流量计下限是 1 m3/h,那就得先做线性偏移:
"Flow_Instant" := 1.0 + ("AI_Flow_Raw" / 27648.0) * 99.0;这类偏置如果搞错,累积量在一开始就会有固定误差,并且误差会随时间线性增长,现场核对总量时很难定位。建议在博途的监控表里同时看AI_Flow_Raw和Flow_Instant,用标准信号源或者手操器给一个已知流量值,先把比例调对再管累积。
4. S7-1500流量累积的常见坑与参数调整
4.1 采样周期 vs 中断周期:丢了周期怎么办
循环中断不是不会丢周期的。当 CPU 被高优先级 OB 占用,或者 OB30 自身的执行时间超过设定周期,博途会在诊断缓冲区里记录事件“循环中断 OBs 的启动时间已超出”。一旦出现这个提示,说明你的累积周期不是名义上的 100ms 了,而程序还按 100ms 计算,累积量就会偏小。这时候除了优化 OB30 里的指令,最有效的办法是把累积量的计算改成时间戳差值法,也就是第 2.1 节里的做法。这样即使某次中断晚了几毫秒,deltaT会变长,该补的时间照样补回来。
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 累积值偏小 | 中断周期被抢占,程序仍按名义周期算 | 使用时间戳差值法 |
| 累积值偏大 | deltaT 单位把毫秒当日历秒 | 检查除法从 3600000 写成 3600 |
| 累积值突然跳变 | 掉电恢复后时间戳保持,与当前时间差很大 | 上电后重新初始化 prevTime |
如果你用的是 OB1 里的定时器,那么 OB1 扫描周期一波动,定时器周期也会波动,而且你还感知不到,这是最危险的。我的原则是:流量累积必须放在固定周期中断 OB 里,并且要保留真实时间间隔用于修正。
4.2 累积量溢出、清零与掉电保持的取舍
LREAL 的数值范围很大,正常工况下几乎不可能溢出。但清零逻辑要小心,如果Reset是 HMI 上的一个带保持的按钮,可能导致每个扫描周期都触发清零。正确做法是在 OB1 里生成一个上升沿脉冲,然后置位一个单独的Reset_Command,累积 FB 再对这个命令信号执行清零。实际上我遇到过因为 HMI 上开关量没做脉冲,导致流量累积值一直归零的情况,查了两小时才定位到。
关于掉电保持,博途里有两个办法:一是把 FB 的实例 DB 改成保持型,这样所有静态变量都保持;二是把Total接口放到一个单独的保持型 DB 里,FB 内只操作这个接口。方案一省事,但会把中间变量也保持,占用保持存储区;方案二更精准,适合希望每次上电都重置时间戳的场合。我比较推荐方案二,因为prevTime和init这些中间变量在上电后必须重新初始化,如果也跟着保持,程序会在旧时间戳基础上硬算,容易产生一个非常大的 deltaT,累积量会猛跳一大截。
4.3 博途SCL中TON怎么用?为什么不能用TON做流量累积
“博途SCL中TON怎么用”是很多入门者会搜的问题。TON 在 SCL 里可以直接以函数块方式调用,例如:
"TON_Instance"( IN := #startCondition, PT := T#1S, Q => #done, ET => #elapsed);这段代码的逻辑是:IN为 TRUE 后计时,计时到 PT 后Q置 TRUE,IN为 FALSE 时ET清零。单独看它没有问题,但你要是想做流量累积,TON 只能提供一个“到点脉冲”,累积还是在 OB1 里做。OB1 扫描周期一变,你得到的累积间隔就不是 1s,可能是逐次扫描的随机值。而且 TON 在 OB1 里运行时,如果 OB1 被中断,TON 的计时依然按系统时钟走,但你的采样动作没有同步发生,瞬时流量值还是上一次扫描的旧值,这就会造成“时间到了但数据是旧的”的系统误差。
所以我的做法是:TON 留在 OB1 里做设备逻辑顺控,流量累积全部走 OB30 和 GET_SYSTEM_TIME。如果搜索博途 TON 的时候你搜到了很多教程,先别急着把流量累积也套进去,看你手里有没有一个固定的中断源。
4.4 仿真与在线调试:用监控表验证累积误差
流量累积程序调试不能光看 HMI 上的数字跳不跳,要能验证单位时间内增量和瞬时流量是否对得上。博途的仿真功能支持 S7-1500,虽然没有模拟量硬件,但可以给AI_Flow_Raw强制一个数值。步骤如下:先把 OB30 周期设为 1000ms,然后在监控表里把Flow_Instant强制成 60.0(m3/h),跑 10 分钟,理论累积量应该是 60.0 * 10 / 60 = 10.0 m3。如果监控值不是 10.0,优先检查deltaT的单位。很多新手把 MS 直接当成秒,导致累积结果放大 3600 倍。
另一个坑是博途仿真里的系统时间会跟随电脑时间,但如果你中途改过 OB30 周期或下载过程序,时间戳不会断裂,因为GET_SYSTEM_TIME读的是绝对时间,不依赖 OB 调用次数。这比用计数器加假设的周期靠谱得多。验证时建议做一个简单的离线累加表:记录每分钟结束时的Total差值,和该分钟内Flow_Instant的平均值做对比,偏差超过 0.5% 就要回头查量程转换和中断周期配置。
5. 进阶技巧:按日期自动切换日累积量,并缓存昨日结存量
5.1 读取系统日期,判断是否跨天
现场报表通常需要“今日累积量”和“昨日累积量”。与其等上位机去读总累积量做差分,不如在 PLC 里直接维护两日累计。在 S7-1500 里,GET_SYSTEM_TIME返回的 DTL 结构带有年、月、日字段,SCL 可以直接读取并比较。
在 OB30 中调用流量累积 FB 后,增加一段跨天判断:
nowDate := GET_SYSTEM_TIME(); IF (nowDate.YEAR <> curDate.YEAR) OR (nowDate.MONTH <> curDate.MONTH) OR (nowDate.DAY <> curDate.DAY) THEN yestTotal := todayTotal; todayTotal := 0.0; curDate := nowDate; END_IF; todayTotal := todayTotal + InstantFlow * deltaT;这段代码里curDate需要是 DTL 类型且掉电保持,yestTotal和todayTotal建议用 LREAL。注意跨天判断放在累积计算之前,这样不会把新一天的第一笔流量算到昨日。如果你希望昨日值在跨天后保持一整天,可以把yestTotal设置成保持型,并在上位机确认后再手动复位。
5.2 掉电恢复时的日期处理
如果 PLC 停机一天以上才上电,curDate因为保持属性仍然是旧日期,上电第一周期会立即触发跨天,todayTotal清零,yestTotal变成停机前一天的累积值。这个行为一般符合“昨日结存量”的定义。真正要小心的是todayTotal在停机期间的累计丢失,如果工艺要求不能丢量,就得靠仪表侧的机械计数器或者断电续存模块兜底。PLC 的程序再怎么优化,也只能保证上电后的计算连续性。
5.3 用仿真验证跨天逻辑
把 Windows 系统时间改到 23:59:59,或者直接用博途仿真里的系统时间功能,运行 1 秒后即可看到yestTotal被赋值。仿真中还要验证日期边界在月底或年底是否正常,因为 DTL 的月份进位是系统完成的,不需要你写闰年判断。最后用一张表记录验证结果:开始时今日值、昨日值、跨天后的今日值,三者前后关系必须对得上。这一步做完,流量累积程序就可以放心交付了。
本文还有配套的精品资源,点击获取