🎓 总站 🏠 本课目录 01 概述 02 ARM与STM32 03 开发环境 04 最小系统与C 05 GPIO 06 中断 07 串口通信 08 DMA 09 定时器 10 ADC 11 从模块到项目 12 嵌入式操作系统
智能嵌入式系统设计 · 第10章

模数转换器 ADC

基于 STM32F103 · 零基础讲解 ADC 原理、转换模式、通道配置、标准库与 HAL 库编程。

📚 学习进度
0%

🎯学习目标

  • 了解 ADC 的基本概念、作用和应用场景;
  • 理解 STM32F103 ADC 的主要特性(分辨率、通道数、转换时间等);
  • 掌握 ADC 的四种工作模式及其适用场景;
  • 理解 ADC 通道、采样时间和转换时间的关系;
  • 掌握 ADC 的完整配置流程(时钟、GPIO、参数、校准);
  • 理解 ADC 校准的原理和作用;
  • 掌握 ADC+DMA 传输的原理和配置方法;
  • 熟悉 ADC 相关的标准库函数和 HAL 库函数;
  • 能够独立完成单通道、多通道 ADC 采集程序的编写。

1ADC 概述

ADC(Analog-to-Digital Converter,模数转换器)将模拟信号(如电压)转换为数字信号,供 CPU 处理。

💡 一句话理解ADC 就是单片机的"耳朵",能听懂外界的模拟声音(电压),并转换成数字信号让 CPU 理解。
💡 通俗理解:为什么需要 ADC?

现实世界是模拟的(温度、光线、声音、压力),但单片机只能处理数字信号(0和1)。ADC 就像翻译官,把模拟信号翻译成数字信号。

生活例子:

  • 🌡️ 温度传感器输出 0.75V → ADC 转换成数字 3000 → CPU 计算出温度 30°C
  • 💡 光敏电阻输出 2.5V → ADC 转换成数字 3100 → CPU 判断是白天还是黑夜
  • 🎤 麦克风输出变化的电压波形 → ADC 连续采样 → CPU 得到数字音频
💡 通俗理解:ADC 的工作过程

想象 ADC 是一个听写员

  1. 采样(Sample):听写员竖起耳朵听一下(读取当前电压)
  2. 保持(Hold):把听到的内容记在脑子里(保持电压不变)
  3. 量化(Quantize):把听到的声音对应到具体的数字(把电压分成 4096 个等级)
  4. 编码(Encode):把数字写在纸上(输出 12 位二进制数)

整个过程就像把连续变化的电压切成4096 个小格子,然后看电压落在哪个格子里。

模拟信号 连续变化的电压 ADC 转换 采样→量化→编码 数字信号 0~4095 的数值 实际例子:温度传感器采集 传感器输出 0.75V → ADC 转换为 3000 → CPU 计算:3000 × 3.3V / 4095 ≈ 2.42V(对应温度值) ADC 值 = 4095 × (输入电压 / 参考电压) = 4095 × (0.75 / 3.3) ≈ 931
图1 · ADC 工作原理示意图

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.6VVDDA 引脚供电
参考电压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_0PA0ADC_Channel_8PB0
ADC_Channel_1PA1ADC_Channel_9PB1
ADC_Channel_2PA2ADC_Channel_10PC0
ADC_Channel_3PA3ADC_Channel_11PC1
ADC_Channel_4PA4ADC_Channel_12PC2
ADC_Channel_5PA5ADC_Channel_13PC3
ADC_Channel_6PA6ADC_Channel_14PC4
ADC_Channel_7PA7ADC_Channel_15PC5
💡 记忆技巧PA0~PA7 对应通道 0~7,PB0~PB1 对应通道 8~9,PC0~PC5 对应通道 10~15。ADC3 只有通道 0~7(PA0~PA7)。

3ADC 工作模式

ADC 有多种工作模式,可以通过组合实现不同的采集需求:

🔄 转换模式(单次 vs 连续)

🔄

单次转换

