【STM32专题】深入ADC外设:从SAR原理到CubeMX配置多通道DMA采集实战(基于F103C8T6)
本文以STM32F103C8T6为例,详细讲解ADC(模数转换器)的应用开发。首先介绍12位SARADC的工作原理和关键参数,重点说明时钟分频(不超过14MHz)和采样时间设置等注意事项。随后通过两个实战案例:单通道轮询采集电位器电压和多通道DMA采集(含内部温度传感器),对比不同采集模式的优缺点。文中提供了完整的CubeMX配置步骤和代码实现,并给出电压换算公式和常见问题解决方案。最后强调DMA
摘要:在嵌入式开发中,传感器是感知世界的“五官”,而 ADC(模数转换器) 则是将这些模拟信号(电压、温度、光照)转化为单片机能听懂的数字信号的“翻译官”。
本文将以 STM32F103C8T6 为核心,拒绝枯燥的寄存器手册,通俗讲解 SAR ADC 的工作原理,手把手教你使用 STM32CubeMX 配置单通道与多通道扫描,并对比 查询模式(Polling) 与 DMA模式 的实战代码区别。附带电压换算公式与常见避坑指南。
一、 ADC 的“前世今生”:核心原理通俗解
1.1 为什么要用 ADC?
我们的单片机是数字芯片,它只认识 0 (0V) 和 1 (3.3V)。但现实世界是模拟的,比如电池电压是 2.5V,光敏电阻的电压是 1.2V。
ADC(Analog-to-Digital Converter)的作用就是把这些连续变化的电压值,量化成离散的数字值。
1.2 STM32F103 的 ADC 特性
STM32F103C8T6 内部集成了 2 个 12位 ADC(ADC1 和 ADC2),共有 10 个外部通道。
- 12位精度:意思是它把 0V ~ 3.3V 的电压切成了 2^12=4096。
- 最小分辨率 = 3.3V/4096≈0.8mV。
- 转换速度:最快 1us 一次(1MHz)。
- 输入范围:0≤VIN≤VREF+(通常是 3.3V)。
1.3 核心原理:SAR(逐次逼近型)
STM32 的 ADC 属于 SAR(Successive Approximation Register) 类型。
通俗理解:
想象你在玩“猜数字”游戏,数字范围是 0-4095,你必须用最少的次数猜对。
- ADC 先猜中间值 2048(2.5V的一半)。
- 如果实际电压 > 猜的电压,ADC 就保留 1,猜下一个高位。
- 如果实际电压 < 猜的电压,ADC 就填 0,猜下一个低位。
- 经过 12 次比较(因为是 12 位),最终确定数字。
这就是为什么 ADC 转换需要时间(采样时间 + 转换时间)。
二、 关键参数与“避坑指南”(非常重要)
在配置 CubeMX 之前,必须理解这几个概念,否则代码跑不起来:
2.1 ADC 时钟(ADCCLK)
【坑点 1】:STM32F103 的 ADC 时钟最大不能超过 14MHz!
- 系统主频通常是 72MHz。
- ADC 挂载在 APB2 总线上。
- 如果不分频,72MHz 直接给 ADC,芯片会罢工或数据乱跳。
- 正确做法:必须设置分频系数为 6分频 (12MHz) 或 8分频 (9MHz)。
2.2 采样时间 (Sampling Time)
这是 ADC 对内部电容充电的时间。
- 信号源内阻大(如光敏电阻):需要长采样时间(如 239.5 Cycles),否则电压充不满,读数偏小。
- 信号源内阻小(如运放输出):可以用短采样时间(如 1.5 Cycles),速度更快。
2.3 左对齐 vs 右对齐
- 右对齐(Right Alignment):(推荐) 12位数据存在 16位寄存器的低12位,直接读就是真实值。
- 左对齐:通常用于只需要 8 位精度的场景(直接读高8位)。
三、 实战一:单通道轮询采集(测量电位器电压)
我们先从最简单的开始:使用 CPU 傻等(Polling)方式读取 PA1 引脚的电压。
3.1 CubeMX 配置步骤
- 引脚设置:
- 点击
PA1-> 选择ADC1_IN1。
- 点击

- ADC配置 (Analog -> ADC1):
- Mode:勾选
IN1。 - Configuration -> Parameter Settings:
Data Alignment: Right (右对齐)。Scan Conversion Mode: Disable (只测一个,不用扫描)。Continuous Conversion Mode: Enable (开启后,它会不停地采集,不用每次都按开始键)。Discontinuous Conversion Mode: Disable。
- Mode:勾选

- 时钟配置 (Clock Configuration):
- 重点:找到
ADC Prescaler,设置为 /6 (12MHz) 或 /8 (9MHz)。千万别选 /2 或 /4!
- 重点:找到
时钟源选择外部晶振


