模数转换器 ADC
基于 STM32F103 · 零基础讲解 ADC 原理、转换模式、通道配置、标准库与 HAL 库编程。
🎯学习目标
- 了解 ADC 的基本概念、作用和应用场景;
- 理解 STM32F103 ADC 的主要特性(分辨率、通道数、转换时间等);
- 掌握 ADC 的四种工作模式及其适用场景;
- 理解 ADC 通道、采样时间和转换时间的关系;
- 掌握 ADC 的完整配置流程(时钟、GPIO、参数、校准);
- 理解 ADC 校准的原理和作用;
- 掌握 ADC+DMA 传输的原理和配置方法;
- 熟悉 ADC 相关的标准库函数和 HAL 库函数;
- 能够独立完成单通道、多通道 ADC 采集程序的编写。
1ADC 概述
ADC(Analog-to-Digital Converter,模数转换器)将模拟信号(如电压)转换为数字信号,供 CPU 处理。
现实世界是模拟的(温度、光线、声音、压力),但单片机只能处理数字信号(0和1)。ADC 就像翻译官,把模拟信号翻译成数字信号。
生活例子:
- 🌡️ 温度传感器输出 0.75V → ADC 转换成数字 3000 → CPU 计算出温度 30°C
- 💡 光敏电阻输出 2.5V → ADC 转换成数字 3100 → CPU 判断是白天还是黑夜
- 🎤 麦克风输出变化的电压波形 → ADC 连续采样 → CPU 得到数字音频
想象 ADC 是一个听写员:
- 采样(Sample):听写员竖起耳朵听一下(读取当前电压)
- 保持(Hold):把听到的内容记在脑子里(保持电压不变)
- 量化(Quantize):把听到的声音对应到具体的数字(把电压分成 4096 个等级)
- 编码(Encode):把数字写在纸上(输出 12 位二进制数)
整个过程就像把连续变化的电压切成4096 个小格子,然后看电压落在哪个格子里。
2ADC 主要特性
STM32F103 的 ADC 是一款12 位逐次逼近型模数转换器,具有以下主要特性:
📊 核心参数一览
| 特性 | 参数 | 说明 |
|---|---|---|
| 分辨率 | 12 位 | 转换结果范围 0~4095(2¹² - 1) |
| ADC 数量 | 3 个(ADC1/ADC2/ADC3) | 可独立工作或配合使用 |
| 外部通道数 | ADC1/2:16 个;ADC3:8 个 | 对应不同 GPIO 引脚 |
| 内部通道 | 2 个 | 温度传感器、内部参考电压 |
| 转换时间 | 最短 1.17 μs | 采样时间 + 12.5 个周期 |
| 供电电压 | 2.4V ~ 3.6V | VDDA 引脚供电 |
| 参考电压 | VREF+ = VDDA | 输入电压不能超过此值 |
| 输入范围 | 0 ~ VREF+ | 即 0 ~ 3.3V(典型) |
ADC 值 = 4095 × (模拟输入电压 / 参考电压)
实际电压 = ADC 值 × 参考电压 / 4095
例如:参考电压 3.3V,输入 1.65V,ADC 值 = 4095 × 1.65 / 3.3 = 2047
反之:ADC 值 2047,实际电压 = 2047 × 3.3 / 4095 ≈ 1.65V
分辨率就是 ADC 能把电压"切成多少份":
- 8 位:切成 256 份(2⁸),精度 = 3.3V / 256 ≈ 12.9 mV
- 10 位:切成 1024 份(2¹⁰),精度 = 3.3V / 1024 ≈ 3.2 mV
- 12 位:切成 4096 份(2¹²),精度 = 3.3V / 4096 ≈ 0.8 mV
份数越多,精度越高,能分辨的电压变化越小。STM32F103 的 12 位 ADC 可以分辨 0.8mV 的电压变化!
📌 ADC 通道与 GPIO 引脚对应表(ADC1/ADC2)
| 通道 | GPIO | 通道 | GPIO |
|---|---|---|---|
| ADC_Channel_0 | PA0 | ADC_Channel_8 | PB0 |
| ADC_Channel_1 | PA1 | ADC_Channel_9 | PB1 |
| ADC_Channel_2 | PA2 | ADC_Channel_10 | PC0 |
| ADC_Channel_3 | PA3 | ADC_Channel_11 | PC1 |
| ADC_Channel_4 | PA4 | ADC_Channel_12 | PC2 |
| ADC_Channel_5 | PA5 | ADC_Channel_13 | PC3 |
| ADC_Channel_6 | PA6 | ADC_Channel_14 | PC4 |
| ADC_Channel_7 | PA7 | ADC_Channel_15 | PC5 |
3ADC 工作模式
ADC 有多种工作模式,可以通过组合实现不同的采集需求:
🔄 转换模式(单次 vs 连续)
单次转换
Single连续转换
Continuous单次转换像按快门拍照:按一下拍一张,想再拍得再按一次。适合"我需要看一下现在电压是多少"。
连续转换像开录像机:开机后一直录,不需要反复按。适合"我要持续监控电压变化"。
📊 通道模式(扫描 vs 间断)
扫描模式
Scan间断模式
Discontinuous扫描模式像自动扫地机器人:设定好路线(通道顺序),机器人自动按顺序清扫(转换),不需要你一个个房间指挥。
没有扫描模式时,你得手动指挥:先扫房间0,再扫房间1,再扫房间2……很麻烦。
⚡ 触发方式(软件 vs 硬件)
软件触发
Software硬件触发
External四种模式可以组合使用,常见组合:
- 单次 + 非扫描:最简单,采集一个通道一次
- 连续 + 非扫描:持续采集一个通道
- 单次 + 扫描:依次采集多个通道一次
- 连续 + 扫描:循环采集多个通道(最常用)
4ADC 通道与采样时间
⏱️ 转换时间计算
ADC 的转换时间由两部分组成:
总转换时间 = 采样时间 + 12.5 个 ADC 时钟周期
其中 12.5 个周期是固定的逐次逼近转换时间(SAR)。
ADC 时钟频率(ADCCLK)由 PCLK2 分频得到,最大不能超过 14 MHz:
| 分频系数 | ADCCLK (PCLK2=72MHz) | 说明 |
|---|---|---|
| /2 | 36 MHz | ❌ 超过 14MHz,不可用 |
| /4 | 18 MHz | ❌ 超过 14MHz,不可用 |
| /6 | 12 MHz | ✅ 可用 |
| /8 | 9 MHz | ✅ 推荐 |
📋 可选采样时间
STM32F103 提供 7 种采样时间可选:
| 采样时间设置 | 周期数 | 时间 (ADCCLK=12MHz) | 适用场景 |
|---|---|---|---|
| ADC_SampleTime_1Cycles5 | 1.5 周期 | 0.125 μs | 高速采集,信号源阻抗低 |
| ADC_SampleTime_7Cycles5 | 7.5 周期 | 0.625 μs | 一般用途 |
| ADC_SampleTime_13Cycles5 | 13.5 周期 | 1.125 μs | 一般用途 |
| ADC_SampleTime_28Cycles5 | 28.5 周期 | 2.375 μs | 信号源阻抗较高 |
| ADC_SampleTime_41Cycles5 | 41.5 周期 | 3.458 μs | 信号源阻抗较高 |
| ADC_SampleTime_55Cycles5 | 55.5 周期 | 4.625 μs | 高精度采集 |
| ADC_SampleTime_71Cycles5 | 71.5 周期 | 5.958 μs | 高精度采集 |
| ADC_SampleTime_239Cycles5 | 239.5 周期 | 19.958 μs | 最高精度 |
采样时间就像倒水的时间:你要用杯子接水(采样),水龙头开的时间越长,杯子里的水越满越稳定。
- 采样时间太短:杯子没接满,水不准(采样值不稳定)
- 采样时间太长:接得很准,但速度慢(采集频率低)
- 信号源阻抗高:水管细,需要更长时间才能接满(需要更长采样时间)
实际建议:一般使用 ADC_SampleTime_55Cycles5,兼顾精度和速度。
条件:ADCCLK = 12 MHz,采样时间 = 55.5 周期
计算:
- 总周期数 = 55.5 + 12.5 = 68 个周期
- 总转换时间 = 68 / 12MHz = 5.67 μs
- 最大采样率 = 1 / 5.67μs ≈ 176 kSPS(每秒采样 17.6 万次)
5ADC 配置流程
使用 ADC 的完整配置步骤:
- 使能时钟:使能 ADC 时钟和对应 GPIO 时钟;
- 配置 GPIO:将 GPIO 配置为模拟输入模式;
- 配置 ADC 参数:设置工作模式、触发源、通道数、对齐方式等;
- 配置通道:设置通道的采样顺序和采样时间;
- 使能 ADC:开启 ADC 模块;
- 执行校准:复位校准 + 启动校准,提高精度;
- 启动转换:软件触发或等待硬件触发;
- 读取结果:等待转换完成标志,读取数据寄存器。
// ============ 1. 使能时钟 ============
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // ADC1 时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // GPIOA 时钟
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADCCLK = 72MHz/6 = 12MHz
// ============ 2. 配置 GPIO ============
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PA0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN; // 模拟输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
// ============ 3. 配置 ADC 参数 ============
ADC_InitTypeDef ADC_InitStructure;
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 独立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 关闭扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None; // 软件触发
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右对齐
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1 个通道
ADC_Init(ADC1, &ADC_InitStructure);
// ============ 4. 配置通道 ============
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// ============ 5. 使能 ADC ============
ADC_Cmd(ADC1, ENABLE);
// ============ 6. ADC 校准 ============
ADC_ResetCalibration(ADC1); // 复位校准
while(ADC_GetResetCalibrationStatus(ADC1)); // 等待复位完成
ADC_StartCalibration(ADC1); // 启动校准
while(ADC_GetCalibrationStatus(ADC1)); // 等待校准完成
// ============ 7. 启动转换 ============
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 软件触发
// ============ 8. 读取结果 ============
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC)); // 等待转换完成
uint16_t adc_value = ADC_GetConversionValue(ADC1); // 读取结果
float voltage = adc_value * 3.3 / 4095; // 计算电压
- 必须先使能时钟:ADC 和 GPIO 的时钟都要开,否则配置无效
- GPIO 必须配置为模拟输入:GPIO_Mode_AIN,否则影响精度
- 必须执行校准:复位校准 + 启动校准,两步都不能少
- 等待标志位:转换完成后才能读取结果(EOC 标志置 1)
6ADC 校准与 DMA 传输
🎯 ADC 校准
ADC 校准是为了消除芯片制造工艺偏差带来的误差,提高转换精度。
就像电子秤归零:新买的秤可能有误差,放上去显示 0.1kg,需要按"归零"键让它显示 0.0kg。
ADC 校准也是同样的道理:芯片出厂时 ADC 可能有微小误差,校准后可以消除这个误差。
// 步骤 1:复位校准寄存器
ADC_ResetCalibration(ADC1);
// 步骤 2:等待复位完成
while(ADC_GetResetCalibrationStatus(ADC1));
// 步骤 3:启动校准
ADC_StartCalibration(ADC1);
// 步骤 4:等待校准完成
while(ADC_GetCalibrationStatus(ADC1));
注意:校准必须在 ADC 使能(ADC_Cmd)之后执行,每次上电只需校准一次。
📦 ADC + DMA 传输
当需要采集大量数据时(如多通道扫描、连续采集),使用 DMA 可以大大减轻 CPU 负担。
想象你在搬砖:
- 不用 DMA:你(CPU)自己一块一块搬,搬砖时不能干别的事
- 用 DMA:你雇了个搬运工(DMA),搬运工自动搬砖,你可以去干别的事
ADC 每次转换完,DMA 自动把结果搬到内存,CPU 不需要干预,效率大大提高!
- 多通道扫描:一次扫描多个通道,DMA 自动搬运每个通道的结果
- 高速连续采集:采样率很高,CPU 来不及处理
- 大数据量采集:如音频采样、波形记录
// 1. 使能 DMA 时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 2. 配置 DMA
DMA_InitTypeDef DMA_InitStructure;
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR; // ADC 数据寄存器地址
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&adc_buffer; // 内存缓冲区地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 外设→内存
DMA_InitStructure.DMA_BufferSize = 3; // 传输 3 个数据
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外设地址不自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 内存地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
// 3. 使能 DMA 通道
DMA_Cmd(DMA1_Channel1, ENABLE);
// 4. ADC 使能 DMA
ADC_DMACmd(ADC1, ENABLE);
// 5. 配置 ADC 为扫描模式 + 连续转换
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE;
7标准库函数
常用 ADC 标准库函数(源码在 stm32f10x_adc.c):
| 类型 | 函数 | 功能 |
|---|---|---|
| 初始化 | ADC_Init() | 按结构体参数初始化 ADC |
ADC_DeInit() | 恢复默认复位值 | |
| 使能控制 | ADC_Cmd() | 使能/禁用 ADC |
ADC_DMACmd() | 使能/禁用 ADC 的 DMA 请求 | |
| 通道配置 | ADC_RegularChannelConfig() | 配置规则通道(通道号、序号、采样时间) |
ADC_InjectedChannelConfig() | 配置注入通道 | |
ADC_InjectedSequencerLengthConfig() | 设置注入通道序列长度 | |
| 转换控制 | ADC_SoftwareStartConvCmd() | 软件启动规则通道转换 |
ADC_ExternalTrigConvCmd() | 使能/禁用外部触发 | |
ADC_AnalogWatchdogCmd() | 使能模拟看门狗 | |
| 校准 | ADC_ResetCalibration() | 复位校准 |
ADC_StartCalibration() | 启动校准 | |
| 状态查询 | ADC_GetResetCalibrationStatus() | 获取复位校准状态 |
ADC_GetCalibrationStatus() | 获取校准状态 | |
| 标志与中断 | ADC_GetFlagStatus() | 获取标志位(EOC、STRT 等) |
ADC_ClearFlag() | 清除标志位 | |
ADC_ITConfig() | 配置 ADC 中断 | |
| 数据读取 | ADC_GetConversionValue() | 获取规则通道转换结果 |
// 配置规则通道
void ADC_RegularChannelConfig(
ADC_TypeDef* ADCx, // ADC1、ADC2 或 ADC3
uint8_t ADC_Channel, // 通道号:ADC_Channel_0 ~ ADC_Channel_17
uint8_t Rank, // 转换顺序:1~16
uint8_t ADC_SampleTime // 采样时间:ADC_SampleTime_xxCycles5
);
// 获取转换结果
uint16_t ADC_GetConversionValue(ADC_TypeDef* ADCx);
// 标志位操作
FlagStatus ADC_GetFlagStatus(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
void ADC_ClearFlag(ADC_TypeDef* ADCx, uint8_t ADC_FLAG);
// 常用标志位
// ADC_FLAG_EOC - 转换完成标志
// ADC_FLAG_STRT - 转换开始标志
// ADC_FLAG_JEOC - 注入通道转换完成标志
8HAL 库函数
HAL 库中 ADC 相关函数更加简洁易用:
| 类型 | 函数 | 功能 |
|---|---|---|
| 初始化 | HAL_ADC_Init() | 初始化 ADC |
HAL_ADC_DeInit() | 反初始化 ADC | |
| 通道配置 | HAL_ADC_ConfigChannel() | 配置 ADC 通道 |
| 转换控制 | HAL_ADC_Start() | 启动 ADC(轮询方式) |
HAL_ADC_Stop() | 停止 ADC | |
HAL_ADC_PollForConversion() | 等待转换完成 | |
| 数据读取 | HAL_ADC_GetValue() | 获取转换结果 |
| DMA 方式 | HAL_ADC_Start_DMA() | 启动 ADC + DMA |
HAL_ADC_Stop_DMA() | 停止 ADC + DMA | |
| 校准 | HAL_ADCEx_Calibration_Start() | ADC 校准(F1 系列) |
// 启动 ADC 转换
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc);
// 等待转换完成
HAL_StatusTypeDef HAL_ADC_PollForConversion(
ADC_HandleTypeDef* hadc,
uint32_t Timeout // 超时时间(ms)
);
// 获取转换结果
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc);
// 启动 ADC + DMA
HAL_StatusTypeDef HAL_ADC_Start_DMA(
ADC_HandleTypeDef* hadc,
uint32_t* pData, // 数据缓冲区指针
uint32_t Length // 数据长度
);
使用 STM32CubeMX 可以图形化配置 ADC:
- 选择 ADC 通道对应的引脚
- 配置 ADC 参数(分辨率、对齐方式、触发源等)
- 配置 DMA(如果需要)
- 自动生成初始化代码
开发者只需在用户代码区添加启动和读取代码即可。
// CubeMX 生成的初始化代码(已自动添加)
// hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
// hadc1.Init.ContinuousConvMode = DISABLE;
// hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
// hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
// hadc1.Init.NbrOfConversion = 1;
// 用户代码:启动转换并读取结果
HAL_ADC_Start(&hadc1); // 启动 ADC
HAL_ADC_PollForConversion(&hadc1, 100); // 等待转换完成,超时 100ms
uint32_t adc_value = HAL_ADC_GetValue(&hadc1); // 读取结果
float voltage = adc_value * 3.3 / 4095; // 计算电压
⭐重点例题
题目:用 ADC1 通道 0(PA0)采集电位器电压,转换为实际电压值并打印。
思路:① 开时钟 → ② 配 GPIO → ③ 配 ADC → ④ 校准 → ⑤ 采集 → ⑥ 计算电压。#include "stm32f10x.h"
void ADC1_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6); // ADCCLK = 12MHz
// 2. 配置 PA0 为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置 ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = DISABLE;
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE;
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 1;
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 配置通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
// 5. 使能并校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
uint16_t ADC1_Read(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
return ADC_GetConversionValue(ADC1);
}
int main(void)
{
uint16_t adc_val;
float voltage;
ADC1_Init();
USART1_Init(115200); // 假设已实现串口初始化
while(1)
{
adc_val = ADC1_Read();
voltage = adc_val * 3.3 / 4095;
printf("ADC值: %d, 电压: %.2fV\r\n", adc_val, voltage);
delay_ms(500);
}
}
关键点:① GPIO 必须配置为模拟输入(AIN);② 校准必须在使能之后;③ 转换完成才能读取结果。
题目:用 ADC1 扫描采集 PA0(通道0)、PA1(通道1)、PA2(通道2)三个通道的电压。
思路:开启扫描模式,配置 3 个通道,每次转换后读取结果。uint16_t adc_buffer[3]; // 存储 3 个通道的结果
void ADC1_MultiChannel_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 1. 使能时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 2. 配置 PA0、PA1、PA2 为模拟输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置 ADC(扫描模式,3 个通道)
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE; // 开启扫描
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 单次扫描
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = 3; // 3 个通道
ADC_Init(ADC1, &ADC_InitStructure);
// 4. 配置 3 个通道(注意序号 1、2、3)
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_2, 3, ADC_SampleTime_55Cycles5);
// 5. 使能并校准
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
}
void ADC1_MultiChannel_Read(void)
{
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
// 依次读取 3 个通道的结果
for(int i = 0; i < 3; i++)
{
while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));
adc_buffer[i] = ADC_GetConversionValue(ADC1);
ADC_ClearFlag(ADC1, ADC_FLAG_EOC); // 清除标志,准备读下一个
}
}
关键点:① 扫描模式需要设置通道数;② 每个通道的序号(Rank)要按顺序设置;③ 每次读取后要清除 EOC 标志。
题目:用 ADC1 + DMA 连续采集 PA0、PA1 两个通道,数据自动存入数组。
思路:配置 DMA,开启 ADC 扫描+连续模式,DMA 自动搬运数据。#define ADC_CHANNELS 2
uint16_t adc_buffer[ADC_CHANNELS]; // DMA 目标缓冲区
void ADC1_DMA_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
// 1. 使能时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1 | RCC_APB2Periph_GPIOA, ENABLE);
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 2. 配置 GPIO
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AIN;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 3. 配置 DMA(DMA1 通道 1 对应 ADC1)
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&ADC1->DR;
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)adc_buffer;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = ADC_CHANNELS;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // 循环模式
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel1, ENABLE);
// 4. 配置 ADC
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent;
ADC_InitStructure.ADC_ScanConvMode = ENABLE;
ADC_InitStructure.ADC_ContinuousConvMode = ENABLE; // 连续转换
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right;
ADC_InitStructure.ADC_NbrOfChannel = ADC_CHANNELS;
ADC_Init(ADC1, &ADC_InitStructure);
// 5. 配置通道
ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5);
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 2, ADC_SampleTime_55Cycles5);
// 6. 使能 DMA、使能 ADC、校准
ADC_DMACmd(ADC1, ENABLE);
ADC_Cmd(ADC1, ENABLE);
ADC_ResetCalibration(ADC1);
while(ADC_GetResetCalibrationStatus(ADC1));
ADC_StartCalibration(ADC1);
while(ADC_GetCalibrationStatus(ADC1));
// 7. 启动连续转换
ADC_SoftwareStartConvCmd(ADC1, ENABLE);
}
int main(void)
{
ADC1_DMA_Init();
while(1)
{
// 数据自动更新在 adc_buffer 中,无需 CPU 干预
float voltage0 = adc_buffer[0] * 3.3 / 4095;
float voltage1 = adc_buffer[1] * 3.3 / 4095;
printf("CH0: %.2fV, CH1: %.2fV\r\n", voltage0, voltage1);
delay_ms(1000);
}
}
关键点:① DMA 使用循环模式持续搬运;② ADC 开启扫描+连续模式;③ 数据自动更新,CPU 无需等待。
题目:使用 HAL 库实现与例题 1 相同的功能。
// CubeMX 配置:
// ADC1: Channel 0, Scan Disable, Continuous Disable, Software Trigger
// GPIO: PA0 -> ADC1_IN0
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
uint32_t adc_value;
float voltage;
char buf[50];
while(1)
{
HAL_ADC_Start(&hadc1); // 启动 ADC
HAL_ADC_PollForConversion(&hadc1, 100); // 等待转换完成
adc_value = HAL_ADC_GetValue(&hadc1); // 读取结果
voltage = adc_value * 3.3 / 4095;
sprintf(buf, "ADC: %lu, Voltage: %.2fV\r\n", adc_value, voltage);
HAL_UART_Transmit(&huart1, (uint8_t*)buf, strlen(buf), 100);
HAL_Delay(500);
}
}
🎯自测(点击展开)
ADC 的全称是什么?作用是什么?
STM32F103 ADC 的分辨率是多少位?转换结果范围是多少?
ADC 的转换公式是什么?
ADC 通道对应的 GPIO 应配置为什么模式?
ADC 的转换时间由哪两部分组成?
ADC 校准的步骤是什么?
ADC 扫描模式的作用是什么?
ADC 单次转换和连续转换有什么区别?
为什么 ADC + DMA 可以提高效率?
HAL 库中启动 ADC 转换的函数是什么?
STM32F103 有几个 ADC?每个 ADC 最多有多少个外部通道?
ADC 时钟频率最大不能超过多少?
📝强化题库
选择题点选即时判分;填空题输入后"检查"或"显示答案"。