Single
每次触发只转换一次,转换完成后自动停止。适合偶尔采集,如按键检测电池电压
🔁

连续转换

Continuous
转换完成后自动开始下一次,循环往复。适合实时监测,如温度、光照持续采集
💡 通俗理解:单次 vs 连续

单次转换按快门拍照:按一下拍一张,想再拍得再按一次。适合"我需要看一下现在电压是多少"。

连续转换开录像机:开机后一直录,不需要反复按。适合"我要持续监控电压变化"。

📊 通道模式(扫描 vs 间断)

📊

扫描模式

Scan
自动按顺序转换多个通道(如通道0→1→2→...)。适合多路信号同时采集,如多路传感器
🔢

间断模式

Discontinuous
将通道分成若干组,每次触发转换一组。适合分批采集,减少 CPU 干预
💡 通俗理解:扫描模式

扫描模式自动扫地机器人:设定好路线(通道顺序),机器人自动按顺序清扫(转换),不需要你一个个房间指挥。

没有扫描模式时,你得手动指挥:先扫房间0,再扫房间1,再扫房间2……很麻烦。

⚡ 触发方式(软件 vs 硬件)

💻

软件触发

Software
程序直接调用函数启动转换。简单直接,适合简单应用。

硬件触发

External
由外部事件启动转换(定时器、EXTI等)。精确控制采样时刻。
⭐ 模式组合(核心考点)

四种模式可以组合使用,常见组合:

  • 单次 + 非扫描:最简单,采集一个通道一次
  • 连续 + 非扫描:持续采集一个通道
  • 单次 + 扫描:依次采集多个通道一次
  • 连续 + 扫描:循环采集多个通道(最常用)

4ADC 通道与采样时间

⏱️ 转换时间计算

ADC 的转换时间由两部分组成:

⭐ 转换时间公式

总转换时间 = 采样时间 + 12.5 个 ADC 时钟周期

其中 12.5 个周期是固定的逐次逼近转换时间(SAR)。

ADC 时钟频率(ADCCLK)由 PCLK2 分频得到,最大不能超过 14 MHz

分频系数ADCCLK (PCLK2=72MHz)说明
/236 MHz❌ 超过 14MHz,不可用
/418 MHz❌ 超过 14MHz,不可用
/612 MHz✅ 可用
/89 MHz✅ 推荐

📋 可选采样时间

STM32F103 提供 7 种采样时间可选:

采样时间设置周期数时间 (ADCCLK=12MHz)适用场景
ADC_SampleTime_1Cycles51.5 周期0.125 μs高速采集,信号源阻抗低
ADC_SampleTime_7Cycles57.5 周期0.625 μs一般用途
ADC_SampleTime_13Cycles513.5 周期1.125 μs一般用途
ADC_SampleTime_28Cycles528.5 周期2.375 μs信号源阻抗较高
ADC_SampleTime_41Cycles541.5 周期3.458 μs信号源阻抗较高
ADC_SampleTime_55Cycles555.5 周期4.625 μs高精度采集
ADC_SampleTime_71Cycles571.5 周期5.958 μs高精度采集
ADC_SampleTime_239Cycles5239.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 的完整配置步骤:

  1. 使能时钟:使能 ADC 时钟和对应 GPIO 时钟;
  2. 配置 GPIO:将 GPIO 配置为模拟输入模式;
  3. 配置 ADC 参数:设置工作模式、触发源、通道数、对齐方式等;
  4. 配置通道:设置通道的采样顺序和采样时间;
  5. 使能 ADC:开启 ADC 模块;
  6. 执行校准:复位校准 + 启动校准,提高精度;
  7. 启动转换:软件触发或等待硬件触发;
  8. 读取结果:等待转换完成标志,读取数据寄存器。
① 使能 ADC+GPIO 时钟 ② GPIO 设为模拟输入 ③ 配置 ADC 参数 ④ 配置通道采样时间 ⑤ 使能 ADC + 校准 ⑥ 启动转换 + 读取结果
图2 · 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?

