ADC基本原理,STM32ADC底层实现逻辑(寄存器版和HAL库版)
本文详细介绍了逐次逼近型ADC的工作原理及其在STM32F1系列中的应用。首先从ADC基本原理出发,阐述了12位ADC的分辨率与量化误差,并以ADC0809为例说明逐次比较算法流程。接着重点分析了STM32内部ADC结构,对比了规则通道(16通道共享1个DR寄存器)和注入通道(4通道独立JDR寄存器)的特点与风险。文章详细讲解了规则序列的配置方法(通过SQR1-SQR3寄存器设置通道顺序和数量),
文章目录
- 前言
- 一、ADC简介
- 二、ADC基本原理
-
- 1.ADC0809结构原理
- 2.STM32结构原理
- 3.规则序列和注入序列
-
- 3.1规则序列
- 3.2注入序列
- 三、STM32软件配置
-
- 1、对应寄存器配置讲解
- 2、HAL库实现
- 总结
前言
本文从ADC的原理出发,讲解了逐次逼近型的物理结构、逐次逼近型的实现算法;STM32F1系列的内部ADC的实现原理,以及主要寄存器的配置;从寄存器的角度出发,讲解如何用代码实现对寄存器的配置;也讲解了ADC的各种模式。希望能够帮助大家对ADC底层理解有帮助!
一、ADC简介
简单一句话,ADC是将模拟信号变成计算机可处理的离散信号,学过通信原理和数字信号处理的都知道(学过但不知道的,????),学的采样定理,这种采样出来的信号不是离散信号,那只是时间上离散,时间离散之后然后再对幅度进行量化后,才是离散信号。因此ADC不只是简简单单的采样,他还包括量化编码。
二、ADC基本原理
大家谈论ADC时肯定避免不开,“你这个ADC是多少位的?”这个多少位指的是什么,有什么用。这个是跟他的分辨率相关的,比如,这个ADC是12位的,那数值范围是0~2^12-1, 我采集3.3V的信号,那我每一个数值表示的电压就是3.3/(2^12-1)V,这样是不是位数越高,可分辨的电压就越精细。
1.ADC0809结构原理
我们首先先用一个ADC芯片了解整体结构,再去看STM32内部自带的ADC结构。STM32与ADC0809采用的都是逐次比较型,用下面这个部分将逐次比较型也一次讲清楚。
这是从芯片ADC0809数据手册里截取出来的原理图,主要有这几个部分:电压比较器(紫色:comparator)、DAC(红色框)、SAR(蓝色S.A.R)、时钟控制(橙色:control & timing)。工作原理是:首先SAR先给个一半的值(1000 0000),去往DAC,输出电压值,再与通道采集的电压进行比较看大了还是小了,大了就是1,小了就是0,每次都取一半的值,反复直到8位都被成功赋值,与二分查找的算法一模一样。
直接举例子,为了简单起见,举例子就用4位的,通道采到的电压是3.3V,参考的最高电压是4V。最终输出的是1101,具体流程看下面表格。
| 步骤 | SAR输出值 | 比较 | 最终值 |
|---|---|---|---|
| 1 | 1000 (2V) | 小于 | 1 |
| 2 | 1100(3V) | 小于 | 1 |
| 3 | 1110 (3.5V) | 大于 | 0 |
| 4 | 1101(3.25V) | 小于 | 1 |
1101转换出来应该是3.46V,中间这个差值就是量化误差导致的,这也从侧面说明位数越高,最终采到的电压就越准。
2.STM32结构原理

