单片机常见滤波算法

单片机常用滤波算法详解

文档说明

本文档聚焦单片机应用场景,整理了 6 种最常用的滤波算法,涵盖算法原理(含核心公式)、优缺点分析、可直接移植的 C 语言代码,适配 51、STM32 等主流单片机,兼顾 “资源占用少、易实现” 的嵌入式开发特点。

目录

  1. 滤波算法概述

  2. 各滤波算法详解

  • 2.1 限幅滤波(程序判断滤波)

  • 2.2 中位值滤波

  • 2.3 算术平均滤波

  • 2.4 滑动平均滤波

  • 2.5 加权滑动平均滤波

  • 2.6 一阶滞后滤波(指数平滑滤波)

  1. 算法选型指南

  2. 总结

1. 滤波算法概述

单片机采集传感器数据(如 ADC 电压、温度、压力)时,易受电磁干扰、机械抖动等影响产生噪声,滤波算法的核心是剔除无效噪声,保留有效信号趋势。嵌入式场景下,滤波算法需满足:RAM / 算力占用低、代码简洁、参数易调试。

2. 各滤波算法详解

2.1 限幅滤波(程序判断滤波)

2.1.1 算法原理

核心逻辑:设定数据允许变化的最大幅度阈值,若当前采样值与上一次有效值的偏差超出阈值,判定为噪声,沿用前一次有效值;否则取当前采样值。

核心判断条件

|X(n) - Y(n-1)| leq Delta rightarrow Y(n) = X(n)

|X(n) - Y(n-1)| > Delta rightarrow Y(n) = Y(n-1)

  • X(n):第 n 次采样值

  • Y(n):第 n 次滤波输出值

  • Y(n-1):第 n-1 次滤波输出值

  • Delta:最大允许偏差阈值

2.1.2 优缺点

优点 缺点
代码极简,RAM / 算力占用极低 无法滤除连续突变的噪声
响应速度极快(无延迟) 阈值Delta需根据传感器精度调试,设置不当易失真
适合剔除单次瞬时跳变噪声 仅能抑制 “幅度超阈值” 的噪声,对小幅随机噪声无效

2.1.3 核心代码(C 语言)

// 配置参数:根据传感器精度调整最大允许偏差

#define LIMIT_DELTA 10 

// 保存上一次有效滤波值,初始值可设为传感器默认值

uint16_t last_limit_val = 0; 

/**

* @brief 限幅滤波函数

* @param current_val 当前采样值(如ADC读取值)

* @return 滤波后的值

*/

uint16_t limit_filter(uint16_t current_val) {

   uint16_t filter_val;

   // 偏差超出阈值则沿用前值,否则取当前值

   if (abs(current_val - last_limit_val) > LIMIT_DELTA) {

       filter_val = last_limit_val;

   } else {

       filter_val = current_val;

   }

   // 更新上一次有效值

   last_limit_val = filter_val;

   return filter_val;

}

2.2 中位值滤波

2.2.1 算法原理

核心逻辑:连续采集奇数个样本,对样本排序后取中间值作为输出,彻底剔除极端异常值。

核心步骤

  1. 采集 N 个样本(N 为奇数,如 3/5/7);

  2. 对样本排序;

  3. 取第N/2位的中间值作为滤波输出。

2.2.2 优缺点

优点 缺点
彻底剔除脉冲干扰、极端异常值 需连续采集 N 个样本,响应速度受采样个数影响
对随机噪声抑制效果好 不适合快速变化的信号(如高速电压波动)
代码实现简单,无需复杂计算 采样过程需短延时,占用少量 RAM 存储样本

2.2.3 核心代码(C 语言)

// 配置参数:采样个数(必须为奇数,建议3/5/7)

#define MEDIAN_SAMPLE_CNT 5 

/**

* @brief 中位值滤波函数

* @note adc_read()需替换为实际的传感器采样函数

* @return 滤波后的值

*/

uint16_t median_filter(void) {

   uint16_t sample_buf[MEDIAN_SAMPLE_CNT];

   uint16_t temp;

   // 1. 连续采集N个样本

   for (uint8_t i = 0; i < MEDIAN_SAMPLE_CNT; i++) {

       sample_buf[i] = adc_read();  // 替换为实际采样逻辑

       delay_ms(1);  // 短延时避免采样过于集中

   }

   // 2. 冒泡排序(嵌入式场景最易实现)

   for (uint8_t i = 0; i < MEDIAN_SAMPLE_CNT - 1; i++) {

       for (uint8_t j = 0; j < MEDIAN_SAMPLE_CNT - 1 - i; j++) {

           if (sample_buf[j] > sample_buf[j+1]) {

               temp = sample_buf[j];

               sample_buf[j] = sample_buf[j+1];

               sample_buf[j+1] = temp;

           }

       }

   }

   // 3. 返回中间值

   return sample_buf[MEDIAN_SAMPLE_CNT / 2];

}

