引言

在嵌入式系统设计中,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 / (2BW)。若信号带宽为 100Hz,要获得 16 位,需要 f_s = 2BW*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() 简单,但效率低。为提升采样率,直接操作寄存器。

// 配置 ADC 为自由运行模式,预分频 16(采样率约 62.5kHz)
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; // 读取 10 位结果
  }
  return sum;
}

结果处理

将累加和右移 (16-10) = 6 位,得到 16 位有效值。但需注意:若 OSR 不是 4 的幂,需按公式计算移位量。

uint16_t result = (uint16_t)(sum >> 6); // 假设 OSR=4096

抖动注入

使用定时器 PWM 生成约 5mV 噪声:

// 设置 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 之间。

完整示例代码

// 过采样实现 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 和抖动策略,避免盲目追求位数。