ADC采样滤波的常用算法与选型指南
在嵌入式系统开发中,ADC(模数转换) 是连接模拟世界与数字世界的桥梁。无论是电源电压监控、环境温度采集、还是音频信号处理,我们都离不开 ADC。但ADC 采样必然伴随噪声—— 电源纹波、电磁干扰、电路杂散信号都会让采样数据产生波动、毛刺,直接使用原始数据会导致系统判断错误、控制抖动、精度暴跌。
噪声不可避免,但可以通过软件滤波算法驯服。不同应用场景对数据的实时性、平滑度、抗突发干扰能力要求天差地别,选错滤波算法,轻则数据滞后,重则功能失效。
本文将深度拆解 5 种嵌入式最常用的 ADC 滤波算法:算术平均滤波、滑动平均滤波、中值滤波、一阶滞后滤波、限幅滤波,从核心原理、优缺点、适用场景,到可直接移植的 C 语言标准代码,再到实战波形对比与选型决策树,帮你一次性搞定 ADC 滤波所有问题。
首先把总结内容放在前面:

1 算术平均滤波

原理:连续采集N次ADC采样数据,将所有数据求和后除以N,得到的平均值作为最终有效数据。核心逻辑是用大量采样抵消噪声的正负波动。
适用场景:温湿度传感器、气压传感器等信号几乎无突变的场景。
/**
* @brief 算术平均滤波
* @param hadc: ADC句柄
* @param times: 采样次数
* @retval 滤波后结果
*/
uint16_t Arithmetic_Average_Filter(ADC_HandleTypeDef *hadc, uint8_t times)
{
uint32_t sum = 0;
// 连续采样N次
for(uint8_t i=0; i<times; i++)
{
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10);
sum += HAL_ADC_GetValue(hadc);
}
// 求平均值
return sum / times;
}
2 滑动平均滤波

原理:维护一个固定长度N的采样队列,每次新采样值覆盖队列中最旧的值,再对整个队列求平均。相比算术平均,他不会等待N次采样完成,实时性大幅提升。
适用场景:电源电压监控、液位检测等需要实时性、同时要求数据平滑的场景。
#define SLIDE_WIN_SIZE 10 // 滑动窗口大小
uint16_t slide_buf[SLIDE_WIN_SIZE] = {0};
uint8_t slide_index = 0;
/**
* @brief 滑动平均滤波
* @param hadc: ADC句柄
* @retval 滤波后结果
*/
uint16_t Slide_Average_Filter(ADC_HandleTypeDef *hadc)
{
uint32_t sum = 0;
// 新值覆盖旧值
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10);
slide_buf[slide_index++] = HAL_ADC_GetValue(hadc);
// 循环队列
if(slide_index >= SLIDE_WIN_SIZE) slide_index = 0;
// 求和平均
for(uint8_t i=0; i<SLIDE_WIN_SIZE; i++)
{
sum += slide_buf[i];
}
return sum / SLIDE_WIN_SIZE;
}
3 中值滤波

原理:采集N次采样值后从小到大排序,剔除最大值和最小值的干扰,取中间位置的值作为结果,对突发尖峰、脉冲干扰有“秒杀级”的过滤效果。
适用场景:工业现场、电机周边、强电磁环境下的ADC采样、专门对抗尖峰毛刺。
/**
* @brief 中值滤波
* @param hadc: ADC句柄
* @param times: 采样次数(建议奇数:3/5/7)
* @retval 滤波后结果
*/
uint16_t Median_Filter(ADC_HandleTypeDef *hadc, uint8_t times)
{
uint16_t buf[times];
// 采样
for(uint8_t i=0; i<times; i++)
{
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10);
buf[i] = HAL_ADC_GetValue(hadc);
}
// 冒泡排序
for(uint8_t i=0; i<times-1; i++)
{
for(uint8_t j=0; j<times-i-1; j++)
{
if(buf[j] > buf[j+1])
{
uint16_t temp = buf[j];
buf[j] = buf[j+1];
buf[j+1] = temp;
}
}
}
// 返回中间值
return buf[times/2];
}
4 一阶滞后滤波

原理:软件模拟硬件RC低通滤波器,本次结果 = 系数 × 新采样值 + (1 - 系数)× 上一次结果。仅需保存上一次结果,内存占用极低。
适用场景:单片机、资源极度紧张的设备,信号变化平缓的采集场景。
#define ALPHA 0.2f // 滤波系数 0<α<1,越小越平滑
uint16_t last_filter_val = 0;
/**
* @brief 一阶滞后滤波
* @param hadc: ADC句柄
* @retval 滤波后结果
*/
uint16_t First_Order_Filter(ADC_HandleTypeDef *hadc)
{
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc, 10);
uint16_t new_val = HAL_ADC_GetValue(hadc);
// 核心公式
uint16_t filter_val = (uint16_t)(ALPHA * new_val + (1 - ALPHA) * last_filter_val);
last_filter_val = filter_val;
return filter_val;
}
5 限幅滤波

原理:设定最大允许变化阈值,若本次采样值与上一次差值超过阈值,直接判定为无效数据,使用上一次值替代。作用是剔除极端错误值,必须搭配其他滤波算法使用。
适用场景:所有ADC采样的第一道防线,防止异常数据击穿后续逻辑。
#define AMP_LIMIT 50 // 最大变化幅度阈值
uint16_t last_val = 0;
/**
* @brief 限幅滤波(前级保护)
* @param now_val: 本次原始采样值
* @retval 限幅后结果
*/
uint16_t Amplitude_Limit_Filter(uint16_t now_val)
{
if(now_val > last_val)
{
// 超过上限,丢弃本次值
if((now_val - last_val) > AMP_LIMIT) return last_val;
}
else
{
if((last_val - now_val) > AMP_LIMIT) return last_val;
}
last_val = now_val;
return now_val;
}
6 组合滤波(限幅滤波+中值滤波+滑动平均)
uint16_t Combo_Filter(ADC_HandleTypeDef *hadc)
{
// 1. 限幅滤波
HAL_ADC_Start(hadc);
HAL_ADC_PollForConversion(hadc,10);
uint16_t adc_val = Amplitude_Limit_Filter(HAL_ADC_GetValue(hadc));
// 2. 中值滤波
uint16_t median_val = Median_Filter(hadc, 5);
// 3. 滑动平均滤波
return Slide_Average_Filter(hadc);
}
更多推荐

所有评论(0)