2.3 算术平均滤波

2.3.1 算法原理

核心逻辑:连续采集 N 个样本,计算算术平均值作为输出,平滑随机噪声。

核心公式

Y = frac{1}{N} sum_{i=1}^{N} X_i

  • X_i:第 i 次采样值

  • N:采样个数

  • Y:滤波输出值

2.3.2 优缺点

优点 缺点
实现简单,对平稳随机噪声平滑效果好 N 越大滤波效果越好,但响应速度越慢
计算量小,适合资源受限的单片机 对脉冲干扰抑制差(极端值会拉低 / 拉高平均值)
适合慢变信号(如温度、湿度) 需连续采集 N 个样本,实时性差

2.3.3 核心代码(C 语言)

// 配置参数:采样个数(N越大滤波越强,响应越慢)

#define AVG_SAMPLE_CNT 10 

/**

* @brief 算术平均滤波函数

* @note adc_read()需替换为实际的传感器采样函数

* @return 滤波后的值

*/

uint16_t avg_filter(void) {

   // 用32位变量避免求和溢出

   uint32_t sum = 0;

   for (uint8_t i = 0; i < AVG_SAMPLE_CNT; i++) {

       sum += adc_read();

       delay_ms(1);

   }

   // 返回平均值

   return (uint16_t)(sum / AVG_SAMPLE_CNT);

}

2.4 滑动平均滤波

2.4.1 算法原理

核心逻辑:建立固定长度的缓冲区,新采样值入队时,最旧的采样值出队,始终对缓冲区中所有值求平均,平衡实时性与滤波效果。

核心公式

Y(n) = frac{1}{M} left( sum_{i=1}^{M-1} Y_{buf}(i) + X(n) right)

  • M:缓冲区长度

  • Y_{buf}(i):缓冲区中第 i 个旧值

  • X(n):当前新采样值

  • Y(n):当前滤波输出值

2.4.2 优缺点

优点 缺点
实时性优于算术平均滤波(无需等待 N 个样本) 需占用缓冲区 RAM(M 越大,RAM 占用越多)
输出无明显延迟,平滑效果可调 对脉冲干扰抑制效果一般
适合常规传感器(如液位、压力) 缓冲区长度 M 需调试,过大会降低响应速度

2.4.3 核心代码(C 语言)

// 配置参数:缓冲区长度(建议为2的幂次,优化除法效率)

#define SLIDE_BUF_LEN 8 

// 采样缓冲区(初始化为0)

uint16_t slide_buf[SLIDE_BUF_LEN] = {0};

// 缓冲区索引(循环更新)

uint8_t buf_index = 0;

// 缓冲区总和(避免重复求和,提升效率)

uint32_t slide_sum = 0; 

/**

* @brief 滑动平均滤波函数

* @param current_val 当前采样值

* @return 滤波后的值

*/

uint16_t slide_avg_filter(uint16_t current_val) {

   // 1. 减去即将被移出的旧值

   slide_sum -= slide_buf[buf_index];

   // 2. 新值入队

   slide_buf[buf_index] = current_val;

   slide_sum += current_val;

   // 3. 更新索引(循环缓冲区)

   buf_index = (buf_index + 1) % SLIDE_BUF_LEN;

   // 4. 返回平均值(SLIDE_BUF_LEN为2的幂次时,可用移位代替除法)

   return (uint16_t)(slide_sum / SLIDE_BUF_LEN);

}

2.5 加权滑动平均滤波

2.5.1 算法原理

核心逻辑:对滑动缓冲区中的样本分配不同权重(近期样本权重更高),计算加权和后除以权重总和,兼顾实时性与平滑效果。

核心公式

Y = frac{sum_{i=1}^{M} W_i times X_i}{sum_{i=1}^{M} W_i}

  • M:缓冲区长度

  • W_i:第 i 个样本的权重(近期样本权重更高)

  • X_i:第 i 个样本值

  • Y:滤波输出值

2.5.2 优缺点

优点 缺点
近期数据权重高,更贴合信号实时趋势 代码复杂度略高,权重需调试
兼顾滤波效果与响应速度 占用 RAM 略多(缓冲区 + 权重数组)
适合有噪声且需快速响应的信号 算力消耗略高于普通滑动平均

2.5.3 核心代码(C 语言)

// 配置参数:缓冲区长度

#define WEIGHT_BUF_LEN 5 

// 权重系数(近期数据权重更高,总和建议为10/100,方便计算)

uint8_t weight[WEIGHT_BUF_LEN] = {1, 2, 3, 4, 5};

