# 引言 在嵌入式测量领域,ADC 分辨率直接决定数据精度。Arduino Uno 等板载 ATmega328P 的 ADC 为 10 位,对于许多传感器(如称重、温度、压力)而言,10 位分辨率(约 5V/1024 ≈ 4.9mV)往往不够。过采样技术通过多次采样并求平均,利用噪声的随机性来“扩展”有效位数,理论上每过采样 4 倍可增加 1 位分辨率。但要真正达到 16 位,必须解决噪声校准问题,否则结果会被系统噪声和量化误差淹没。 # 过采样原理 过采样基于奈奎斯特定理的扩展:若信号带宽有限,且噪声近似白噪声(均匀分布),则对同一信号采样 N 次并求和,量化噪声的功率会按 N 倍降低。具体地,若原始 ADC 位数为 b,过采样率为 OSR,则有效位数 ENOB = b + 0.5 * log2(OSR)。要达到 16 位,需要 OSR = 4^(16-10) = 4^6 = 4096 倍过采样。但实际中,噪声并非理想白噪声,且存在直流偏置、参考电压波动等,因此需要校准。 # 噪声校准技巧 ## 1. 白噪声注入与抖动 若输入信号本身噪声很小,量化误差会呈周期性,导致过采样失效。此时需人为注入抖动(dithering)。方法:在 ADC 输入引脚串联一个小电阻(如 1kΩ),并在该点并联一个电容(如 0.1μF)到地,利用热噪声或外部噪声源产生随机扰动。更可控的方法是使用伪随机序列通过 PWM 输出叠加到输入信号,但需注意幅度应小于 1 LSB(10 位时约 4.9mV),否则会引入额外误差。 ## 2. 多次采样去偏 系统噪声可能带有直流偏置,例如参考电压纹波或运放失调。校准方法:先对地采样 N 次得到偏置值 offset,再对信号采样 N 次得到 raw,最后真实值 = raw - offset。注意 offset 需在每次校准周期重新测量,因为偏置会随温度漂移。 ## 3. 参考电压校准 Arduino 默认参考电压为 5V(或 3.3V),但实际值可能偏差 1%~2%。使用内部 1.1V 参考(INTERNAL)可提高稳定性,但需确保信号幅度在范围内。校准方法:用高精度万用表测量实际 Vref,然后软件中修正比例因子。例如,若实测 Vref = 5.02V,则实际电压 = ADC值 * (5.02/1023.0)。 ## 4. 软件滤波优化 过采样求平均时,应剔除异常值(如毛刺)。可使用中值滤波或限幅滤波。但注意,剔除异常值会减少有效样本数,需适当增加 OSR。推荐先做中值滤波(窗口 3~5),再做平均。 # 配置步骤 1. **硬件准备**:Arduino Uno,信号源,精密电阻(1kΩ),电容(0.1μF),面包板。 2. **电路连接**:信号经电阻接入 A0 引脚,电容并联到地,形成低通滤波并引入热噪声。 3. **软件配置**:设置 ADC 时钟分频为 16(默认),使能内部参考(可选)。 4. **编写过采样函数**:定义 OSR = 4096,循环采样,累加,右移 6 位(因为 4096 = 2^12,增加 6 位)。 5. **校准偏置**:先采样 64 次接地值,求平均作为 offset。 6. **校准参考**:用已知电压(如 1.0V)测量,计算实际 Vref。 7. **测试与验证**:输入稳定电压,观察输出波动,调整 OSR 或滤波窗口。 # 完整代码示例 ```c // Arduino 16-bit ADC via oversampling with noise calibration // 适用于 ATmega328P (Uno/Nano) const int adcPin = A0; const int OSR = 4096; // 2^12 oversampling const int CAL_SAMPLES = 64; float vref_cal = 5.0; // 默认,后续校准 void setup() { Serial.begin(115200); analogReference(DEFAULT); // 使用 AVCC (5V) // 可选:analogReference(INTERNAL); // 1.1V 内部参考 // 校准参考电压:用已知电压(如 1.0V)测量 float known_voltage = 1.0; // 输入到 A0 的已知电压 int raw_known = readOversampled(adcPin, OSR); float measured = raw_known * (5.0 / 1023.0); // 假设默认 5V vref_cal = known_voltage * 1023.0 / raw_known; Serial.print("Calibrated Vref: "); Serial.println(vref_cal, 4); } void loop() { // 1. 测量偏置(对地采样) float offset = 0; for (int i = 0; i < CAL_SAMPLES; i++) { offset += readOversampled(adcPin, OSR); // 注意:这里应接 GND,但为演示用同一引脚 } offset /= CAL_SAMPLES; // 2. 测量信号(实际连接信号源) int raw = readOversampled(adcPin, OSR); // 3. 去偏置 float corrected = raw - offset; // 4. 转换为电压(使用校准后的 Vref) float voltage = corrected * (vref_cal / 1023.0); // 5. 输出 16 位数字值(0-65535) unsigned int value16 = (unsigned int)(corrected * 64); // 因为 OSR=4096,右移 6 位 Serial.print("16-bit value: "); Serial.println(value16); Serial.print("Voltage: "); Serial.print(voltage, 4); Serial.println(" V"); delay(500); } // 过采样读取函数:采样 OSR 次,累加后右移 6 位 unsigned int readOversampled(int pin, int osr) { unsigned long sum = 0; for (int i = 0; i < osr; i++) { sum += analogRead(pin); } return (unsigned int)(sum >> 6); // 右移 6 位得到 16 位结果 } ``` # 注意事项 - **采样时间**:过采样 4096 次,每次 analogRead 约 100μs,总耗时约 0.4 秒,不适合快速变化信号。若需实时性,可降低 OSR 或使用 DMA。 - **噪声源**:若环境噪声不足,过采样效果有限。可尝试在 AREF 引脚加电容滤波,或使用外部基准芯片(如 ADR421)。 - **偏置校准**:代码中 offset 测量使用了同一引脚,实际应切换至 GND 引脚,否则会引入信号分量。建议使用多路复用器或手动切换。 - **数据溢出**:累加 sum 为 unsigned long(32 位),4096*1023 ≈ 4.19e6,未溢出,但若 OSR 更大需注意。 - **参考电压**:使用内部 1.1V 参考时,分辨率更高(约 1.1mV/LSB),但信号幅度受限。 - **代码优化**:analogRead 函数有额外开销,可改用直接寄存器操作(ADMUX、ADCSRA)以提升速度。 # 总结 通过过采样与噪声校准,Arduino 的 ADC 可有效提升至 16 位分辨率,但必须结合硬件噪声注入、偏置去除和参考电压校准。本文提供的技巧和代码可直接应用于高精度测量项目。记住,过采样不是万能的,它依赖于噪声的随机性,因此实际有效位数可能低于理论值。建议使用高精度电压源验证,并根据结果调整 OSR 和滤波策略。