☰
工控信号滤波实战:10大经典算法原理、代码与选型指南
2026/10/28 18:25:47 网站建设 项目流程

工控现场的信号问题,十个人里有八个都栽在“看着像抖动,其实破了防”这种坑里。传感器接线出去转一圈回来,ADC读到的数上下跳,PID跟着抖,执行机构被磨得咣咣响,这种故事我见太多了。手里如果没有两套拿得出手的滤波算法,干工控就跟没带万用表出门一样,心里永远不踏实。这篇文章就把我这些年实际用过的10大经典滤波算法挨个拆开讲清楚,从原理、代码到选型建议一次补齐,适合刚入行的嵌入式工程师、设备维护人员,以及所有被现场信号噪声折磨过的人。

1. 滤波前先搞明白:工控信号里到底藏着什么“脏东西”

滤波不是上来就写代码,你得先知道信号是被谁污染的。工控现场的干扰来源,我总结下来基本跑不出下面这几类。

1.1 噪声的五个常见来源

  • 电磁耦合干扰:变频器、大功率电机、接触器通断瞬间,会在空间里产生强烈的电磁场,长距离传输的信号线就像一个天线,把干扰全收进来了。
  • 地环路问题:多个设备接地电位不一致,地线上流过的电流在信号回路里转换成差模电压,表现出来就是50Hz工频及其谐波。
  • 传感器自身噪声:热电偶的热噪声、应变片的微弱信号、光电传感器受光照波动影响,都属于信号源本身的随机扰动。
  • 量化误差与ADC噪声:ADC分辨率不够、基准源抖动、采样时钟不稳,都会在数字侧引入新的噪声。
  • 振动与机械因素:压力传感器装在振动剧烈的管道上,测出来的波动是真实的机械振动,不是电气干扰。

区分这些来源很重要,因为不同的干扰对应不同的滤波策略。比如电磁耦合多为高频尖峰,工频干扰集中在50Hz附近,机械振动频率往往低于10Hz,它们的处理方式完全不同。

1.2 滤波的两个常见误区

误区一:滤波万能论。有些人觉得只要滤波算法写得够猛,硬件布线随便搞搞就行了。这个想法在工控现场非常危险。干扰如果是接地问题或者屏蔽层断裂引起的,你不去处理源头,单纯在软件里滤波,等于给人吃止疼药不处理伤口,只会越拖越严重。

误区二:滤波越狠越好。滤波强度上去了,噪声是压住了,但信号的动态响应也跟着被吃掉。温度波动本来要5秒才能反映到输出,你上一个重滤波,变成50秒,报警系统形同虚设。做滤波必须时刻记住:你是在噪声抑制和响应速度之间找平衡,不是单纯把信号“抹平”。

1.3 采样率、信号频带和混叠的三角关系

滤波算法的选择,和你信号的采样率、有效频带有直接关系。香农采样定理告诉我们,采样频率必须大于信号最高频率的两倍,否则高频成分会混叠到低频段,表现为“假”的低频波动,这种情况下光靠数字滤波根本救不回来。

所以我要再次强调硬件抗混叠滤波的作用。很多工控采集卡或PLC模拟量模块前端会有一个RC低通或运放有源滤波电路,先把高频成分压掉,然后ADC采样,最后软件再做一层滤波。硬件处理粗,软件处理细,两者配合才是完整的滤波链。

2. 10大经典滤波算法拆解:原理、代码和适用边界

现在进入正题。这10个算法都来自实践,属于那种“一个函数就能解决大问题”的经典套路。我先给出一张速览表,然后逐个讲透。

2.1 十大算法速览对比

