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%精度是底线,别用5%的。电池电压可能到4.2V,MCU的ADC只能接受0~3.3V,必须分压。
- 加一个RC低通滤波器。电阻1kΩ,电容0.1μF,截止频率大约1.6kHz。能滤掉大部分高频噪声。
- 采样引脚加一个TVS管。防静电,防浪涌。我曾经因为省了这个管子,烧了三个ADC通道。
- 走线要短,远离干扰源。别跟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。
- 硬件分压:100kΩ + 33kΩ,分压比约0.248。4.2V分压后约1.04V。
- RC滤波:1kΩ + 0.1μF,截止频率约1.6kHz。
- 软件采样:每100ms采样一次,连续采8次。
- 限幅滤波:如果当前值与上次值相差超过20个ADC码(约16mV),丢弃。
- 滑动平均:取最近8次有效值的平均。
- 换算电压:电压 = (ADC值 × 3.3 / 4095) / 分压比。
这样一套下来,电池电压的波动能控制在±10mV以内。对于电量显示来说,完全够用了。
一个小细节:分压比不要用理论值,要实际测量。因为电阻有误差。我习惯在产线校准时,用万用表测一下实际分压比,写到EEPROM里。这样每台设备都是准的。
嗯,这一章就到这里。ADC采样和滤波,说白了就是“硬件打好底,软件擦好屁股”。两者缺一不可。下一章咱们聊聊电池电量估算,那又是另一门学问了。