目录
[五、STM32 ADC 寄存器方式配置与使用](#五、STM32 ADC 寄存器方式配置与使用)
[5.1 ADC 时钟配置](#5.1 ADC 时钟配置)
[5.2 ADC 转换方式配置](#5.2 ADC 转换方式配置)
[5.2.1 扫描模式(ADC_CR1->SCAN)](#5.2.1 扫描模式(ADC_CR1->SCAN))
[5.2.2 连续/单次转换(ADC_CR2->CONT)](#5.2.2 连续/单次转换(ADC_CR2->CONT))
[5.3 数据对齐模式选择](#5.3 数据对齐模式选择)
[5.4 转换时间配置](#5.4 转换时间配置)
[5.4.1 总转换时间计算](#5.4.1 总转换时间计算)
[5.4.2 采样时间配置寄存器](#5.4.2 采样时间配置寄存器)
[5.5 ADC 编码转换为电压数据](#5.5 ADC 编码转换为电压数据)
[5.6 ADC 核心寄存器详解与配置](#5.6 ADC 核心寄存器详解与配置)
[5.6.1 控制寄存器2(ADC_CR2)关键位](#5.6.1 控制寄存器2(ADC_CR2)关键位)
[5.6.2 规则序列寄存器(ADC_SQR1~3)](#5.6.2 规则序列寄存器(ADC_SQR1~3))
[5.6.3 采样时间寄存器(ADC_SMPR1)](#5.6.3 采样时间寄存器(ADC_SMPR1))
[5.7 寄存器方式 ADC 完整配置流程(总结)](#5.7 寄存器方式 ADC 完整配置流程(总结))
一、ADC模数转换简介
ADC(analog digital convert)模拟-数字转换。
在嵌入式领域,ADC模块是现实世界与数字世界的桥梁,在现实世界中,信号是连续变化的模拟量,ADC模块通过采集不断变化的模拟电压信号,将其转换为具有特定含义的二进制编码,实现信号的输入。
二、AD转换工作原理
AD转换指的就是模数转换。转换的阶段可以分为 采样阶段 和 量化阶段 。采样实现 时间离散 ,量化实现 幅值离散,再经编码完成模数转换。
(1)采样阶段
对于模拟量,不可能每时每刻都采集到它的值,在实际应用中,采用的策略是:每一段时间采集一次,这样得到一组离散的数据量。
**tips:**若采样频率满足采样定理,便可由采样后的离散数据无失真还原原始模拟波形。
(2)量化阶段
将采样得到的 连续电压幅值 ,按照 ADC 的分辨率划分成若干个 等级刻度 ,把无限连续的电压值 近似归整 到最接近的数字刻度上。再对每个量化后的等级进行 二进制编码,最终输出单片机能识别的数字量。
量化的方法:
- 逐次逼近型:最常见,通过逼近法模拟信号的大小,性能均衡。
- **sigma-delta:**通过比较两个信号的累积值和一个参考值,产生一个高精度的输出,精度高,但是转换时间长
- **flash:**通过一组比较器和编码器,以高速并行的方式进行转换,速度快,但是分辨率低,精度一般。
(3)逐次逼近型工作原理简介
在STM32中的ADC模使用的就是逐次逼近型ADC,其工作原理类似砝码天平称重的原理。下图是芯片中的ADC电路图:

假如8位DA转换器的参考电压是256V(方便计算,这样二进制数每增加1,就代表了电压增加1)
待测电压33V
在测量时,会按照如下步骤测量:
规则:

每位单独置位电压:D7=128V、D6=64V、D5=32V、D4=16V、D3=8V、D2=4V、D1=2V、D0=1V - 逻辑控制电路先将 8 位寄存器D7 位置位 ,DA 转换器输出电压128V;
- 比较器与待测电压比较,待测电压低于 128V,比较器输出低电平;
- 8 位寄存器D7 清零 ,D6 位置位 ,DA 转换器输出64V;
- 比较器与待测电压比较,待测电压低于 64V,比较器输出低电平;
- 8 位寄存器D6 清零 ,D5 位置位 ,DA 转换器输出32V;
- 比较器与待测电压比较,待测电压高于 32V,比较器输出高电平;
- 8 位寄存器D5 保持为 1 ,D4 位置位 ,DA 转换器输出 48V;
- 比较器与待测电压比较,待测电压低于 48V,比较器输出低电平;
- 8 位寄存器D5 保持、D4 清零 ,D3 位置位 ,DA 转换器输出 40V;
- 比较器与待测电压比较,待测电压低于 40V,比较器输出低电平;
- 8 位寄存器D5 保持、D4 清零、D3 清零 ,D2 位置位 ,DA 转换器输出 36V;
- 比较器与待测电压比较,待测电压低于 36V,比较器输出低电平;
- 8 位寄存器D5 保持、D4/D3/D2 清零 ,D1 位置位 ,DA 转换器输出32V;
- 比较器与待测电压比较,待测电压高于 32V,比较器输出高电平;
- 8 位寄存器D5、D1 保持 ,D0 位置位 ,DA 转换器输出33V;
- 比较器与待测电压比较,等于待测电压,比较器输出高电平;
总结:
- 以 8 位逐次逼近型 ADC、参考电压 256V 为例,逻辑控制电路从最高位 D7 到最低位 D0 依次逐位试探:先将最高位置 1,由 DAC 输出对应权值电压,与待测电压送入比较器对比;若待测电压低于 DAC 输出电压,该位清零 ;若高于 ,该位保留置 1;逐位试探完毕后,8 位寄存器中保存的二进制数值,即为待测电压对应的数字量。
- 比较器仅能输出高低电平,只能判断电压大小,无法识别电压相等;因此逐次逼近 ADC 必须从最高位到最低位逐位全部比较完成,不能中途提前结束转换。
三、ADC主要参数
(1)参考电压
作为 ADC 模数转换的电压基准,决定了 可测量模拟电压的最大幅度范围,所有模拟输入电压都以参考电压为量程上限进行量化换算。
(2)通道数
ADC 具备多路独立模拟输入接口, 通道数越多,可同时或分时采集更多路不同来源的模拟信号,如温度、电压、光照等多路模拟量。
(3)采样率
指 ADC 每秒能够完成的采样次数。采样率越高,ADC 捕捉高频变化模拟信号的能力越强,不易发生信号失真。
(4)分辨率
表示 ADC 能把满量程模拟电压划分成多少个 离散量化等级,通常以位数表示。
例如:12 位 ADC,总级数为 2^12 = 4096 个离散级别;位数越高,划分越精细,电压识别精度越高。
(5)转换时间
指 ADC 完成 一次模数转换所需要的时间,由 ADC 工作时钟频率、分辨率以及转换架构共同决定;分辨率越高,通常单次转换时间越长。
四、使用STM32中的ADC
在STM32中,集成了ADC功能,不同的系列里的资源有所不同,部分型号参考下图:

不论如何,所有系列至少都有一个ADC1,并且其功能基本上是通用的,我们就再ADC1上展开讨论。
(1)功能框图

(2)输入通道
ADC的输入通道用来输入模拟电压的通道,ADC1模块有18个通道:
*
- 16个外部通道(ADCx_IN ~ ADCx_IN15),对应16个IO口;
- 内部温度传感器(测量芯片温度):通道16;
- 内部电压,(测量芯片供电电压):通道17。
所有的通道都接到了模拟多路开关,可以通过编程对通道进行控制,实现输入信号的选择和切换。
ADC同一时刻只能转换一路信号,在需要采集多路信号时,可以将多路信号进行排列,按照一定的顺序依次进行检测、转换、输出。在芯片中,为ADC设置了规则通道组和插入通道组两种类型的队列。 - 规则通道组:其实更应该翻译为常规组。
- 插入通道组:更应该翻译为特殊组,可以插队。
1.规则通道组
可理解为 常规 / 普通通道组,是最常用的转换队列。
- 最多可配置 16 个通道;
- 转换完成后,数据存入共用的规则数据寄存器 DR;
- 由于多通道共用一个寄存器,新数据会直接覆盖旧数据;
- 为避免数据丢失,必须及时读取数据 ,或开启 DMA 自动搬运。
2.注入通道组
可理解为 优先 / 可插队通道组,用于紧急、实时性要求高的信号。
- 最多支持 4 个通道;
- 每个通道拥有独立的数据寄存器,不会出现数据覆盖;
- 可随时打断规则通道的转换,执行优先级更高的任务;
- 转换完成后无 DMA 请求,通常由 CPU 直接读取。
**注意:**在ZET6系列芯片中,ADC2没有DMA通道,ADC1和ADC3有DMA通道
五、STM32 ADC 寄存器方式配置与使用
5.1 ADC 时钟配置
STM32的ADC模块的时钟频率不能超过14MHz,因此RCC外设中专门为ADC配置了预分频器(通过RCC->ADCPRE寄存器),用于选择输入时钟的分频系数,具体配置如下:
RCC->ADCPRE 寄存器配置(2位控制位):
- 00:PCLK2 2分频
- 01:PCLK2 4分频
- 10:PCLK2 6分频
- 11:PCLK2 8分频
5.2 ADC 转换方式配置
ADC的转换方式主要通过ADC_CR1和ADC_CR2两个寄存器的对应位配置,分为扫描模式和连续/单次转换模式,具体如下:
5.2.1 扫描模式(ADC_CR1->SCAN)
- 开启(置1):多通道转换模式,按顺序对通道组中的所有通道依次扫描、逐个转换;
- 关闭(置0):单通道转换模式,仅转换通道组中第一个通道。
5.2.2 连续/单次转换(ADC_CR2->CONT)
- 开启(置1):连续转换模式,转换一轮完成后,无需等待外部触发,自动开始下一轮转换,持续进行;
- 关闭(置0):单次转换模式,转换一轮完成后停止,需等待下次触发(软件或外部触发)才能启动新的转换。
5.3 数据对齐模式选择
ADC的转换结果存储在16位的DR寄存器中,而ADC的分辨率为12位,因此存在数据对齐问题,通过ADC_CR2->ALIGN寄存器配置,默认采用右对齐模式,具体说明如下:
- 右对齐(默认):转换结果的低12位有效,高4位补零,是实际使用中最常用的对齐方式;
- 左对齐:转换结果的高12位有效,低4位补零。
注意:注入组转换得到的结果包含符号位,若为正数,高位补0;若为负数,高位补1。
5.4 转换时间配置
ADC转换分为采样和模数转换两个阶段:首先使用若干个ADC时钟周期对输入电压进行采样(采样周期可通过寄存器配置),随后经过固定的12.5个ADC时钟周期完成模数转换。
5.4.1 总转换时间计算
总转换时间 = 采样时间 + 12.5个ADC周期
示例:当ADC时钟为14MHz(最大允许频率),假设采样时间配置为1.5个ADC周期,则总转换时间 = 1.5 + 12.5 = 14个ADC周期,对应时间为1μs(14MHz时钟周期为1/14μs,14个周期即1μs)。
5.4.2 采样时间配置寄存器
采样时间通过ADC_SMPR1寄存器进行配置,可根据实际需求选择不同的采样周期,以平衡转换速度和采样精度。
5.5 ADC 编码转换为电压数据
模拟电压经过ADC转换后,会得到一个12位的数字值,直接打印该数字值可读性较差,需将其转换为对应的模拟电压值。
实际设计中,ADC的输入电压范围通常设定为0~3.3V(与STM32的IO口供电电压匹配),由于ADC为12位分辨率,其满量程(所有位为1)对应的数字值为2¹² - 1 = 4095,数字值0对应模拟电压0V。
设转换后的数字值为x,对应的模拟电压为Y,两者满足以下等式(线性关系):
Y = (x / 4095) × 3.3V
通过该等式可将数字值换算为直观的模拟电压。
5.6 ADC 核心寄存器详解与配置
ADC的所有配置均通过对应寄存器实现,以下为核心寄存器的功能及配置要点,结合前文配置需求,明确各寄存器的作用及使用方法:
5.6.1 控制寄存器2(ADC_CR2)关键位
- CONT:连续转换控制位,对应5.2.2节,控制ADC为连续或单次转换模式;
- ALIGN:数据对齐方式控制位,对应5.3节,控制转换结果为右对齐或左对齐,默认右对齐;
- EXTTRIG:外部触发选择位,用于开启或关闭外部触发转换功能;
- EXTSEL:触发源选择位,当开启外部触发(EXTTRIG置1)时,用于选择触发ADC转换的外部信号源;
- SWSTART:软件触发控制位,置1时启动一次ADC转换(适用于软件触发模式);
- CAL:校准控制位,置1时启动ADC校准,校准完成后由硬件自动清零(校准需在ADC转换前执行,确保转换精度);
- ADON:ADC模块启动位,第一次置1启动ADC模块,再次置1触发一次ADC转换(单次转换模式下)。
5.6.2 规则序列寄存器(ADC_SQR1~3)
规则组通道的配置通过ADC_SQR1、ADC_SQR2、ADC_SQR3三个寄存器实现,具体要点如下:
- ADC_SQR1->L:规则组通道长度控制位,4位宽度,取值范围0~15,对应规则组包含1~16个通道;
- 通道编号:STM32 ADC共18个通道,每个通道需用5位控制位表示,三个SQR寄存器共可提供80位(16×5)控制位,刚好覆盖1~16个规则通道的配置(ADC_SQR1_SQ相关位为5位宽度,用于配置具体通道号)。

5.6.3 采样时间寄存器(ADC_SMPR1)
用于配置各通道的采样时间,通过对应位的配置,可选择不同的采样周期(如1.5、7.5、13.5等ADC周期),结合ADC时钟频率,决定采样精度和转换速度(采样周期越长,采样精度越高,转换速度越慢)。
5.7 寄存器方式 ADC 完整配置流程(总结)
结合上述所有配置要点,寄存器方式配置ADC的完整流程如下:
- 配置 ADC 时钟 :通过RCC->CFGR的ADCPRE位设置预分频,确保不超过 14MHz;
- 配置转换模式 :通过ADC_CR1->SCAN设置扫描 / 单通道,ADC_CR2->CONT设置连续 / 单次;
- 配置数据对齐 :通过ADC_CR2->ALIGN设置右对齐(默认)或左对齐;
- 配置采样时间 :通过ADC_SMPR1/2设置各通道采样周期;
- 配置规则组通道 :通过ADC_SQR1~3设置序列长度与通道号;
- ADC 上电 :置位ADC_CR2->ADON,给 ADC 上电(必须先上电);
- ADC 校准 :置位ADC_CR2->CAL启动校准,等待CAL自动清零;
- 启动转换 :软件触发置位SWSTART,或等待外部触发,开始转换;
- 读取数据 :转换完成后读取DR寄存器,换算成电压。
六、寄存器方式使用ADC读取芯片内部温度与参考电压的实现
(1)整体思路
通过ADC的规则组队列采集多路信号时,为了防止DR中的数据被覆盖。需要打开ADC的DMA功能,每转换完成一路信号,通过DMA传送到内存中,确保数据的准确。
初始化函数:
ADC初始化:
1.时钟配置
2.ADC配置
2.1.工作模式:配置扫描模式
2.2.启用连续转换模式(单曲循环)
2.3.数据对齐,右对齐(默认)
2.4.通道配置
2.4.1.设置通道的采样时间SMPR寄存器 001代表7.5个周期
2.4.2.规则组通道序列配置-规则组通道数量配置--L
2.4.3.将通道号保存到通道序列中--SQR3(倒着排列的)
2.5.触发方式选择--使用软件触发AD转换
DMA初始化:
1.时钟配置
2.数据宽度配置
3.传输方向配置
4.地址自增配置
开始转换函数
1.上电唤醒
2.执行校准,等待校准完成
3.开启DMA(DMA必须在上电后开启,否则不生效)
3.1.设置DMA,源地址、目的地址、数据长度等
4.启动转换
5.等待转换完成
计算函数
1.读取数据后进行计算并输出
(2)实现代码
文件名adc.c
cpp
#include "adc.h"
void ADC_Init(void)
{
// 1.打开时钟
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
// 2.ADC输入时钟频率设置,主时钟频率6分频-12MHz-10
RCC->CFGR |= RCC_CFGR_ADCPRE_1;
RCC->CFGR &= ~RCC_CFGR_ADCPRE_0;
// 3.ADC工作模式配置
// 3.1.扫描模式开
ADC1->CR1 |= ADC_CR1_SCAN;
// 3.2.连续转换模式开
ADC1->CR2 |= ADC_CR2_CONT;
// 3.3.外部触发配置
ADC1->CR2 |= ADC_CR2_EXTTRIG;
ADC1->CR2 |= ADC_CR2_EXTSEL;
// 3.4.数据对齐设置,右对齐(默认)
ADC1->CR2 &= ~ADC_CR2_ALIGN;
// 3.5.使用芯片内部传感器,开启对应控制位
ADC1->CR2 |= ADC_CR2_TSVREFE;
// 4.转换列表配置,列表长度2
ADC1->SQR1 &= ~ADC_SQR1_L;
ADC1->SQR1 |= ADC_SQR1_L_0;
ADC1->SQR3 &= ~ADC_SQR3_SQ1;
ADC1->SQR3 &= ~ADC_SQR3_SQ2;
ADC1->SQR3 |= 16 << 0;
ADC1->SQR3 |= 17 << 5;
// 5.采样频率设置,
/*
内置温度传感器的采样频率按照手册要求,不能低于17.1us,因此通道16的周期需要设置为239.5;-SMP - 111
通道17为检测芯片工作电压,采样频率无需过高,也可以采用239.5
*/
ADC1->SMPR1 |= ADC_SMPR1_SMP16;
ADC1->SMPR1 |= ADC_SMPR1_SMP17;
// 6.ADC模块上电
ADC1->CR2 |= ADC_CR2_ADON;
// 7.开启DMA(注意,该步骤需要在ADC模块上电后才可操作,否则无效)
ADC1->CR2 |= ADC_CR2_DMA;
}
void ADC_Start_ON_DMA(uint16_t* data, uint16_t size)
{
// 1.开启ADC校准,并等待校准完成
ADC1->CR2 |= ADC_CR2_CAL;
while(ADC1->CR2 & ADC_CR2_CAL);
// 2.设置DMA传输信息
DMA_Truansit((uint32_t)&(ADC1->DR), (uint32_t)data, size);
// 3.开启ADC转换
ADC1->CR2 |= ADC_CR2_SWSTART;
}
文件名:dma.c
cpp
#include "dma.h"
void DMA_Init(void)
{
// 1.打开时钟
RCC->AHBENR |= RCC_AHBENR_DMA1EN;
// 2.DMA工作模式配置
// 2.1.数据宽度设置, 16位数据宽度-01
DMA1_Channel1->CCR &= ~DMA_CCR1_MSIZE_1;
DMA1_Channel1->CCR |= DMA_CCR1_MSIZE_0;
DMA1_Channel1->CCR &= ~DMA_CCR1_PSIZE_1;
DMA1_Channel1->CCR |= DMA_CCR1_PSIZE_0;
// 2.2.数据传输方向设置,从外设读-DIR- 0
DMA1_Channel1->CCR &= ~DMA_CCR1_DIR;
// 2.3.地址自增设置,外设地址不自增,内存地址自增
DMA1_Channel1->CCR &= ~DMA_CCR1_PINC;
DMA1_Channel1->CCR |= DMA_CCR1_MINC;
// 2.3.循环模式配置,打开循环模式
DMA1_Channel1->CCR |= DMA_CCR1_CIRC;
// // 3.开启DMA通道
// DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
void DMA_Truansit(uint32_t src, uint32_t dst, uint16_t dataLen)
{
// 1.暂停DMA通道
DMA1_Channel1->CCR &= ~DMA_CCR1_EN;
// 2.设置源地址
DMA1_Channel1->CPAR = src;
// 3.设置目的地址
DMA1_Channel1->CMAR = dst;
// 4.设置数据长度
DMA1_Channel1->CNDTR = dataLen;
// 5.开启DMA通道
DMA1_Channel1->CCR |= DMA_CCR1_EN;
}
文件名main.c
cpp
#include "usart.h"
#include "dma.h"
#include "adc.h"
#include "delay.h"
int main(void)
{
// data[0]存放通道16读取的温度原始值
// data[1]存放通道17读取的电压原始值
uint16_t data[2] = {0};
float temperature = 0.0f;
float voltage = 0.0f;
Usart1_Init();
DMA_Init();
ADC_Init();
ADC_Start_ON_DMA(data, 2);
printf("test\n");
while(1)
{
temperature = (1.43f - (float)data[0] * 3.3 / 4095.0f) / 0.0043f + 25.0f;
voltage = (float)data[1] * 3.3 / 4095.0f;
printf("Temperature = %.2f\nVoltage = %.2f\n", temperature, voltage);
Delay_nms(1000);
}
}
七、HAL库实现
(1)基础配置(按实际情况)




(2)ADC与DMA配置


(3)调用HAL库实现业务
cpp
int main(void)
{
/* USER CODE BEGIN 1 */
uint16_t adc_data[2] = {0};
float temperature = 0.0f;
float voltage = 0.0f;
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_DMA_Init();
MX_ADC1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
// 数据校准
HAL_ADCEx_Calibration_Start(&hadc1);
// adc开始转换
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_data, 2);
//HAL_ADCEx_MultiModeStart_DMA(&hadc1, (uint32_t*)adc_data, 2);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
temperature = (1.43f - (float)adc_data[0] * 3.3f / 4095.0f) / 0.0043f + 25.0f;
voltage = (float)adc_data[1] * 3.3f / 4095.0f;
printf("temperature = %.2f\nvoltage = %.2f\n", temperature, voltage);
HAL_Delay(1000);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}