算法核心思路优点缺点典型应用
限幅滤波突变值超过阈值则丢弃抗大脉冲干扰无法消除周期性噪声瞬时尖峰保护
中位值滤波连续采N个值取中间值抗脉冲干扰强实时性差,数据量多温度、压力采样
算术平均滤波N个值直接平均抑制随机噪声响应慢,占用RAM缓变信号
递推平均滤波滑动窗口平均,最新值进旧值出平滑度高,实时性较好RAM占用固定,对脉冲敏感PLC模拟量周期采样
中位值平均滤波去掉最大最小再平均兼顾脉冲和随机噪声计算量大仪表测量,采集卡
限幅平均滤波先限幅再滑动平均抗脉冲又平滑逻辑复杂,参数多现场变送器信号
一阶滞后滤波本次值与上次值按系数融合实现简单,实时性好滞后大,对高频不敏感PID测量值平滑
加权递推平均滤波窗口内按权重加权平均降低滞后,平滑兼顾权重设计需经验快速温控回路
消抖滤波信号需稳定持续N次才更新消除按键抖动、稳态毛刺响应延迟固定开关量、状态量
限幅消抖滤波限幅后落入窗口再消抖综合抗脉冲和抖动参数多,调起来费劲电机电流、位置反馈

2.2 限幅滤波:最简单但最实用的第一道防线

限幅滤波的核心逻辑是一个“信任阈值”。我把上一次的有效值存下来,这次新进来的值和上次值比较,差值超过设定阈值就直接丢掉,保留旧值。这个算法干的是筛选的活,不是平滑的活。

#define THRESHOLD 50 // 最大允许偏差,根据信号变化率折算 int16_t amplitude_limit_filter(int16_t new_value, int16_t last_value) { if (abs(new_value - last_value) > THRESHOLD) { return last_value; // 认为新值是干扰 } return new_value; }

阈值怎么定?不是拍脑袋写的,要看信号的真实物理变化率。比如热电偶测温,正常工况下每秒温度变化不超过2度,采样周期200ms,那相邻两次采样理论上最多变化0.4度,留两到三倍余量,阈值设1~2度就够了。反过来,如果设备启动时温度本来就会突跳,你阈值设太小,会把真实信号当干扰滤掉,那就矫枉过正了。

这个函数放在所有滤波链的最前面性价比最高。它牺牲极小的计算量,先把脉冲型尖峰干掉,后面别的算法处理起来压力就小很多。

2.3 中位值滤波:脉冲干扰的天敌

中位值滤波的思路是把连续N次采样值排序,取中间的数值输出。比如连采5次,排序后第3个值就是输出值。这个方法对付瞬间脉冲干扰特别有效,因为它天然免疫“极端值”。

#define MEDIAN_N 5 int16_t median_filter(int16_t *buf, uint8_t len) { int16_t temp[MEDIAN_N]; memcpy(temp, buf, sizeof(int16_t) * MEDIAN_N); // 简单冒泡排序 for (uint8_t i = 0; i < len - 1; i++) { for (uint8_t j = i + 1; j < len; j++) { if (temp[j] < temp[i]) { int16_t swap = temp[i]; temp[i] = temp[j]; temp[j] = swap; } } } return temp[len / 2]; }

点数N取多少要动脑子。N越大,抗脉冲能力越强,但输出延迟也越大。工控场景一般取5~7就够了,取太多反而让实时性变差。这个算法有个前提:采到的N个值里脉冲干扰不能超过半数,否则中位数本身就被污染了。另外它是吃RAM的大户,每个通道要额外开N个int16的缓冲区,通道多的时候注意内存预算。

2.4 算术平均滤波:教科书算法,但要会挑场景

算术平均就是这N次采样加起来除以N。理论上,对于均值为真实信号的随机噪声,平均后噪声功率会降低到原来的1/N,信噪比提升明显。这涉及微弱信号检测中最基础的一个原理:多次采样取平均,能压制零均值的白噪声。

#define AVERAGE_N 16 int16_t average_filter(int16_t *buf, uint8_t len) { int32_t sum = 0; for (uint8_t i = 0; i < len; i++) { sum += buf[i]; } return (int16_t)(sum / len); }