- 生成代码。
3.2 代码编写
在 main.c 中添加变量和逻辑:
/* USER CODE BEGIN PV */
uint16_t ADC_Value = 0; // 存放原始值 (0-4095)
float Voltage = 0.0; // 存放换算后的电压
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
// 1. 开启 ADC 自校准 (建议加上,提高精度)
HAL_ADCEx_Calibration_Start(&hadc1);
// 2. 启动 ADC 转换
HAL_ADC_Start(&hadc1);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
while (1)
{
// 3. 等待转换完成 (轮询方式,第二个参数是超时时间)
if (HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
{
// 4. 获取原始值
ADC_Value = HAL_ADC_GetValue(&hadc1);
// 5. 换算电压: V = (ADC / 4095) * 3.3
Voltage = (float)ADC_Value / 4095.0 * 3.3;
// 打印调试 (需要重定向printf)
// printf("ADC: %d, Vol: %.2fV\r\n", ADC_Value, Voltage);
}
HAL_Delay(100);
}
/* USER CODE END 3 */
四、 实战二:多通道 DMA 采集(进阶必备)
痛点:如果你要同时测 4 个传感器,用上面的轮询法,CPU 就要一直等 ADC 忙完,效率极低。
解法:DMA (Direct Memory Access)。让 DMA 搬运工在后台把 ADC 数据直接搬到内存数组里,CPU 只要去数组里取现成的数就行了!
4.1 CubeMX 配置步骤
假设我们要测两个通道:PA1 (电位器) 和 内部温度传感器。
- ADC设置:
- Mode:勾选
IN1和Temperature Sensor Channel。
- Mode:勾选

-
- Parameter Settings:
- 找到 ADC_Regular_ConversionMode(规则组转换模式)这一栏。
- 找到 Number of Conversion(转换数量)。它现在的默认值肯定是 1。
- 手动把 1 改成 2(或者你勾选的通道总数)
Scan Conversion Mode: Enabled (自动开启,因为选了多通道)。Continuous Conversion Mode: Enabled (不停地轮询扫描)。DMA Continuous Requests: Enabled (关键!让 DMA 也不停地搬运)。
- Parameter Settings:

-
-
- Rank (采样顺序):
- Rank 1: Channel 1 (PA1)。
- Rank 2: Temperature Sensor。
- 注意:温度传感器采样时间建议设最大 (239.5 Cycles)。
- Rank (采样顺序):
-
- DMA设置 (System Core -> DMA):
- 点击
Add-> SelectADC1。 - Mode:
Circular(循环模式,这很重要,否则搬运一次就停了)。 - Data Width:
Half Word(16bit),因为 ADC 数据是 12位的,需要用 16位存。 - Memory Increment: 勾选 (每次搬运后,内存地址+1,存到数组下一个位置)。
- 点击

4.2 代码编写
无需在 while(1) 里写任何 ADC 代码,全自动!
/* USER CODE BEGIN PV */
// 定义一个数组存放数据,长度=通道数
uint16_t ADC_DMA_Buffer[2];
// Buffer[0] 存放 PA1 电位器
// Buffer[1] 存放 内部温度
/* USER CODE END PV */
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);
// 启动 ADC 的 DMA 模式
// 这里的参数是:句柄、接收数组地址、数据长度
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)ADC_DMA_Buffer, 2);
/* USER CODE END 2 */
/* USER CODE BEGIN 3 */
while (1)
{
// 直接读数组就是最新数据!CPU 零负担!
float vol_pa1 = (float)ADC_DMA_Buffer[0] / 4095.0 * 3.3;
// STM32F103 内部温度计算公式 (V25=1.43V, Avg_Slope=4.3mV/C)
// Temp = (V25 - V_sense) / Avg_Slope + 25
float vol_temp = (float)ADC_DMA_Buffer[1] / 4095.0 * 3.3;
float temperature = (1.43 - vol_temp) / 0.0043 + 25.0;
// printf("Vol: %.2f V, Temp: %.1f C\r\n", vol_pa1, temperature);
HAL_Delay(500);
}
/* USER CODE END 3 */
五、 常见问题 FAQ
Q1: 读出来的电压为什么会波动?
- 电源纹波:USB 供电不稳,建议并联 100nF 电容。
- 采样时间太短:信号源阻抗匹配没做好,增加 Sampling Time。
- 软件滤波:在软件里做“均值滤波”,采集 10 次取平均值。
Q2: 多通道采集数据错位了?
- 检查 DMA 是否开启了 Circular 模式。
- 检查代码中
HAL_ADC_Start_DMA的第三个参数长度是否和通道数一致。
Q3: 为什么读到的值一直是 4095?
- 引脚可能悬空了。
- 输入电压超过了 3.3V(这可能烧坏 IO 口!)。
六、 总结
通过本文,我们掌握了:
- SAR ADC 原理:逐次逼近,12位精度。
- CubeMX 核心配置:时钟分频 (<14MHz) 是关键。
- 两种模式:
- 轮询模式:适合低频、单次采集。
- DMA 模式:适合多通道、高速、连续采集(推荐)。
掌握了 ADC,你就可以连接光敏、热敏、摇杆、火焰传感器等各种模拟器件,真正打通单片机与物理世界的桥梁!
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐



所有评论(0)