本文是一篇系统的 ADC(模数转换器)学习笔记:前半部分讲透 ADC 的基本原理(采样/保持/量化/编码、各种架构对比),后半部分以 STM32 为例,从模块框图、转换流程一路讲到 CubeMX + HAL 库的四个实战(轮询 → DMA 多通道 → 定时器触发 → 内部传感器校准),最后附精度优化和避坑指南。

适合:单片机/嵌入式入门同学、准备笔试面试需要快速过一遍知识点的同学。

阅读路线

  • 赶时间直接上手 → 看 第六章 实战

  • 面试/考试抱佛脚 → 看 第九章 速查表

  • 想真正理解 → 从头读,重点看第三、四章。


一、ADC 是什么:模拟世界与数字世界的桥梁

我们生活的物理世界本质上是模拟的:温度、光照、声音、压力、电池电压……这些量在时间和幅值上都是连续变化的。而单片机(CPU)只认识 0 和 1 组成的离散数字信号

ADC(Analog-to-Digital Converter,模数转换器)就是这两者之间的桥梁:每隔一段时间取一次模拟电压的"快照",并把它换算成一个整数,交给 CPU 处理。

举几个日常例子感受一下 ADC 无处不在:

应用场景 被测模拟量 典型需求
数字万用表 电压、电流 高精度、低速
温控器 NTC 热敏电阻分压 中低速、稳定
麦克风采集 音频信号 44.1kHz 等固定采样率
无人机 IMU 陀螺仪/加速度模拟输出 高速、实时
电池电量显示 锂电池电压 3.0~4.2V 低速、低功耗

💡 一句话总结:ADC 负责"量",CPU 负责"算"。 单片机片内集成的 ADC(如 STM32 的 12 位 ADC)能覆盖 80% 的中低速应用;要求更高精度或更快速度时,才需要外挂专用 ADC 芯片(如 ADS1115、AD7606)。


二、ADC 的四步曲:采样 → 保持 → 量化 → 编码

任何一本教材里,ADC 的转换过程都可以拆成四个阶段。理解这四步,后面所有概念都迎刃而解。

2.1 采样(Sampling)与奈奎斯特定理

采样,就是每隔固定时间 Ts(采样周期)读取一次信号的瞬时值,采样率 fs = 1/Ts

这步有一个黄金定律——奈奎斯特采样定理

fs ≥ 2 × fmax(采样率必须大于信号最高频率的 2 倍),否则会发生混叠(Aliasing):高频信号会被错误地"折叠"成一个不存在的低频信号。

图中可以看到:fs 不足时,采样点"看上去"属于另一路假信号,而且事后用任何软件手段都无法还原。所以工程上的做法是:ADC 前端加一个低通滤波器(抗混叠滤波),把高频成分先滤掉。

2.2 保持(Hold)

采样只是一瞬间的事,转换需要时间。采样保持电路(S/H)用一个电容把采样瞬间的电压"冻住"(保持为阶梯状),让 ADC 在这段平坦期内慢慢完成量化。STM32 内部就集成了 S/H 电路,我们能配置的"采样时间"长短,说的就是这段给采样电容充电的时间(第四章细讲)。

2.3 量化(Quantization)与分辨率

保持下来的电压值有无穷多种可能,但输出的数字是有限的——必须"四舍五入"到最近的台阶上,这就是量化

这里引出 ADC 最重要的参数——分辨率 N(位数):把满量程电压 Vref 划分成 2^N 份:

1 LSB = Vref / 2^N
  • 1 LSB 就是 ADC 能分辨的最小电压差;

  • 例:12 位 ADC、参考电压 3.3V → 1 LSB = 3.3 / 4096 ≈ 0.81 mV

  • 任何时候量化误差都不超过 ±1/2 LSB(理想情况);

  • 理论极限信噪比:SNR ≈ 6.02 × N + 1.76 (dB),12 位约 74 dB。每多 1 位分辨率,SNR 提升约 6 dB。

📌 拿到 ADC 读数怎么算回电压? 最常用的一个公式:

电压 Vin = ADC原始值 × Vref / 4095      (12 位、右对齐)

注意除以 4095 还是 4096 的争论:严格地说输出码 4095 对应"接近 Vref",工程上两种写法差异不到 0.03%,CSDN 上两种都能见到,统一即可,不必纠结。

2.4 编码(Encoding)

