ADC/DAC 采集电路设计:精度、噪声与滤波

2026年8月29日 0 By admin

ADC/DAC 采集电路设计:精度、噪声与滤波

做嵌入式这么多年,我见过太多项目在 ADC 采样上栽跟头。传感器信号进来,采出来的数却飘得厉害,一查,往往不是芯片不行,而是电路和软件没设计对。这篇文章不讲厂商手册上那些照抄的参数,而是从工程落地的角度,聊聊 ADC/DAC 采集电路里真正影响精度和噪声的几个关键点,以及对应的滤波手段。

一、先搞清楚:有效位数才是真的位数

很多人一看 STM32 的 12 位 ADC,或者某颗 16 位 Σ-Δ ADC,就默认自己拿到了 12 位、16 位的精度。实际上,标称分辨率(Resolution)和有效位数(ENOB,Effective Number of Bits)是两回事。

在没有噪声的理想情况下,12 位 ADC 的理论信噪比大约是 74 dB。而真实电路里,电源纹波、参考电压温漂、走线串扰、接地不良,都会把这个数字往下拉。最后你用 RMS 抖动算出来的有效位数,可能只有 9 位甚至更少。

所以拿到一块新板子,第一件事不是急着写业务逻辑,而是先测底噪:把 ADC 输入短接到地(或接一个稳定的参考电压),连续采几千个点,看输出的标准差和峰峰值跳动。

// 采集 1000 点,统计噪声(输入短接地)
#define SAMPLE_N 1000
uint16_t buf[SAMPLE_N];
for (int i = 0; i < SAMPLE_N; i++) {
    buf[i] = HAL_ADC_GetValue(&hadc1);  // 或裸机读寄存器
    HAL_Delay(1);
}

uint32_t sum = 0, min = 0xFFFF, max = 0;
for (int i = 0; i < SAMPLE_N; i++) {
    sum += buf[i];
    if (buf[i] < min) min = buf[i];
    if (buf[i] > max) max = buf[i];
}
float mean = (float)sum / SAMPLE_N;

// 计算标准差(RMS 噪声,单位 LSB)
double var = 0;
for (int i = 0; i < SAMPLE_N; i++) {
    double d = buf[i] - mean;
    var += d * d;
}
float rms_lsb = sqrt(var / SAMPLE_N);

printf("mean=%.1f  min=%u  max=%u  rms=%.2f LSB\r\n",
       mean, min, max, rms_lsb);

理想情况下,一颗 12 位 ADC 的 RMS 噪声应该控制在 1 LSB 以内,峰峰值跳动不超过 3~5 LSB。如果测出来 RMS 就有十几 LSB,那说明底噪有问题,再怎么软件滤波也是治标不治本——先把硬件的噪声压下去。

二、参考电压:精度的天花板

ADC 的本质是拿一个已知的参考电压去跟输入电压做比较,所以参考电压的稳定度直接决定了采样精度的上限。单片机内置 ADC 常常默认用 VDD(3.3V)做参考,但板子上的 3.3V 是给数字电路供电的,纹波大、负载变化大,用它当参考,采样结果自然跟着抖。

几个实用的处理办法:

  1. 用独立基准源。像 REF3030、ADR361、LM4040 这类低噪声基准芯片,几块钱能换来稳定的参考电压。把基准芯片的输出接到 MCU 的 VREF+ 引脚,ADC 参考切换到外部基准。
  2. 基准芯片周围要铺好去耦。基准基准芯片的输出脚,紧挨着放一个 0.1µF 和一个 10µF 的电容,抑制高频噪声。
  3. 别让大电流回路穿过基准走线。电源的回流路径如果跟基准信号共用地线,开关噪声会串进来。

还有一点容易被忽略:基准电压有温度系数。一颗 20 ppm/°C 的基准,温度变化 50°C,参考电压会漂移 0.1%,对于 12 位 ADC 来说就是 4 LSB。如果你的产品要在宽温范围工作,要么选低漂移的基准,要么在软件里做温度补偿。

三、信号调理:阻抗匹配与抗混叠

ADC 输入不是理想的。SAR(逐次逼近型)ADC 内部有一个采样保持电容,采样瞬间会向输入端抽取电荷,如果信号源阻抗太高,就会出现采样误差。很多高精度应用里,精度不是丢在 ADC 本身,而是丢在信号源的输出阻抗上。

解决办法是在 ADC 输入前加一级运放做缓冲,或者至少保证信号源阻抗足够低。如果信号源是高阻的(比如某些传感器分压出来的信号),一定要加运放跟随器隔离。

另一个关键点是抗混叠滤波。根据奈奎斯特定理,采样率至少要高于信号最高频率的两倍,超过采样率一半的频率成分会折叠(aliasing)回低频,造成虚假信号。所以在 ADC 前面通常要放一个低通滤波器(RC 无源或运放有源),把高于 fs/2 的频率成分滤掉。

一个最简单的做法:在 ADC 输入脚和地之间加一个 RC 低通。

传感器 → R(1k) → 节点A ── ADC输入
                    │
                   C(100nF)
                    │
                   GND

假设截止频率 fc = 1/(2πRC) ≈ 1.6 kHz,对于几百 Hz 以下的慢变信号(温度、压力、液位)足够用,同时能显著压低高频噪声。

不过 RC 滤波有一个坑:电阻串联在信号通路上,会跟 ADC 内部采样电容形成分压,电阻太大反而引入误差。对于信号源阻抗低、采样率不高的场合,几十欧到 1k 是合理区间;再大就要考虑运放缓冲了。