// 权重总和(1+2+3+4+5)

#define WEIGHT_SUM 15 

// 采样缓冲区

uint16_t weight_buf[WEIGHT_BUF_LEN] = {0};

// 缓冲区索引

uint8_t weight_index = 0; 

/**

* @brief 加权滑动平均滤波函数

* @param current_val 当前采样值

* @return 滤波后的值

*/

uint16_t weight_slide_filter(uint16_t current_val) {

   // 1. 新值入队

   weight_buf[weight_index] = current_val;

   weight_index = (weight_index + 1) % WEIGHT_BUF_LEN;

   // 2. 计算加权和

   uint32_t weight_sum = 0;

   for (uint8_t i = 0; i < WEIGHT_BUF_LEN; i++) {

       // 索引偏移,保证最新值对应最高权重

       uint8_t idx = (weight_index + i) % WEIGHT_BUF_LEN;

       weight_sum += weight_buf[idx] * weight[i];

   }

   // 3. 返回加权平均值

   return (uint16_t)(weight_sum / WEIGHT_SUM);

}

2.6 一阶滞后滤波(指数平滑滤波)

2.6.1 算法原理

核心逻辑:通过平滑系数 α 融合当前采样值与上一次滤波结果,平衡滤波效果与响应速度。

核心公式

Y(n) = alpha times X(n) + (1 - alpha) times Y(n-1)

  • alpha:平滑系数(0 < α < 1)

  • X(n):当前采样值

  • Y(n-1):上一次滤波输出值

  • Y(n):当前滤波输出值

2.6.2 优缺点

优点 缺点
代码极简,RAM / 算力占用极低 对快速变化信号响应慢(α 越小越慢)
α 可调,兼顾滤波效果与响应速度 α 设置不当易导致信号滞后或滤波不足
适合慢变 + 随机噪声的信号(如温度) 浮点版需单片机支持 FPU(如 STM32)

2.6.3 核心代码(C 语言)

整数版(适配 51 等无 FPU 单片机)
// 配置参数:平滑系数(0<α<10,对应实际α=α/10)

#define ALPHA 2 

// 保存上一次滤波值

uint16_t last_lag_val = 0; 

/**

* @brief 一阶滞后滤波(整数版,无浮点运算)

* @param current_val 当前采样值

* @return 滤波后的值

*/

uint16_t lag_filter(uint16_t current_val) {

   // 公式优化:Y(n) = (α*X(n) + (10-α)*Y(n-1)) / 10

   last_lag_val = (ALPHA * current_val + (10 - ALPHA) * last_lag_val) / 10;

   return last_lag_val;

}
浮点版(精度更高,适配 STM32 等有 FPU 单片机)
// 平滑系数(0<α<1,α越大响应越快)

#define ALPHA_FLOAT 0.2 

// 保存上一次滤波值

float last_lag_float_val = 0.0; 

/**

* @brief 一阶滞后滤波(浮点版)

* @param current_val 当前采样值

* @return 滤波后的值

*/

float lag_filter_float(float current_val) {

   last_lag_float_val = ALPHA_FLOAT * current_val + (1 - ALPHA_FLOAT) * last_lag_float_val;

   return last_lag_float_val;

}

3. 算法选型指南

应用场景 推荐算法 关键调参建议
按键消抖、电压瞬时跳变 限幅滤波 Δ 设为传感器精度的 2~3 倍
工业电磁干扰、脉冲噪声 中位值滤波 采样个数设为 3/5(兼顾速度与效果)
温度、湿度等慢变平稳信号 算术平均 + 一阶滞后 算术平均 N=10,α=0.2
液位、压力(需平衡响应与滤波) 滑动平均滤波 缓冲区长度 M=8/16
快速变化且含噪声的信号 加权滑动平均 权重按 “1:2:3:4:5” 递增
资源极度受限(如 51 单片机) 限幅 / 一阶滞后 优先用整数版代码,避免浮点运算

4. 总结

单片机滤波算法的核心是 “按需选型”:

  1. 资源优先:选限幅、一阶滞后(RAM / 算力占用最低);

  2. 抗脉冲干扰:选中位值滤波;

  3. 慢变信号平滑:选算术平均 + 一阶滞后;

  4. 实时性要求高:选滑动平均 / 加权滑动平均。

实际应用中可组合使用(如 “限幅 + 中位值”),先通过限幅剔除瞬时跳变,再通过中位值滤除脉冲干扰,兼顾效果与效率。所有代码均可直接移植,只需将adc_read()替换为实际的传感器采样函数(如 ADC 读取、串口数据接收)。

posted @ 2025-12-06 10:53  Afangdong  阅读(111)  评论(0)    收藏  举报