但你别一上来就把所有信号都套平均。平均算法最忌讳的是“把突发干扰也算进平均值里”,一个巨大的尖峰可以瞬间把平均值拉歪。此外它天然带延迟,N次采样才能出一个结果,实时性堪忧。它最适合的场景是温度、液位这类变化本来就很慢的物理量,而不是电流环、速度环这种需要迅速响应的控制信号。计算时用int32累计,避免N个小数据相加溢出——这是新手最容易踩的坑。

2.5 递推平均滤波:滑动窗口的性价比之王

递推平均滤波(滑动平均)是工控里最常用的算法之一,没有“之一”我甚至都信。它维护一个固定长度的队列,每次新值进队尾,最旧的出队,然后整个窗口求平均。相比算术平均,它的实时性好得多,每个采样周期都能输出一个新值,而非攒满N个才能算。

#define WINDOW_SIZE 8 typedef struct { int16_t buf[WINDOW_SIZE]; uint8_t count; uint8_t head; int32_t sum; } SlidingAverage; int16_t sliding_average_filter(SlidingAverage *ctx, int16_t new_value) { if (ctx->count < WINDOW_SIZE) { ctx->buf[ctx->count] = new_value; ctx->sum += new_value; ctx->count++; return (int16_t)(ctx->sum / ctx->count); } int16_t oldest = ctx->buf[ctx->head]; ctx->buf[ctx->head] = new_value; ctx->head = (ctx->head + 1) % WINDOW_SIZE; ctx->sum = ctx->sum - oldest + new_value; return (int16_t)(ctx->sum / WINDOW_SIZE); }

窗口长度直接影响平滑效果和滞后。窗口越长,滤波越平滑,但对信号变化的响应越慢。我实测过一套压力采集,窗口从4加到16,噪声峰峰值从±15降到±3,但阶跃响应时间从0.4ms变成1.6ms。具体怎么取舍,要看你的控制周期容忍度。这个算法的另一个坑是:它对单次大脉冲干扰非常敏感,脉冲还在窗口里没滑出去之前,输出都会偏离真实值,所以最好在前面串限幅滤波。

2.6 中位值平均滤波:防脉冲和消噪声一锅端

中位值平均滤波很好理解,先把N次采样排序,去掉最大和最小值,剩下的取平均。你可以把它想象成中位值滤波和算术平均的合体:既拥有中位值对脉冲的免疫力,又有平均对随机噪声的压制力。

#define N 10 int16_t median_average_filter(int16_t *buf, uint8_t len) { int16_t temp[N]; memcpy(temp, buf, sizeof(int16_t) * N); // 排序 for (uint8_t i = 0; i < N - 1; i++) for (uint8_t j = i + 1; j < N; j++) if (temp[j] < temp[i]) { int16_t s = temp[i]; temp[i] = temp[j]; temp[j] = s; } int32_t sum = 0; for (uint8_t i = 1; i < N - 1; i++) sum += temp[i]; return (int16_t)(sum / (N - 2)); }

这个算法在电动阀门开度反馈、温度巡检仪这类中等速度、干扰模式复杂的场景里很抗打。N一般取5~15,注意去掉两个极值后分母是N-2,别写死成N。它的缺点是计算量大,每个周期要排序一次,对MCU性能要求略高。放在低主频8位单片机上做高频采样时,要掂量一下算力。

2.7 限幅平均滤波:什么是“先拦截再平均”

限幅平均滤波是一种复合策略:先拿限幅逻辑把可疑的大偏差值剔除或替换,再用滑动窗口去平均。这种“两道关口”的设计,对现场那种既有随机噪声又有偶发强脉冲的混合干扰场景非常好用。

我写过一套炼钢厂烟气成分采集程序,信号里既有20Hz左右的随机波动,又时不时冒出一个幅值很大的尖峰。单用滑动平均,尖峰要滑好几拍才消失;单用限幅,随机噪声完全压不住。最后串起来用:先限幅,再滑动平均,效果立竿见影,曲线又稳又不失真。代码基本上就是把2.2和2.5的两个函数串起来,逻辑不复杂,核心是参数配合。这个算法没有单独的固定范式,具体写法随场景变,但思想是一致的。

