TIA博途SCL实现滑动平均值滤波FB库文件实战详解
2026/9/5 13:16:48 网站建设 项目流程

简介:工业自动化现场,模拟量信号常伴有随机噪声与毛刺,直接影响控制系统的稳定性。滑动平均值滤波作为一种经典算法,通过维护固定长度队列对采样值求平均,能够有效平滑信号。其高效实现依赖环形队列与增量求和技巧,使运算开销保持恒定,非常适合PLC这类资源受限的实时系统。在工程实践中,基于TIA博途的SCL语言可将该算法封装为标准FB函数块,并通过库文件实现跨项目复用,显著提升开发效率。该方案适用于液位、温度、压力等模拟量信号的预处理,也支持多通道扩展与参数实时调整。围绕滑动平均值滤波FB的设计、编码与调试全流程进行拆解,为电气工程师提供一套可落地的信号处理工程方案。 处理工业现场的模拟量信号,最烦的就是那种“看似平稳、实则毛刺一堆”的测量值。液位波动、压力脉动、称重传感器受振动干扰,这些场景我基本都遇到过。早期做项目,为了把波动压下去,不少人直接在程序里写个平均值,却发现要么反应迟钝,要么滤波完还是一条“锯齿”。后来在TIA博途里用SCL写了一个滑动平均值滤波的FB,封进库文件,这些问题才真正解决。这篇文章就把这个FB从原理、建模、编码到调试的经验完整拆开讲清楚,照着做,你也能在博途里复现一个属于自己的滤波库。

这个项目核心就是“TIA博途SCL语言_滑动平均值滤波算法_FB库文件”,适合正在做过程控制、设备配套调试的电气工程师,也适合刚接触SCL想找一个能落地的练习案例的朋友。它的价值不在于代码多复杂,而在于把“采样—队列—求均值”这套逻辑做成标准块,工程上直接复用,参数可调,节省大量重复开发时间。老规矩,先讲设计思路,再上代码细节,最后是调试实录。

1. 项目概述与设计思路拆解

1.1 为什么要专门做一个滑动平均值滤波FB

模拟量信号进PLC之后,第一步不是急着去比较、报警,而是先“洗干净”。滑动平均值滤波是性价比最高的入门选择,它不像一阶惯性滤波那样存在相位滞后较大的问题,也不像中值滤波那样对采样点数敏感,它做的事情很简单:维护一个固定长度的数据队列,每次新采样进来,丢到队尾,队头挤出去,然后对队列里所有数据求算术平均,输出这个平均值。

听起来容易,但工程上有几个绕不开的痛点。第一,博途的FB是支持多实例背景数据块的,如果你把数组、指针、循环逻辑都揉在一个大块里,实例化两三个通道之后,PLC的内存和扫描周期都会肉眼可见地恶化。第二,SCL里做队列移位,如果用FOR循环逐个移动数组元素,窗口长度一旦上到几十甚至上百,每次扫描的指令开销就非常可观,这是初学者最容易踩的坑。第三,滤波效果和响应速度天然冲突,窗口越长越平滑,但跟随真实值变化越慢,这个参数怎么调、放给用户还是写死,本身就是设计决策。

所以这个FB在设计上要解决三件事:一是用环形队列替代数据搬移,让运算开销基本恒定;二是把窗口长度、滤波使能、输出模式做成参数,灵活适应不同工况;三是封装成标准库文件,方便跨项目复用。后面所有代码都是围绕这三点展开的。

1.2 滑动平均滤波的适用边界与技术选型

不是所有场景都适合上滑动平均。我一般这样判断现场类型:

  • 如果是缓慢变化的液位、温度,滑动平均很合适,它能把小幅随机噪声磨平;
  • 如果是快速变化的流量、压力,滑动平均会带来明显的响应滞后,这时候要考虑加权滑动平均或者一阶惯性滤波;
  • 如果是偶发性的尖峰干扰(比如设备启停瞬间的电磁干扰),滑动平均反而会把尖峰“拖”成一个宽平台,看起来更难受,这种应该优先用中值滤波或者限幅滤波。

做技术选型不能只看算法本身,还得看PLC的扫描周期和采样频率。举个例子,某台设备的模拟量模块刷新周期是200ms,而你OB1的扫描周期是20ms,那同一个通道连续两次读到的值可能根本没变化。这种情况下滑动平均的窗口再大,也只是对同一个“旧值”反复求均值,并没有增加有效信息量。所以我在做这个FB时,特意留了一个采样间隔计数器,用户可以设定每隔N个扫描周期才采集一次数据进队列,这样既不影响扫描周期,又能保证队列里的数据是真正有意义的采样点。

