“ STM32F4xx 的 ADC 是连接模拟与数字世界的关键模块,具备多通道、高分辨率等特性,支持多种工作模式,可满足传感器数据采集等场景需求。本文围绕其配置与应用展开,助于掌握相关开发要点。”
01
—
stm32 ADC简介
ADC(Analog-to-Digital Converter,模数转换器)是一种把连续变化的模拟电信号转换成离散数字量的电路或模块。它以“采样-保持-量化-编码”为核心流程,输出一串可供数字系统(MCU、DSP、CPU)处理的二进制数值。ADC 是物理世界与数字世界的“翻译官”,没有它,智能设备就成了“聋子”和“瞎子”。
1.控制器简介
• 3 组独立 12-bit 逐次逼近型 ADC(ADC1/2/3);
• 可独立运行,也可双/三重模式并联,提高采样率或实现交错采样;
• 每组含 16 个外部通道 + 3 个内部通道(温度、VREFINT、VBAT,仅 ADC1 可用), 最多 19 路复用通道;
• 通道分两类:规则通道(最多 16 路,主循环)和注入通道(最多 4 路,中断级)。
2.电气特性
• 供电:2.4 V – 3.6 V(全速) / 1.8 V – 3.6 V(低速);
• 输入范围:VREF- ≤ VIN ≤ VREF+(VREF- 通常接 VSSA);
• 分辨率:12/10/8/6 bit 可配;
• 最高转换速率:2.4 MSPS(ADCCLK ≤ 36 MHz,采样周期=3 时,转换时间≈0.41 µs);
• 采样保持电容:≈4 pF;外部输入阻抗需 ≤ 50 kΩ(保证精度)。
3.典型应用场景
• 多路电池/传感器电压巡检
• 高速示波器/音频采样(DMA + 双/三重模式)
• 温度、VBAT、内部参考电压监控
• 电机控制中电流环同步采样(双 ADC 同时触发)
02
—
ADC功能框图
STM32F4xx 的 ADC可将外部模拟信号转换为数字信号。以下结合其参考手册,对单个 ADC 框图各模块功能、ADC 工作原理,以及相关寄存器进行介绍:
1.单个 ADC 框图
①模拟输入相关模块
GPIO 端口与模拟复用器
ADC 可以通过 GPIO 端口连接外部模拟信号源。GPIO 端口的引脚可配置为模拟输入模式,连接到模拟复用器。模拟复用器能够选择多达 16 个外部模拟输入通道(ADCx_IN0 - ADCx_IN15),此外还可以连接内部的温度传感器、VREFINT 以及 VBAT 信号。
对于 STM32F40x 和 STM32F41x 器件,温度传感器内部连接到通道 ADC1_IN16。内部参考电压 VREFINT 连接到 ADC1_IN17。
对于 STM23F42x 和 STM32F43x 器件,温度传感器内部连接到与 VBAT 共用的通道ADC1_IN18。一次只能选择一个转换(温度传感器或 VBAT)。同时设置了温度传感器和 VBAT 转换时,将只进行 VBAT 转换。内部参考电压 VREFINT 连接到 ADC1_IN17。
VBAT通道连接到通道 ADC1_IN18。该通道也可转换为注入通道或规则通道。
以STM32F40x 为例,ADC的各个通道IO引脚实际如下表所示:
|
通道 |
ADC1 GPIO/信号 |
ADC2 GPIO/信号 |
ADC3 GPIO/信号 |
|
IN0 |
PA0 |
PA0 |
PA0 |
|
IN1 |
PA1 |
PA1 |
PA1 |
|
IN2 |
PA2 |
PA2 |
PA2 |
|
IN3 |
PA3 |
PA3 |
PA3 |
|
IN4 |
PA4 |
PA4 |
PF6 |
|
IN5 |
PA5 |
PA5 |
PF7 |
|
IN6 |
PA6 |
PA6 |
PF8 |
|
IN7 |
PA7 |
PA7 |
PF9 |
|
IN8 |
PB0 |
PB0 |
PF10 |
|
IN9 |
PB1 |
PB1 |
PF3 |
|
IN10 |
PC0 |
PC0 |
未引出 |
|
IN11 |
PC1 |
PC1 |
未引出 |
|
IN12 |
PC2 |
PC2 |
未引出 |
|
IN13 |
PC3 |
PC3 |
未引出 |
|
IN14 |
PC4 |
PC4 |
PF4 |
|
IN15 |
PC5 |
PC5 |
PF5 |
|
IN16 |
温度传感器 |
内部接地,不可用 |
内部接地,不可用 |
|
IN17 |
VREFINT |
内部接地,不可用 |
内部接地,不可用 |
|
IN18 |
VBAT |
内部接地,不可用 |
内部接地,不可用 |
相关寄存器:GPIO 相关的配置寄存器(如 GPIOx_MODER 等)用于设置引脚为模拟输入模式;ADC 本身的通道选择配置通过规则序列寄存器(ADC_SQR3、ADC_SQR2、ADC_SQR1)和注入序列寄存器(ADC_JSQR)来实现。
VREF+、VREF-、VDDA、VSSA
VREF+ 和 VREF- 是 ADC 的参考电压正负极,为 ADC 转换提供电压基准,决定了 ADC 能够转换的模拟电压范围(通常 VREF- 接地,VREF+ 接稳定的参考电压,如 3.3V 时,输入电压范围为 0 - 3.3V);VDDA 是芯片模拟电源,VSSA 是芯片模拟地,为 ADC 模块提供稳定的电源。
相关寄存器:这些引脚的连接和配置在硬件设计阶段确定,在软件层面没有直接配置的寄存器,但 ADC 的转换结果会依赖于参考电压的稳定性。
②通道分组与转换模块
规则通道
规则通道最多支持 16 个通道,可以按照设定的顺序进行转换。在转换过程中,通道的转换顺序和数量由规则序列寄存器(ADC_SQR3、ADC_SQR2、ADC_SQR1)来设置。转换完成后,数据会存储在规则数据寄存器(ADC_DR)中。
相关寄存器:ADC_SQR1 的 L [3:0] 位决定要转换的通道数量,SQ1 - SQ16 位用于配置每个通道的转换顺序;ADC_DR 用于存放转换后的结果,数据格式可以通过 ADC_CR2 的 ALIGN 位设置为左对齐或右对齐。
注入通道
注入通道最多支持 4 个通道,具有更高的优先级。当注入通道触发转换时,会打断规则通道的转换,优先完成注入通道的转换,然后再恢复规则通道的转换。注入通道转换后的数据存储在注入数据寄存器(ADC_JDRx,x = 1 - 4)中。
相关寄存器:ADC_JSQR 的 JL [1:0] 位决定注入通道的数量,JSQR4 - JSQR1 位用于配置注入通道的转换顺序;ADC_JDRx 用于存放注入通道转换后的结果,数据格式同样受 ADC_CR2 的 ALIGN 位控制。
③触发源模块
EXTSEL [3:0] 和 JEXTSEL [3:0]
EXTSEL [3:0] 用于选择规则通道的外部触发源,JEXTSEL [3:0] 用于选择注入通道的外部触发源。触发源可以是定时器的触发信号(如 TIM1_CH1、TIM2_TRGO 等),也可以是外部中断线(EXTI_15、EXTI_11 等)。
相关寄存器:ADC_CR2 中的 EXTSEL [3:0] 和 JEXTSEL [3:0] 位用于设置触发源选择;EXTEN [1:0] 和 JEXTEN [1:0] 位用于设置触发极性(禁止触发检测、上升沿检测、下降沿检测、上升沿和下降沿均检测)。
软件触发
除了外部触发,还可以通过软件触发 ADC 转换。向 ADC_CR2 的 ADON 位写 1 启动 ADC,写 0 停止 ADC;写 1 到 ADC_CR2 的 SWSTART 位可以启动规则通道转换,写 1 到 ADC_CR2 的 JSWSTART 位可以启动注入通道转换。
相关寄存器:ADC_CR2 中的 ADON、SWSTART、JSWSTART 位用于软件触发控制。
外部触发极性设置方法如下表所示:
规则通道的触发源选择方法如下表所示:
注入通道的触发源选择方法如下表所示:
④转换时间相关模块
ADCCLK(ADC 时钟)
ADCCLK 是 ADC 进行转换的时钟信号,由 PCLK2 经过分频产生。ADC 的转换时间由采样时间和转换周期组成,ADCCLK 的频率会影响转换速度。
相关寄存器:通过 ADC 公共控制寄存器(ADC_CCR)的 ADCPRE [1:0] 位设置分频系数,可选 2、4、6 和 8 分频,从而调整 ADCCLK 的频率。
采样时间设置
ADC 需要一定的时间对输入模拟信号进行采样,采样时间通过 ADC 采样时间寄存器(ADC_SMPR1 和 ADC_SMPR2)来设置。不同通道可以设置不同的采样时间,以适应不同的信号特性。
相关寄存器:ADC_SMPR2 控制通道 0 - 9 的采样时间,ADC_SMPR1 控制通道 10 - 17 的采样时间,通过 SMP [2:0] 位设置采样周期数。
转换时间计算方法
STM32F4xx 的 ADC 属于逐次逼近型,总转换时间 Tconv 由两段相加:Tconv = Tsampling + Tconvert。
Tsampling(采样保持时间) 由 ADC_SMPRx 寄存器中的 SMP[2:0] 决定,可选 3/15/28/56/84/112/144/480 个 ADCCLK 周期。
Tconvert(逐次逼近时间)固定为 12.5 个 ADCCLK 周期(12 位分辨率时,如果设置为 10/8/6 位,则Tconvert分别为 10.5 / 8.5 / 6.5 个ADCCLK 周期)。
举例:
设置ADCCLK = 21 MHz(APB2=84 MHz,预分频 4);ADC_SMPR1. SMP[2:0] 设置为001B,则Tsampling=15 个 ADCCLK 周期;设置ADC_CR1.RES[1:0]设置为00B(12 位分辨率),则Tconvert=12.5 个 ADCCLK 周期;
转换时间Tconv = Tsampling + Tconvert=(12.5+15)/ 21 MHz ≈ 1.31 µs。
⑤中断与标志模块
中断使能位与标志位
包括转换结束标志(EOC)、注入转换结束标志(JEOC)、模拟看门狗事件标志(AWD)、DMA 溢出标志(OVR)等。中断使能位(EOCIE、JEOCIE、AWDIE、OVRIE)用于使能对应的中断,当标志位被置位且中断使能时,会向 NVIC(嵌套向量中断控制器)发送中断请求,通知 MCU 进行相应处理。
相关寄存器:ADC_ISR 寄存器用于读取这些标志位,ADC_IER 寄存器用于设置中断使能。
模拟看门狗
模拟看门狗可以监控 ADC 转换的输入电压,当输入电压超出设定的阈值上限或低于阈值下限时,会触发模拟看门狗事件,置位 AWD 标志,并根据使能情况产生中断。
相关寄存器:通过 ADC_CR1 的 AWDEN 位使能模拟看门狗,ADC_LTR 和 ADC_HTR 寄存器分别用于设置阈值下限和上限。
2.多重 ADC 框图
①模拟输入前端
GPIO 端口
作为模拟信号输入接口,可接入外部模拟信号(ADCx_IN0~IN15 )、内部信号(温度传感器、VREFINT 、VBAT )。需通过 GPIO 配置寄存器(如 GPIOx_MODER ),将引脚设为模拟输入模式,断开数字功能,避免干扰。ADC的各个通道IO引脚同单个 ADC GPIO 端口。
多路选择器
按配置选通不同通道信号,送至 ADC 转换器。规则 / 注入通道的选通逻辑,由规则序列寄存器(ADC_SQR 系列)、注入序列寄存器(ADC_JSQR )控制,决定通道转换顺序、数量。
②主 / 从 ADC 核心( ADC1永远为主,ADC2/3只能为从)
ADC1(主)
具备完整规则 / 注入通道逻辑,可独立或主导多 ADC 协同工作。规则通道用于常规多通道轮询,注入通道支持 “插队” 优先转换。转换后数据存入规则数据寄存器(16 位,ADC_DR)、注入数据寄存器(4×16 位,ADC_JDRx) 。
ADC2/3(从)
功能与主 ADC 类似,但多 ADC 模式下,需由主 ADC 或统一触发源协调。从 ADC 转换数据同样存入自身 ADC_DR 、ADC_JDRx ,或在双 / 三重模式下,按需共享到通用规则数据寄存器(32 位,ADC_CDR) 。
③多 ADC 模式控制(通用部分)
双 / 三重模式控制
通过 ADC_CCR(ADC 公共控制寄存器)配置,如 MULTI[5:0] 位,设定多 ADC 协同模式(同步触发、交叉采样等 )。可实现多 ADC 同时采样同一信号(提升精度)、分时采样不同信号(增加通道数 )。
通用规则数据寄存器(ADC_CDR)
双 / 三重模式下,存储多 ADC 规则通道转换结果(如 ADC1、ADC2 结果合并存储 ),便于批量读取。
④触发与同步逻辑
启动触发复用(规则组 / 注入组)
规则组触发源可来自定时器(TIMx_CH 、TRGO )、外部中断(EXTI_11/15 )等,由 ADC_CR2 的 EXTSEL[3:0] 选择;注入组触发由 JEXTSEL[3:0] 配置。多 ADC 模式下,主 ADC 可作为触发源,同步启动从 ADC 转换。
内部触发链路
主 ADC 转换事件可作为从 ADC 的触发信号,通过 ADC_CCR 配置触发关联,实现多 ADC 同步采样 / 转换。
⑤数据存储与输出
规则 / 注入数据寄存器
单 ADC 独立工作时,转换结果存入自身 ADC_DR(规则)、ADC_JDRx(注入);多 ADC 协同模式下,可按需将结果合并到 ADC_CDR ,或通过 DMA 直接传输到内存,减少 CPU 干预。
03
—
ADC模式介绍
1.ADC常见模式
ADC 配置参数及选项如下:
工作模式:决定有几个 ADC 同时工作。独立模式 / 双重 ADC 模式 / 三重 ADC 模式(3 种);
通道数量:一次转换序列里包含 1 个还是多个通道。单通道 / 多通道(2 种);
DMA 使用:转换结果是否由 DMA 自动搬运。使用 DMA / 不使用 DMA(2 种);
通道类型:数据放到哪个寄存器。规则通道 / 注入通道 / 规则 + 注入通道(3 种);
扫描方式:转换完成后停止(单次)或持续循环(连续)。单次扫描 / 连续扫描(2 种);
触发方式:谁来下达“开始转换”命令。软件触发 / 外部触发(2 种)。
以下是几种常见模式的配置流程及关键寄存器设置:
|
序号 |
工作模式 |
通道数量 |
DMA 使用 |
通道类型 |
扫描方式 |
触发方式 |
|
1 |
独立模式 |
单通道 |
无 DMA |
规则通道 |
单次扫描 |
软件触发 |
|
2 |
独立模式 |
单通道 |
无 DMA |
规则通道 |
连续扫描 |
软件触发 |
|
3 |
独立模式 |
单通道 |
无 DMA |
注入通道 |
单次扫描 |
软件触发 |
|
4 |
独立模式 |
多通道 |
无 DMA |
规则通道 |
单次扫描 |
软件触发 |
|
5 |
独立模式 |
多通道 |
有 DMA |
规则通道 |
连续扫描 |
软件触发 |
|
6 |
独立模式 |
多通道 |
无 DMA |
规则 + 注入 |
连续扫描 |
外部触发 |
|
7 |
双重 ADC |
多通道 |
无 DMA |
规则通道 |
单次扫描 |
软件触发 |
|
8 |
三重 ADC |
多通道 |
有 DMA |
规则通道 |
连续扫描 |
外部触发 |
|
… |
… |
… |
… |
… |
… |
… |
注:实际有效组合约 30 余种,表格仅展示部分典型组合。
2.配置流程及寄存器设置
① 独立模式 - 单规则通道 - 单次扫描 - 软件触发(无 DMA)
-
使能ADC时钟:RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-
配置ADC_CR1:
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 &= ~ADC_CR1_SCAN; // 单通道禁用扫描
-
配置ADC_CR2:
连续转换:ADC1->CR2 &= ~ADC_CR2_CONT; // 单次转换
触发方式:ADC1->CR2 |= ADC_CR2_EXTSEL_0; // 软件触发
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置规则序列:
通道数:ADC1->SQR1 &= ~ADC_SQR1_L; // 1个转换
通道:ADC1->SQR3 = (0 << 0); // 通道0(PA0)
-
启动转换:ADC1->CR2 |= ADC_CR2_SWSTART;
-
等待EOC标志:while(!(ADC1->SR & ADC_SR_EOC));
-
读取数据:uint16_t value = ADC1->DR。
②独立模式 - 单规则通道 - 连续扫描 - 软件触发(无 DMA)
-
使能ADC时钟:RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-
配置ADC_CR1:
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 &= ~ADC_CR1_SCAN; // 单通道
-
配置ADC_CR2:
连续转换:ADC1->CR2 |= ADC_CR2_CONT; // 连续模式
触发方式:ADC1->CR2 |= ADC_CR2_EXTSEL_0; // 软件触发
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置规则序列:
通道数:ADC1->SQR1 &= ~ADC_SQR1_L; // 1个转换
通道:ADC1->SQR3 = (0 << 0); // 通道0(PA0)
-
启动转换:ADC1->CR2 |= ADC_CR2_SWSTART;
-
循环读取数据:
while(1) {
if (ADC1->SR & ADC_SR_EOC) {
uint16_t value = ADC1->DR;
// 处理数据
}
}
③独立模式 - 单注入通道 - 单次扫描 - 软件触发(无 DMA)
-
使能ADC时钟:RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-
配置ADC_CR1:
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
-
配置ADC_CR2:
注入触发:ADC1->CR2 |= ADC_CR2_JEXTSEL_0; // 软件触发
注入自动触发:ADC1->CR2 &= ~ADC_CR2_JAUTO; // 禁用自动触发
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置注入序列:
通道数:ADC1->JSQR &= ~ADC_JSQR_JL; // 1个注入通道
通道:ADC1->JSQR = (18 << 0); // 通道18(VBAT)
-
使能注入转换完成中断:
ADC1->CR1 |= ADC_CR1_JEOCIE;
NVIC_EnableIRQ(ADC_IRQn);
-
触发注入转换:ADC1->CR2 |= ADC_CR2_JSWSTART;
/* 中断处理函数 */
void ADC_IRQHandler(void) {
if (ADC1->SR & ADC_SR_JEOC) {
uint16_t vbat_value = ADC1->JDR1;
ADC1->SR &= ~ADC_SR_JEOC; // 清除标志
}
}
④独立模式 - 多规则通道 - 单次扫描 - 软件触发(无 DMA)
-
使能ADC时钟:RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-
配置ADC_CR1:
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 |= ADC_CR1_SCAN; // 多通道启用扫描
-
配置ADC_CR2:
连续转换:ADC1->CR2 &= ~ADC_CR2_CONT; // 单次转换
触发方式:ADC1->CR2 |= ADC_CR2_EXTSEL_0; // 软件触发
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置规则序列(假设3个通道):
通道数:ADC1->SQR1 |= (2 << 20); // 3个转换
通道顺序:
ADC1->SQR3 = (0 << 0) | (1 << 5); // 通道0、1
ADC1->SQR2 = (2 << 0); // 通道2
-
启动转换:ADC1->CR2 |= ADC_CR2_SWSTART;
-
循环读取所有通道数据:
for (int i = 0; i < 3; i++) {
while(!(ADC1->SR & ADC_SR_EOC));
uint16_t value = ADC1->DR;
// 处理数据
}
⑤独立模式 - 多规则通道 - 连续扫描 - DMA(定时器触发)
-
使能ADC、DMA和定时器时钟:
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
RCC->APB1ENR |= RCC_APB1ENR_TIM2EN;
-
配置ADC_CR1:
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 |= ADC_CR1_SCAN; // 多通道
-
配置ADC_CR2:
连续转换:ADC1->CR2 |= ADC_CR2_CONT; // 连续模式
DMA模式:ADC1->CR2 |= ADC_CR2_DMA | ADC_CR2_DDS; // 启用DMA循环
触发方式:ADC1->CR2 |= ADC_CR2_EXTSEL_2 | ADC_CR2_EXTEN_0; // TIM2_TRGO上升沿触发
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置规则序列(假设4个通道):
通道数:ADC1->SQR1 |= (3 << 20); // 4个转换
通道顺序:
ADC1->SQR3 = (0 << 0) | (1 << 5) | (2 << 10); // 通道0、1、2
ADC1->SQR2 = (3 << 0); // 通道3
-
配置DMA2 Stream0:
DMA2_Stream0->CR |= (0 << 25);// 通道0
DMA2_Stream0->CR |= DMA_SxCR_DIR_0;// 外设到内存
DMA2_Stream0->CR |= DMA_SxCR_CIRC;// 循环模式
DMA2_Stream0->CR |= DMA_SxCR_MINC;// 内存递增
DMA2_Stream0->NDTR = 4;// 传输4个数据
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;// 外设地址
DMA2_Stream0->M0AR = (uint32_t)adc_values;// 内存地址
DMA2_Stream0->CR |= DMA_SxCR_EN;// 启用DMA
-
配置定时器2(1kHz触发):
TIM2->PSC = 83; // 84MHz / 84 = 1MHz
TIM2->ARR = 999; // 1MHz / 1000 = 1kHz
TIM2->CR2 |= TIM_CR2_MMS_1; // 主模式选择:更新事件
TIM2->CR1 |= TIM_CR1_CEN; // 启用定时器
⑥独立模式 - 规则 + 注入通道 - 连续扫描(无 DMA)
-
使能ADC时钟:RCC->APB2ENR |= RCC_APB2ENR_ADC1EN;
-
配置ADC_CR1:
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 |= ADC_CR1_SCAN; // 多通道
-
配置ADC_CR2:
连续转换:ADC1->CR2 |= ADC_CR2_CONT; // 规则通道连续转换
注入触发:ADC1->CR2 |= ADC_CR2_JEXTSEL_2; // TIM3_TRGO触发
注入自动触发:ADC1->CR2 |= ADC_CR2_JAUTO; // 自动注入
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置规则序列(假设2个通道):
通道数:ADC1->SQR1 |= (1 << 20); // 2个转换
通道顺序:
ADC1->SQR3 = (0 << 0) | (1 << 5); // 通道0、1
-
配置注入序列(假设1个通道):
通道数:ADC1->JSQR &= ~ADC_JSQR_JL; // 1个注入通道
通道:ADC1->JSQR = (16 << 0); // 通道16(温度传感器)
-
使能注入转换完成中断:
ADC1->CR1 |= ADC_CR1_JEOCIE;
NVIC_EnableIRQ(ADC_IRQn);
-
配置定时器3(用于触发注入转换):
TIM3->PSC = 83;
TIM3->ARR = 9999; // 约840Hz
TIM3->CR2 |= TIM_CR2_MMS_1; // 主模式选择:更新事件
TIM3->CR1 |= TIM_CR1_CEN; // 启用定时器
⑦双重 ADC 模式 - 规则通道 - 交替采样(无 DMA)
-
使能ADC1和ADC2时钟:
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_ADC2EN;
-
配置ADC_CCR(公共控制寄存器):
ADC->CCR |= ADC_CCR_MULTI_0 | ADC_CCR_MULTI_1; // 双重ADC交替模式
-
配置ADC1_CR1(主ADC):
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 &= ~ADC_CR1_SCAN; // 单通道
-
配置ADC1_CR2:
连续转换:ADC1->CR2 |= ADC_CR2_CONT; // 连续模式
触发方式:ADC1->CR2 |= ADC_CR2_EXTSEL_0; // 软件触发
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置ADC2_CR2(从ADC):
使能ADC2:ADC2->CR2 |= ADC_CR2_ADON;
-
配置规则序列(主ADC):
通道数:ADC1->SQR1 &= ~ADC_SQR1_L; // 1个转换
通道:ADC1->SQR3 = (0 << 0); // 通道0(PA0)
-
配置ADC2规则序列(与ADC1相同):
通道数:ADC2->SQR1 &= ~ADC_SQR1_L;
通道:ADC2->SQR3 = (0 << 0);
-
启动转换(仅需启动主ADC):
ADC1->CR2 |= ADC_CR2_SWSTART;
-
读取组合结果(32位数据):
uint32_t combined_value = ADC1->DR;
uint16_t adc1_value = combined_value & 0xFFFF;
uint16_t adc2_value = (combined_value >> 16) & 0xFFFF;
⑧三重 ADC 模式 - 规则通道 - 交替采样 - DMA(外部触发)
-
使能ADC1、ADC2、ADC3和DMA时钟:
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN | RCC_APB2ENR_ADC2EN | RCC_APB2ENR_ADC3EN;
RCC->AHB1ENR |= RCC_AHB1ENR_DMA2EN;
-
配置ADC_CCR(公共控制寄存器):
ADC->CCR |= ADC_CCR_MULTI_0 | ADC_CCR_MULTI_1 | ADC_CCR_MULTI_2; // 三重ADC交替模式
-
配置ADC1_CR1(主ADC):
分辨率:ADC1->CR1 &= ~ADC_CR1_RES; // 12位
扫描模式:ADC1->CR1 &= ~ADC_CR1_SCAN; // 单通道
-
配置ADC1_CR2:
连续转换:ADC1->CR2 |= ADC_CR2_CONT; // 连续模式
触发方式:ADC1->CR2 |= ADC_CR2_EXTSEL_1 | ADC_CR2_EXTEN_0; // TIM8_TRGO上升沿触发
DMA模式:ADC1->CR2 |= ADC_CR2_DMA | ADC_CR2_DDS; // 启用DMA循环
使能ADC:ADC1->CR2 |= ADC_CR2_ADON;
-
配置ADC2和ADC3(从ADC):
使能ADC2和ADC3:
ADC2->CR2 |= ADC_CR2_ADON;
ADC3->CR2 |= ADC_CR2_ADON;
-
配置规则序列(所有ADC相同):
通道数:ADC1->SQR1 &= ~ADC_SQR1_L; // 1个转换
通道:ADC1->SQR3 = (0 << 0); // 通道0(PA0)
同样配置ADC2和ADC3的SQR3
-
配置DMA2 Stream0:
DMA2_Stream0->CR |= (0 << 25);// 通道0
DMA2_Stream0->CR |= DMA_SxCR_DIR_0;// 外设到内存
DMA2_Stream0->CR |= DMA_SxCR_CIRC;// 循环模式
DMA2_Stream0->CR |= DMA_SxCR_MINC;// 内存递增
DMA2_Stream0->NDTR = 3;// 传输3个数据(每个ADC一个)
DMA2_Stream0->PAR = (uint32_t)&ADC1->DR;// 外设地址
DMA2_Stream0->M0AR = (uint32_t)adc_values;// 内存地址
DMA2_Stream0->CR |= DMA_SxCR_EN;// 启用DMA
-
配置定时器8(高速触发):
TIM8->PSC = 0; // 84MHz
TIM8->ARR = 41; // 约2MHz采样率
TIM8->CR2 |= TIM_CR2_MMS_1; // 主模式选择:更新事件
TIM8->CR1 |= TIM_CR1_CEN; // 启用定时器
04
—
硬件说明
手动调节电位器旋钮,产生0~3.3V电压到VR,VR连接到stm32 PB0(ADC12_IN8)引脚。配置ADC采集VR的电压,通过串口打印采集的电压数据,使用串口调试助手观察。
05
—
程序设计案例
本次 STM32f4xx ADC 程序配置的模式为:独立模式、单通道、使用 DMA、规则通道、连续扫描软件触发,具体实现如下。
编程步骤:
1)初始 ADC 用到的 GPIO;
2)设置 ADC 的工作参数并初始化;
3)设置 ADC 工作时钟;
4)设置 ADC 转换通道顺序及采样时间;
5)配置使能 ADC 转换完成中断,在中断内读取转换完数据;
6)使能 ADC;
7)使能软件触发 ADC 转换。
main.c
int main(void){ /* 配置系统时钟为168 MHz */ SystemClock_Config(); /* 初始化USART1 配置模式为 115200 8-N-1 */ DEBUG_USART_Config(); Rheostat_Init(); while (1) { ADC_Vol =(float) ADC_ConvertedValue/4096*(float)3.3; // 读取转换的AD值 printf("\r\n The current AD value = 0x%04X \r\n", ADC_ConvertedValue); printf("\r\n The current AD value = %f V \r\n",ADC_Vol); Delay(0x8fffff); } }
bsp_adc.c
static void Rheostat_ADC_GPIO_Config(void){ GPIO_InitTypeDef GPIO_InitStructure; // 使能 GPIO 时钟 RHEOSTAT_ADC_GPIO_CLK_ENABLE(); // 配置 IO GPIO_InitStructure.Pin = RHEOSTAT_ADC_GPIO_PIN; GPIO_InitStructure.Mode = GPIO_MODE_ANALOG; GPIO_InitStructure.Pull = GPIO_NOPULL ; //不上拉不下拉 HAL_GPIO_Init(RHEOSTAT_ADC_GPIO_PORT, &GPIO_InitStructure); } static void Rheostat_ADC_Mode_Config(void){ // ------------------DMA Init 结构体参数 初始化-------------------------- // ADC1使用DMA2,数据流0,通道0,这个是手册固定死的 // 开启DMA时钟 RHEOSTAT_ADC_DMA_CLK_ENABLE(); // 数据传输通道 DMA_Init_Handle.Instance = RHEOSTAT_ADC_DMA_STREAM; // 数据传输方向为外设到存储器 DMA_Init_Handle.Init.Direction = DMA_PERIPH_TO_MEMORY; // 外设寄存器只有一个,地址不用递增 DMA_Init_Handle.Init.PeriphInc = DMA_PINC_DISABLE; // 存储器地址固定 DMA_Init_Handle.Init.MemInc = DMA_MINC_ENABLE; // // 外设数据大小为半字,即两个字节 DMA_Init_Handle.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD; // 存储器数据大小也为半字,跟外设数据大小相同 DMA_Init_Handle.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD; // 循环传输模式 DMA_Init_Handle.Init.Mode = DMA_CIRCULAR; // DMA 传输通道优先级为高,当使用一个DMA通道时,优先级设置不影响 DMA_Init_Handle.Init.Priority = DMA_PRIORITY_HIGH; // 禁止DMA FIFO ,使用直连模式 DMA_Init_Handle.Init.FIFOMode = DMA_FIFOMODE_DISABLE; // FIFO 大小,FIFO模式禁止时,这个不用配置 DMA_Init_Handle.Init.FIFOThreshold = DMA_FIFO_THRESHOLD_HALFFULL; DMA_Init_Handle.Init.MemBurst = DMA_MBURST_SINGLE; DMA_Init_Handle.Init.PeriphBurst = DMA_PBURST_SINGLE; // 选择 DMA 通道,通道存在于流中 DMA_Init_Handle.Init.Channel = RHEOSTAT_ADC_DMA_CHANNEL; //初始化DMA流,流相当于一个大的管道,管道里面有很多通道 HAL_DMA_Init(&DMA_Init_Handle); HAL_DMA_Start (&DMA_Init_Handle,RHEOSTAT_ADC_DR_ADDR,(uint32_t)&ADC_ConvertedValue,1); // 开启ADC时钟 RHEOSTAT_ADC_CLK_ENABLE(); // -------------------ADC Init 结构体 参数 初始化------------------------ // ADC1 ADC_Handle.Instance = RHEOSTAT_ADC; // 时钟为fpclk 4分频 ADC_Handle.Init.ClockPrescaler = ADC_CLOCKPRESCALER_PCLK_DIV4; // ADC 分辨率 ADC_Handle.Init.Resolution = ADC_RESOLUTION_12B; // 禁止扫描模式,多通道采集才需要 ADC_Handle.Init.ScanConvMode = DISABLE; // 连续转换 ADC_Handle.Init.ContinuousConvMode = ENABLE; // 非连续转换 ADC_Handle.Init.DiscontinuousConvMode = DISABLE; // 非连续转换个数 ADC_Handle.Init.NbrOfDiscConversion = 0; //禁止外部边沿触发 ADC_Handle.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE; //使用软件触发,外部触发不用配置,注释掉即可 //ADC_Handle.Init.ExternalTrigConv = ADC_EXTERNALTRIGCONV_T1_CC1; //数据右对齐 ADC_Handle.Init.DataAlign = ADC_DATAALIGN_RIGHT; //转换通道 1个 ADC_Handle.Init.NbrOfConversion = 1; //使能连续转换请求 ADC_Handle.Init.DMAContinuousRequests = ENABLE; //转换完成标志 ADC_Handle.Init.EOCSelection = DISABLE; // 初始化ADC HAL_ADC_Init(&ADC_Handle); //--------------------------------------------------------------------------- ADC_Config.Channel = RHEOSTAT_ADC_CHANNEL; ADC_Config.Rank = 1; // 采样时间间隔 ADC_Config.SamplingTime = ADC_SAMPLETIME_56CYCLES; ADC_Config.Offset = 0; // 配置 ADC 通道转换顺序为1,第一个转换,采样时间为3个时钟周期 HAL_ADC_ConfigChannel(&ADC_Handle, &ADC_Config); HAL_ADC_Start_DMA(&ADC_Handle, (uint32_t*)&ADC_ConvertedValue, 1);}
06
—
实验结果
通过调节电位器旋钮,每个1秒返回ADC测量的电压值,可以看到电压从0V左右缓慢上升到3.3V左右。
07
—
代码链接
工程代码链接:
https://gitee.com/ylm1101111/stm32_basic9.git
0