把量化后的台阶编号写成二进制输出,12 位 ADC 就输出 0 ~ 4095(0x000 ~ 0xFFF)。至此,一个模拟电压变成了一个 CPU 能读的数字。


三、常见 ADC 架构:原理与选型

ADC 的实现方案有很多种,本质上就是在"速度、精度、成本、功耗"之间做不同的取舍。重点理解 SARΣ-Δ 两种。

3.1 SAR 逐次逼近型(重点!MCU 内置 ADC 几乎都是它)

结构:输入 → 采样保持 → 比较器 ↔ SAR 逻辑 ↔ 内部 DAC → 输出码。

工作方式像用天平二分称量:从最高位(MSB)开始,每个时钟节拍试探一位:

  1. 先猜一半:输出码设为 100..0,DAC 给出 Vref/2;

  2. 比较器判断 Vin 和 DAC 谁大:Vin 大 → 这一位留 1,否则清 0;

  3. 把已确定的位锁存,继续试探下一位;

  4. N 位 ADC 需要 N 个节拍,逐位逼近出完整结果。

下面这张 4 位 ADC 的例子(Vref=5V,Vin=3.0V)把过程画得很清楚——最终输出 1001

特点:中高速、低延迟、低功耗、成本低,分辨率和速度互相制约(位数越多节拍越多)。STM32、MSP430、AVR 等单片机内置的 ADC 基本都是 SAR。

3.2 Σ-Δ 型(高精度之王)

思路完全不同:先求和(Σ积分)再取差分(Δ),用远高于信号带宽的频率过采样,把量化噪声"推"到高频段,再用数字滤波器滤掉高频噪声并抽取数据。模拟部分极其简单,复杂的是数字滤波。

特点:分辨率轻松做到 24 位甚至 32 位、超高精度、抗混叠能力强;代价是速度慢(几 SPS ~ 几百 kSPS)、有滤波器延迟。典型应用:电子秤、温度计、心电图等精密慢速测量(如 ADS1115、HX711 都是这类外置 ADC)。

3.3 其他常见架构(了解即可)

架构 原理一句话 分辨率 速度 典型应用
Flash 并行比较 2^N−1 个比较器同时比较,一次出结果 低(4~10位) 最快(GSPS 级) 示波器、高速采集
流水线 Pipeline 多级 Flash 级联,每级算几位再拼起来 8~16 位 很快(MSPS~GSPS) 通信、视频采集卡
SAR 逐次逼近 二分搜索,一拍一位 8~18 位 中高速 MCU 内置、数据采集
Σ-Δ 过采样 + 噪声整形 + 数字滤波 最高(16~32位) 精密测量、音频编解码
双积分 先对信号定时积分,再对基准反向积分计时 16~24 位 最慢(几 SPS) 数字万用表

选型思路:要快 → Flash/流水线;要高精度慢信号 → Σ-Δ;速度精度均衡 + 集成在单片机里白用 → SAR。本文后面的实战全部围绕单片机内置的 SAR ADC。


四、STM32 ADC 深度解析:架构与转换流程

铺垫结束,进入实战部分的主角——STM32 片上 ADC。以最经典的 STM32F103(12 位 SAR ADC)为例讲解,F4/G4/H7 等系列原理完全相通,只是时钟上限、分辨率位数等参数不同。

4.1 特性速览

  • 12 位逐次逼近型 ADC,读数范围 0 ~ 4095;

  • 最多 18 个通道:16 个外部通道 + 内部温度传感器 + Vrefint 内部基准;

  • ADCCLK 最高 14MHz,最快转换 1µs(1 Msps);

  • 支持规则组(16 通道序列)和注入组(4 通道,可插队优先转换);

  • 单次/连续/扫描/间断多种转换模式,软件/定时器/外部引脚多种触发;

  • 结果可左/右对齐存入 16 位寄存器;支持 DMA、多个中断和模拟看门狗

4.2 总体框图:一张图看懂数据走向

跟着这张图从左到右走一遍:模拟信号从 GPIO(需配置为模拟输入模式)进来 → 多路开关选定本次转换的通道 → 按规则组/注入组的队列安排进入转换核心 → SAR 在 ADCCLK 驱动下逐位逼近 → 结果写入 DR / JDR 数据寄存器 → 触发 EOC 中断或 DMA 请求把数搬走。期间触发源决定"什么时候开始转",模拟看门狗则充当电压越界的"报警器"。