2. 核心细节解析与实操要点

2.1 FB接口定义与参数设计

这个FB我命名为“FB_SlideAvg”,接口设计如下:

参数名类型方向说明
bEnableBoolIN滤波使能,TRUE时执行,FALSE时输出直接等于输入
rRawValueRealIN原始采样值
iWindowLenIntIN滤波窗口长度(2~256),运行时可调
iSampleCycleIntIN采样间隔周期数,1表示每个扫描周期都采样
rAvgValueRealOUT滤波输出值
rRawValueMirrorRealOUT原始值镜像输出,方便监控和比较
iQueueUsedIntOUT当前队列有效点数,填充不满时输出实际点数均值
stInitDoneBoolOUT完成初始化标志

内部静态变量:

变量名类型说明
arQueueArray[0..255] of Real环形队列存储区
iHeadInt队头指针
iLenInt当前队列长度
rSumReal队列元素之和(增量维护)
iSampleCntInt采样间隔计数器
bInitBool初始化标志

有两个点是刚开始写代码时特别容易忽略的。第一个是窗口长度 iWindowLen 的范围合法性,如果用户不小心填了0或负值,程序运行时数组下标直接越界,轻则数据错乱,重则报编程错误停机。第二个是 iSampleCycle 的语义,很多人会把它误解成“每隔几个扫描周期取一次平均值”,但真正的含义是“每隔几个扫描周期向队列里塞一个新数据”,这个区别直接决定滤波效果。

2.2 环形队列结构与增量求和

环形队列是实现高效滑动平均的核心。普通做法是每次新数据进来,把数组里所有元素往前移一格,然后把新值放到最后。窗口长度是N时,每次操作的时间复杂度是O(N)。如果用环形队列,通过头指针和尾指针的循环移动,每次操作的时间复杂度是O(1),跟窗口大小无关。这对于在PLC这种资源受限环境下运行尤其重要。

增量求和是另一个关键技巧。常规做法是每次求平均时把队列所有元素重新加一遍,时间复杂度也是O(N)。增量维护的意思是,只在入队时把新值加到 rSum 上,把被覆盖的旧值从 rSum 里减掉,这样求均值时只需要做一次除法,直接把 rSum 除以当前队列长度即可。在大窗口场景下,这个优化直接决定了FB还能不能跑在高速中断OB里。

3. 实操过程与核心环节实现

3.1 TIA博途里新建FB并编写SCL代码

打开TIA博途,在PLC数据类型或者程序块的“添加新块”里选择“函数块”,语言选“SCL”,命名FB_SlideAvg。下面这段代码是完整实现,我逐段解释:

IF NOT #bInit THEN #iHead := 0; #iLen := 0; #rSum := 0.0; #bInit := TRUE; #stInitDone := TRUE; END_IF; IF #bEnable THEN #iSampleCnt := #iSampleCnt + 1; IF #iSampleCnt >= #iSampleCycle THEN #iSampleCnt := 0; // 窗口长度合法性限制 #iWindowLen := MIN(MAX(#iWindowLen, 2), 256); // 队列已满时,先减去将被覆盖的旧值 IF #iLen >= #iWindowLen THEN #rSum := #rSum - #arQueue[#iHead]; #arQueue[#iHead] := #rRawValue; #iLen := #iWindowLen; ELSE #arQueue[#iHead] := #rRawValue; #iLen := #iLen + 1; END_IF; #rSum := #rSum + #rRawValue; // 环形指针前进 #iHead := #iHead + 1; IF #iHead >= #iWindowLen THEN #iHead := 0; END_IF; END_IF; // 输出均值,队列未满时按实际长度平均 IF #iLen > 0 THEN #rAvgValue := #rSum / INT_TO_REAL(#iLen); ELSE #rAvgValue := #rRawValue; END_IF; ELSE #rAvgValue := #rRawValue; END_IF; #rRawValueMirror := #rRawValue; #iQueueUsed := #iLen;

3.2 每一段代码的设计意图解读

初始化段不是可有可无的,因为FB的背景数据块断电保持后,静态变量可能是旧值。上电第一次扫描如果不初始化,队列里全是垃圾数据,rSum 也不是队列元素的和,输出结果必然错误。所以 bInit 标志配合第一个扫描循环把队列清干净,这是长期运行稳定性的第一道保险。