2.8 一阶滞后滤波:工控圈的低通滤波器

一阶滞后滤波,也叫一阶惯性滤波,本质就是数字RC低通。公式特别简单:

输出 = α × 本次输入 + (1 - α) × 上次输出

α值越小,滤波越强,响应越慢;α值越大,滤波越弱,响应越快。它最大的优点是计算开销几乎可以忽略,实时性非常好,因此在MCU里被大量用于PID测量值的预处理。

typedef struct { float alpha; float last_output; } FirstOrderLPF; float first_order_filter(FirstOrderLPF *ctx, float input) { ctx->last_output = ctx->alpha * input + (1.0f - ctx->alpha) * ctx->last_output; return ctx->last_output; }

α怎么算?可以从时间常数推:α ≈ T / (T + τ),其中T是采样周期,τ是滤波时间常数。举个例子,采样周期10ms,希望滤除50Hz工频干扰,那时间常数τ可以设在20ms左右,α算出来大约0.33。这个公式的推导基于一阶RC电路的离散化,数值上很稳,可以放心用。

这个算法的坑在于浮点运算。在低端MCU上没有FPU,float运算会被模拟得很慢。处理大量通道时建议改成长整型定点数实现,例如放大256倍再算,最后再缩回来。我实际测过,定点实现比浮点快3倍以上,误差也完全可以接受。

2.9 加权递推平均:让新数据更有发言权

滑动平均把所有数据一视同仁,但实际上一批数据内部,最新的那个往往最能反映当前状态。加权递推平均就给不同位置的数据分配不同权重,离当前时刻越近的权重越大,从而在平滑和响应之间找到更好的平衡。

常见的权重表比如一组窗口长度为5,权重可以设成{1, 2, 3, 4, 5},总和15,计算时相应位置乘上权重再除总和。

#define W 5 static int16_t coeff[W] = {1, 2, 3, 4, 5}; static int16_t buf[W]; static uint8_t head = 0; int16_t weighted_sliding_filter(int16_t new_value) { buf[head] = new_value; head = (head + 1) % W; int32_t sum = 0, weight_sum = 0; for (uint8_t i = 0; i < W; i++) { int16_t idx = (head + i) % W; // 从最旧到最新 sum += (int32_t)buf[idx] * coeff[i]; weight_sum += coeff[i]; } return (int16_t)(sum / weight_sum); }

权重怎么定是门经验活。我的一般做法是:先确定窗口长度,然后让最新权重大概是最旧权重的3~5倍。权重差距太小没有意义,差距太大等于“旧数据说了不算”,滤波效果退化成一阶滞后。这个算法在温度快速爬升的加热控制中很好用,它会让你比等权滑动平均更早看到温度拐点,从而减少超调。

2.10 消抖滤波:给开关量准备的“性格过滤器”

消抖滤波不处理模拟量连续信号,更擅长对付开关量和状态量。它的逻辑是:当检测到输入发生变化时,先不采信,持续采样N次确认变化确实依然存在,才更新输出。按键消抖用的就是这个思路,在工控里,它被用在限位开关、光电传感器、继电器反馈等数字信号上。

#define DEBOUNCE_TIMES 5 typedef struct { uint8_t stable_value; uint8_t candidate_value; uint8_t count; } Debounce; uint8_t debounce_filter(Debounce *ctx, uint8_t input) { if (input != ctx->candidate_value) { ctx->candidate_value = input; ctx->count = 0; } else { ctx->count++; if (ctx->count >= DEBOUNCE_TIMES) { ctx->stable_value = ctx->candidate_value; } } return ctx->stable_value; }

消抖滤波有个隐藏价值:它可以把“抖动状态”作为一种诊断信息。我在写设备维护程序时,用消抖滤波统计单位时间内输出翻转了多次,如果抖动次数异常升高,说明传感器或接触器可能快要损坏了,提前给运维发预警,相当于把一个滤波算法用成了预测性维护工具。