4.3 时钟(ADCCLK):越快不一定越好

ADCCLK 由 APB2 总线时钟(PCLK2,F103 最大 72MHz)分频得到,分频系数只能选 /2 /4 /6 /8,并且有个硬性上限:

⚠️ F1 系列 ADCCLK ≤ 14MHz。72MHz 主频下常用 72/6=12MHz;如果系统时钟比较低(如 48MHz),直接选 /4=12MHz 或更低即可。其他系列上限不同,如 F4 可到 36MHz,G4/H7 更高,原则一样:按手册不超过上限。

很多人问"为什么不直接拉到 14MHz?"——ADCCLK 越快转换越快,但采样电容需要足够时间充满,信号源阻抗大时盲目拉高时钟反而降低精度。所以时钟和采样时间要配合着调(4.7 节讲计算)。

4.4 输入通道

  • 外部通道 ADC_IN0 ~ IN15:连接到具体 GPIO,CubeMX 里点选引脚的 ADCx_INy 复用功能即可,该引脚自动设为模拟模式(无上下拉,直达 ADC);

  • 内部温度传感器:占用 ADC1 的通道 16(IN16),要测芯片/环境温度不用外围器件;

  • Vrefint 内部基准:通道 17(IN17),出厂电压约 1.20V,常用来反推 VDDA 实际值以校准读数(实战四详解)。

4.5 规则组 vs 注入组:普通队列与 VIP 通道

  • 规则组(Regular):日常使用的主力,最多 16 个通道排成一条转换序列,按 Rank1→Rank16 顺序依次转换;

  • 注入组(Injected):最多 4 个通道的"高优先级小队",可以打断正在进行的规则组转换,转完再把规则组"续上"——典型用法是电机控制里,PWM 波形特定点触发电流采样这类"一刻也不能晚"的任务。

入门阶段只用规则组即可,知道注入组是"插队"的就够了。

4.6 四种转换模式:搞清楚这节很重要

模式由 连续开关(CONT)扫描开关(SCAN) 组合出来的:

