基于 Arduino 的 AVR 单片机低功耗模式 ADC 采样噪声抑制实战技巧
在物联网、可穿戴设备等电池供电的嵌入式系统中,低功耗是核心需求,而 ADC 采样又是感知物理世界的必要环节。然而,当 AVR 单片机进入睡眠模式(如 SLEEP_MODE_ADC)后,ADC 采样结果往往出现明显抖动或偏移,这源于电源噪声、参考电压波动以及数字电路耦合。本文以 Arduino Uno(ATmega328P)为例,分享一套经过验证的噪声抑制方案。
一、噪声来源分析
低功耗模式下 ADC 噪声主要来自三方面:
- 电源纹波:睡眠时稳压器或电池电压波动,尤其当系统其他外设(如无线模块)间歇性工作时,纹波可达数十毫伏。
- 参考电压不稳定:内部 1.1V 参考源(bandgap)在低功耗下驱动能力下降,受温度影响明显。
- 数字信号耦合:I/O 引脚翻转、SPI 通信等数字活动通过衬底或电源线耦合到模拟输入端。
二、硬件设计要点
- 电源去耦:在 AVCC 引脚与 GND 之间放置 10µF 钽电容 + 0.1µF 陶瓷电容,且尽量靠近引脚。
- 模拟地分离:将模拟地(AGND)与数字地(GND)单点连接,避免数字电流流过模拟地平面。
- 输入滤波:在 ADC 输入引脚前串联 1kΩ 电阻并并联 0.1µF 电容,构成低通滤波器,截止频率约 1.6kHz,可有效抑制高频噪声。
三、关键寄存器配置
AVR 的 ADCSRA 和 ADCSRB 寄存器控制 ADC 模式。低功耗采样时,我们采用“自动触发”模式,让 ADC 在睡眠中自动完成转换,然后唤醒 CPU。
// 设置 ADC 时钟分频为 128(16MHz/128=125kHz,符合 ADC 要求)
ADCSRA = (1<<ADEN) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
// 使能自动触发,选择定时器0 比较匹配作为触发源
ADCSRB = (1<<ADTS2) | (1<<ADTS1); // 触发源 = Timer0 Compare Match A
// 使能 ADC 中断
ADCSRA |= (1<<ADIE);
注意:触发源选择需根据实际需求,常用的是 Timer0 或外部中断。使用定时器触发可精确控制采样间隔,避免 CPU 空转。
四、采样策略:多次采样取平均
单次采样在低功耗下噪声明显,建议采用“连续采样 N 次,取平均值”的策略。N 通常取 8 或 16,既能平滑噪声,又不会过多消耗功耗。
#define ADC_SAMPLES 8
uint16_t read_adc_average(uint8_t channel) {
uint16_t sum = 0;
for (uint8_t i = 0; i < ADC_SAMPLES; i++) {
// 选择通道,并启动转换
ADMUX = (ADMUX & 0xF0) | (channel & 0x0F);
ADCSRA |= (1<<ADSC); // 启动转换
while (ADCSRA & (1<<ADSC)); // 等待完成
sum += ADC;
}
return sum / ADC_SAMPLES;
}
五、完整示例:睡眠采样 + 唤醒
以下代码演示了如何让 AVR 进入 ADC 睡眠模式,由定时器触发采样,完成后自动唤醒并处理数据。
#include <avr/sleep.h>
#include <avr/interrupt.h>
volatile uint16_t adc_result;
void setup() {
Serial.begin(9600);
// 配置 ADC
ADMUX = (1<<REFS0); // 使用 AVCC 为参考,通道0
ADCSRA = (1<<ADEN) | (1<<ADIE) | (1<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
ADCSRB = (1<<ADTS2) | (1<<ADTS1); // Timer0 比较匹配触发
// 配置 Timer0 用于触发(例如每 1ms)
TCCR0A = (1<<WGM01); // CTC 模式
OCR0A = 124; // 16MHz/64/125 = 2000Hz
TIMSK0 = (1<<OCIE0A); // 使能比较匹配中断
}
ISR(TIMER0_COMPA_vect) {
// 定时器中断,用于唤醒(但实际由 ADC 完成唤醒)
}
ISR(ADC_vect) {
adc_result = ADC; // 读取结果
// 可以在此处设置标志,主循环处理
}
void loop() {
// 进入 ADC 睡眠模式
set_sleep_mode(SLEEP_MODE_ADC);
sleep_enable();
sei();
sleep_cpu();
sleep_disable();
// 唤醒后处理数据
Serial.println(adc_result);
delay(100); // 简单延时,避免刷屏
}
代码说明:
- 系统进入 SLEEP_MODE_ADC 后,CPU 停止,但 ADC 时钟仍运行。
- Timer0 比较匹配事件触发 ADC 转换,转换完成后 ADC 中断唤醒 CPU。
- 注意:在 sleep_cpu() 前必须确保全局中断开启,否则无法唤醒。
六、注意事项
- 参考电压选择:若对精度要求高,建议使用外部参考(如 2.5V 精密基准),内部参考在低功耗下噪声较大。
- 采样保持时间:在低功耗下,ADC 采样电容充电可能不足,可通过延长采样时间(设置 ADCSRA 的 ADSC 位)或增加前置滤波电容来改善。
- 避免数字引脚翻转:在采样期间,尽量保持数字 I/O 不变,尤其是靠近模拟输入的引脚。
- 功耗权衡:多次采样取平均会增加唤醒时间,从而增加功耗。建议根据实际噪声水平调整采样次数,例如噪声小时用 4 次,噪声大时用 16 次。
- 使用 Arduino 的 analogRead() 的局限:Arduino 库函数 analogRead() 会阻塞等待转换完成,不适合低功耗场景。务必使用寄存器操作。
七、总结
通过合理配置 ADC 自动触发模式、采用多次采样平均策略,并结合硬件滤波,可以在 AVR 低功耗模式下获得稳定的 ADC 采样结果。本文提供的代码可直接用于 Arduino IDE,但建议开发者深入理解寄存器含义,以便移植到其他 AVR 型号。嵌入式开发中,功耗与性能的平衡需要精细调优,希望这些技巧能帮助你在实际项目中少走弯路。