在嵌入式系统中,AD采集的数据往往夹杂着各种噪声——白噪声、脉冲干扰、周期性波动等。如何从这些“脏数据”中提取出真实信号,是每个嵌入式工程师必须面对的课题。本文基于一份典型的STM32工程代码,详细剖析10种经典数字滤波算法的原理、实现及适用场景,并指出常见实现误区,助你轻松应对不同噪声环境。


一、为什么需要数字滤波?

模拟电路硬件滤波(RC、LC)固然简单,但参数固定、无法自适应,且占用PCB面积。数字滤波则可通过软件灵活调整系数,实现复杂算法,且不增加硬件成本。尤其在STM32这类Cortex-M平台上,利用CPU空闲时间做滤波计算,性价比极高。

本文所有算法均围绕一个预先生成的正弦波查找表ftable[255])展开,模拟连续采样的数据流,通过串口输出滤波前后的数值,便于在PC端绘制波形对比。


二、10种经典数字滤波算法

1. 限幅滤波法(又称程序判断滤波)

原理:根据经验设定两次采样允许的最大偏差值 A。若当前采样值与上次有效值之差超过 A,则判定本次值为干扰,予以丢弃,仍用上次值;否则采用本次值。

代码实现

#define A 51
u16 Value1;  // 上一次有效值

u16 filter1() {
    u16 NewValue = ftable[b];   // 本次采样
    b++; if(b==255) b=1;
    // 判断差值是否超限
    if ((NewValue > Value1 ? NewValue - Value1 : Value1 - NewValue) > A)
        return Value1;   // 超限,保持旧值
    else {
        Value1 = NewValue;  // 更新有效值
        return NewValue;
    }
}

优点:有效克服偶然脉冲干扰(如雷击、开关噪声)。
缺点:无法抑制周期性干扰,平滑度差;阈值 A 需凭经验设定,不当则效果打折。
适用场景:压力、液位等变化缓慢的物理量。


2. 中位值滤波法

原理:连续采样 N 次(N 取奇数),将采样值排序,取中间值作为本次有效值。

代码片段

#define N 5
u16 filter2() {
    u16 buf[N], i, j, temp;
    for (i=0; i<N; i++) {
        buf[i] = ftable[a++]; if(a==255) a=0;
    }
    // 冒泡排序(从小到大)
    for (i=0; i<N-1; i++)
        for (j=0; j<N-1-i; j++)
            if (buf[j] > buf[j+1]) { temp = buf[j]; buf[j]=buf[j+1]; buf[j+1]=temp; }
    return buf[(N-1)/2];
}

优点:对偶然脉冲干扰有极强抑制作用(因为极值不会落在中位)。
缺点:对快速变化的信号(如流量、速度)反应迟钝;排序耗时,N不宜过大。
适用场景:温度、液位等变化缓慢且伴有偶发尖峰噪声的场合。


3. 算术平均滤波法

原理:连续采样 N 个值,计算算术平均值。

代码

#define N 5
u16 filter3() {
    u32 sum = 0;
    for (u8 i=0; i<N; i++) {
        sum += ftable[a++]; if(a==255) a=0;
    }
    return sum / N;
}

优点:平滑随机噪声,信号平滑度随 N 增大而提高。
缺点:占用RAM存储N个数据;响应慢,N过大时实时性差;不能有效滤除脉冲干扰。
适用场景:随机噪声占主导的信号,如压力传感器(N=4)或流量传感器(N=12)。


4. 递推平均滤波法(滑动平均)

原理:维护一个长度为 N 的队列,每次采样到新数据入队,同时丢弃队首最旧数据,然后计算队列平均值。相当于“先进先出”的算术平均。

代码

#define N 3
u16 buf[N+1];
u16 filter4() {
    u16 sum = 0;
    buf[N] = ftable[a++]; if(a==255) a=0;
    for (i=0; i<N; i++) {
        buf[i] = buf[i+1];   // 左移
        sum += buf[i];
    }
    return sum / N;
}

优点:对周期性干扰有良好抑制,平滑度高,适用于高频振荡系统。
缺点:对偶发脉冲干扰抑制较差;占用RAM较多。
适用场景:交流信号整流后的平滑、电机电流采样等。


5. 中位值平均滤波法(防脉冲干扰平均滤波)

原理:先采样 N 个数据,去掉一个最大值和一个最小值,再对剩余 N-2 个求平均。融合了中位值滤波和算术平均的优点。

代码

#define N 5
u16 filter5() {
    u16 buf[N], sum=0;
    // 采集并排序(同中位值滤波)
    // 然后从 index=1 到 N-2 累加
    for (i=1; i<N-1; i++) sum += buf[i];
    return sum / (N-2);
}