组合 行为 典型场景
单次转换 触发一次,转一个通道一次然后停 按键采样、偶尔读一次电量
连续转换 对同一通道不停重复转换 实时监控单一模拟量
扫描模式 一次触发,把规则组所有通道按序扫一遍 一次采集多路传感器(必须配 DMA
扫描 + 连续 多通道一轮接一轮循环扫 最常用的多路采集方式

⚠️ 一个经典坑:扫描模式下每个通道转换完都想往同一个 DR 寄存器里写,后面的数据会覆盖前面的,用轮询只能读到最后一个通道的值。所以多通道扫描几乎一定要配 DMA,每个通道 EOC 时 DMA 立刻把数据搬到数组对应位置。

4.7 转换时间怎么算(面试高频)

STM32(F1)的一次转换时间:

Tconv = 采样时间 + 12.5 个 ADCCLK 周期
  • "采样时间"可以在 1.5 / 7.5 / 13.5 / 28.5 / 41.5 / 55.5 / 71.5 / 239.5 周期中选;

  • "12.5"是 12 位逐位比较 + 收尾固定的开销。

算例 1:ADCCLK=12MHz(72/6),采样时间 55.5 周期 → Tconv = (55.5+12.5)/12MHz ≈ 5.67µs,即该通道每秒最多转约 176k 次。

算例 2:采样时间选最短的 1.5 周期、ADCCLK 拉满 14MHz → Tconv = 14/14MHz = 1µs = 1 Msps,这就是"STM32 ADC 最高 1M 采样率"的出处。

📌 采样时间的选择原则:信号源阻抗越高,采样时间要越长,因为内部采样电容要在这段时间里被充到足够接近真实电压。电位器/运放输出等低阻源可以用短采样时间;分压电阻大、NTC 这类高阻源要拉长采样时间(实在不行在引脚加运放缓冲)。

4.8 数据对齐

12 位结果存放在 16 位的 DR 寄存器里:

  • 右对齐(强烈推荐):高 4 位补 0,读出来直接就是 0~4095;

  • 左对齐:把结果顶到高位,取高 8 位 ≈ 直接得到 8 位精度——极少数想省事降精度的场景用。

4.9 中断、DMA 与模拟看门狗

  • EOC 中断:规则组一次转换完成;JEOC:注入组完成;

  • DMA 请求:每通道 EOC 时都可产生,多通道/连续采集的标配;

  • 模拟看门狗(AWD):设定上/下两个阈值电压,转换结果越界立刻触发中断——"电池过压报警""温度失控报警"这类需求不用 CPU 频繁比较,硬件自动盯梢。


五、三种数据读取方式对比

轮询(阻塞) 中断 DMA
CPU 占用 高(干等) 中(转完打断一次) 几乎为 0
代码难度 ★★ ★★
适用场景 偶尔读一次、demo 验证 单通道、需要及时处理每个值 多通道、连续/高速采集
关键 API HAL_ADC_PollForConversion HAL_ADC_Start_IT + 回调 HAL_ADC_Start_DMA

经验法则:教学最先学轮询,项目里差不多都用 DMA。中断方式介于两者之间,但要注意转换很快时(比如 1Msps)中断都来不及处理。


六、上手实战(STM32CubeMX + HAL 库)

硬件准备:任意一块 STM32F103 开发板(如最白菜的 BluePill)、一个电位器(中间脚接 PA0,两边接 3.3V 和 GND)、USB 转串口模块。

6.0 通用配置流程

不论用哪种方式,CubeMX 里的配置流程都是这七步:

这里把 ADC 参数配置界面里每个选项的意思列成表(CSDN 上被问得最多的就是这些):

配置项 推荐值 说明
Resolution(分辨率) 12 位(F1 固定) 部分系列可选 6/8/10/12 位
Data Alignment Right 右对齐 读数直接为 0~4095
Scan Conversion Mode 多通道时 Enabled 单通道 Disable
Continuous Conversion Mode 按需 连续自动转换开关
Discontinuous Conversion Mode 一般 Disabled 间断分组模式,入门先不用
DMA Continuous Requests 用 DMA 时 Enabled 扫描结束后 DMA 继续请求
End Of Conversion Selection 默认 每个通道转完置 EOC 标志
Number Of Conversion = 通道数 规则组序列长度
Rank 1,2,3… 序列中每个通道的顺序和采样时间
External Trigger Conversion 软件/定时器/EXTI 触发源,实战三用定时器
Sampling Time 1.5 ~ 239.5 cycles 阻抗高则拉长(见 4.7)

另外两件事每次都要做:

  1. 时钟树里确认 ADCCLK:F1 不要超过 14MHz;72MHz 主频常用 /6 = 12MHz;

  2. 代码里(F1 系列)上电后先做一次自校准 HAL_ADCEx_Calibration_Start(&hadc1),能明显减小偏移误差。

6.1 实战一:单通道轮询读取电位器电压

目标:转电位器,每 500ms 把电压值通过串口打印出来。

CubeMX 配置:PA0 → ADC1_IN0;扫描关、连续关、触发源选软件触发;采样时间 55.5 周期;开 USART1(PA9/PA10)。

用户代码(写在 USER CODE 区):

uint32_t adc_raw;
float    voltage;
char     buf[64];
​
/* USER CODE BEGIN 2 */
hadc1.Init.NbrOfConversion = 1;
HAL_ADCEx_Calibration_Start(&hadc1);   // F1系列:上电先自校准!
/* USER CODE END 2 */
​
while (1)
{
    HAL_ADC_Start(&hadc1);                                // ① 软件触发一次转换
    if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)  // ② 阻塞等待转换完(超时10ms)
    {
        adc_raw = HAL_ADC_GetValue(&hadc1);               // ③ 读原始值 0~4095
        voltage = adc_raw * 3.3f / 4095.0f;               // ④ 换算成实际电压
        sprintf(buf, "raw=%lu, voltage=%.3f V\r\n", adc_raw, voltage);
        HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100);
    }
    HAL_ADC_Stop(&hadc1);
    HAL_Delay(500);
}

要点解析

  • "启动 → 等待 EOC → 读 DR" 就是轮询三板斧,期间 CPU 在 PollForConversion 里干等,所以只适合低频、偶尔读取;

  • HAL_ADC_GetValue 只是读寄存器,必须先确认转换完成(PollForConversion 返回 OK)再读,否则拿到的是旧值或 0;

  • 若读数全是 0:检查 ADC 时钟使能、引脚是否为 Analog 模式、是否忘记 HAL_ADC_Start

6.2 实战二:DMA 多通道采集(强烈推荐)

