博途SCL模拟量平均值滤波FB:算术平均与切尾平均实现
简介这份文档面向西门子PLC编程人员与自动化工程师聚焦SCL语言下模拟量平均值滤波功能块的实现与复用。内容围绕滤波FB的引脚定义与参数配置展开梳理算术平均值滤波与切尾平均值滤波两种模式的适用差异并说明采集时间、滤波强度对响应速度与平滑效果的权衡逻辑。创建流程部分从新建FB块、编写SCL处理算法到在OB1中先做模拟量转换再串联滤波FB最后将FB块拖入全局库主模板完成库文件封装形成可长期调用的工程资产。资源为1个docx文档压缩包约595KB适合对照文档边看边在博途软件中动手实践。已有296人学习对于希望掌握库化编程、提升现场信号抗干扰能力的初、中级自控从业者具备较高的参考与落地价值。1. 变频器一起停HMI 上那路压力值就在 ±3% 里来回蹦PID 输出跟着抽风阀门一会儿开一会儿关。这类抖动源头常常不在变送器而在信号线走向、共地方式和不走运的采样时刻。基于 SCL 语言的模拟量平均值滤波 FB 库处理的就是这一段你把它串在模拟量转换之后输入已经是工程量——比如 01.6MPa 的实际压力值它按设定的采集时间攒一批采样点算完再从输出脚吐出来中间给了算术平均和切尾平均两种模式。对用博途写 S7-1200/1500 程序的人来说这个 FB 的价值不在算法本身而在于它把「采样节拍」和「滤波窗口」从主程序里剥了出来不用额外建定时中断 OB也不用在 OB1 里手写计数逻辑采集时间管脚填 T#1s、滤波强度填 10剩下的采样间隔、缓冲挪位、输出刷新都由 FB 内部完成。温度、压力、液位这类过程量只要变化速度不快、对稳定性要求高都适合挂上去。一句实话先摆在这平均值滤波换来的是平滑代价是滞后。窗口拉得越长曲线越好看对阶跃的响应也越迟钝。这个边界选参数之前得先想明白。2. 算术平均与切尾平均算法选型与 SCL 核心实现2.1 两种模式的差别不在「谁更平滑」算术平均值滤波就是窗口内所有采样点的算术平均权值全为 1。它对白噪声类的高斯干扰抑制效果最好但一个孤立的尖峰能直接污染整窗输出——极端情况下10 点窗口里出现一个 8% 的野值输出就会被抬高 0.8%。切尾平均Trimmed Mean先把窗口内的样本排序从两端各去掉一定比例再对中间段求平均。它牺牲的是极值携带的信息换来的是对尖峰的不敏感。拿一组真实数据看更直观窗口样本MPa算术平均切尾平均每端 10%0.502, 0.503, 0.501, 0.505, 0.5040.50300.50300.502, 0.503, 0.501, 0.505, 0.5900.52020.50270.502, 0.503, 0.501, 0.505, 0.590, 0.6040.53420.5033第二行那个 0.590 就是典型的尖峰干扰。算术平均把输出拉到 0.5202偏差 3.4%切尾平均削掉最大值后输出 0.5027几乎不受影响。所以原文里那句「尖峰干扰较多时用切尾模式」是有量化依据的不是玄学。提示切尾比例不要设得太大。每端切 10% 是常见起点样本数少于 5 点时切尾会退化成中位数甚至无样本可用这时应自动回退到算术平均。2.2 FB 的接口变量怎么定先把接口按「工程语义」定死再写算法。这个库的管脚可以这样规划输入输出全部走接口不依赖任何全局符号后面做库文件时才不会带着依赖跑。引脚数据类型方向说明inValueRealIN已转换的工程量非原始整数iModeIntIN0 算术平均1 切尾平均tWindowTimeIN采集时间即整个滤波窗口的时长iStrengthIntIN滤波强度即窗口内容纳的采样点数outValueRealOUT滤波后的工程量iSampleCntIntOUT当前有效样本数便于上位机判断窗口是否已满这里要澄清一个容易混淆的点采集时间和滤波强度不是两个独立的旋钮它们共同决定采样间隔——采样间隔 采集时间 / 滤波强度。采集时间 T#1s、滤波强度 10意味着每 100ms 采一个点窗口长度 1s。原文注意事项 3 里说「较小的采集时间和滤波强度响应更快」本质是缩短了窗口而不是改变了采样密度。2.3 用 TIME_TCK 做采样节拍和 OB1 扫描周期解耦FB 挂在 OB1 里每个扫描周期都会被调用但采样必须按固定节拍走。S7-1500 的 SCL 有TIME_TCK()返回系统毫秒时钟用它做差值判断最省事。// 参数合法性钳位防止上层误填导致数组越界 IF #iStrength 1 THEN #iStrength : 1; END_IF; IF #iStrength 64 THEN #iStrength : 64; END_IF; IF #tWindow T#10ms THEN #tWindow : T#10ms; END_IF; // 采样间隔 采集时间 / 滤波强度显式做 DINT 转换避免 Time 除法歧义 #tIntv : DINT_TO_TIME(TIME_TO_DINT(#tWindow) / #iStrength); // 采样节拍判断距上次采样不足一个间隔就直接跳过 #tNow : TIME_TCK(); IF (#tNow - #tLast) #tIntv THEN #tLast : #tNow; // 移位式缓冲新值进队尾老值向队首挪 FOR #i : 1 TO #iStrength - 1 DO #aBuf[#i] : #aBuf[#i 1]; END_FOR; #aBuf[#iStrength] : #inValue; IF #iSampleCnt #iStrength THEN #iSampleCnt : #iSampleCnt 1; END_IF; END_IF;逻辑说明TIME_TO_DINT拿到毫秒数再整除避免直接对 TIME 做除法在不同博途版本上的兼容问题。缓冲用整段移位而不是环形索引代价是每次采样要挪 64 个 Real1500 上约几微秒换来的是数组顺序即时间顺序后面排序和取值都不用再做索引回绕调试时打开监视表看得一清二楚。参数说明#tLast是静态变量存上次采样时刻#tIntv每次调用重算这样运行中在线修改采集时间或滤波强度能立刻生效不用重新下载。#iSampleCnt记录窗口填充进度上电初期还没采满时取数范围要跟着缩否则会把数组里的 0 当成有效样本平均进去。2.4 两种模式的计算分支采样和计算分开写输出刷新放在每次 OB1 调用上这样 HMI 读到的永远是最新值。// 有效数据的起点移位式缓冲下老数据在队尾方向堆积 #iStart : #iStrength - #iSampleCnt 1; IF #iMode 0 OR #iSampleCnt 5 THEN // 算术平均窗口内全部有效样本 #rSum : 0.0; FOR #i : #iStart TO #iStrength DO #rSum : #rSum #aBuf[#i]; END_FOR; #outValue : #rSum / INT_TO_REAL(#iSampleCnt); ELSE // 切尾平均先拷贝有效窗口再冒泡排序 FOR #i : #iStart TO #iStrength DO #aTmp[#i] : #aBuf[#i]; END_FOR; FOR #i : #iStart TO #iStrength - 1 DO FOR #j : #iStart TO #iStrength - 1 - (#i - #iStart) DO IF #aTmp[#j] #aTmp[#j 1] THEN #rTmp : #aTmp[#j]; #aTmp[#j] : #aTmp[#j 1]; #aTmp[#j 1] : #rTmp; END_IF; END_FOR; END_FOR; // 每端切掉 10%至少 1 点 #iTrim : #iSampleCnt / 10; IF #iTrim 1 THEN #iTrim : 1; END_IF; #rSum : 0.0; FOR #i : #iStart #iTrim TO #iStrength - #iTrim DO #rSum : #rSum #aTmp[#i]; END_FOR; #outValue : #rSum / INT_TO_REAL(#iSampleCnt - 2 * #iTrim); END_IF;逻辑说明排序在副本#aTmp上做不破坏原始时间序列方便后续加「变化率报警」之类的功能。冒泡排序在 1020 点窗口下完全够用64 点全排也才 2000 次比较1500 上不到 100μs。要是真上到 64 点还嫌慢常见做法是换成插入排序新样本只在有序表里找位置单次采样摊下来只有 O(n)。参数说明#iTrim由有效样本数算出来而不是用固定值这样窗口没填满时切尾比例依然合理。分母用#iSampleCnt - 2 * #iTrim而不是#iStrength否则前期输出会整体偏小。3. 博途里建 FB从模拟量转换到 OB1 调用链3.1 转换在前、滤波在后别把顺序搞反滤波的对象是工程量而不是原始整数这一点必须先立住。S7-1200/1500 的模拟量输入通道读回来是 027648 的整数直接对它做平均也能用但输出还得再缩放一次多一层浮点误差而且滤波强度不好按工程量直观评估。标准链路是NORM_X归一化到 0.01.0再SCALE_X映射到工程量。// OB1 中的调用链原始值 → 归一化 → 工程量 → 滤波 #rNorm : NORM_X(MIN : 0, VALUE : IW_Pressure, MAX : 27648); #rEng : SCALE_X(MIN : 0.0, VALUE : #rNorm, MAX : 1.6); // 变送器量程 0~1.6MPa FB_AvgFilter_1(inValue : #rEng, iMode : 0, tWindow : T#1s, iStrength : 10, outValue #rPressureFlt);逻辑说明NORM_X的 MIN/MAX 对应通道原始值范围SCALE_X的 MIN/MAX 对应工程物理量量程两者配对使用才不会出现线性偏移。参数说明MAX : 27648适用于单极性 020mA 或 010V 通道如果是 420mA 变送器常见做法是仍取 027648把 SCALE_X 的下限设成量程的 -25%这样断线时读数会掉到负值区反而变成一种简单的断线判据。3.2 块接口声明与访问方式在博途里新建 FB 时块属性中「优化的块访问」保持勾选。优化访问下数组可以用变量索引第 2 章代码里#aBuf[#i]这种写法必须依赖它如果为了兼容老程序改成标准访问数组下标会被编译成绝对地址偏移循环变量的动态索引虽然还能用但性能会明显下降。变量声明顺序也有讲究输入、输出放前面静态变量集中放后面。FB 的每个实例 DB 都会按声明顺序保存静态区把aBuf、aTmp、tLast这类运行态变量和输入参数混在声明段里后期加变量容易踩到实例 DB 的结构偏移。声明段变量用途VAR_INPUTinValue / iMode / tWindow / iStrength上层填写的配置与数据VAR_OUTPUToutValue / iSampleCnt输出给 HMI、PID 或报警逻辑VARaBuf / aTmp / tLast / tIntv / iSampleCnt跨扫描周期保持随实例走VAR_TEMPtNow / i / j / rSum / rTmp / iStart / iTrim每次调用重建不占 DB 空间多路通道别复制粘贴出十个 FB 实例 DB。常见做法是建一个上层 FB把滤波 FB 声明成它的多重背景按通道声明成Array[1..8] of FB_AvgFilter调用时循环索引整个项目的实例 DB 数量从十几个降到一两个。3.3 为什么模拟量转换不放定时中断 OB原文注意事项 1 说得很明确模拟量转换不宜在定时中断 OB 中调用在 OB1 里调用、用采集时间管脚定周期即可。这背后是两套时间基准打架的问题——OB35 之类的中断周期本身已经定义了采样节拍再往 FB 里塞一个采集时间实际采样间隔就变成了两者的最小公约数窗口长度变得不可预测。调用位置采样节拍来源实际表现只在 OB1 调用FB 内 TIME_TCK 判断节拍稳定窗口长度等于采集时间设定值放在 OB35 调用中断周期采集时间管脚被架空改参数不生效放在 OB35 且不给采集时间中断周期节拍可用但排序耗时直接计入中断执行时间OB1 调用多路每路独立 tLast各路采样时刻错开CPU 负载更平缓还有一层考虑是中断里的时间预算。切尾平均要做排序64 点冒泡在最坏情况下几十微秒平时无所谓但如果中断周期设成 1ms再加上中断里还调了别的块循环时间监控很容易报超时。放 OB1 里扫描周期有富余排序摊到哪个周期都不影响实时性。4. 封装成全局库主模板、库类型与跨项目复用4.1 先想清楚拖「主模板」还是建「库类型」原文给的方法是新建全局库、选中 FB 拖进「主模板」。这个操作本质是复制一份快照快照和源 FB 之间没有联动关系。如果这个滤波块只有你在用、设备型号单一主模板就够了。但如果是多台设备、多个项目都要用同一套滤波逻辑更值得用的是库类型Type。库类型带版本号字段源库更新后引用它的项目会提示「类型不一致」可以选择性地更新每个项目还能自己决定升不升。两者的差别可以这么记对比项主模板Master copies库类型Types本质复制一份块引用一个带版本的模板源改后项目里毫无感知提示版本差异可批量更新适用一次性复用、临时存一份多项目统一维护、长期演进改名行为拖入时可直接重命名改名会脱离类型变成普通 FB实际操作路径是项目树下方「库」选项卡 → 新建全局库 → 命名并选择保存路径 → 展开「主模板」文件夹 → 从项目树把 FB 直接拖进去。库文件在磁盘上是一个.al1x文件后缀里的数字对应博途版本号V16 是.al16V17 是.al17低版本博途打不开高版本的库文件跨版本分享前先确认对方版本。4.2 让 FB 真正可移植的三个前置条件拖进库里的 FB 能不能在别人的项目里一次调用成功取决于它有没有外部依赖。库文件不会自动带着依赖项走缺什么就在对方项目里报「符号未定义」。第一块内不要直接引用全局 DB 或 I/O 符号所有数据都从接口进出。第 2 章的代码里没有出现过任何引号包裹的符号名就是为了这个。第二不要在 FB 里调用别的自定义 FB。真需要串联把调用放到 OB1 里让滤波 FB 保持单一职责。第 3 章的转换和滤波分成两步写道理也在这里。第三数组上限写死成 64 要在注释里说明或者干脆把上限做成常量MAX_SAMPLES : 64。别人拿到库看到iStrength可以填到 64填 100 会被静默钳位不知道这件事就会以为是滤波效果出了问题。4.3 版本更新与符号冲突库用久了必然会遇到两类问题。一类是源改了、项目里没跟着改。主模板更新后项目里那块 FB 还是旧的得手动重新拖一次覆盖。覆盖前先在项目里对比一下接口——如果新版本给接口加了引脚OB1 里的调用点会变红必须逐个补参数。库类型在这方面友好得多项目里右键选择更新类型即可还能看到版本差异。另一类是块名冲突。项目里已存在同名 FB 时从库拖入不会提示覆盖而是自动加后缀变成FB_AvgFilter_1OB1 里原有的调用还指向老块两组实例 DB 同时存在调试时改了半天发现改错了块这种事不新鲜。拖入前先搜一下项目里有没有重名或者给库里的块统一加前缀比如LIB_AvgFilter一眼就能分辨来源。注意从库拖入的 FB 如果被重命名实例 DB 名字不会自动跟着改多重背景调用时容易出现「块名与实例名对不上」的报错。改名后记得检查调用点。5. 采集时间与滤波强度整定用 Trace 抓波形验证参数参数整定别靠猜用博途自带的 Trace 功能看波形最快。做法是把#rEng滤波前和#rPressureFlt滤波后都加入跟踪采样周期设 20ms记录时长 10s触发条件设成滤波前值越过某个阈值然后在现场制造一次扰动——比如让变频器带载启动。看三件事。第一看平滑度滤波后曲线的峰峰值得比滤波前小一个数量级说明窗口够用。第二看滞后扰动发生到输出跟到稳态值 90% 的时间大约等于采集时间乘以 1.01.5。T#1s 的窗口滞后在 11.5s 之间是正常的PID 的积分时间如果比这个还短那滤波就把控制回路带沟里了。第三看尖峰手动在强制表里给inValue叠一个 10% 的单点脉冲切尾模式输出的变化量应该小于 0.2%超过这个数说明切尾比例不够或者样本数太少没切到位。采集时间滤波强度采样间隔阶跃滞后适用场景T#200ms540ms约 0.20.3s流量、快回路T#1s10100ms约 1.01.5s压力、液位T#5s20250ms约 57s温度、大滞后对象T#10s50200ms约 1015s环境量、仅做显示采样间隔那一列有个容易被忽略的约束它不能小于 OB1 的扫描周期。如果采样间隔算出 40ms 而扫描周期波动在 3050ms采样会变成「有机会就采」实际窗口长度被拉长但又不可预测。判断方法是在 OB1 里挂一个RUNTIME测扫描周期采样间隔至少留 3 倍余量。窗口开了又关、输出小幅度周期性波动八成就是这个原因。最后一个技巧把iSampleCnt也画进 Trace。窗口没填满时它是递增的锯齿填满后是一条直线。如果在运行中看到它周期性地从 1 重新爬升说明 FB 所在实例 DB 被别的逻辑复位了或者 CPU 经历了重启这时候输出的波动跟参数无关得往上查调用链。本文还有配套的精品资源点击获取