2.11 限幅消抖滤波:综合型选手

限幅消抖滤波是前面几个算法的组合拳。先做限幅拦截突变值,再对落入限幅范围内的新值做消抖确认,既防大脉冲,又防小幅高频抖动。它适合那些信号真实趋势稳定、但叠加了大量毛刺的场合,比如电机电流反馈、阀位反馈。

这个算法的代价是参数多:限幅阈值、消抖次数、稳定窗口长度都得根据具体工况标定。调参数的时候我建议用现场录制的原始数据回放,而不是边跑边调。把原始数据存进数组,用上位机或串口看效果,参数收敛速度要快得多。这是嵌入式调试滤波算法一个非常重要的方法论。

3. 实战选型:不同工控信号该用哪把钥匙

算法再多,不会选等于零。以下是我针对几种常见现场信号的实际选型经验。

3.1 热电偶/RTD温度信号

温度信号的特点是变化缓慢、带宽极低,通常在0.1~1Hz以下。噪声可能来自热电动势的随机扰动、工频耦合和仪表放大器的偏置漂移。我的建议是限幅+中位值平均的组合。先限幅防大尖峰,再中位值平均压随机噪声。窗口可以放宽到10~15拍,反正温度响应本来就慢,多点延时完全能接受。如果控制回路对超调敏感,还可以在输出端再挂一阶滞后,把残余毛刺彻底揉平。

3.2 4-20mA模拟量变送器信号

4-20mA信号在工控里神一样的存在,抗干扰能力强,但接入PLC后依然可能被地环路和变频器噪声污染。这类信号的特点是底噪中等、偶发尖峰明显。我会用限幅+滑动平均,窗口长度4~8拍。响应速度适中,能跟上压力、液位的正常变化,又能滤掉变频器启停引起的毛刺。这个方案我实测在很多DCS/PLC项目上表现稳,参数调好了基本上不用去现场二次搞。

3.3 旋转编码器/PWM反馈信号

编码器反馈信号频率高,数据率大,滤波最忌引入明显延迟。这时候一阶滞后滤波非常合适,计算开销小,α可以取0.4~0.7之间,既压制高频毛刺又能保住快速动态。PWM形式的反馈信号则更依赖占空比测量,软硬件要配合,单纯滤波算法能做的有限,一般是在解调后加一级中位值滤波,防止解调器输出偶发的错误占空比。

3.4 电机电流快速回路

电流环是控制环路里最“娇气”的环节,延迟稍大就会引发振荡甚至发散。所以电流采样几乎不推荐用强滤波,最多用限幅滤波拦掉ADC毛刺,或者极短窗口的滑动平均(窗口2~3拍)。我之前见过有人给电流环套了15拍滑动平均,结果系统直接高频振荡,电机嗡嗡响,拆掉滤波反而稳了。滤波不是越强越好,是“够用就好”。

3.5 选型决策表

信号特点推荐方案原因
慢变+随机噪声算术平均/中位值平均不在乎延迟,追求稳定
慢变+偶发脉冲限幅+中位值平均防尖峰同时压噪声
中速+混合干扰限幅+滑动平均实时性和平滑度的折中
高速控制回路限幅/短窗滑动平均保动态,防止过度延迟
开关量/状态量消抖滤波天然适配数字抖动
需要快速辨识趋势加权递推平均新数据权重高,反应快

4. 滤波链设计:从原始采样到控制器的完整链路

理解了单个算法之后,真正的高手会开始设计“滤波链”,即多个算法按顺序配合。我建议的通用链路是这样的:

原始采样 → 硬件RC/有源抗混叠 → 限幅滤波 → 核心滤波(滑动平均/中位值平均/一阶滞后) → 输出平滑(可选) → 控制器使用