目标:同时采集电位器(PA0)、光敏电阻(PA1)、温度探头(PA2)三路,CPU 不参与搬运。

CubeMX 配置变化(相对实战一):

  1. ADC1 勾选 IN0、IN1、IN2 三个通道;

  2. Scan Conversion Mode → EnabledContinuous → Enabled

  3. Number Of Conversion = 3,Rank1~3 分别绑定通道 0/1/2;

  4. 左侧 DMA Settings → Add,方向 ADC1→Memory,模式选 Circular(循环),数据宽度 Half Word;

  5. 注意生成工程顺序:MX_DMA_Init() 必须在 MX_ADC1_Init() 之前(CubeMX 会自动排好)。

用户代码:

uint16_t adc_buf[3];    // [0]=电位器 [1]=光敏 [2]=温度,与Rank顺序一一对应
​
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, 3);   // 启动后就不用管了
/* USER CODE END 2 */
​
while (1)
{
    // 随时读 adc_buf,DMA 已在后台默默刷新
    float v0 = adc_buf[0] * 3.3f / 4095.0f;
    float v1 = adc_buf[1] * 3.3f / 4095.0f;
    float v2 = adc_buf[2] * 3.3f / 4095.0f;
    char buf[80];
    sprintf(buf, "CH0=%.2fV CH1=%.2fV CH2=%.2fV\r\n", v0, v1, v2);
    HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100);
    HAL_Delay(500);   // 打印快慢你自己定,采样始终在自动进行
}

要点解析

  • DMA 循环模式:ADC 每转完一个通道,DMA 立即把 DR 里的值搬到 adc_buf 对应下标,一轮结束后自动从头再来,CPU 零参与

  • adc_buf 与 Rank 序列的一一对应关系不能乱,数据"张冠李戴"都是这里排错序;

  • 常见误区:HAL_ADC_GetValue 在 DMA 模式下不要用——值早被 DMA 搬走了,直接读数组就行。

6.3 实战三:定时器触发 + DMA,实现"精确采样率"

轮询 + HAL_Delay 的采样间隔误差很大(代码执行时间、中断抖动都会影响),做音频、震动分析、波形显示时不可接受。正确做法:用定时器的更新事件(TRGO)精准触发 ADC,采样率完全由硬件决定。

CubeMX 配置:

  1. TIM3:Clock Source = Internal Clock;Trigger Event Selection = Update Event;PSC=71、ARR=99(72MHz/72/100 = 10kHz);

  2. ADC1:External Trigger Conversion Source 选 Timer 3 Capture Compare/Trigger Out;触发沿 Rising Edge;Continuous Disabled(节奏已由定时器掌控);DMA 保持 Circular。

用户代码(并采用"乒乓双半缓冲",采 200 个点一组处理):

#define BUF_LEN 200
uint16_t buf[BUF_LEN];
​
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_TIM_Base_Start(&htim3);                    // ① 先开定时器
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)buf, BUF_LEN);  // ② ADC外部触发
/* USER CODE END 2 */
​
/* 前半缓冲(buf[0:100))传满:趁后台采集后半,处理这 100 个点 */
void HAL_ADC_ConvHalfCpltCallback(ADC_HandleTypeDef *hadc)
{
    // 取出 buf[0..99] 做滤波 / FFT / 串口发送
}
/* 全部传满:处理 buf[100:200) */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)
{
    // 取出 buf[100..199] 做滤波 / FFT / 串口发送
}

说明:两个回调函数让"采集"与"处理"交替进行——半满时处理前 100 点的同时,DMA 继续往后 100 点写,全满时反过来,循环无缝。这是示波器、音频采集类项目的标准写法,受限于篇幅这里只给框架,核心 API 都在这了。

要点解析

  • 采样率公式:fs = f_TIM / ((PSC+1)×(ARR+1)),改 ARR 即可改采样率;

  • 定时器触发时必须关掉 Continuous,否则 ADC 会脱离触发自己狂转;

  • 定时器 TRGO 触发源的可选范围与具体型号有关,在 CubeMX 的 External Trigger 下拉框里挑。

6.4 实战四:内部温度传感器 + Vrefint 校准

两个内部通道(IN16/IN17)非常实用:一个免费测温,一个能校准 VDDA(当你的 3.3V 供电并不准的时候——比如 USB 口供电常常只有 3.2V,ADC 读数就会整体偏移)。

