4、ADC采样与滤波:模数转换原理、Marlin中的ADC驱动、过采样技术、滑动平均滤波算法
聊到温度控制,第一步就是要把物理世界的温度信号,变成单片机认识的数字信号。这个环节,就是ADC(模数转换器)的活儿。说白了,ADC就是给单片机装上一双“电子眼睛”,让它能看到电压的变化。
4.1 模数转换原理:从模拟到数字的桥梁
ADC的核心任务很简单:测量一个模拟电压,然后输出一个对应的数字值。Marlin常用的AVR单片机(比如Mega2560)内部集成了10位ADC。10位是什么意思?就是它能输出0到1023之间的整数,一共1024个等级。
举个例子,如果参考电压是5V,那么ADC读到的数字值N和实际电压V的关系就是:
V = N × (5V / 1024)
读数为512时,电压就是2.5V。这个分辨率大约是4.88mV。嗯,对于热敏电阻测温来说,这个精度其实够用了。但为什么实际打印时温度还是会跳来跳去?问题出在噪声上。
关键点:ADC的精度取决于参考电压的稳定性和噪声水平。我见过不少机器,就是因为电源纹波太大,导致温度读数像心电图一样上下乱窜。
4.2 Marlin中的ADC驱动:底层是怎么跑的
Marlin的ADC驱动藏在 HAL 层里。不同平台(AVR、ARM、ESP32)的ADC硬件差异很大,但Marlin通过硬件抽象层把它们统一了。
以AVR为例,ADC的初始化代码大概长这样:
void setup_adc() {
ADMUX = (1 << REFS0); // 选择AVCC作为参考电压
ADCSRA = (1 << ADEN) // 使能ADC
| (1 << ADPS2) // 预分频128
| (1 << ADPS1)
| (1 << ADPS0);
}
这里有个细节:预分频128。为什么选这么大?因为ADC需要一个稳定的时钟源,太快了反而不准。AVR的ADC时钟推荐在50kHz到200kHz之间,16MHz主频除以128,得到125kHz,刚好落在最佳区间。
启动一次转换的代码更简单:
uint16_t read_adc(uint8_t channel) {
ADMUX = (ADMUX & 0xF0) | (channel & 0x0F);
ADCSRA |= (1 << ADSC); // 开始转换
while (ADCSRA & (1 << ADSC)); // 等待完成
return ADC;
}
你可能会问:为什么不用中断?我个人习惯在温度采样这种周期性任务里用轮询,因为中断太多反而容易打架。Marlin的PID循环是固定频率触发的,在循环里同步读ADC,时序更可控。
4.3 过采样技术:用时间换精度
10位ADC不够用怎么办?换硬件?别急,有个技巧叫“过采样”。
原理其实很简单:如果你对同一个信号采样4次,把结果加起来,就相当于多了2位精度。为什么?因为噪声是随机的,多次采样取平均,能有效抑制噪声,等效于提高了分辨率。
公式是这样的:
要增加n位分辨率,需要采样 4^n 次
比如从10位提升到12位,需要采样16次。Marlin里确实用了这个技巧,但用得比较克制。我记得在某个版本的Marlin中,热床的ADC采样就做了4次过采样,等效到了12位。
我的经验:过采样不是万能的。如果信号本身噪声太小,过采样的效果会打折扣。你想想看,如果每次读到的值都一样,采再多也没用。所以有些主板会故意在ADC引脚上加一点噪声,反而有助于过采样。
4.4 滑动平均滤波算法:让温度曲线平滑下来
好了,ADC读回来了,但数值还在跳。这时候就需要滤波了。Marlin用的是滑动平均滤波,也叫移动平均滤波。
它的思路是维护一个固定长度的缓冲区,每次新数据进来,就计算所有数据的平均值。比如缓冲区长度是8:
// 伪代码示意
#define FILTER_SIZE 8
uint16_t buffer[FILTER_SIZE];
uint8_t index = 0;
uint16_t filter(uint16_t new_value) {
buffer[index] = new_value;
index = (index + 1) % FILTER_SIZE;
uint32_t sum = 0;
for (uint8_t i = 0; i < FILTER_SIZE; i++) {
sum += buffer[i];
}
return sum / FILTER_SIZE;
}
这个算法好处很明显:实现简单,计算量小,对周期性噪声抑制效果好。但缺点也有——它会有延迟。缓冲区越长,延迟越大。
| 缓冲区长度 | 平滑效果 | 延迟(采样周期) | 适用场景 |
|---|---|---|---|
| 4 | 一般 | 2个周期 | 快速响应的喷嘴 |
| 8 | 较好 | 4个周期 | 热床 |
| 16 | 很好 | 8个周期 | 环境温度监测 |
注意:我曾经遇到过一个问题:缓冲区长度设得太长,导致PID控制器看到的温度变化总是慢半拍。结果就是温度过冲严重,打印件表面都起泡了。后来我把缓冲区从16改到8,问题就解决了。滤波不是越平滑越好,得看实际需求。
Marlin里还有一个优化:它没有用除法,而是用移位操作。因为缓冲区长度通常是2的幂次,比如8、16、32。这样平均值计算就可以用右移代替除法,快很多。
// Marlin实际代码风格
return sum >> 3; // 除以8
这种细节,就是嵌入式工程师该抠的地方。你想想看,PID循环每200ms跑一次,每次都要算滤波,如果这里用除法,累积下来也是不小的开销。
知识体系总览
下面这张图,把ADC采样到滤波的完整链路串起来了:
从热敏电阻的模拟电压,到ADC转换出数字值,再到过采样提升分辨率,最后用滑动平均滤掉噪声——每一步都有它的道理。Marlin把这套流程跑得很成熟,但理解背后的原理,才能在你自己的项目里灵活调整。
嗯,ADC这部分就聊到这儿。记住一句话:采样是基础,滤波是手段,稳定才是目的。