在嵌入式系统开发中,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);
}

Logo

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

更多推荐