CubeMX:勾选 ADC1 的 Temperature Sensor ChannelVrefint Channel(会自动占用 IN16/IN17)。注意温度传感器需要的采样时间较长,建议采样时间选最长的 239.5 周期(F1 手册要求 T_S_temp ≥ 17.1µs)。

uint16_t adc_buf[2];   // [0]=温度传感器 [1]=Vrefint
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)adc_buf, 2);
​
while (1)
{
    /* 第一步:用 Vrefint(约1.20V) 反推 VDDA 真实值 */
    float vdda = 1.20f * 4095.0f / adc_buf[1];     // 电源不是精确3.3V时很关键
​
    /* 第二步:换算温度电压并代入F103手册公式 */
    float vsense = adc_buf[0] * vdda / 4095.0f;
    float temp   = (1.43f - vsense) / 0.0043f + 25.0f;  // V25=1.43V, 斜率4.3mV/℃
​
    char buf[64];
    sprintf(buf, "VDDA=%.3fV, chip temp=%.1f C\r\n", vdda, temp);
    HAL_UART_Transmit(&huart1, (uint8_t *)buf, strlen(buf), 100);
    HAL_Delay(1000);
}

要点解析

  • F103 温度传感器参数:V25(25℃ 时电压)≈ 1.43V,平均斜率 ≈ 4.3mV/℃——不同系列数值不同,以你手头芯片的数据手册为准(例如 F4 是 0.76V / 2.5mV/℃);

  • 内部温度传感器测的是芯片结温,受芯片自身发热影响,只能当"芯片温度参考",测环境温度还是要外接传感器;

  • 部分系列(F0/F4/L4 等)出厂在芯片里存了 Vrefint 校准值(VREFINT_CAL),校准公式与本文不同,思路一致:由已知基准反推 VDDA。


七、精度优化与避坑指南

同一个 ADC,别人能测稳到 0.01V,你的读数却乱跳 ±50 个码?问题多半在下面这张表里:

7.1 硬件层面

措施 说明
稳定干净的 VDDA/Vref+ ADC 的天花板是参考电压。Vref+ 上加磁珠+去耦电容(100nF + 1µF),有独立 LDO 更好
模拟输入加 RC 滤波 引脚串 100Ω~1kΩ 电阻 + 对地 100nF 电容,即一阶低通兼抗混叠
模拟地/数字地单点相连 大电流回路、开关电源远离模拟走线
注意信号源内阻 内阻大 → 拉长采样时间或加运放跟随(见 4.7)
输入保护 别让输入超 Vref+ 或为负压,需要时加钳位二极管

7.2 软件层面

  • 多次平均(过采样):采 N 次求平均,噪声下降 √N 倍。例:连采 16 次取平均,简单有效;

  • 中值滤波:剔除偶发尖刺(脉冲干扰场景首选);

  • 滑动平均/一阶 IIRy = y*0.9 + x*0.1,慢变信号平滑好用;

  • 软件校准:F1 系列开机 HAL_ADCEx_Calibration_Start;用 Vrefint 修正 VDDA(实战四)。

  • 丢掉头尾:切换通道后第一次转换可能受前一通道电压残留影响,多通道快速扫描时可舍弃每组首个样本。

/* 最常用的均值滤波示例:32次采样取平均 */
uint32_t ADC_ReadAvg(ADC_HandleTypeDef *hadc, uint8_t times)
{
    uint32_t sum = 0;
    for (uint8_t i = 0; i < times; i++)
    {
        HAL_ADC_Start(hadc);
        HAL_ADC_PollForConversion(hadc, 10);
        sum += HAL_ADC_GetValue(hadc);
    }
    HAL_ADC_Stop(hadc);
    return sum / times;
}

八、常见问题 FAQ

Q1:读数总是 0? A:逐步检查:ADC 时钟使能了吗?GPIO 是 Analog 模式吗?忘写 HAL_ADC_Start / HAL_ADCEx_Calibration_Start 了吗?DMA 模式下却在用 HAL_ADC_GetValue

Q2:读数上下跳动几十个码? A:正常现象(哪怕理想的 12 位 ADC 也有 ±1 LSB 起步的噪声)。依次试:加大采样时间 → 输入端加 100nF 电容 → 软件平均 → 检查 Vref 是否干净。