使能判断在外层的原因很直接:当 bEnable 为 FALSE 时,我们希望系统表现得像个“直通”通道,用户给什么输出就是什么,方便调试时把滤波旁路掉。这个设计在工程调试阶段非常实用,可以在不断电的情况下对比滤波前后的信号。

窗口长度限制那段代码是防御性编程。你永远想象不到操作员会往画面里填什么数字,所以把窗口长度限制在2到256之间非常必要。256也是静态数组定长的上限,这样就不需要动态内存管理。

// 指针的循环回绕是关键,这段代码决定环形队列是否正确工作 #iHead := #iHead + 1; IF #iHead >= #iWindowLen THEN #iHead := 0; END_IF;

这里有人会问,为什么不直接用取模运算 #iHead := (#iHead + 1) MOD #iWindowLen?因为SCL里的MOD运算在博途里生成的代码比比较跳转要耗资源,高速循环里不值得。用IF语句实现回绕,逻辑更直观,指令也更少。

3.3 在OB1中调用FB并绑定背景数据块

调用这个FB非常简单,在OB1中拖入FB_SlideAvg,系统会提示你分配背景数据块。建议给每个通道单独分配一个背景DB,例如 DB_Ch1_SlideAvg、DB_Ch2_SlideAvg,这样监控时每个通道的数据隔离清晰,互不干扰。调用代码示意如下:

"DB_Ch1_SlideAvg"( bEnable := TRUE, rRawValue := "HMI_RawPressure", iWindowLen := 16, iSampleCycle := 2, rAvgValue => "HMI_FilteredPressure", rRawValueMirror => "HMI_RawPressure_Monitor", iQueueUsed => "HMI_QueueUsed", stInitDone => "HMI_InitDone" );

3.4 如何将FB封装为库文件并打包为rar

写好的FB如果不做库,换项目重写一遍代码,那就失去了一次开发多次复用的意义了。在博途的“库”选项卡里,右键你的项目库,选择“添加新库”,把FB_SlideAvg拖进去,记得附带版本信息。然后在全局库中保存,就可以导出为 .zal 文件。用压缩工具打成rar是为了方便在微信、网盘里传播,附件名“TIA博途SCL语言_滑动平均值滤波算法_FB库文件.rar”就是这么来的。

导入方拿到rar后,先解压得到.zal库文件,再在博途里打开库选项卡,点击“全局库”—“打开”—选择.zal文件。打开后就能在库视图里看到FB_SlideAvg,拖进项目即可使用。要注意的是,源项目中FC、DB、UDT等依赖对象要一起导出,否则光有个FB块,引用关系断了照样跑不起来。

4. 从仿真到现场:调试方法与常见问题排查实录

4.1 仿真时如何验证滤波效果

博途的PLCSIM可以模拟这个FB的运行。我在验证时习惯先在DB里给 arQueue 预置一组已知数据,然后在监控表里强制 rRawValue 从0逐步跳到100,观察 rAvgValue 的变化曲线。滑动平均的响应应该是一个“斜坡”而不是“台阶”,斜坡的长度取决于窗口大小。如果输出直接跟着输入跳变说明队列没生效,大概率是采样间隔计数器没走,或者使能端没通。

仿真时还要重点看 iQueueUsed 的变化。注意观察它从0逐步增长到窗口长度,最终稳定在窗口长度。如果它一直停在0,说明 rRawValue 变化时 bEnable 是FALSE,或者在循环里被复位了,要去查调用处的接口绑定,不要一头扎进算法内部找问题。

窗口大小对响应速度的影响也可以直接用 PLCSIM 测试。窗口长度从4调到64,输出的平滑程度和跟随速度一眼就能看出来。现场工程师如果分不清该调哪个参数,我会建议他先看两张趋势图,一张是原始信号,一张是滤波输出,调窗口长度直到噪声被压到可接受范围,再看跟随性是否满足工艺要求。如果两者矛盾,那就是窗口太大,已到滑动平均的性能极限,该换算法了。

4.2 现场运行时的典型坑与排查表

现象可能原因排查方法
输出一直等于输入,滤波无效bEnable没置位;iSampleCycle填了0导致取模异常强制bEnable为TRUE;确认iSampleCycle>=1
输出波动反而变大窗口长度太小;环形指针回绕条件写错调大iWindowLen;查看iHead是否在0~窗口范围内
输出跳变到巨大值rSum被污染;队列被越界写入检查数组索引边界;查看iLen是否超过窗口长度
数据堵塞、PLC扫描周期暴涨窗口长度太大且存在大数组循环将窗口降到64以内;确认使用了环形队列而非搬移法
断电重启后第一次输出异常静态变量未初始化确保bInit初始化段逻辑存在并检查数据块保持属性
多通道实例互相干扰背景DB共用导致数据串扰改用多实例方式,每个通道单独实例化FB