优点:抑制脉冲干扰,同时平滑随机噪声,平滑度高。
缺点:排序耗时,N取3~14为宜;实时性欠佳。
适用场景:工业现场强干扰环境下的慢变信号。


6. 限幅平均滤波法

原理:先对采样值进行限幅处理(同方法1),然后再送入滑动平均队列。

代码

#define N 3
#define A 51
u16 buf[N];
u16 filter6() {
    u16 new = ftable[a++]; if(a==255) a=0;
    if (abs(new - buf[N-1]) > A) new = buf[N-1];  // 限幅
    // 滑动平均(略)
}

优点:兼备限幅和平均滤波的优点,有效抑制脉冲和周期干扰。
缺点:消耗两倍RAM(限幅需保存上次值,平均需队列)。
适用场景:既有尖峰脉冲又有周期性噪声的恶劣环境。


7. 一阶滞后滤波法

原理:本次输出 = (1-α) × 本次采样值 + α × 上次输出值,α 为0~1之间的系数。相当于一个数字低通滤波器。

代码

#define ALPHA 0.1f
u16 Value;
u16 filter7() {
    u16 new = ftable[b++]; if(b==255) b=1;
    Value = (u16)((1-ALPHA) * new + ALPHA * Value);
    return Value;
}

优点:对周期性干扰抑制效果好,平滑度高,代码量极小(无需队列)。
缺点:相位滞后,灵敏度低;α 调优困难。
适用场景:高频振荡信号、电源纹波滤除。


8. 加权递推平均滤波法

原理:对滑动平均进行改进,不同时刻的数据赋予不同权重,越新的数据权重越大。

代码

#define N 12
const u8 coe[N] = {1,2,3,4,5,6,7,8,9,10,11,12};
u16 sum_coe = 78;
u16 buf[N+1];
u16 filter8() {
    u32 sum=0;
    buf[N] = ftable[a++]; if(a==255) a=0;
    for (i=0; i<N; i++) {
        buf[i] = buf[i+1];
        sum += buf[i] * coe[i];
    }
    return sum / sum_coe;
}

优点:提高系统响应灵敏度,适用于大滞后对象。
缺点:权重系数需根据实际对象调优;计算量稍大。
适用场景:温度控制系统、压力缓慢变化但需快速反映突变的场合。


9. 消抖滤波法

原理:设置一个计数器,若采样值持续与当前有效值不同,计数器累加,当超过上限 N 时,才更新有效值。相当于“去抖”处理。

代码

#define N 5
u16 Value, cnt=0;
u16 filter9() {
    u16 new = ftable[b++]; if(b==255) b=1;
    if (Value != new) {
        cnt++;
        if (cnt > N) { cnt=0; Value = new; }
    } else cnt = 0;
    return Value;
}

优点:避免临界值附近的反复跳动,显示稳定。
缺点:对快速变化信号响应迟钝;若计数器溢出时恰巧为干扰值,则会误更新。
适用场景:按键消抖、开关量信号处理、仪表显示防抖。


10. 限幅消抖滤波法

原理:先限幅,再消抖。结合方法1和方法9。

代码

#define A 51
#define N 5
u16 Value, cnt;
u16 filter10() {
    u16 new = ftable[b++]; if(b==255) b=1;
    if (abs(new - Value) > A) new = Value;  // 限幅
    // 消抖逻辑(同方法9)
    ...
    return Value;
}

优点:继承限幅和消抖的双重优势,避免干扰值被误导入。
缺点:对快速变化信号不适用。
适用场景:极慢变化且需杜绝跳变显示的场合。


三、如何选择合适的滤波算法?

噪声类型 推荐算法
偶发脉冲尖峰 限幅滤波、中位值滤波
随机白噪声 算术平均、滑动平均
周期性干扰(工频等) 滑动平均、一阶滞后
既有脉冲又有周期噪声 限幅平均、中位值平均
显示防抖动 消抖滤波、限幅消抖
实时性要求高 限幅滤波、一阶滞后(计算快)
RAM资源紧张 限幅滤波、一阶滞后(无队列)

通常在实际项目中,会组合使用多种滤波(如先限幅再滑动平均),以达到最佳效果。


四、总结

数字滤波是嵌入式信号处理的基本功。本文列举的10种方法覆盖了绝大多数工程需求,它们各有优劣,无绝对“最佳”,只有“最适合”。开发者应根据信号特征、实时性、资源限制综合评估。

最后,建议在实际调试时,将原始值和滤波值同时通过串口上传至上位机绘制曲线,直观对比不同算法的效果,这是最快的学习和调优途径。

希望本文能帮助你在STM32项目中轻松驾驭噪声,还原纯净信号!


延伸阅读:卡尔曼滤波、自适应滤波、FFT频域滤波等高级算法,适用于更高要求的应用,可进一步深入研究。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