Q3:多通道数据总是串/错位? A:99% 是没开 DMA,或 Rank 顺序与 adc_buf 下标对应错了;也可能是采样时间太短,上一通道的电荷还没泄放(拉长采样时间试试)。

Q4:DMA 模式下 adc_buf 一直不更新? A:检查 DMA 是否是 Circular 模式、MX_DMA_Init() 是否在 MX_ADC1_Init() 之前、NDTR 长度与数组长度是否一致。

Q5:内部温度测得比室温高、还飘? A:正常。它测的是芯片结温,芯片一上电就自热,且个体差异大(±10℃ 量级),只适合做相对变化参考和过温保护。

Q6:非要更快速度(>1Msps)或更高精度(>12位)怎么办? A:F1 可以用"双 ADC 交叉模式"等玩法提倍采样率;或者上外置 ADC 芯片:想要精度选 Σ-Δ 型(如 ADS1115,16 位),想要更高速度选专用 SAR 或并行 ADC(如 AD9226 等),通过 SPI / I2C / 并口接入。


九、知识点速查表(背完敢去面试)

  • ADC 四步:采样 → 保持 → 量化 → 编码

  • 奈奎斯特:fs ≥ 2×fmax,不足则混叠,混叠不可恢复;

  • 1 LSB = Vref / 2^N;量化误差 ≤ ±1/2 LSB;理想 SNR ≈ 6.02N + 1.76 dB

  • 电压换算:Vin = ADC值 × Vref / 4095(12 位右对齐);

  • SAR = 二分/逐位逼近,一拍一位;MCU 内置 ADC 的主流架构;Σ-Δ = 过采样+噪声整形,精度最高;

  • STM32F1 ADC:12 位、18 通道(16 外 + 温度 IN16 + Vrefint IN17)、ADCCLK ≤ 14MHz(PCLK2 分频 /2/4/6/8);

  • Tconv = 采样时间 + 12.5 周期,最快 1.5+12.5=14 周期,14MHz 时 1µs(1Msps);

  • 规则组 16 通道排队转换;注入组 4 通道可"插队"打断;

  • 模式:单次 / 连续 / 扫描 / 扫描+连续;扫描必须配 DMA(DR 会被覆盖);

  • 三种读数方式:轮询(简单)→ 中断(及时)→ DMA(常用,零 CPU 开销)

  • F1 上电要自校准:HAL_ADCEx_Calibration_Start

  • 精确采样率:定时器 TRGO 触发,fs = fTIM/((PSC+1)(ARR+1)),关连续模式;

  • 提精度三板斧:稳 Vref、加滤波/拉长采样时间、软件平均;修正 VDDA 用 Vrefint。


十、进阶阅读方向

  • 双 ADC 交叉模式(双倍采样率)、注入组在电机控制(FOC 电流环)中的应用;

  • STM32G4/H7 的 16 位 ADC、硬件过采样单元(无需 CPU 直接提高有效位数);

  • 外置 ADC 芯片:ADS1115(I2C 16 位 Σ-Δ)、AD7606(8 通道 16 位同步采样)、HX711(称重专用);

  • DMA 双缓冲 + FFT(CMSIS-DSP 库)做频谱分析;

  • RTOS 任务中的 ADC 采集架构设计(信号量 + 队列传递数据)。

参考资料

  1. ST《STM32F10x 参考手册 RM0008》第 11 章:Analog-to-digital converter (ADC)

  2. ST 应用笔记 AN2834《How to get the best ADC accuracy in STM32》

  3. 【CSDN】使用HAL库开发STM32:ADC基础使用:使用HAL库开发STM32:ADC基础使用_stm32 hal adc-CSDN博客

  4. 【CSDN】SD ADC(Σ-Δ)与 SAR ADC 核心区别:SD ADC(Σ-Δ ADC)与 SAR ADC(逐次逼近型 ADC)核心区别_sdadc-CSDN博客

  5. 【CSDN】STM32 ADC原理详解及应用编程:【STM32】单片机ADC原理详解及应用编程_stm32adc工作原理-CSDN博客

  6. 【知乎】什么是 SAR ADC?:https://www.zhihu.com/question/471760740

  7. onlycalm 单片机ADC采样笔记:https://www.onlycalm.cn/docs/


📝 写这篇文章的目的是帮助自己理解 ADC"从模拟到数字"的全流程,并结合 STM32 实战把知识落地。文中如有疏漏,欢迎评论区指正,共同进步!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