想象你在搬砖

  • 不用 DMA:你(CPU)自己一块一块搬,搬砖时不能干别的事
  • 用 DMA:你雇了个搬运工(DMA),搬运工自动搬砖,你可以去干别的事

ADC 每次转换完,DMA 自动把结果搬到内存,CPU 不需要干预,效率大大提高!

⭐ DMA 适用场景
  • 多通道扫描:一次扫描多个通道,DMA 自动搬运每个通道的结果
  • 高速连续采集:采样率很高,CPU 来不及处理
  • 大数据量采集:如音频采样、波形记录
ADC + DMA 配置要点(标准库)
// 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 系列)
HAL 库关键函数原型
// 启动 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           // 数据长度
);
💡 HAL 库 ADC 配置

使用 STM32CubeMX 可以图形化配置 ADC:

  1. 选择 ADC 通道对应的引脚
  2. 配置 ADC 参数(分辨率、对齐方式、触发源等)
  3. 配置 DMA(如果需要)
  4. 自动生成初始化代码

开发者只需在用户代码区添加启动和读取代码即可。

HAL 库单通道采集完整示例
// 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;             // 计算电压

重点例题

例题 1:单通道 ADC 采集电压(标准库)

题目:用 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);② 校准必须在使能之后;③ 转换完成才能读取结果。
例题 2:多通道扫描采集(标准库)

题目:用 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 标志。
例题 3:ADC + DMA 连续采集(标准库)

题目:用 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 无需等待。
例题 4:HAL 库单通道采集

题目:使用 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 的全称是什么?作用是什么?
ADC = Analog-to-Digital Converter,模数转换器。作用是将模拟信号(如电压)转换为数字信号,供 CPU 处理。
STM32F103 ADC 的分辨率是多少位?转换结果范围是多少?
12 位分辨率,转换结果范围 0~4095(2¹² - 1)。
ADC 的转换公式是什么?
ADC 值 = 4095 × (输入电压 / 参考电压)。反之:实际电压 = ADC 值 × 参考电压 / 4095。
ADC 通道对应的 GPIO 应配置为什么模式?
模拟输入模式(GPIO_Mode_AIN)。这是 ADC 专用模式,关闭施密特触发器,信号直连 ADC。
ADC 的转换时间由哪两部分组成?
总转换时间 = 采样时间 + 12.5 个 ADC 时钟周期。12.5 个周期是固定的逐次逼近转换时间。
ADC 校准的步骤是什么?
四步:① ADC_ResetCalibration() → ② 等待复位完成 → ③ ADC_StartCalibration() → ④ 等待校准完成。必须在 ADC_Cmd() 之后执行。
ADC 扫描模式的作用是什么?
扫描模式可以自动按顺序转换多个通道,适合多路信号同时采集。配合 DMA 可以自动将结果搬运到内存。
ADC 单次转换和连续转换有什么区别?
单次转换:每次触发只转换一次,完成后停止。连续转换:完成后自动开始下一次,适合实时监测。
为什么 ADC + DMA 可以提高效率?
DMA 自动将 ADC 转换结果搬运到内存,CPU 无需干预,可以同时处理其他任务,大大减轻 CPU 负担。
HAL 库中启动 ADC 转换的函数是什么?
HAL_ADC_Start(&hadc1) 启动 ADC,HAL_ADC_PollForConversion() 等待完成,HAL_ADC_GetValue() 读取结果。
STM32F103 有几个 ADC?每个 ADC 最多有多少个外部通道?
3 个 ADC(ADC1、ADC2、ADC3)。ADC1/ADC2 有 16 个外部通道,ADC3 有 8 个外部通道。
ADC 时钟频率最大不能超过多少?
14 MHz。ADCCLK 由 PCLK2 分频得到,常用 /6(12MHz)或 /8(9MHz)。

📝强化题库

选择题点选即时判分;填空题输入后"检查"或"显示答案"。

已答 0/0答对 0正确率
已答 0/0答对 0