tags: [source-summary] type: source source: "尚硅谷嵌入式技术之STM32单片机(进阶篇)V1.0.1 — ADC章节 + 配套代码32~35" author: "尚硅谷研究院" date: 2026-07-15
用生活理解:ADC 就像一把精密的电压尺子——输入 0~3.3V 的模拟电压,输出 0~4095 的数字(12 位)。就像用一把刻有 4096 个刻度的尺子量高度,输入 1.65V 时输出 2048(中间值)。采样时间就是"尺子读数需要的时间",采样时间越长读数越准。规则组和注入组就像"普通队列"和"VIP 通道"——VIP 可以插队。
ADC = Analog-to-Digital Converter(模数转换器),将连续的模拟信号转换为离散的数字信号。
STM32F103ZET6 共有 3 个 ADC 模块(ADC1/ADC2/ADC3):
| 特性 | 值 |
|---|---|
| 分辨率 | 12 位(输出范围 0~4095) |
| 通道数 | 最多 18 个通道(16 个外部引脚 + 2 个内部) |
| 转换时间 | 最快 1.17μs(14MHz 时钟下) |
| 输入电压 | 0 ~ VREF+(通常 = VDDA = 3.3V) |
| ADC 时钟 | 来自 APB2,经预分频器,最大 14MHz |
| 通道 | 连接 | 说明 |
|---|---|---|
| 通道16 | 内部温度传感器 | 测量芯片内部温度 |
| 通道17 | VREFINT | 内部参考电压(1.2V) |
STM32F103 使用逐次逼近型(SAR)ADC:
输入电压 VIN = 1.8V, VREF = 3.3V
第1步: VIN > 3.3V/2=1.65V ? → YES → Bit11=1, 余量 = 1.8-1.65 = 0.15V
第2步: VIN > 1.65+1.65/2=2.475V ? → NO → Bit10=0
第3步: VIN > 1.65+0.825/2=2.0625V ? → NO → Bit9=0
第4步: VIN > 1.65+0.4125/2=1.85625V ? → NO → Bit8=0
第5步: VIN > 1.65+0.20625/2=1.753125V ? → YES → Bit7=1
...
最终: 12 次比较后得到 12 位结果
数字值 = VIN / VREF × 4095 = 1.8 / 3.3 × 4095 ≈ 2234
特点:
ADC 有两种转换序列:
| 组 | 最大通道数 | 特点 | 用途 |
|---|---|---|---|
| 规则组 | 16 通道 | 常规采集,按顺序转换 | 周期性采集传感器数据 |
| 注入组 | 4 通道 | 可打断规则组的转换 | 紧急事件触发采集 |
类比:规则组是普通队列(一个个来),注入组是 VIP 通道——来了就插队。
| 模式 | 说明 | 适用场景 |
|---|---|---|
| 单次转换 | 每触发一次转换一个通道 | 按键触发采集 |
| 连续转换 | 转换完自动开始下一次 | 持续监测电压 |
| 扫描模式 | 按序列依次转换所有通道 | 多通道循环采集 |
| 间断模式 | 每触发一次转换一部分通道 | 超多通道分批次 |
| 触发源 | 说明 |
|---|---|
| 软件触发 | CR2.SWSTART=1 启动转换 |
| 定时器触发 | TIM1/2/3/4 的 TRGO 事件 |
| 外部引脚触发 | EXTI 线上升沿/下降沿 |
ADC_CLK = APB2_CLK / ADCPRE[1:0]
ADCPRE 分频系数:
00: /2 (36MHz) → 超限! ADC_CLK > 14MHz
01: /4 (18MHz) → 超限!
10: /6 (12MHz) → ✅ 推荐(12MHz < 14MHz)
11: /8 (9MHz) → ✅ 较慢但更准
参考:参考手册 §11(ADC 寄存器描述) APB2 时钟 = 72MHz,ADCPRE=Div6 → ADC_CLK=12MHz 是最均衡选择
每个通道可独立配置采样时间(SMPR1/SMPR2 寄存器):
| SMP[2:0] | 采样周期数 | 总转换时间(12MHz ADC_CLK) |
|---|---|---|
| 000 | 1.5 | (1.5+12.5) / 12MHz = 1.17μs |
| 001 | 7.5 | (7.5+12.5) / 12MHz = 1.67μs |
| 010 | 13.5 | 2.17μs |
| 011 | 28.5 | 3.42μs |
| 100 | 41.5 | 4.50μs |
| 101 | 55.5 | 5.67μs |
| 110 | 71.5 | 7.00μs |
| 111 | 239.5 | 21.00μs(最准) |
采样时间选择原则:
| 寄存器 | 地址偏移 | 功能 |
|---|---|---|
| SR | 0x00 | 状态寄存器(EOC=转换结束,STRT=开始) |
| CR1 | 0x04 | 控制1(SCAN=扫描,JAUTO=自动注入) |
| CR2 | 0x08 | 控制2(ADON=开启,CONT=连续,SWSTART=软触发,EXTTRIG=外部触发,ALIGN=对齐,DMA=DMA 使能) |
| SMPR1 | 0x0C | 采样时间寄存器1(通道10~17) |
| SMPR2 | 0x10 | 采样时间寄存器2(通道0~9) |
| SQR1 | 0x2C | 规则序列寄存器1(L=序列长度,SQ13~16) |
| SQR2 | 0x30 | 规则序列寄存器2(SQ7~12) |
| SQR3 | 0x34 | 规则序列寄存器3(SQ1~6) |
| DR | 0x4C | 规则组数据寄存器(12 位结果,右/左对齐) |
参考:参考手册 §11 完整寄存器位域定义
ADC 上电后需要校准以提高精度:
// 校准流程
ADC1->CR2 |= ADC_CR2_ADON; // 开启 ADC
// 等待稳定(约 2 个 ADC 时钟)
ADC1->CR2 |= ADC_CR2_CAL; // 开始校准
while (ADC1->CR2 & ADC_CR2_CAL) // 等待校准完成(CAL 位硬件自动清 0)
{
}
// 校准完成,ADC 准备就绪
HAL 库版本(CubeMX 自动在 MX_ADC1_Init() 后由用户手动调用):
HAL_ADCEx_Calibration_Start(&hadc1);
校准原理:ADC 内部测量一次 VREFINT,根据误差调整内部电容阵列。
本笔记包含 4 个实验项目,覆盖单/双通道 × 寄存器/HAL 的全部组合:
| 项目 | 通道数 | 实现方式 | DMA | 关键文件 |
|---|---|---|---|---|
| 32_adc_single_register | 单通道(PC0/CH10) | 寄存器 | 无 | Hardware/ADC/adc.c/.h, User/main.c |
| 33_adc_single_hal | 单通道(PC0/CH10) | HAL | 无 | Core/Src/adc.c, Core/Src/main.c |
| 34_adc_double_register | 双通道(PC0/CH10, PC2/CH12) | 寄存器 | DMA1 CH1 | Hardware/ADC/adc.c/.h, User/main.c |
| 35_adc_double_hal | 双通道(PC0/CH10, PC2/CH12) | HAL | DMA1 CH1 | Core/Src/adc.c, Core/Src/dma.c, Core/Src/main.c |
项目路径:stm32/32_adc_single_register
需求:使用 ADC1 通道 10(PC0)采集模拟电压,通过串口打印原始值和电压值。
配置摘要:
文件:32_adc_single_register/Hardware/ADC/adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
void ADC1_Init(void);
void ADC1_StartConvert(void);
double ADC1_ReadV(void);
#endif
文件:32_adc_single_register/Hardware/ADC/adc.c
#include "adc.h"
void ADC1_Init(void)
{
// 1. 时钟配置
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->CFGR |= RCC_CFGR_ADCPRE_1;
RCC->CFGR &= ~RCC_CFGR_ADCPRE_0;
// 2. GPIO配置:PC0 - 模拟输入
GPIOC->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
// 3. ADC配置
// 3.1 关闭扫描模式
ADC1->CR1 &= ~ADC_CR1_SCAN;
// 3.2 连续转换模式
ADC1->CR2 |= ADC_CR2_CONT;
// 3.3 右对齐
ADC1->CR2 &= ~ADC_CR2_ALIGN;
// 3.4 采样时间:001 - 7.5周期
ADC1->SMPR1 &= ~ADC_SMPR1_SMP10;
ADC1->SMPR1 |= ADC_SMPR1_SMP10_0;
// 3.5 通道序列
// 3.5.1 L = 0,1个通道
ADC1->SQR1 &= ~ADC_SQR1_L;
// 3.5.2 SQ1 = 通道10
ADC1->SQR3 &= ~ADC_SQR3_SQ1;
ADC1->SQR3 |= 10 << 0;
}
void ADC1_StartConvert(void)
{
// 1. 上电
ADC1->CR2 |= ADC_CR2_ADON;
// 2. 校准
ADC1->CR2 |= ADC_CR2_CAL;
while (ADC1->CR2 & ADC_CR2_CAL)
{}
// 3. 启动转换(连续模式)
ADC1->CR2 |= ADC_CR2_ADON;
// 4. 等待首次转换结束
while ((ADC1->SR & ADC_SR_EOC) == 0)
{}
}
double ADC1_ReadV(void)
{
return ADC1->DR * 3.3 / 4095;
}
文件:32_adc_single_register/User/main.c
#include "usart.h"
#include "adc.h"
#include "delay.h"
int main(void)
{
USART_Init();
ADC1_Init();
printf("Hello, world!\n");
ADC1_StartConvert();
while (1)
{
printf("V = %.2f\n", ADC1_ReadV());
Delay_ms(1000);
}
}
说明:连续转换模式下,只需在 ADC1_StartConvert() 中等待首次 EOC,后续硬件自动持续转换,ADC1_ReadV() 直接读取 DR 寄存器获得最新结果。
项目路径:stm32/33_adc_single_hal
需求:与实验一相同,使用 HAL 库实现 ADC1 通道 10(PC0)单通道连续采集。
文件:33_adc_single_hal/Core/Src/adc.c
#include "adc.h"
ADC_HandleTypeDef hadc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_7CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (adcHandle->Instance == ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if (adcHandle->Instance == ADC1)
{
__HAL_RCC_ADC1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0);
}
}
文件:33_adc_single_hal/Core/Src/main.c
#include "main.h"
#include "adc.h"
#include "usart.h"
#include "gpio.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
printf("Hello, world\n");
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start(&hadc1);
while (1)
{
double v = HAL_ADC_GetValue(&hadc1) * 3.3 / 4095;
printf("V = %.2f\n", v);
HAL_Delay(1000);
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2);
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_ADC;
PeriphClkInit.AdcClockSelection = RCC_ADCPCLK2_DIV6;
HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit);
}
说明:
adc.c(MX_ADC1_Init + MSP 回调)和 gpio.c(MX_GPIO_Init,使能 GPIOC 时钟)SystemClock_Config() 中用 RCC_ADCPCLK2_DIV6 设置 ADC 预分频HAL_ADCEx_Calibration_Start(CubeMX 不自动执行)HAL_ADC_Start → 循环中 HAL_ADC_GetValue(连续模式下自动更新 DR)项目路径:stm32/34_adc_double_register
需求:使用 ADC1 通道 10(PC0)和通道 12(PC2)采集两路模拟电压,通过 DMA1 通道 1 自动搬运转换结果到内存数组。
配置摘要:
文件:34_adc_double_register/Hardware/ADC/adc.h
#ifndef __ADC_H
#define __ADC_H
#include "stm32f10x.h"
void ADC1_Init(void);
void ADC1_DMA_Init(void);
void ADC1_DMA_StartConvert(uint32_t destAddr, uint8_t len);
#endif
文件:34_adc_double_register/Hardware/ADC/adc.c
#include "adc.h"
void ADC1_Init(void)
{
// 1. 时钟配置
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->CFGR |= RCC_CFGR_ADCPRE_1;
RCC->CFGR &= ~RCC_CFGR_ADCPRE_0;
RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;
// 2. GPIO配置:PC0、PC2 - 模拟输入
GPIOC->CRL &= ~(GPIO_CRL_MODE0 | GPIO_CRL_CNF0);
GPIOC->CRL &= ~(GPIO_CRL_MODE2 | GPIO_CRL_CNF2);
// 3. ADC配置
// 3.1 开启扫描模式
ADC1->CR1 |= ADC_CR1_SCAN;
// 3.2 连续转换模式
ADC1->CR2 |= ADC_CR2_CONT;
// 3.3 右对齐
ADC1->CR2 &= ~ADC_CR2_ALIGN;
// 3.4 采样时间:001 - 7.5周期(通道10 和 通道12)
ADC1->SMPR1 &= ~ADC_SMPR1_SMP10;
ADC1->SMPR1 |= ADC_SMPR1_SMP10_0;
ADC1->SMPR1 &= ~ADC_SMPR1_SMP12;
ADC1->SMPR1 |= ADC_SMPR1_SMP12_0;
// 3.5 通道序列
// 3.5.1 L = 1,2个通道
ADC1->SQR1 &= ~ADC_SQR1_L;
ADC1->SQR1 |= ADC_SQR1_L_0;
// 3.5.2 SQ1 = 通道10, SQ2 = 通道12
ADC1->SQR3 &= ~ADC_SQR3_SQ1;
ADC1->SQR3 |= 10 << 0;
ADC1->SQR3 &= ~ADC_SQR3_SQ2;
ADC1->SQR3 |= 12 << 5;
}
void ADC1_DMA_Init(void)
{
// 1. DMA时钟
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 2. 方向:从外设读取
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
// 3. 数据宽度:16位
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE_1;
DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE_1;
DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;
// 4. 地址增量:外设不增、存储器增
DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
// 5. 循环模式
DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
// 6. ADC使能DMA请求
ADC1->CR2 |= ADC_CR2_DMA;
}
void ADC1_DMA_StartConvert(uint32_t destAddr, uint8_t len)
{
// 0. DMA参数配置
DMA1_Channel1->CPAR = (uint32_t)&(ADC1->DR);
DMA1_Channel1->CMAR = destAddr;
DMA1_Channel1->CNDTR = len;
DMA1_Channel1->CCR |= DMA_CCR1_EN;
// 1. 上电
ADC1->CR2 |= ADC_CR2_ADON;
// 2. 校准
ADC1->CR2 |= ADC_CR2_CAL;
while (ADC1->CR2 & ADC_CR2_CAL)
{}
// 3. 启动转换
ADC1->CR2 |= ADC_CR2_ADON;
// 4. 等待首次转换结束
while ((ADC1->SR & ADC_SR_EOC) == 0)
{}
}
文件:34_adc_double_register/User/main.c
#include "usart.h"
#include "adc.h"
#include "delay.h"
uint16_t data[2] = {0};
int main(void)
{
USART_Init();
ADC1_Init();
ADC1_DMA_Init();
printf("Hello, world!\n");
ADC1_DMA_StartConvert((uint32_t)data, 2);
while (1)
{
printf("V_PC0 = %.2f, V_PC2 = %.2f\n",
data[0] * 3.3 / 4095, data[1] * 3.3 / 4095);
Delay_ms(1000);
}
}
关键要点:
CR1_SCAN)必须开启,否则只能转换第一个通道ADC_SQR1_L_0(=1)表示 2 个通道ADC1->DR)、内存地址递增(CMAR 指向数组,自动偏移)CNDTR 初始值为 2,每完成一次 DMA 传输自动减 1,循环模式下到达 0 时自动重载项目路径:stm32/35_adc_double_hal
需求:与实验三相同,使用 HAL 库 + CubeMX 实现双通道 DMA 采集。
文件:35_adc_double_hal/Core/Src/adc.c
#include "adc.h"
ADC_HandleTypeDef hadc1;
DMA_HandleTypeDef hdma_adc1;
void MX_ADC1_Init(void)
{
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 2;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_10;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_12;
sConfig.Rank = ADC_REGULAR_RANK_2;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_ADC_MspInit(ADC_HandleTypeDef* adcHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (adcHandle->Instance == ADC1)
{
__HAL_RCC_ADC1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0 | GPIO_PIN_2;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
// ADC1 DMA Init
hdma_adc1.Instance = DMA1_Channel1;
hdma_adc1.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_adc1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_adc1.Init.MemInc = DMA_MINC_ENABLE;
hdma_adc1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_adc1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_adc1.Init.Mode = DMA_CIRCULAR;
hdma_adc1.Init.Priority = DMA_PRIORITY_LOW;
HAL_DMA_Init(&hdma_adc1);
__HAL_LINKDMA(adcHandle, DMA_Handle, hdma_adc1);
}
}
void HAL_ADC_MspDeInit(ADC_HandleTypeDef* adcHandle)
{
if (adcHandle->Instance == ADC1)
{
__HAL_RCC_ADC1_CLK_DISABLE();
HAL_GPIO_DeInit(GPIOC, GPIO_PIN_0 | GPIO_PIN_2);
HAL_DMA_DeInit(adcHandle->DMA_Handle);
}
}
文件:35_adc_double_hal/Core/Src/dma.c
#include "dma.h"
void MX_DMA_Init(void)
{
__HAL_RCC_DMA1_CLK_ENABLE();
}
文件:35_adc_double_hal/Core/Src/gpio.c
#include "gpio.h"
void MX_GPIO_Init(void)
{
__HAL_RCC_GPIOC_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
文件:35_adc_double_hal/Core/Src/main.c
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "usart.h"
#include "gpio.h"
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
printf("Hello, world\n");
uint16_t data[2] = {0};
HAL_ADCEx_Calibration_Start(&hadc1);
HAL_ADC_Start_DMA(&hadc1, (uint32_t *)data, 2);
while (1)
{
printf("V_PC0 = %.2f, V_PC2 = %.2f\n",
data[0] * 3.3 / 4095, data[1] * 3.3 / 4095);
HAL_Delay(1000);
}
}
关键要点:
MX_GPIO_Init() → MX_DMA_Init() → MX_ADC1_Init()。DMA 必须在 ADC 之前初始化,因为 HAL_ADC_Init() 内部会调用 HAL_ADC_MspInit(),而 MSP 中配置了 DMA(__HAL_LINKDMA)。MX_DMA_Init() 仅使能 DMA1 时钟,实际的 DMA 通道配置在 HAL_ADC_MspInit() 中完成。ADC_SAMPLETIME_1CYCLE_5(默认),实际项目中需根据信号源阻抗调整。HAL_ADC_ConvCpltCallback 可通过重载实现每轮转换完成后的处理。| 操作 | 寄存器版(单通道) | HAL 版(单通道) | 寄存器版(双通道+DMA) | HAL 版(双通道+DMA) |
|---|---|---|---|---|
| 开启 ADC | `CR2 | = ADON` | HAL_ADC_Init() |
`CR2 |
| 校准 | `CR2 | = CAL; while(CR2 & CAL)` | HAL_ADCEx_Calibration_Start() |
`CR2 |
| 配置通道 | SQR3.SQ1 = ch |
ADC_ChannelConfTypeDef + HAL_ADC_ConfigChannel |
SQR3.SQ1/SQ2 = ch |
同上(多通道重复调用) |
| 启动转换 | `CR2 | = ADON` | HAL_ADC_Start() |
`CR2 |
| 查询完成 | while(!(SR & EOC)) |
HAL_ADC_PollForConversion() |
while(!(SR & EOC))(首次等待) |
回调 HAL_ADC_ConvCpltCallback |
| 读取结果 | val = DR |
HAL_ADC_GetValue() |
DMA 自动搬运到 data[] |
DMA 自动搬运到 data[] |
| DMA 配置 | N/A | N/A | 手动配置 DMA1 寄存器 | MX_DMA_Init() + MSP 中 HAL_DMA_Init() |
| 电压换算 | val × 3.3 / 4095 |
同上 | data[n] × 3.3 / 4095 |
同上 |
| 停止 ADC | CR2 &= ~ADON |
HAL_ADC_Stop() |
CR2 &= ~ADON + CCR &= ~EN |
HAL_ADC_Stop_DMA() |
MX_DMA_Init() 在 MX_ADC1_Init() 之前调用,否则 MSP 中的 __HAL_LINKDMA 不会生效