1. 精度不是ADC一个人的事:先理清误差账本
做 ADC 高精度采样这件事,我踩过的坑比写过的代码多。很多新手拿到 N32 系列 MCU 的第一反应是:手册写着 12 位 ADC,那我的采样精度就是 1/4096,约 0.8mV 每 LSB(按 3.3V 参考算)。实际一上电,读数在最后三四位上蹿下跳,算下来有效位可能只有 9 位出头。这不是芯片不行,而是ADC 高精度采样从来不是单一外设的事,它是一整条链路的事——从基准源、电源纹波、前端阻抗、PCB 回流,一直到采样时序、校准和数字滤波,任何一环掉链子,前面所有努力都白费。
这篇内容适合三类人看:一是刚上手 N32 系列、想搞清楚 ADC 到底怎么配的嵌入式新手;二是产品里要做电流、电压、温度、传感器采样,被跳动和偏移折磨过的工程师;三是想把 12 位 ADC 榨到 11 位以上有效精度、又不愿意加外置 ADC 芯片的 cost-down 场景。我会把硬件优化和软件配置两条线拆开讲透,公式、参数、代码、实测对比都给到,能直接抄的部分我会写清楚抄哪里、改哪里。
1.1 从"12 位"到"有效 9 位"中间发生了什么
先把误差账本摊开。一颗 12 位 SAR ADC 标称的分辨率是 1 LSB = VREF/4096,但"分辨率"和"精度"是两个概念。分辨率是尺子上的刻度有多细,精度是你量出来的数离真值有多近。刻度再细,尺子本身弯了、热胀冷缩了、拿尺子的手抖了,读数照样不准。
实际链路里主要有五类误差在吃你的精度:
- 量化误差:理论上是 ±0.5 LSB,这是不可消除的,只能靠过采样和平均去压。
- 失调误差(Offset):整条曲线平移,表现为零点不为零。这个可以靠校准消掉。
- 增益误差(Gain):斜率不对,满量程附近偏差最大。校准也能修,但前提是基准准。
- INL/DNL(积分/微分非线性):SAR 内部电容阵列失配导致,芯片出厂就定了,只能靠选型。
- 噪声:电源纹波、参考噪声、数字串扰、热噪声,这是现场最容易失控的一项。
前四项里,失调和增益靠校准能压到 1~2 LSB 以内;INL/DNL 是芯片体质,N32 这类 MCU 的 12 位 ADC 典型 INL 在 ±2 LSB 量级;真正让读数"跳"的是噪声。你把噪声压下去,有效位数(ENOB)自然就上来了。我做过一组粗略统计:同样的硬件,只是把采样时间从 1.5 个周期改到 41.5 个周期,再把软件平均加上,跳动峰峰值从 30 多个码降到 4 个码以内——什么都没换,只是把时序配对。
提示:判断"跳"是噪声还是别的,有个土办法:输入端直接短接到一个稳定的中点电压(比如用两个同型号电阻分压),连续采 1000 个点看峰峰值。如果这时候还在跳,那问题在芯片内部或电源参考;如果短接很稳、接上传感器就跳,问题在外部前端。
1.2 N32 片上 ADC 的关键参数与手册里容易看漏的地方
N32 系列的 ADC 是逐次逼近型(SAR),12 位分辨率,多通道,支持规则组和注入组,支持外部定时器触发和 DMA。看手册的时候,大部分人只关注"12 位""多少通道""多快",但真正决定精度的是下面这几个参数,我建议你翻手册时重点标注:
| 参数 | 典型量级 | 为什么重要 |
|---|---|---|
| 采样保持电容 C_sample | 4~8 pF | 决定外部源阻抗能允许多大、采样时间要配多长 |
| 采样开关导通电阻 R_switch | 几十到几百欧 | 与 C_sample 一起构成 RC 充电时间常数 |
| 参考电压范围 | 2.4V ~ VDDA | 参考越低,单 LSB 越小,但噪声裕量也越小 |
| 内部参考温漂 | 几十 ppm/℃ 量级 | 做温度补偿时必须考虑,或者干脆用外部基准 |
| 总转换时间 | 采样周期 + 12.5 个 ADC 时钟 | 决定最大采样率,也决定你能不能"多采几次平均" |
| 注入组与规则组切换 | 需要额外稳定时间 | 多通道轮询时串扰的主要来源 |
这里有个非常容易被忽略的点:ADC 时钟(ADCCLK)不等于系统时钟。N32 的 ADC 时钟来自总线时钟分频,分频系数配得太高,采样和转换都变慢;配得太低,超过 ADC 的最大工作频率,转换结果直接失真。我见过有人为了追求采样率把分频配到极限,结果读数整体偏移、线性度崩掉,查了两天才发现是超频。手册里 ADC 最大时钟频率那个数字,一定要按最坏情况(高温、低压)留 20% 余量。
另一个看漏的点是VDDA 和 VREF+ 的关系。很多封装里 VREF+ 和 VDDA 是分开的引脚,也有直接内部短接的。如果你的板子把 VREF+ 直接接到 VDDA,那电源上每一个 mV 的纹波都会 1:1 出现在采样结果里。3.3V 电源上 10mV 的开关纹波,折算到 12 位就是 12 个码的跳动——这已经吃掉 3~4 位有效精度了。
1.3 精度目标的量化拆解:先算误差预算再动手
动手改硬件之前,先做一张误差预算表。假设目标:12 位、3.3V 参考、要求总误差不超过 ±3 LSB(约 ±2.4mV),采样信号是 0~3V 的传感器输出,源阻抗 10kΩ。
| 误差项 | 估算值 | 处理手段 |
|---|---|---|
| 量化噪声 | ±0.5 LSB | 过采样 + 平均 |
| 失调 + 增益 | ±2 LSB(未校准) | 上电校准,降到 ±0.5 LSB |
| 参考与电源噪声 | ±5 LSB(VDDA 直连) | 独立基准 + LC 滤波,降到 ±1 LSB |
| 前端 RC 未充满 | ±8 LSB(采样时间不足) | 加长采样时间,降到 ±0.5 LSB |
| 通道串扰 | ±4 LSB | 加长采样 + 通道间插入空闲 |
| 数字串扰 | ±2 LSB | 布局隔离 + 采样时静默 |
算下来未处理时总误差可能到 ±15 LSB,也就是有效 8~9 位。逐项压下去之后能到 ±3 LSB 以内,有效位 11 位左右。这张表的价值在于:它告诉你钱该花在哪。如果你的主要误差来自电源,那再怎么优化软件滤波都是徒劳;如果主要来自采样时间不足,那换基准芯片也白搭。我一般的顺序是:先保时序(免费),再保布局(免费但费时间),最后才动基准和运放(花钱)。
2. 硬件层:把参考、电源和前端网络先做对
硬件是地基。软件能救回来一部分,但救不回全部——尤其是参考噪声和前端充电不足这两项,属于"物理上信息已经丢了",后面再怎么滤波都是猜。这一章讲三件事:参考和电源怎么处理、前端 RC 怎么和采样时间匹配、PCB 怎么布线。
2.1 参考电压与 VDDA 的处理方案
先说结论:想要 11 位以上有效精度,别用 VDDA 当参考。这不是危言耸听。VDDA 上挂着 MCU 本身的数字电流、GPIO 翻转电流、外设开关噪声,纹波轻松到几十 mV。你可以用示波器加个弹簧地针在 VDDA 引脚上量一下,AC 耦合、20MHz 带宽,看到的波形通常会让人沉默。
三个档次的方案,按预算选:
- 零成本方案:VREF+ 接 VDDA,但做好去耦。VDDA 引脚就近放 100nF + 10μF,中间串一颗磁珠(600Ω@100MHz)做隔离,磁珠前后各放 10μF。这套下来纹波能压到 5mV 以内,够 10 位左右的应用。
- 低成本方案:用一颗低压差线性稳压器单独给 VDDA 供电,输入从主 5V 或 3.3V 取,输出 3.3V 只供模拟部分。LDO 的 PSRR 在低频段通常有 60dB 以上,能再压一个数量级。成本增加几毛钱。
- 正经方案:外置基准源,比如 2.5V 或 3.0V 的串联基准,初始精度 0.1%、温漂 20ppm/℃ 以内,配 1μF~10μF 输出电容。这时候采样结果直接对应基准电压,跟系统电源基本脱钩。注意基准的输出电容有稳定性要求,别随便加大容量,看手册的 ESR 允许范围。
这里有个实操细节:基准电压越低,单 LSB 越小,但噪声的相对影响越大。比如同样 1mV 的噪声,在 3.3V 参考下是 1.24 个码,在 2.5V 参考下是 1.64 个码。所以不要盲目追求低基准,除非你的信号本身幅度就小、需要更高的分辨率利用率。我的经验是:信号满量程能覆盖到参考的 70%~90% 最划算,既用足了分辨率,又留了过冲余量。
还有一个隐藏坑:上电顺序。如果基准芯片比 MCU 晚上电,MCU 会在基准还没稳定的情况下完成第一次校准,校准系数就错了。解决办法是在初始化里加延时,或者读基准的 PGOOD 引脚(如果有),或者干脆每次采样前重新校准一次——后者费时间但最稳。
2.2 输入前端 RC 与采样保持时间的匹配计算
这是整套方案里最"物理"的一环,也是最容易算错的一环。SAR ADC 的采样过程,本质上是让内部采样电容 C_sample 通过外部源阻抗 R_source 和内部开关电阻 R_switch 充电,充电到与输入电压的误差小于 0.5 LSB 才算采准。
充电是指数过程,误差随时间衰减:
V_error(t) = V_step × e^(-t / (R_total × C_sample))要求 V_error < 0.5 LSB = VREF / 2^(N+1),即:
t_sampling ≥ R_total × C_sample × ln(2^(N+1))12 位时 ln(2^13) ≈ 9.01,工程上取 10 更保险。代入典型值 C_sample = 8pF:
| 外部源阻抗 R_source | R_total(含开关电阻 100Ω) | 最小采样时间 |
|---|---|---|
| 100 Ω | 200 Ω | 16 ns |
| 1 kΩ | 1.1 kΩ | 88 ns |
| 5 kΩ | 5.1 kΩ | 408 ns |
| 10 kΩ | 10.1 kΩ | 808 ns |
| 50 kΩ | 50.1 kΩ | 4.0 μs |
| 100 kΩ | 100.1 kΩ | 8.0 μs |
这张表说明一个很反直觉的结论:很多人以为"ADC 输入阻抗高,随便接",实际上源阻抗超过 10kΩ 就必须认真对待采样时间了。而分压采样电路里,两个 10kΩ 电阻分压的等效源阻抗是 5kΩ(戴维南等效),这是很多人算漏的地方。
再说前端 RC 的滤波电容。加电容有两个作用:一是抗混叠,二是给采样电容"补电荷",降低对源阻抗的要求。但电容不是越大越好。RC 时间常数要满足两个条件:
- 对采样有利:C 越大,采样瞬间电压跌落越小。经验值是 C ≥ 10 × C_sample,即至少 100pF 量级,实际用 1nF~100nF 都常见。
- 对信号带宽有约束:RC 构成低通,截止频率 f = 1/(2πRC)。如果被测信号本身变化快,电容太大会把信号拖慢甚至失真。
我一般按这个流程定:先确定信号最高频率 f_signal,取截止频率 f_c = 5~10 × f_signal,选定 C(通常 1nF~10nF),再反算 R = 1/(2π f_c C)。然后把 R 代入上面的采样时间公式,验证采样时间够不够。如果不够,要么降低 R 增大 C(保持截止频率不变),要么加长采样时间。
注意:如果前端是运放输出,运放驱动容性负载可能振荡。这时候要么在运放输出串 50~100Ω 隔离电阻,要么把电容放在隔离电阻之后。我见过一个案例,运放直接带 10nF 负载,输出自激,采样值一直在某个频率上抖,查了一周才发现是运放不稳。
2.3 PCB 布局、地与回流路径
布局这部分,很多软件出身的同学会跳过,但它对 ADC 精度的影响经常比代码还大。三条硬规则:
第一条:模拟区域和数字区域分开,但地要完整。不要天真地"割地"——把地平面切成模拟地和数字地两块,只在一个点连接,这个做法在低频模拟电路里成立,但在有高速数字信号的板子上适得其反,因为回流路径被切断,信号会绕远路走,辐射和串扰反而变大。我的做法是:用一个完整地平面,靠布局分区而不是靠割地。模拟器件集中放在一角,数字器件远离,模拟走线不跨过数字区域。
第二条:采样走线短、直、包地。ADC 输入走线尽量短(实在不行控制在几厘米内),两侧用地线包围,下方地平面完整不开口。走线不要和 PWM、SPI、USB 这类高速或大电流信号平行走,如果必须交叉,走直角交叉而不是平行。
第三条:去耦电容的回路要短。100nF 去耦电容的意义在于给高频电流提供本地回路,但如果电容离引脚 5mm、地回路绕了一大圈,寄生电感就把高频滤除能力废掉了。正确做法是电容紧贴引脚,地焊盘直接打多个过孔到地平面,过孔离焊盘越近越好。
再讲一个真实踩坑:有一版板子,ADC 采样值在电机启动瞬间直接跳 200 多个码。一开始怀疑是电源塌陷,加了电容没用。后来用示波器差分探头量 ADC 输入引脚和 AGND 之间的电压,发现电机启动时地平面上有 50mV 的瞬态压差——采样走线正好跨在数字回流路径上,被感应了。改板时把采样走线挪到模拟区边缘、加了包地,问题消失。所以遇到"某个动作一发生采样就跳"的现象,先怀疑地,别怀疑 ADC。
2.4 抗混叠与通道间串扰抑制
抗混叠这个词听起来学术,说白了就是:采样频率是 fs,那么频率高于 fs/2 的信号会被"折叠"到低频,变成你根本分不清的假信号。比如你 10kHz 采样,一个 9kHz 的干扰会折叠成 1kHz,看起来像是信号本身有 1kHz 波动。
硬件上的抗混叠就是前面那个 RC 低通。软件上也有办法:过采样 + 平均本身就是一种抗混叠手段,因为平均相当于一个低通滤波器。但软件抗混叠不能替代硬件,因为折叠发生在采样瞬间,一旦折进去就分不出来了。
通道间串扰是另一个大问题。多通道轮询时,从通道 A 切到通道 B,采样电容上还残留着 A 的电荷,如果 B 的采样时间不够长,采出来的值会偏向 A。N32 的规则组支持一次转换多个通道,通道之间没有额外的稳定时间,所以必须靠加长采样时间来兜底。我的经验值:通道切换后的第一次采样,采样时间至少给到平时的 2 倍;如果通道间电压差很大(比如一个测 0.1V、一个测 3V),给到 4 倍更稳。
另外一个容易被忽略的是注入组的使用。注入组可以打断规则组,适合做电流的同步采样。但注入组打断规则组时,规则组的采样会被中断,恢复后第一个数据的准确性会下降。如果对时序要求严,建议规则组和注入组不要混用,或者干脆全用规则组 + DMA 扫多通道。
3. 软件层:采样时序、校准与数据通路
硬件搭好之后,软件的任务是把误差再压一层。这一章四件事:采样时间怎么配、校准怎么做、数据通路怎么保证确定性、数字滤波怎么设计。
3.1 采样时间到底该配多少:公式与查表
N32 的 ADC 采样时间通过采样时间寄存器配置,通常可选 1.5、7.5、13.5、28.5、41.5、55.5、71.5、239.5 个 ADC 周期(不同型号档位略有差异,以手册为准)。总转换时间 = 采样周期数 + 12.5 个周期(12 位逐次逼近)。
计算流程:
- 确定 ADCCLK。假设总线时钟 60MHz,ADC 分频 4,ADCCLK = 15MHz。
- 由 2.2 节的公式算出最小采样时间 t_min。
- 换算成周期数:n = t_min × ADCCLK,向上取到最近的档位。
- 计算总转换时间和最大采样率。
举个例子:R_source = 10kΩ,C_sample = 8pF,t_min = 808ns,取 1μs 保险。ADCCLK = 15MHz,1μs 对应 15 个周期,选 28.5 周期档位(留足余量)。总转换时间 = 28.5 + 12.5 = 41 个周期 = 2.73μs,单通道最大采样率约 366kSPS。
| 采样周期档位 | 15MHz 下采样时间 | 总转换时间 | 最大采样率 | 适配源阻抗 |
|---|---|---|---|---|
| 1.5 | 100 ns | 933 ns | 1.07 MSPS | < 1 kΩ |
| 7.5 | 500 ns | 1.33 μs | 750 kSPS | < 6 kΩ |
| 13.5 | 900 ns | 1.73 μs | 578 kSPS | < 11 kΩ |
| 28.5 | 1.90 μs | 2.73 μs | 366 kSPS | < 23 kΩ |
| 41.5 | 2.77 μs | 3.60 μs | 278 kSPS | < 34 kΩ |
| 71.5 | 4.77 μs | 5.60 μs | 179 kSPS | < 59 kΩ |
这张表可以直接拿去用,前提是你的 ADC 时钟是 15MHz。如果时钟不同,按比例换算。
提示:不要无脑选最大采样时间。采样时间越长,单次转换越慢,你能做的平均次数就越少。平均次数每翻一倍,噪声降 3dB(约 0.5 位)。所以这里有个最优解:在满足充电要求的前提下,选最小的采样时间,把省下来的时间用于多次采样平均。我实测下来,采样时间刚好够(1.2~1.5 倍余量)+ 8 次平均,比采样时间给 4 倍余量 + 1 次采样,效果更好。
3.2 校准流程与上电初始化顺序
N32 的 ADC 有内置自校准功能,能修掉一部分内部失调。校准的触发方式是置位控制寄存器里的校准位,然后等待完成标志。听起来简单,但顺序错了就白做。
我总结的初始化顺序是这样的:
- 使能 GPIO 和 DMA 时钟,配置采样引脚为模拟输入模式(这一步要在使能 ADC 之前)。
- 使能 ADC 时钟,等待时钟稳定(读一次寄存器或者延时几个周期)。
- 退出 ADC 的深度掉电模式(如果有),给 ADC 上电。
- 延时至少 1μs,等 ADC 内部稳定。
- 触发一次自校准,等待校准完成(轮询标志位,注意加超时)。
- 配置采样时间、通道顺序、触发源、DMA。
- 使能 ADC,延时后丢弃前 3~5 次转换结果。
第 7 步的"丢弃前几次"很多人不做,但很关键。ADC 刚使能时,内部采样保持电路和参考缓冲还没完全建立,前几次结果会偏。我在一个项目里量过,前 3 次转换结果比稳定值低 5~8 个码,第 5 次之后才稳住。
关于校准的时机,有两个流派:上电校准一次和每次采样前校准。前者的风险是温度变化后失调漂了;后者的代价是每次校准要几十个 ADC 周期,采样率大打折扣。我的折中做法是:上电校准一次,然后每隔一段时间(比如 1 秒)或者温度变化超过 5℃ 时重新校准一次。如果你的应用对长期漂移敏感,可以加一个内部参考通道(如果有)作为基准,定期比对修正。
还有一个隐蔽问题:校准期间不要有转换在跑。如果 DMA 还在搬运、定时器还在触发,校准会失败或者结果异常。正确做法是先关触发源、关 DMA、等当前转换结束,再启动校准。
3.3 定时器触发 + DMA 的确定性采样
很多人的采样是这么写的:主循环里调一次采样函数,读一个值。这样做的问题是采样时刻不确定。主循环里有其他任务、中断、通信,采样间隔抖动可能到几十微秒。对于一个 1kHz 的信号,这个抖动足以让相位和幅度都不准。
正确做法是让硬件自己跑:定时器按固定频率触发 ADC,ADC 转换完成后通过 DMA 把结果搬到内存,CPU 只负责在缓冲区满了之后处理数据。这条链路上 CPU 完全不参与,采样时刻由定时器硬件保证,抖动在纳秒级。
配置要点:
- 定时器触发源:选定时器的比较/更新事件作为 ADC 外部触发,注意触发极性(上升沿)。
- 触发频率:由定时器预分频和重装载值决定。触发频率不能超过 3.1 节算出来的最大采样率,否则转换会丢。
- DMA 模式:用循环模式,缓冲区大小设成 N 的整数倍(N 是后续平均的点数),这样每次半满/全满中断里处理一批数据,处理时间和采样时间解耦。
- DMA 位宽:ADC 结果是 16 位寄存器,用半字(16 位)搬运,不要用字节,否则高低位分离。
这里有个细节值得说:采样期间让数字外设安静。如果采样时刻正好赶上 SPI 在传数据、或者 GPIO 在翻转大电流负载,采样值会有毛刺。彻底的做法是让定时器触发和通信外设错开,或者干脆在采样窗口内关闭不必要的外设。我在一个项目里把 ADC 触发和 PWM 载波同步,让采样点落在 PWM 开关的"安静区"(比如开关管关断期间),噪声直接降了一半。
3.4 过采样与数字滤波:把 1 位有效精度抠出来
软件层面提升精度最有效的手段就是过采样。原理很简单:白噪声背景下,N 次平均能让噪声降到 1/√N。理论上每 4 倍过采样能提升 1 位有效分辨率,16 倍提升 2 位。
但这里有个前提:噪声必须是随机的。如果你的噪声是周期性的(比如开关电源的固定频率纹波),平均是消不掉的,甚至可能因为采样点总是落在纹波同一相位而完全无效。所以过采样要和"采样时刻抖动"配合——让采样点相对于干扰源的相位随机化,平均才有效。
实际做法我推荐"三层滤波":
- 第一层:去极值。每次取 8 个点,去掉最大和最小各 1~2 个,剩下的平均。这一步专治偶发尖峰(比如静电、开关瞬态)。
- 第二层:滑动平均。维护一个长度 16 或 32 的环形缓冲区,每次输出缓冲区均值。滑动平均的响应是线性的,延迟固定,适合实时控制。
- 第三层:一阶低通。y = y + α(x - y),α 取 0.1~0.3。这一层进一步压高频噪声,但会引入相位滞后,用在不需要快速响应的显示类场合。
三层叠加,实测能把 12 位 ADC 的跳动从 ±15 码压到 ±2 码,等效有效位从 8.9 位提到 11.4 位左右。代价是采样率下降和响应变慢。如果你的应用是测电池电压、温度这种慢变量,完全值得;如果是做电流环控制,第一层加第二层就够,别加第三层。
注意:滑动平均的缓冲区长度要和控制周期匹配。缓冲区长度 32、采样率 32kHz,那输出更新的等效延迟就是 1ms。如果你的控制环周期是 100μs,这个延迟会导致相位裕度不足,环路震荡。这种时候要么降低缓冲区长度,要么改用"抽取 + 平均"(每 N 个点只输出一个),避免累积延迟。
4. 实操全流程:从裸机工程到实测数据
前面讲的是原理和取舍,这一章给可以直接对照的代码和实测数据。说明一下:具体寄存器名和位定义以你手上型号的参考手册为准,下面用的是标准外设库风格的写法,思路通用。
4.1 工程搭建与时钟、GPIO 配置
时钟配置的关键是ADC 时钟分频。假设系统时钟 120MHz,总线时钟 60MHz,ADC 分频选 4,得到 15MHz。这个值要跟 3.1 节的表对上。
/* 1. 使能时钟:GPIO、DMA、ADC */ RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA | RCC_APB2_PERIPH_ADC1, ENABLE); RCC_EnableAHBPeriphClk(RCC_AHB_PERIPH_DMA1, ENABLE); /* 2. 配置 ADC 时钟分频:60MHz / 4 = 15MHz */ RCC_SetADCDiv(RCC_ADCDIV_DIV4); /* 3. 采样引脚配为模拟输入(关键:必须先于 ADC 使能) */ GPIO_InitType gpio; gpio.Pin = GPIO_PIN_1; gpio.GPIO_Mode = GPIO_Mode_AN; /* 模拟输入,关闭上下拉和施密特 */ gpio.GPIO_Speed = GPIO_Speed_2MHz; GPIO_InitPeripheral(GPIOA, &gpio);这里有个容易出错的点:模拟输入模式下,一定要关闭内部上下拉。有些库的默认配置会带上拉,一上拉就等于在输入端并了个 30~50kΩ 到电源,直接破坏分压比。我见过采样值整体偏高 5% 的案例,就是上拉没关。
4.2 关键代码逐段说明
校准、配置、触发、DMA 四段,我分开写并说明意图。
/* --- 校准 --- */ void ADC_Calibrate(void) { ADC_Enable(ADC1, DISABLE); ADC_EnableCalib(ADC1, ENABLE); /* 轮询等待,必须加超时,防止死等 */ uint32_t timeout = 0xFFFFF; while ((ADC_GetFlagStatus(ADC1, ADC_FLAG_CAL) == RESET) && (--timeout)); if (timeout == 0) { /* 校准超时:多半是 ADC 时钟没开或未上电 */ Error_Handler(); } }校准这段最需要注意的是超时。我在现场见过因为 ADC 时钟没使能,校准标志永远不置位,程序卡死在 while 里,看门狗复位后又是同样的死循环,表现为"上电就重启"。加超时是最低成本的防御。
/* --- 规则组通道与采样时间配置 --- */ void ADC_ConfigChannels(void) { ADC_InitType adc; adc.Multichannel = ADC_MULTI_SINGLE; adc.Sequence = ADC_SEQ_SCAN; /* 多通道扫描 */ adc.Trigger = ADC_TRIG_SOFTWARE; /* 先软触发,后面切定时器 */ adc.DataAlign = ADC_DATA_ALIGN_RIGHT; adc.ScanDirection = ADC_SCAN_DIR_UPWARD; adc.ContinueConv = DISABLE; adc.DMACfg = ADC_DMA_ENABLE; /* 必须开,否则多通道读不到 */ adc.Resolution = ADC_RESOLUTION_12B; ADC_Init(ADC1, &adc); /* 通道 1:41.5 周期,适配 10k 源阻抗 */ ADC_ConfigRegularChannel(ADC1, ADC_CH_1, 1, ADC_SAMP_TIME_41CYCLES5); /* 通道 2:28.5 周期 */ ADC_ConfigRegularChannel(ADC1, ADC_CH_2, 2, ADC_SAMP_TIME_28CYCLES5); /* 通道 3:239.5 周期,用于高阻信号 */ ADC_ConfigRegularChannel(ADC1, ADC_CH_3, 3, ADC_SAMP_TIME_239CYCLES5); /* 使能 DMA 请求 */ ADC_EnableDMA(ADC1, ENABLE); ADC_Enable(ADC1, ENABLE); DelayUs(10); /* 等内部参考缓冲稳定 */ }给不同通道配不同采样时间,是多通道高精度采样的关键技巧。低阻抗通道配短时间提高采样率,高阻抗通道配长时间保精度。这一点在只用一个全局采样时间的配置里做不到,必须用逐通道配置。
/* --- 定时器触发 + DMA 循环搬运 --- */ #define BUF_LEN 32 static uint16_t adc_buf[BUF_LEN]; void ADC_StartDMA(void) { DMA_InitType dma; DMA_DeInit(DMA1_CH1); dma.PeriphAddr = (uint32_t)&ADC1->DAT; /* 数据寄存器地址 */ dma.MemAddr = (uint32_t)adc_buf; dma.Direction = DMA_DIR_PERIPH_SRC; dma.BufferSize = BUF_LEN; dma.PeriphInc = DMA_PERIPH_INC_DISABLE; dma.MemInc = DMA_MEM_INC_ENABLE; dma.PeriphDataSize = DMA_PERIPH_DATA_SIZE_HALFWORD; dma.MemDataSize = DMA_MEMORY_DATA_SIZE_HALFWORD; dma.CircularMode = DMA_MODE_CIRCULAR; /* 循环模式,不会停 */ dma.Priority = DMA_PRIORITY_HIGH; DMA_Init(DMA1_CH1, &dma); DMA_EnableChannel(DMA1_CH1, ENABLE); /* 定时器触发:假设 TIM2 更新事件,1kHz 采样 */ ADC_EnableExternalTrig(ADC1, ADC_EXT_TRIG_TIM2_TRGO); }循环模式加半满/全满中断,是我最推荐的组合。半满中断处理前半段、全满中断处理后半段,处理和采集完全并行,不会因为处理慢而丢数据。
/* --- 三层滤波 --- */ typedef struct { uint16_t ring[16]; uint8_t idx; uint32_t sum; int32_t lpf; } AdcFilter_t; uint16_t Adc_Filter(AdcFilter_t *f, uint16_t raw) { /* 第一层:环形缓冲累加,实现 16 点滑动平均 */ f->sum -= f->ring[f->idx]; f->ring[f->idx] = raw; f->sum += raw; if (++f->idx >= 16) f->idx = 0; int32_t avg = (int32_t)(f->sum / 16); /* 第二层:一阶低通,alpha = 1/8,用移位避免浮点 */ f->lpf += (avg - f->lpf) >> 3; return (uint16_t)f->lpf; }用移位代替乘除,在中断里跑也不心疼。α = 1/8 对应的截止频率约为采样率的 1/50。如果采样率 1kHz,截止频率约 20Hz,适合测温度、电池电压这类慢变量。
4.3 实测数据与配置对比
同一块板子(外部 3.0V 基准、10kΩ 源阻抗、输入端加了 1kΩ + 10nF 的 RC),不同配置下的实测结果:
| 配置组合 | 跳动峰峰值(码) | 有效位估算 | 最大采样率 |
|---|---|---|---|
| 采样 1.5 周期,无平均,VDDA 当参考 | 48 | 8.2 | 1.07 MSPS |
| 采样 1.5 周期,无平均,外置基准 | 31 | 8.7 | 1.07 MSPS |
| 采样 13.5 周期,无平均,外置基准 | 12 | 9.9 | 578 kSPS |
| 采样 41.5 周期,无平均,外置基准 | 7 | 10.6 | 278 kSPS |
| 采样 41.5 周期,16 点平均,外置基准 | 2 | 11.7 | 17 kSPS |
| 采样 41.5 周期,16 点平均 + 去极值 | 2 | 11.7 | 17 kSPS |
有效位估算用的是 ENOB ≈ log2(满量程 / 有效噪声 RMS)。表里可以看到几个关键结论:
第一,从 1.5 周期到 13.5 周期,是最划算的一步,跳动从 31 码降到 12 码,采样率只损失不到一半,EEPROM 都不用改。这一步基本上就是"免费"的,很多人没做。
第二,外置基准把 48 码降到 31 码,效果明显但不如时序优化显著。这也印证了 1.3 节的判断:先保时序,再花钱买基准。
第三,16 点平均把 7 码降到 2 码,但代价是采样率掉到 1/16。这就是典型的"用带宽换精度"。你的应用如果采样率要求不高,这一招性价比最高。
我额外测了一个点:在电机运行的情况下重复上面的测量,所有配置的跳动都明显变大,最好的配置也只能到 5~6 码。说明在有强干扰的环境里,硬件屏蔽和布局的优先级要提到时序之前。同一个板子,把采样线加包地、远离电机驱动线之后,电机运行时的跳动从 40 多码降到 8 码。所以别迷信软件,物理隔离永远是第一位的。
5. 常见问题与排查速查
5.1 五类典型现象与定位路径
现象一:采样值一直轻微跳动,峰峰值 10 码以内。这是正常的量化噪声加少量热噪声,属于物理极限。处理方法就是过采样平均,别指望硬件再优化。如果跳动超过 20 码,往下看现象二。
现象二:跳动大,且短接输入端也跳。说明问题在芯片内部或电源参考。定位步骤:先用万用表量 VREF 和 VDDA 的实际值,看是否偏离标称;再用示波器 AC 耦合量纹波;最后检查 ADC 时钟分频是否超频。这三步能定位 80% 的问题。
现象三:短接很稳,接上信号源就跳。问题在外部前端。优先怀疑源阻抗过高导致充电不足,用 4.2 节的方法算一遍采样时间。其次怀疑前端 RC 引入了振荡(运放带容性负载自激),把电容去掉试试,如果变稳了就是这个问题。
现象四:多通道轮询时,各通道互相"污染"。典型的串扰。措施:加长采样时间、通道间插入一次丢弃转换、降低通道间电压差。如果用了注入组打断规则组,先试试不用注入组。
现象五:低温和常温正常,高温时精度明显下降。两个方向:外部基准的温漂、内部参考的温漂。前者换低温漂基准,后者用内部温度传感器做补偿。也有可能是焊接问题——某些封装在热胀冷缩时虚焊,表现就是温度相关。用手按压芯片看读数是否变化,能快速排除。
5.2 速查表
| 现象 | 最可能原因 | 快速验证 | 解决 |
|---|---|---|---|
| 零点不为 0 | 未校准或校准失效 | 短接输入看是否归零 | 重新校准,检查校准顺序 |
| 满量程偏差大 | 增益误差或参考值不符 | 输入已知电压比对 | 用实际基准值重算系数 |
| 读数整体偏高 | 输入引脚上拉未关 | 查 GPIO 配置寄存器 | 配为纯模拟输入 |
| 某通道数值异常 | 通道映射错位 | 单通道逐个测 | 核对通道号与引脚对应 |
| 干扰时跳变 | 地回流或屏蔽不足 | 加临时屏蔽铜箔试 | 走线包地、远离干扰源 |
| 采样率达不到 | 总转换时间算错 | 用 GPIO 翻转计时 | 按公式重算分频与周期 |
| 校准标志不置位 | ADC 时钟未使能 | 检查 RCC 配置 | 先使能时钟再校准 |
| 上电首次读数偏 | 参考未稳定 | 延时后重读 | 上电延时 + 丢弃前几次 |
5.3 踩过的坑与心得
第一个坑:"分辨率高就是精度高"的误解。有人为了提精度去用 16 位外置 ADC,结果前端噪声没处理,有效位还是 10 位,钱白花了。ADC 的位数决定上限,前端和参考决定你能达到多少。先做噪声预算,再决定要不要换芯片。
第二个坑:分压电阻选太大。为了省电,把分压电阻从 10k 改成 100k,功耗是降到 1/10 了,但源阻抗变成 50k,按 2.2 节的表需要 4μs 采样时间,采样率直接掉到 200kSPS 以下。正确做法是在分压点加一个电容,给采样瞬间提供电荷,这样既能省电又能保精度。电容值按 C ≥ 100 × C_sample 取,1nF 起步。
第三个坑:DMA 缓冲区太小导致数据覆盖。缓冲区只有 4 个点,处理函数稍慢一点,新数据就把旧数据覆盖了,读出来全是重复值。缓冲区至少要能装下"处理一次所需时间内采集的所有点"的两倍。我的经验值是最小 32,一般用 64 或 128。
第四个坑:采样时间配置寄存器的位定义记错。不同型号的采样时间编码不一样,有的是 3 位、有的是 4 位,档位顺序也不同。这个错误的表现是采样时间跟你以为的完全不同,而且不报错,很难查。建议配完之后用示波器量一下转换周期(在转换开始和结束各翻转一个 GPIO),跟算出来的值对比。
第五个坑,也是最隐蔽的:参考电压的实际值和标称值有偏差。标称 3.0V 的基准,实测 2.987V,差 0.4%。这个偏差会 1:1 反映到所有采样结果上,表现为"读数整体偏高 0.4%"。如果你的应用有精度要求,一定要用六位半表实测基准电压,把实测值写进换算系数。这一条能省掉很多莫名其妙的"精度不达标"投诉。
6. 适用边界与扩展思路
6.1 这套方案能覆盖的场景
把前面所有措施都做到位,N32 的片上 12 位 ADC 在 1kSPS 左右的速率下,能稳定做到 11 位以上有效精度,对应约 1.5mV 的分辨能力(3V 参考)。这个水平能覆盖的场景其实很多:
- 电池电压监测:12V 电池组经过分压,单节电压精度要求通常 ±20mV,绰绰有余。
- 温度采集:NTC 或热电偶加运放,精度要求 ±0.5℃,折合电压精度几 mV 量级,够用。
- 电流采样:配合霍尔或采样电阻 + 运放,1% 精度的电流环,够用。
- 工业 4~20mA 环路:12 位有效精度对应 0.024% 满量程,实际应用里 0.1% 就够。
- 消费类传感器:光敏、压力、湿度,基本都在这个量级。
反过来说,如果你的应用需要 14 位以上有效精度、或者信号带宽在几百 kHz 以上、或者要求极低的温漂(比如精密测量仪器),片上 ADC 就到极限了,该考虑外置。
6.2 什么时候该上外置 ADC 或仪表放大器
判断标准有三条,满足任意一条就该考虑外置:
第一条:需要的有效位超过 12 位。片上 ADC 靠过采样硬撑到 13 位已经很勉强(要 64 倍过采样,采样率掉到 1/64),不划算。外置 16 位或 24 位 ΔΣ ADC 在低速率下能轻松做到 16 位有效,而且内置 PGA,省掉运放。
第二条:信号幅度极小。比如热电偶输出几十微伏、应变片输出几毫伏,片上 ADC 直接采会被噪声淹没,必须先用仪表放大器放大 100~1000 倍。这时候用带 PGA 的外置 ADC 更省事,也更省板面积。
第三条:需要真正的差分输入和高共模抑制。片上 ADC 大多是单端的,伪差分也只有有限抑制。长线传输、电机电流采样这类共模电压高的场合,需要用真差分 ADC 或者隔离放大器。
反过来说,如果你的信号在 0.1V~3V 之间、带宽不高、精度要求 0.1% 量级,就别折腾了,把片上 ADC 的前端和时序做扎实,性价比最高。
6.3 后续可以扩展的几个方向
如果你已经把基础版本跑通了,还有几个方向可以继续挖:
一是用内部温度传感器做参考漂移补偿。很多 N32 型号带内部温度通道,虽然精度不高,但用来做趋势补偿够用。思路是定期采一次内部温度,根据基准的温漂系数(手册给)反算参考电压的偏移量,实时修正换算系数。这一招在宽温场合能把温漂误差压掉一半以上。
二是把采样和 PWM 做硬件同步。电机控制里这是标准做法:PWM 载波中点触发 ADC,采到的电流正好是开关周期内的平均值,避免开关纹波。N32 的定时器支持这种联动,配置好之后电流采样噪声能降一个数量级。这个我在一个电机项目里验证过,效果比任何软件滤波都明显。
三是搭一套自动化的精度自检流程。用 DAC 或 PWM+RC 生成一组已知电压,定期注入 ADC 输入,比对实测值和标称值,自动更新校准系数。这样产品出厂后还能自校准,抵消长期漂移。对于需要长期免维护的设备,这个思路值得投入。
我个人在实际操作中的体会是:ADC 精度这件事,80% 的收益来自前 20% 的工作量——把采样时间配够、把上拉关掉、把参考和电源处理好、上电校准一次。剩下的 20% 收益要靠过采样、布局优化、温度补偿一点点抠,投入产出比明显下降。所以别一上来就想着怎么把 12 位榨成 13 位,先把那 80% 做扎实,很多"精度不够"的投诉其实在这一步就解决了。