红色框是采样的通道,从0-15,总共16个通道;绿色的是中断触发的信号;蓝色的是关键,是将模拟信号转换成数字信号的关键部分,类似于上文ADC0809的作用;这里我们可以看到主要的通道分为注入通道和规则通道,这两个通道是ADC的关键:
注入通道:一次最多可接入4个通道进行模拟信号到数字信号的转换,他具有高优先级,可以打断任何规则转换,后端有4个字节的数据寄存器进行存储(JDR1、JDR2、JDR3、JDR4),不会对规则通道的数据有任何影响。他就像“VIP插队通道”。
规则通道:一次可以接入16个通道进行模拟信号到数字信号的转换,但是,这16个通道不是一次就可以转换成功的,每次只能转换一个,而且后端的数据寄存器DR,只有16位,也就是说一次只能存储一个值。他就像“排队登机(经济舱队伍)”。
| 特性 | 规则通道 (单通道) | 注入通道 (多通道) |
|---|---|---|
| 接入通道 | 16个 | 4个 |
| 数据寄存器 | 1个 (DR) |
4个独立寄存器 (JDR1~JDR4) |
| 单通道风险 | 连续模式会覆盖 | 永不覆盖 |
| 多通道风险 | 必须用DMA,否则严重覆盖 | 无风险(各通道独立寄存器) |
| 读取时机 | 需在下次转换前读取 | 可随时读取 |
| 典型读取方式 | 轮询DR/DMA/中断 |
直接访问JDRx |
我们现在思考,规则通道最多接入16个通道进来,那这个顺序是怎么样的,先转换谁后转换谁,这种模式行吗?俗话说无规矩不成方圆,是不是得规定个规则让STM32按照我们的想法去工作,注入通道也是同理,所以这就引出来下面要说的规则序列、注入序列。
3.规则序列和注入序列
3.1规则序列
在实际应用中,经常需要按特定的顺序轮流采集多个模拟通道(比如温度传感器、光照传感器、电位器等连接在不同的ADC引脚上)。规则序列转换就是为了满足这种多通道采集需求而设计的机制。
主要的思想就是,将多个模拟输入通道预先定义为一个转换序列(列表)。当一次转换启动(触发)发生时,ADC会自动按照这个预定义的顺序,依次对这个序列中的所有通道执行一次完整的SAR转换。下面是ADC寄存器的映射,在“HAL库底层代码分析”这篇文章我已经讲过这个是怎么映射的。
typedef struct
{
__IO uint32_t SR;
__IO uint32_t CR1;
__IO uint32_t CR2;
__IO uint32_t SMPR1;
__IO uint32_t SMPR2;
__IO uint32_t JOFR1;
__IO uint32_t JOFR2;
__IO uint32_t JOFR3;
__IO uint32_t JOFR4;
__IO uint32_t HTR;
__IO uint32_t LTR;
__IO uint32_t SQR1;
__IO uint32_t SQR2;
__IO uint32_t SQR3;
__IO uint32_t JSQR;
__IO uint32_t JDR1;
__IO uint32_t JDR2;
__IO uint32_t JDR3;
__IO uint32_t JDR4;
__IO uint32_t DR;
} ADC_TypeDef;
首先,我们得告诉STM32我们这个规则序列里有几个通道,STM32F1最多支持16个通道(包括外部通道和内部通道如温度传感器、Vrefint)在一个规则序列中。入下图红色框所示,通过寄存器 ADC_SQR1 的 L[3:0] 位设置(0表示1个通道,15表示16个通道)。
(1) 通道顺序(Sequence): 你可以自由指定序列中每个位置对应哪个具体的ADC通道。这是通过一组寄存器实现的:
ADC_SQR1: 存放序列中第13位(SQ13)到第16位(SQ16)的通道号。
ADC_SQR2: 存放序列中第7位(SQ7)到第12位(SQ12)的通道号。
ADC_SQR3: 存放序列中第1位(SQ1)到第6位(SQ6)的通道号。
注:ADC_SQR2、ADC_SQR3没有列出来,大家可自行查看参考手册。
例如,你想按顺序采集通道3、通道8、通道2、通道15。那么首先你需要设置序列长度 L[3:0] = 3(表示有4个通道,因为0=1, 1=2, …, 3=4),再设置具体通道:
SQ1 = 3
SQ2 = 8
SQ3 = 2
SQ4 = 15
(2) 已经知道通道是怎么设置的,但是按照什么样的规则去采集,这个是接下来说的,STM32F1有四种转换模式,如下表所示:
| 组合模式 | SCAN位 | CONT位 | 行为描述 |
|---|---|---|---|
| 单通道单次转换 | DISABLE | DISABLE | 一次触发仅转换一个通道,需手动重启。 |
| 多通道单次转换 | ENABLE | DISABLE | 一次触发扫描所有通道后停止,需下次触发重新扫描。 |
| 单通道连续转换 | DISABLE | ENABLE | 自动重复转换同一通道,无需触发 |
| 多通道连续转换 | ENABLE | ENABLE | 循环扫描所有通道,永不停止 |
STM32F1的ADC模式由两个关键寄存器控制位组合决定:
扫描模式(SCAN,在ADC_CR1中):决定是否自动扫描多个通道。
连续模式(CONT,在ADC_CR2中):决定是否自动重启转换。
(3) 举例子说明这四个通道:
假设场景: 我们要用ADC测量3个模拟信号:
通道0 (PA0): 测量电位器电压 (0-3.3V)
通道1 (PA1): 测量光照传感器电压 (0-3.3V)
通道2 (PA2): 测量NTC热敏电阻分压电压 (反应温度)
模式 1:单通道单次转换
行为: 一次启动信号(触发)只转换一个预先选定的通道,转换一次后就停止。需要再次启动才能转换同一个或另一个通道。
配置要点:
扫描模式(SCAN) = 禁用
连续模式(CONT) = 禁用
规则序列长度(SQR1.L) = 1 (因为只转换一个通道)
规则序列1(SQR3.SQ1) = 你要转换的通道号 (如 0)
操作流程示例 (测量通道0):
配置ADC:SCAN=0, CONT=0, SQR1.L=0 (表示1个通道), SQR3.SQ1=0 (通道0)。
启动转换: 软件设置SWSTART位或等待硬件触发。
ADC动作:对通道0执行一次完整的SAR转换。
转换完成:EOC标志置位(可能产生中断)。
读取数据: 从ADC_DR寄存器读取通道0的转换结果。
ADC停止: 等待下一次启动信号。
但我想测量通道1怎么办?
重新配置: 修改规则序列1(SQR3.SQ1) = 1 (通道1)。
启动转换: 再次触发。
ADC动作:对通道1执行一次SAR转换。
读取数据:从ADC_DR读取通道1结果。
模式 2:多通道单次扫描
行为: 一次启动信号(触发)后,ADC自动按预先设定的顺序扫描转换规则序列中指定的所有通道(通道0->1->2),全部转换完成后停止。需要下一次启动信号才会重新开始扫描整个序列。
配置要点:
扫描模式(SCAN) = 启用
连续模式(CONT) = 禁用
规则序列长度(SQR1.L) = 2 (表示3个通道,0=1, 1=2, 2=3, 3=4…)
规则序列:
SQR3.SQ1 = 0 (第一个转换:通道0)
SQR3.SQ2 = 1 (第二个转换:通道1)
SQR3.SQ3 = 2 (第三个转换:通道2)
这种模式必须使用DMA或中断读取每个通道结果!因为前文说了规则通道只有一个16位的寄存器去存储数据,一次只能存一个,那你连着采集三个通道,会造成数据覆盖。
操作流程示例 (测量所有3个通道):
配置DMA:源地址=ADC_DR,目标地址=一个数组(如adcResults[3]),数据长度=3,每次ADC_DR有数据就搬运。
启动转换: 触发一次。
ADC动作:
转换SQ1 (通道0) -> 结果存入ADC_DR -> DMA搬运adcResults[0]
自动转换SQ2 (通道1) -> 结果存入ADC_DR (覆盖通道0结果) -> DMA搬运adcResults[1]
自动转换SQ3 (通道2) -> 结果存入ADC_DR (覆盖通道1结果) -> DMA搬运adcResults[2]
所有通道转换完成:EOC标志置位(可能产生中断)。
ADC停止: 等待下一次触发。
此时数组adcResults[0], [1], [2] 分别保存了通道0, 1, 2的最新数据。
模式 3:单通道连续转换
行为: 只需要启动一次,ADC就会不停地、连续地转换同一个通道。永不停止,除非你关闭连续模式或禁用ADC。
配置要点:
扫描模式(SCAN) = 禁用
连续模式(CONT) = 启用
规则序列长度(SQR1.L) = 1
规则序列1(SQR3.SQ1) = 目标通道号 (如 0)。
操作流程示例 (连续测量通道0):
配置ADC:SCAN=0, CONT=1, SQR1.L=0, SQR3.SQ1=0。
启动转换: 触发一次 (软件SWSTART或硬件触发)。
ADC动作:
转换通道0 -> 结果存入ADC_DR -> EOC置位。
立即自动开始下一次转换通道0 -> 结果存入ADC_DR (覆盖前一次) -> EOC置位。
循环往复,永不停止…
读取数据:你需要在每次EOC后(或使用DMA)尽快读取ADC_DR,否则结果会被下一次转换覆盖。通常用循环缓冲+DMA或定时读取。
模式 4:多通道连续扫描
行为: 只需要启动一次,ADC就会不停地、循环地按顺序扫描转换规则序列中的所有通道 (通道0->1->2->0->1->2->…)。永不停止。
配置要点:
扫描模式(SCAN) = 启用
连续模式(CONT) = 启用
规则序列长度(SQR1.L) = 2 (3个通道)
规则序列:SQR3.SQ1=0, SQ2=1, SQ3=2。
必须使用DMA! (数据流太快,中断来不及处理)。
操作流程示例 (循环测量3个通道):
**配置DMA:**源地址=ADC_DR,目标地址=一个循环缓冲区 (如adcResults[3][N],N组数据),数据长度=3 (或DMA循环模式),每次ADC_DR有数据就搬运。
启动转换: 触发一次。
ADC动作:
转换SQ1 (通道0) -> ADC_DR -> DMA搬运到 adcResults[0][0]
转换SQ2 (通道1) -> ADC_DR -> DMA搬运到 adcResults[1][0]
转换SQ3 (通道2) -> ADC_DR -> DMA搬运到 adcResults[2][0]
自动回到SQ1 (通道0) -> ADC_DR -> DMA搬运到 adcResults[0][1]
转换SQ2 (通道1) -> ADC_DR -> DMA搬运到 adcResults[1][1]
… 无限循环…
总结一下这四种模式:
| 模式 | SCAN | CONT | 触发次数 | 通道数 | 动作 | 数据读取关键 | 典型应用场景 |
|---|---|---|---|---|---|---|---|
| 单通道单次转换 | 0 | 0 | 每通道1次 | 1 | 转1次停 | 读ADC_DR | 偶尔测一个传感器 |
| 多通道单次扫描 | 1 | 0 | 1次/轮询 | 多 | 扫完所有停 | DMA/中断 (防覆盖) | 周期性读多个传感器 |
| 单通道连续转换 | 0 | 1 | 1次启动 | 1 | 连续转同一通道 | DMA/频繁读 (防覆盖) | 高速采集单信号 (如音频) |
| 多通道连续扫描 | 1 | 1 | 1次启动 | 多 | 循环扫所有通道 | 必须DMA (循环缓冲) | 实时监控多路信号 (如电机控制) |
(3) 规则序列的转换需要由一个事件来启动。可以是:
软件触发: 通过设置寄存器位(如 ADC_CR2 的 SWSTART 或 JSWSTART 用于注入组)来立即启动转换。
硬件触发: 由外部信号(如定时器捕获/比较事件、EXTI线)触发转换。触发源通过 ADC_CR2 的 EXTSEL[2:0] 位选择。
(4) 在使用规则组时有个最关键的限制,它只有一个数据寄存器 ADC_DR 来存放结果。这意味着:当ADC自动转换序列中的后续通道时,前一个通道的结果会被覆盖掉。如果你想保留序列中所有通道的转换结果,必须在每次单个通道转换完成后(或者至少在下一个通道转换开始前)将 ADC_DR 的值读走并保存起来,因此这就是为什么adc要配合DMA或者中断来进行的原因,读取数据大多数情况下用的时DMA,中断效率太低了,一般不采用。
(5) 转换时间(总序列时间):
整个规则序列完成所需的总时间取决于有多少个通道、每个通道的采样时间、每个通道逐次逼近的转换时间。
每个通道的采样时间:通过 ADC_SMPRx 寄存器为每个通道配置采样保持电容的充电时间(越长越准确,抗噪能力越强,但转换也越慢)。采样时间占用若干个ADC时钟周期。
每个通道的逐次逼近转换时间:固定为12.5或13.5个ADC时钟周期(取决于 ADC_CR1 的 RES[1:0] 分辨率设置,12位模式通常是12.5)。
ADC时钟频率(ADCCLK):由 RCC_CFGR 的 ADCPRE 分频器设置,最高不超过14MHz(STM32F1)。
总转换时间 ≈ 序列长度 * (采样时间 + 12.5 或 13.5) 个 ADCCLK 周期。
3.2注入序列
注入序列的模式主要分为两种单通道注入和多通道注入两种。
单通道注入:序列中只配置一个通道。
多通道注入:序列配置多个通道(最多4个),按顺序转换
如下图所示,这是注入通道配置的顺序,JL[1:0]这一位是说明需要转换几个通道,JSQ4~1是用来确定转换的是哪个通道,注意看紫色框,告诉了他是什么顺序,如果配置了4个通道,那顺序就是JSQ1>>JSQ2>>JSQ3>>JSQ4;如果配置的是3个通道,那顺序是JSQ2>>JSQ3>>JSQ4;如果配置的是2个通道,那顺序是JSQ3>>JSQ4;如果配置的是1个通道,那顺序是JSQ4。
三、STM32软件配置
1、对应寄存器配置讲解
需要明确ADC在开始工作前我们需要进行校准,在STM32的ADC采样前进行校准主要是解决硬件固有误差。下面是对寄存器进行配置的代码,有助于提高对底层逻辑的理解。主要是对CR1和CR2寄存器进行配置,大家看这个得对着手册去看,这里就不粘贴出手册了,直接说寄存器。
step1:配置工作模式,这个是配置协同工作的,就比如有两三个ADC,那就需要配置这个寄存器,但我们只是一个ADC在工作,所以就配成独立工作模式;配置非扫描模式。
step2:配置成单次转换,SWSTART触发(软件触发),这里要注意当你用外部触发时,你得使能他,这个使能寄存器就是EXTTRIG寄存器,软件触发也不例外;再配置成右对齐模式。
step3:配置通道,L[3:0]=000(转换一个通道),看到这肯定有疑问,怎么没有去配置哪个通道,只有转换的个数,别急,这个是在adc_get_result函数里配置的,直接配置直接读。
step4:开启,然后校准。
剩下的两个函数是配置采样速率和读取数据的函数,大家自己看吧,对着参考手册看也很简单,提前声明:此代码不能直接用,没有配置时钟,主要时让大家学习。
/**
* @brief ADC初始化函数
*/
void adc_init(void)
{
ADC_ADCX->CR1 &= ~(0XF << 16); /* 工作模式清零 */
ADC_ADCX->CR1 |= 0 << 16; /* 独立工作模式 */
ADC_ADCX->CR1 &= ~(1 << 8); /* 非扫描模式 */
ADC_ADCX->CR2 &= ~(1 << 1); /* 单次转换模式 */
ADC_ADCX->CR2 &= ~(7 << 17);
ADC_ADCX->CR2 |= 7 << 17; /* 软件控制转换 */
ADC_ADCX->CR2 |= 1 << 20; /* 使用用外部触发(SWSTART)!!! 必须使用一个事件来触发 */
ADC_ADCX->CR2 &= ~(1 << 11); /* 右对齐 */
ADC_ADCX->SQR1 &= ~(0XF << 20); /* L[3:0]清零 */
ADC_ADCX->SQR1 |= 0 << 20; /* 1个转换在规则序列中 也就是只转换规则序列1 */
ADC_ADCX->CR2 |= 1 << 0; /* 开启AD转换器 */
ADC_ADCX->CR2 |= 1 << 3; /* 使能复位校准 */
while (ADC_ADCX->CR2 & 1 << 3); /* 等待校准结束 */
/* 该位由软件设置并由硬件清除 在校准寄存器被初始化后该位将被清除 */
ADC_ADCX->CR2 |= 1 << 2; /* 开启AD校准 */
while (ADC_ADCX->CR2 & 1 << 2); /* 等待校准结束 */
}
/**
* @brief 设置ADC通道采样时间
* @param adcx : adc结构体指针, ADC1 / ADC2
* @param ch : 通道号, 0~17
* @param stime: 采样时间 0~7, 对应关系为:
*/
void chanal_set (ADC_TypeDef *adcx, uint8_t ch, uint8_t stime)
{
if (ch < 10) /* 通道0~9,使用SMPR2配置 */
{
adcx->SMPR2 &= ~(7 << (3 * ch)); /* 通道ch 采样时间清空 */
adcx->SMPR2 |= 7 << (3 * ch); /* 通道ch 采样周期设置,周期越高精度越高 */
}
else /* 通道10~17,使用SMPR1配置 */
{
adcx->SMPR1 &= ~(7 << (3 * (ch - 10))); /* 通道ch 采样时间清空 */
adcx->SMPR1 |= 7 << (3 * (ch - 10)); /* 通道ch 采样周期设置,周期越高精度越高 */
}
}
/**
* @brief 获得ADC转换后的结果
* @param ch: 通道号, 0~17
* @retval 无
*/
uint32_t get_value (uint8_t ch)
{
adc_channel_set(ADC_ADCX, ch, 7); /* 设置ADCX对应通道采样时间为239.5个时钟周期 */
ADC_ADCX->SQR3 &= ~(0X1F << 5 * 0); /* 规则序列1通道清零 */
ADC_ADCX->SQR3 |= ch << (5 * 0); /* 规则序列1 通道 = ch */
ADC_ADCX->CR2 |= 1 << 22; /* 启动规则转换通道 */
while (!(ADC_ADCX->SR & 1 << 1)); /* 等待转换结束 */
return ADC_ADCX->DR; /* 返回adc值 */
}
2、HAL库实现
这是HAL库的配置方式,实际上当你把寄存器弄懂之后,你看HAL的配置其实很简单,只要对着他的说明去正常配置就可以,主要分为两个结构体,一个是基本配置(ADC_InitTypeDef),另一个通道配置(ADC_ChannelConfTypeDef),我给大家把这个配置结构体的注释写在代码里,以供大家参考,初始化函数就是根据自己所需去配置这些结构体就行。
typedef struct//基本配置
{
/* ADC时钟配置 ---------------------------------------------------------- */
uint32_t ClockPrescaler; /*!< ADC时钟源选择及分频系数:
- 选择同步时钟(源自APB时钟)或异步时钟(源自系统时钟/PLL)
- 参考手册查看可用时钟列表
- 注意:所有ADC实例共享此时钟配置
- 重要限制:同步时钟模式需系统时钟50%占空比
- 异步时钟需在RCC层预先使能
- 只能在所有ADC禁用时修改此参数 */
/* 转换精度配置 -------------------------------------------------------- */
uint32_t Resolution; /*!< ADC转换精度设置:
- 可选值:6位/8位/10位/12位
- 精度越高,转换时间越长 */
/* 数据对齐方式 -------------------------------------------------------- */
uint32_t DataAlign; /*!< 转换结果在数据寄存器中的对齐方式:
- 右对齐:正常模式,数据在DR寄存器的低12位
- 左对齐:方便8位系统读取高位数据 */
/* 扫描模式配置 -------------------------------------------------------- */
uint32_t ScanConvMode; /*!< 规则组扫描序列模式:
- 完全可配置模式:自由设置序列长度和各位置通道
- 非完全可配置模式:通道号固定对应序列位置
- 多通道时自动启用序列器
- 单通道时工作在单次模式
- 可配合间断模式使用 */
/* 转换结束标志选择 ---------------------------------------------------- */
uint32_t EOCSelection; /*!< 转换结束标志选择:
- 单次转换结束(EOC) 或 序列转换结束(EOS)
- 影响轮询和中断行为 */
/* 低功耗自动等待 ------------------------------------------------------ */
FunctionalState LowPowerAutoWait; /*!< 低功耗自动等待模式:
- 使能:新转换在上次结果被读取后才开始
- 自动调节转换频率,防止数据溢出
- 注意:与中断/DMA模式不兼容
- 推荐用于轮询模式节能 */
/* 自动断电模式 -------------------------------------------------------- */
FunctionalState LowPowerAutoPowerOff; /*!< 自动断电模式:
- 使能:转换后自动断电,触发时重新上电
- 可配合自动等待模式使用
- 唤醒时有采样启动时间开销 */
/* 连续转换模式 -------------------------------------------------------- */
FunctionalState ContinuousConvMode; /*!< 连续转换模式开关:
- 禁用:单次转换模式(触发一次转换一次)
- 使能:连续转换模式(自动循环转换) */
/* 规则通道数量 -------------------------------------------------------- */
uint32_t NbrOfConversion; /*!< 规则组转换通道数量:
- 完全可配置模式:设置序列长度(1-8)
- 非完全可配置模式:此参数被忽略
- 只能在无转换进行时修改 */
/* 间断模式 ------------------------------------------------------------ */
FunctionalState DiscontinuousConvMode; /*!< 间断模式开关:
- 使能:将序列分成多个子组
- 要求:扫描模式使能且连续模式禁用
- STM32F1仅支持单通道间断模式 */
/* 外部触发源 ---------------------------------------------------------- */
uint32_t ExternalTrigConv; /*!< 规则组外部触发源选择:
- 选择定时器/EXTI等硬件触发源
- ADC_SOFTWARE_START:使用软件触发
- 注意:触发源为所有ADC共享 */
/* 外部触发边沿 -------------------------------------------------------- */
uint32_t ExternalTrigConvEdge; /*!< 外部触发信号边沿选择:
- 上升沿/下降沿/双边沿触发
- 软件触发时此参数无效 */
/* DMA连续请求 --------------------------------------------------------- */
FunctionalState DMAContinuousRequests; /*!< DMA请求模式:
- 禁用:按转换次数请求(单次模式)
- 使能:连续请求(需DMA配置为循环模式)
- 防止DMA缓冲区溢出 */
/* 数据溢出处理 -------------------------------------------------------- */
uint32_t Overrun; /*!< 数据溢出处理策略:
- 覆盖旧数据 或 保留旧数据
- 保留模式需在中断回调中及时保存数据
- DMA模式下始终报告溢出错误 */
/* 公共采样时间1 ------------------------------------------------------ */
uint32_t SamplingTimeCommon1; /*!< 第一组通道公共采样时间:
- 单位:ADC时钟周期
- 总转换时间 = 采样时间 + 固定处理时间
- 内部通道(VrefInt/Temp)需满足最小采样时间
- 参考数据手册计算最小采样时间 */
/* 公共采样时间2 ------------------------------------------------------ */
uint32_t SamplingTimeCommon2; /*!< 第二组通道公共采样时间:
- 特性同SamplingTimeCommon1
- STM32F1支持两组独立采样时间配置
- 允许不同通道组使用不同采样时间 */
/* 过采样模式 ---------------------------------------------------------- */
FunctionalState OversamplingMode; /*!< 过采样功能开关:
- 使能:提升有效分辨率
- 只能在无转换进行时修改 */
/* 过采样参数 ---------------------------------------------------------- */
ADC_OversamplingTypeDef Oversampling; /*!< 过采样详细参数:
- 包含采样倍数、位移、触发模式
- 覆盖先前过采样配置 */
/* 触发频率模式 -------------------------------------------------------- */
uint32_t TriggerFrequencyMode; /*!< 触发频率模式选择:
- 高频模式:默认模式
- 低频模式:满足tIdle时间要求时使用
- 低频模式增加2个ADC时钟周期延迟 */
} ADC_InitTypeDef;
//通道配置
typedef struct
{
/* ADC通道选择 ---------------------------------------------------------- */
uint32_t Channel; /*!< 指定要配置的ADC通道:
- 物理通道号(如ADC_CHANNEL_0对应PA0)
- 内部通道:温度传感器(ADC_CHANNEL_TEMPSENSOR)、
Vrefint(ADC_CHANNEL_VREFINT)等
- 注意:部分通道在特定封装不可用
- 需参考数据手册确认引脚可用性 */
/* 规则组序列位置配置 ---------------------------------------------------- */
uint32_t Rank; /*!< 设置通道在规则组序列中的位置:
- 完全可配置模式:自由指定通道顺序(Rank1-16)
[通道0可设为Rank3,通道1可设为Rank1]
- 非完全可配置模式:通道号固定对应序列位置
(通道0→Rank1,通道1→Rank2...)
- 特殊值:ADC_RANK_NONE可移除该通道
- 关键作用:决定多通道扫描顺序 */
/* 采样时间配置 --------------------------------------------------------- */
uint32_t SamplingTime; /*!< 指定通道的采样时间:
- 单位:ADC时钟周期
- 总转换时间 = 采样时间 + 固定处理时间
(12位:+12.5周期,10位:+10.5周期等)
- STM32F1支持两组公共采样时间配置
(通过ADC_InitTypeDef中的SamplingTimeCommon1/2设置)
- 内部通道需满足最小采样时间要求
温度传感器:≥10μs,Vrefint:≥3μs */
} ADC_ChannelConfTypeDef;
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
HAL_Delay(1000);
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_ADC1_Init();
/* USER CODE BEGIN 2 */
HAL_ADCEx_Calibration_Start(&hadc1);//ADC校准
HAL_ADC_Start(&hadc1);
ADCbuffer[0] = HAL_ADC_GetValue(&hadc1);
}
/**
* @brief ADC1 Initialization Function
* @param None
* @retval None
*/
static void MX_ADC1_Init(void)
{
/* USER CODE BEGIN ADC1_Init 0 */
/* USER CODE END ADC1_Init 0 */
ADC_ChannelConfTypeDef sConfig = {0};
/* USER CODE BEGIN ADC1_Init 1 */
/* USER CODE END ADC1_Init 1 */
/** Configure the global features of the ADC (Clock, Resolution, Data Alignment and number of conversion)
*/
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc1.Init.LowPowerAutoWait = DISABLE;
hadc1.Init.LowPowerAutoPowerOff = DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.DMAContinuousRequests = DISABLE;
hadc1.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc1.Init.SamplingTimeCommon1 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.SamplingTimeCommon2 = ADC_SAMPLETIME_1CYCLE_5;
hadc1.Init.OversamplingMode = DISABLE;
hadc1.Init.TriggerFrequencyMode = ADC_TRIGGER_FREQ_HIGH;
if (HAL_ADC_Init(&hadc1) != HAL_OK)
{
Error_Handler();
}
/** Configure Regular Channel
*/
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLINGTIME_COMMON_1;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN ADC1_Init 2 */
/* USER CODE END ADC1_Init 2 */
}
总结
本文介绍了逐次逼近型的转换原理,从基础结构出发,讲述STM32内部ADC的运行过程,还有内部寄存器的配置,旨在了解ADC的内部工作原理,没有从HAL库最上层往底层介绍,这是本文的遗憾,写下这篇文章用于日后自己复习,如有错误,欢迎大家评论留言,一起进步!
更多推荐



所有评论(0)