# 引言 在嵌入式系统设计中,ADC 分辨率直接决定测量精度。Arduino Uno(基于 ATmega328P)内置 10 位 ADC,默认分辨率约 4.9mV(5V 参考)。对于需要更高精度的应用(如传感器信号调理),硬件升级到 16 位 ADC 是常见方案,但成本与复杂度增加。过采样技术提供了一种软件替代方案:通过多次采样并求平均,利用噪声将量化误差随机化,从而提升有效分辨率。然而,该技术并非万能,其有效性受严格边界条件约束。本文将深入探讨这些边界,并给出可落地的实现。 # 过采样与抖动原理 ## 量化误差与噪声 理想 ADC 的量化误差为 ±0.5 LSB。当输入信号为直流或缓慢变化时,多次采样得到相同值,平均后误差不变。过采样提升分辨率的关键在于:在采样值中加入足够幅度的噪声(抖动),使量化器输出在多个码值间随机切换。此时,平均多个采样值可消除部分量化误差,等效于更高分辨率。 ## 数学基础 若噪声幅度至少为 1 LSB(峰峰值),且呈均匀分布,则每增加 4 倍过采样率(OSR),有效分辨率增加 1 位。公式: - 有效位数 = 原始位数 + 0.5 * log2(OSR) 例如,10 位 ADC 要获得 16 位,需要 OSR = 4^(16-10) = 4^6 = 4096 次采样。但注意,这仅在理想条件下成立。 ## 抖动来源 - 外部噪声:热噪声、电源纹波、电磁干扰。 - 内部噪声:ADC 自身热噪声、参考电压波动。 - 人为注入:通过 PWM 或 DAC 叠加已知幅度噪声。 Arduino 的 ADC 内部噪声通常不足 1 LSB,因此必须人为注入抖动,否则过采样无效。 # 边界条件分析 ## 1. 噪声幅度必须足够 抖动幅度必须至少覆盖 1 LSB(5V/1024 ≈ 4.88mV)。若噪声过小,量化器输出不会变化,平均无效。若噪声过大(如 >4 LSB),则引入额外误差,需权衡。 ## 2. 采样速率与信号带宽 过采样要求采样率远高于信号带宽。奈奎斯特定理:采样率至少为信号最高频率的 2 倍。过采样率 OSR = f_s / (2*BW)。若信号带宽为 100Hz,要获得 16 位,需要 f_s = 2*BW*4096 = 819.2kHz,远超 Arduino 默认 ADC 采样率(约 10kHz)。因此,仅适用于极低频信号(如温度、压力)。 ## 3. ADC 硬件限制 ATmega328P 的 ADC 转换时间约 104µs(默认 16MHz 时钟,预分频 128)。即使使用自由运行模式,最大采样率约 9.6kHz。要实现 4096 次过采样,单次测量需 0.43 秒,实时性差。此外,ADC 的输入阻抗和采样电容可能限制高频噪声注入。 ## 4. 参考电压稳定性 过采样依赖噪声随机性,但参考电压的漂移会引入系统性误差,无法通过平均消除。需使用低噪声参考(如外部基准)。 ## 5. 内存与计算开销 4096 次采样累加需 32 位变量,Arduino 的 8 位 MCU 处理较慢,但可接受。若需更高 OSR,内存和 CPU 成为瓶颈。 # 实现步骤 ## 硬件准备 - Arduino Uno 或 Nano - 信号源:低频正弦波或直流电压(如电位器分压) - 抖动注入:使用 PWM 引脚产生约 5mV 峰峰值噪声(通过 RC 滤波) ## 配置 ADC 使用 Arduino 的 `analogRead()` 简单,但效率低。为提升采样率,直接操作寄存器。 ```c // 配置 ADC 为自由运行模式,预分频 16(采样率约 62.5kHz) void setupADC() { ADMUX = (1 << REFS0); // AVcc 参考,通道 0 ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADPS2); // 使能,启动,自动触发,预分频 16 ADCSRB = 0; // 自由运行模式 } ``` ## 过采样函数 ```c uint32_t oversampleRead(uint8_t channel, uint16_t osr) { // 设置通道 ADMUX = (ADMUX & 0xF0) | (channel & 0x0F); // 丢弃前几次采样(稳定) for (int i = 0; i < 4; i++) { while (!(ADCSRA & (1 << ADIF))); ADCSRA |= (1 << ADIF); } uint32_t sum = 0; for (uint16_t i = 0; i < osr; i++) { while (!(ADCSRA & (1 << ADIF))); ADCSRA |= (1 << ADIF); sum += ADC; // 读取 10 位结果 } return sum; } ``` ## 结果处理 将累加和右移 (16-10) = 6 位,得到 16 位有效值。但需注意:若 OSR 不是 4 的幂,需按公式计算移位量。 ```c uint16_t result = (uint16_t)(sum >> 6); // 假设 OSR=4096 ``` ## 抖动注入 使用定时器 PWM 生成约 5mV 噪声: ```c // 设置 8 位 PWM,频率约 1kHz,占空比 50%,经 RC 滤波后叠加到信号 void setupDither() { pinMode(9, OUTPUT); // Timer1 PWM TCCR1B = (1 << WGM12) | (1 << CS11); // CTC 模式,预分频 8 OCR1A = 1000; // 约 1kHz TCCR1A = (1 << COM1A1); // 非反相 PWM } ``` 实际中,需用示波器调整 PWM 幅度,确保噪声峰峰值在 1-4 LSB 之间。 # 完整示例代码 ```c // 过采样实现 16 位有效分辨率(Arduino Uno) #define OSR 4096 void setup() { Serial.begin(115200); setupADC(); setupDither(); } void loop() { uint32_t sum = oversampleRead(0, OSR); uint16_t result = (uint16_t)(sum >> 6); // 16 位结果 float voltage = result * (5.0 / 65535.0); // 转换为电压 Serial.println(voltage, 4); delay(100); } void setupADC() { ADMUX = (1 << REFS0); // AVcc 参考,通道 0 ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) | (1 << ADPS2); // 自由运行,预分频 16 ADCSRB = 0; } uint32_t oversampleRead(uint8_t channel, uint16_t osr) { ADMUX = (ADMUX & 0xF0) | (channel & 0x0F); // 稳定时间 for (int i = 0; i < 4; i++) { while (!(ADCSRA & (1 << ADIF))); ADCSRA |= (1 << ADIF); } uint32_t sum = 0; for (uint16_t i = 0; i < osr; i++) { while (!(ADCSRA & (1 << ADIF))); ADCSRA |= (1 << ADIF); sum += ADC; } return sum; } void setupDither() { pinMode(9, OUTPUT); TCCR1B = (1 << WGM12) | (1 << CS11); OCR1A = 1000; TCCR1A = (1 << COM1A1); } ``` # 注意事项 - **噪声验证**:务必用示波器检查 ADC 输入引脚上的噪声幅度,不足时需增加外部抖动电路。 - **信号带宽**:过采样仅适用于低频信号,若信号变化快,会引入混叠误差。 - **参考电压**:使用外部精密基准(如 ADR421)可减少系统性误差。 - **功耗与时间**:4096 次采样约需 65ms(预分频 16),若需实时性,可降低 OSR 或提高时钟。 - **有效位数验证**:通过输入已知直流电压,观察结果的标准差,计算有效位数(ENOB)。 # 结论 过采样技术能在 Arduino 上实现 16 位有效分辨率,但必须满足噪声、带宽和采样率等边界条件。对于低频、高精度测量,该方法是低成本替代方案;对于高频信号,硬件 ADC 仍是必需。开发者应理解原理,根据实际需求调整 OSR 和抖动策略,避免盲目追求位数。