4、ADC采样与滤波:ADC原理、采样电路设计、软件滤波算法

各位同学,咱们今天聊点硬核的——ADC采样与滤波。

说实话,做嵌入式这么多年,我见过太多产品“死”在采样上。电池电压测不准,电量显示乱跳,用户投诉说“你这按摩仪是不是坏了?”其实呢,硬件没坏,就是ADC没处理好。

所以这一章,我把自己踩过的坑、总结的经验,全掏出来给你。咱们从原理讲到电路,再讲到软件滤波,一条龙搞定。

4.1 ADC原理:模数转换到底在干什么?

ADC,全称模数转换器。说白了,就是把连续的模拟电压,变成离散的数字量。

你想想看,电池电压是3.7V,这是个连续值。但单片机只能认0和1。ADC就是那个“翻译官”。

常见的ADC类型有几种:

  • 逐次逼近型(SAR):速度快,精度适中。我用的最多,STM32内置的就是这种。
  • 积分型(双斜率):精度高,但慢。万用表里常见。
  • Σ-Δ型:高精度,适合音频、传感器。功耗稍大。

咱们按摩仪电池管理,一般用MCU内置的SAR ADC就够了。12位分辨率,能分辨出4096个等级。对于3.7V的电池,每个等级大约0.9mV。嗯,够用了。

关键公式:

数字值 = (模拟电压 / 参考电压) × (2^N - 1)

其中N是ADC位数。12位就是4096。

举个例子:参考电压3.3V,采样到电池电压3.0V,那么数字值 = (3.0 / 3.3) × 4095 ≈ 3722。反过来,读到3722,就知道电压是3.0V。

4.2 采样电路设计:别让硬件坑了你

这里我要说一句:软件写得再好,硬件设计烂了,全白搭。

我在项目中遇到过一件事:采样值总是跳,怎么滤波都没用。最后发现,是采样引脚走线太长,跟电机驱动线并行了。干扰直接耦合进来。

所以,采样电路设计有几个要点:

  1. 分压电阻要选精密电阻。1%精度是底线,别用5%的。电池电压可能到4.2V,MCU的ADC只能接受0~3.3V,必须分压。
  2. 加一个RC低通滤波器。电阻1kΩ,电容0.1μF,截止频率大约1.6kHz。能滤掉大部分高频噪声。
  3. 采样引脚加一个TVS管。防静电,防浪涌。我曾经因为省了这个管子,烧了三个ADC通道。
  4. 走线要短,远离干扰源。别跟PWM线、电机线并行走。

我的习惯:分压电阻用100kΩ和33kΩ,把4.2V分到大约1.04V。这样即使电池电压到4.2V,ADC输入也不会超过1.1V,留有余量。

电路图大概这样(文字描述):电池正极 → 100kΩ电阻 → 采样点 → 33kΩ电阻 → GND。采样点再串一个1kΩ电阻,接一个0.1μF电容到GND,然后进MCU的ADC引脚。

4.3 软件滤波算法:让数据稳如老狗

硬件做好了,但采样值还是会有波动。为什么?因为电池本身有内阻,负载变化时电压会跳。电机一启动,电压瞬间掉0.1V很正常。

这时候,软件滤波就派上用场了。

我常用的几种滤波算法,给你列出来:

算法名称 原理 适用场景 缺点
限幅滤波 当前值与前一次值比较,差值超过阈值则丢弃 突发干扰 无法滤除周期性噪声
中位值滤波 连续采样N次,取中间值 偶然的脉冲干扰 采样次数多,响应慢
算术平均滤波 连续采样N次,取平均值 随机噪声 对突发干扰不敏感
一阶低通滤波 新值 = 旧值 × (1-α) + 采样值 × α 平滑变化的数据 有滞后
滑动平均滤波 维护一个队列,每次取队列平均值 需要实时性且平滑 占用RAM较多

我个人最喜欢的是限幅滤波 + 滑动平均滤波的组合。先限幅,把明显异常的值丢掉,再滑动平均,让数据平滑。

避坑指南:我曾经在电池电量显示上只用算术平均滤波,结果用户一开机,电量从80%跳到75%,然后又跳回79%。用户直接投诉。后来加了限幅滤波,每次变化不超过2%,问题解决。

代码示例,给你一个我常用的滑动平均滤波:

#define FILTER_SIZE 8

static uint16_t filter_buf[FILTER_SIZE];
static uint8_t filter_index = 0;
static uint8_t filter_count = 0;

uint16_t sliding_average_filter(uint16_t new_value)
{
    uint32_t sum = 0;
    uint8_t i;

    filter_buf[filter_index] = new_value;
    filter_index = (filter_index + 1) % FILTER_SIZE;

    if (filter_count < FILTER_SIZE)
        filter_count++;

    for (i = 0; i < filter_count; i++)
        sum += filter_buf[i];

    return (uint16_t)(sum / filter_count);
}

这个代码很简单。维护一个8个元素的环形缓冲区,每次采样新值就放进去,然后取所有有效值的平均。注意,刚开始采样次数不够时,只对已采样的值求平均。

还有一个技巧:采样时机要选对。不要在电机启动瞬间采样,等电机稳定了再采。我一般会在电机启动后延迟50ms再采样,数据稳得很。

4.4 实战经验:一个完整的采样流程

好了,理论讲完了,咱们串起来看看实际怎么做。

假设你用的是STM32,12位ADC,参考电压3.3V。电池电压范围3.0V~4.2V。

  1. 硬件分压:100kΩ + 33kΩ,分压比约0.248。4.2V分压后约1.04V。
  2. RC滤波:1kΩ + 0.1μF,截止频率约1.6kHz。
  3. 软件采样:每100ms采样一次,连续采8次。
  4. 限幅滤波:如果当前值与上次值相差超过20个ADC码(约16mV),丢弃。
  5. 滑动平均:取最近8次有效值的平均。
  6. 换算电压:电压 = (ADC值 × 3.3 / 4095) / 分压比。

这样一套下来,电池电压的波动能控制在±10mV以内。对于电量显示来说,完全够用了。

一个小细节:分压比不要用理论值,要实际测量。因为电阻有误差。我习惯在产线校准时,用万用表测一下实际分压比,写到EEPROM里。这样每台设备都是准的。

嗯,这一章就到这里。ADC采样和滤波,说白了就是“硬件打好底,软件擦好屁股”。两者缺一不可。下一章咱们聊聊电池电量估算,那又是另一门学问了。