我尤其想提醒一点:现场调试时,如果发现滤波输出值偶尔出现“毛刺直冲”的现象,先不要怀疑算法,先查OB1的扫描顺序和模拟量模块的转换时间。我遇到过一次,某个模拟量通道在模块量程设置错误时,会间歇性输出一个很大的瞬时值。滑动平均不会剔除这种异常值,反而会把它平均进队列,导致滤波后的信号依然出现一个凸起。这种情况就该配合限幅滤波或者品质位判断来用。

4.3 独家避坑技巧:窗口长度与采样周期怎么配合

工程上最容易调出“又平滑又迟钝”效果的原因,是只调了窗口长度而没管采样周期。这两者的乘积才是真正的时间常数。假设你希望滤波的时间常数为10秒,窗口长度是50,那采样周期就应该设为200ms;如果你保持窗口50但采样周期设成了1秒,时间常数就变成了50秒,现场操作员就会抱怨“阀门反应太慢了”。

所以我建议在HMI上把 iWindowLen 和 iSampleCycle 都开放给工艺人员,而不是只给一个窗口长度。同时在FB内部计算并输出一个“等效时间常数”:

rTimeConst := INT_TO_REAL(#iWindowLen * #iSampleCycle) * 0.001; // 需要结合OB1扫描周期

这样的话,工艺人员看到的是“时间常数几秒”,而不是“窗口长度多少个”,沟通成本大幅降低,调试效率也更高。

5. 滤波算法选型对比与扩展建议

5.1 滑动平均、一阶惯性与中值滤波的实测对比

很多工程师在同一项目里反复纠结要不要换算法。我整理了一张对比表:

算法平滑能力滞后程度尖峰抑制资源占用适用场景
滑动平均中等低(环形队列+增量求和)液位、温度、一般压力
一阶惯性极低快速流量、阀门控制反馈
中值滤波中(需要排序)称重、振动、偶发干扰
滑动平均+限幅中等现场信号品质较差的通用场景

我自己的经验是,现场不知道选什么的时候,默认滑动平均,然后把限幅判断加上。限幅判断很简单:如果当前采样值偏离上次滤波值超过某个阈值,就认为这是偶发干扰,直接丢弃不进队列。这个思路能弥补滑动平均在尖峰抑制上的弱点,代码量增加很少,但实用性提升非常明显。

5.2 基于此FB扩展多通道与分组滤波

这个FB支持多实例化,所以通道扩展只需要复制实例并绑定不同输入输出即可。但如果项目里有十几个通道,每次手动配置会比较繁琐。你可以在FB内部再套一层,比如FB_SlideAvgGroup,里面定义多个FB_SlideAvg实例,统一管理通道使能和参数。这样对外接口就变成了一个数组输入、一个数组输出。博途的SCL支持数组作为FB的IN/OUT参数,这在批量处理模拟量时非常方便。

5.3 变量记录与趋势分析优化

调试滤波算法的效果,最好配合PLC的变量记录功能。在博途里新建变量记录,把 rRawValue、rAvgValue 和 rRawValueMirror 同时记录下来,导出CSV后到Excel里画趋势线。这样能直观看到滤波前后的信号波形,对齐时间轴后还能评估滞后量。这个习惯能帮你积累大量现场数据,以后写滤波选型报告、或者跟工艺部门沟通时都很有说服力。

6. 写在最后的实操心得

我实际用这个FB跑了几个项目,最深的体会是:滤波算法本身并不复杂,难点往往在工程化和现场适配。第一,参数一定要开放给用户,但要加安全限制;第二,环形队列和增量求和必须坚持用,否则窗口一大PLC性能就崩给你看;第三,初始化逻辑不能省,否则断电重启后的第一次输出就可能带来设备误动作。

最后再分享一个小技巧:如果你需要快速判断滑动平均是否适合当前信号,先用博途的Trace功能录制一段原始信号的波形,观察噪声的频率和幅值。如果噪声幅值稳定、频率较高,滑动平均几乎肯定够用;如果噪声是低频漂移,滑动平均不但没用,还会让真实信号的趋势变得更钝,这时候就该考虑高通滤波或者别的信号处理手段了。拿这个FB做底子,你在博途里的信号处理能力会一下子丰富不少。

本文还有配套的精品资源,点击获取

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

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

立即咨询