四、数字滤波:过采样与滑动平均

硬件压不下来的那部分随机噪声,可以用软件手段进一步减小,核心就是过采样

过采样的原理并不复杂:每增加 4 倍采样率,等效分辨率大约能提高 1 位。设想要把 12 位的有效精度提升到 13 位,需要 4 倍过采样;提升到 14 位,需要 16 倍;提升到 16 位,就需要 256 倍。代价是采样率和 CPU 开销。

// 过采样 + 滑动平均:累加 N 次取平均
#define OVERSAMPLE 16   // 16 倍过采样,理论 +2 bit
uint32_t adc_read_oversample(void)
{
    uint32_t sum = 0;
    for (int i = 0; i < OVERSAMPLE; i++) {
        HAL_ADC_Start(&hadc1);
        HAL_ADC_PollForConversion(&hadc1, 10);
        sum += HAL_ADC_GetValue(&hadc1);
    }
    return sum / OVERSAMPLE;   // 返回均值
}

过采样能压白噪声,但对于工频干扰(50Hz/60Hz 及其谐波)这类固定频率的干扰,简单平均效果有限。更有效的做法是把采样周期跟工频周期对齐,做整周期平均,让正负半周的干扰互相抵消。比如工频 50Hz,周期 20ms,你采 20ms 内均匀分布的 N 个点取平均,50Hz 的干扰就被平均掉了大半。

对于慢变信号,滑动平均(Moving Average)和指数滤波(一阶低通)也很常用。滑动平均适合在线实时平滑;指数滤波计算量小、只需存一个状态量:

// 一阶低通(指数滑动平均),alpha 越小平滑越强、响应越慢
float lowpass(float new_val, float *state, float alpha)
{
    *state = alpha * new_val + (1.0f - alpha) * (*state);
    return *state;
}
// alpha 取值范围 0~1,典型 0.05~0.2

注意 alpha 太小会让响应变慢,实时性下降,两者要权衡。对实时性要求高的场景(电机电流环、电源环路),优先硬件滤波和过采样,软件滤波尽量轻。

五、DAC 输出:缓冲、负载与毛刺

DAC 这边关注点略有不同。DAC 把数字量变成模拟量,输出往往是电流型(比如很多音频 DAC)或者低驱动力的电压型,直接去带负载可能电压就塌了,这时候需要后级加运放做电压输出或电流转电压。

工程上 DAC 有几个常见问题:

  1. 输出需要缓冲。如果负载电流达到几 mA 级别,一定要加运放缓冲,否则输出电压会随负载变化,非线性误差明显。
  2. 压摆率与建立时间。DAC 从一个码值跳到另一个码值不是瞬间完成的,有建立时间(Settling Time)。如果软件更新频率过快,输出可能没建立起来就又被改写了,造成波形失真。要根据 DAC 的建立时间合理安排更新间隔。
  3. 毛刺(Glitch)。某些 DAC 在码值切换时,尤其是有很多位同时翻转时(比如从 0111… 跳到 1000…),内部各电流源开关不同步会产生毛刺脉冲。如果需要平滑输出,可以在 DAC 后加一个 RC 低通,把毛刺的高频成分滤掉。

下面是一段用 STM32 的 DAC 配合定时器产生正弦波的简单示例:

// 用 DMA + 定时器驱动 DAC,输出正弦波
#define N 128                     // 一个周期采样点数
uint16_t sine_table[N];
for (int i = 0; i < N; i++) {
    // 12 位 DAC,中点 2048,幅值 2000
    sine_table[i] = (uint16_t)(2048 + 2000 * sinf(2 * PI * i / N));
}

HAL_DAC_Start_DMA(&hdac, DAC_CHANNEL_1,
                  (uint32_t*)sine_table, N,
                  DAC_ALIGN_12B_R);
// 配置定时器触发 DAC 更新,频率决定输出正弦波频率

频率计算公式:输出正弦频率 f = 定时器更新频率 / N。比如定时器 12.8kHz、N=128,输出就是 100Hz。

六、布局走线:别让地线毁了你的精度

最后说硬件设计上最容易被忽视、又最要命的一点——接地和走线。ADC 是模拟电路,对噪声和地弹非常敏感。

几个原则:

  1. 地要分,但不乱分。数字地和模拟地可以在板子上分区,但在电源入口处单点汇接(用 0Ω 电阻或磁珠连接)。不要让数字大电流的回流路径穿过模拟信号区域。
  2. 模拟信号走线远离开关电源、时钟走线。高频数字信号会对平行走线的模拟信号产生串扰,尤其是 ADC 输入这种高阻、低电平的信号线。
  3. 基准电压的走线要短、要包地。基准信号是参考,任何噪声都会直接进到采样结果里,走线越短、远离干扰源越好。
  4. 去耦电容就近放。每颗模拟芯片的电源脚旁边都要有合适的去耦电容,紧贴引脚放置,减小回路电感。

写在最后

ADC/DAC 采集电路设计,说到底是噪声管理阻抗管理。芯片的指标再好,电路和软件没跟上,实际精度也会一塌糊涂。反过来,理解了底噪、参考、阻抗匹配和滤波这几个点,哪怕用一颗普通的 12 位 ADC,也能采出稳定可靠的数据。

我的习惯是:新板先测底噪,确认硬件底子没问题了再用软件滤波锦上添花。顺序不能反——先硬件后软件,底噪压不下去的时候,别指望靠算法硬救。

如果你在调试 ADC 时遇到过什么奇葩的漂移问题,欢迎在评论区聊聊,一起排查。