前端限幅负责挡“可见的恶意干扰”,核心滤波负责“压底噪”,输出平滑负责“让曲线好看”。每一层职责不同,参数也各管各的。这条链路的层次感一定要明确,不要一个算法干所有事,否则参数会互相打架,调起来痛苦不堪。

参数调优顺序也很有讲究:先调限幅阈值,再调核心滤波窗口,最后才碰输出平滑系数。顺序乱掉,你会陷入“改了窗口又改阈值,改了阈值又觉得平滑不够”的死循环。我在几个项目的调试中反复验证,从前往后调是最省时间的路径。

5. 常见问题与排查技巧实录

这部分是我踩过坑之后总结出来的,每一个问题都真实发生在现场。

5.1 滤波后信号仍然不稳

排查优先级:先看原始信号是否包含周期性扰动,比如50Hz工频。如果是,普通滑动平均拉不干净,你得提高窗口长度到与工频周期对齐,或者上一阶滞后滤波把截止频率压到几十赫兹以下。还有一种情况是ADC参考电压在漂,那滤波算法再强也白搭,要去查电源和基准。

5.2 滤波后动态响应太慢,控制效果变差

这是最常见的“副作用”。解决思路有三条:

  • 缩短窗口长度或提高α值;
  • 改用加权递推平均,让新数据权重更高;
  • 把滤波从控制闭环里拿出来,只在显示和记录通道滤波,控制通道保留原始信号或者极限幅。

最后一条很值得记住:显示需要“好看”,控制需要“真实”。两者的需求本来就不一样,不要一个滤波器通吃。

5.3 整数运算溢出导致输出跳变

滑动平均和加权平均都要用累加和,当输入乘积累加到int16溢出时,输出会瞬间变成负数或者乱跳。稳妥做法是累加用int32甚至int64,N也不宜太大。我见过有人把N设成64,累加int16直接爆掉,查了半天问题居然出在变量类型上,非常冤。

5.4 如何区分真实波动和滤波缺陷

用“拉普拉斯变换发散信号”这种非工程化的说法你在现场用不上,但有个笨办法特别灵:把原始采样值和滤波输出值用串口同时打出来,用上位机画两条曲线。如果原始信号本身就乱跳,说明问题在硬件侧;如果原始信号平稳但滤波输出有异常抖动,那肯定是算法或参数的问题。先分清是哪边的锅,再动手,效率最高。

5.5 参数备份和灰度切换

现场调试好的滤波参数一定要固化到配置文件里,千万别只躺在代码注释中。我吃过亏:一个项目调了三天的滤波参数,后来固件升级全丢了,现场信号又开始抖,差点被客户骂到怀疑人生。现在我的习惯是在设备启动时从配置区读取滤波参数,支持远程微调,改完参数不需要重新编译下载固件,运维成本降了一个量级。

6. 我的个人经验与最后的建议

我在几个复杂项目里用过这些算法,讲点掏心窝子的话。滤波算法看着简单,但真正决定成败的往往是非算法因素:信号采集端的接地、屏蔽、线缆走向,采样率的合理选择,以及参数标定的数据支撑。算法只是整个信号链的最后一公里。

个人建议是,在自己的测试台上建立一套“原始信号录制-回放-参数调优”的流程。现场出现问题后,先把原始采样数据完整录下来带回实验室,反复回放测试不同算法和参数,直到找到最优解再去现场验证。这个做法让我把调试时间缩短了至少一半。

最后分享一个小技巧:给每个通道的滤波效果设计一个“旁路开关”,可以在运行中切换“直通”和“滤波”模式。这样排除问题的时候,你只需要切换开关对比曲线,就能快速判断一个异常到底是噪声引起的还是滤波器引起的。这套思路帮我处理过的现场故障不下两位数,真希望当初刚入行的时候就有人告诉我这些。

工控信号滤波说到底不是技术炫技,而是工程判断。选对算法、配好参数、留足调优空间,你的系统才能在噪声堆里稳如泰山。

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

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

立即咨询