STM32F4 ADC 深度解析:从原理到实战,告别踩坑指南
前言
你是否还在为 STM32F4 的 ADC 配置抓耳挠腮?明明按手册步骤操作,却始终读不到正确的 VBAT 电压?温度传感器数据偏差巨大,查遍资料也找不到症结?
这篇文章就是为嵌入式开发者量身打造的ADC 通关秘籍!
我们不只会带你吃透 STM32F4 ADC 的底层原理 ——12 位高精度转换、19 路通道灵活配置、规则 / 注入通道的优先级机制,更用 ”厨师做菜“ 的生动比喻让抽象的寄存器操作一目了然。
从硬件框图解析到 HAL 库代码实战,从温度传感器采集到 VBAT 电压测量,每一步都附带避坑指南:比如 VBAT 通道必须乘以 2 的分压秘密,温度计算公式里隐藏的电压转换细节,这些手册里语焉不详的「暗坑」,这里都给你扒得明明白白。
无论你是刚入门的新手,还是想优化 ADC 性能的老手,这份包含「原理 + 配置 + 代码 + 踩坑总结」的全攻略,都能帮你少走 90% 的弯路。收藏本文,下次调 ADC 时直接对照操作,效率翻倍!
看完觉得有用,别忘了点赞关注,后续还有更多 STM32 实战干货等着你~
一、STM32F4系列微控制器的ADC原理
1.1 ADC外设概述
STM32F4系列微控制器内置了3个高精度的逐次逼近型模数转换器(ADC),是嵌入式系统中实现模拟信号采集的关键外设。该ADC外设具有以下主要特点:
- 高精度转换:12位分辨率,转换结果可左对齐或右对齐存储
- 多通道支持:最多支持19个外部通道,可测量来自 16 个外部源、2个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续采样模式下进行。 ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。
- 灵活的工作模式:支持单次、连续、扫描和间断转换模式
- 高性能:最高可达2.4MHz的转换速率
- 低功耗特性:支持多种低功耗模式下的转换操作
- 丰富的触发源:支持定时器触发、外部触发等多种触发方式
- 模式多样:可以将ADC1和ADC2、ADC3组合起来形成“二重”或者“三重”的ADC模式。
1.2 ADC主要特性详解
1.2.1 基本参数
- 分辨率:12位(可配置为10位、8位或6位)
- 输入电压范围:0-VREF+(通常0-3.3V)
- 转换时间:最短0.41μs(ADC时钟为36MHz,此为最大值)
1.2.2 通道配置
STM32F4的ADC外设提供两种通道类型:
- 规则通道:常规转换通道,最多16个
- 注入通道:可中断规则通道转换的高优先级通道(最多4个)
1.2.3 工作模式
- 单次模式:执行单次转换后停止
- 连续模式:自动重复启动转换
- 扫描模式:自动按顺序转换多个通道
- 不连续模式:将长序列分成多个短序列转换
1.2.4 查看数据手册了解关键参数
下面摘自《STM32F407数据手册》
上图我们优先关注的是ADC的时钟频率,在电源电压在2.4V-3.6V之间的话,ADC的频率最高为36MHz。但如果电压低于2.4V,高于1.8V,那就频率会降低到18MHz。
而VREF+是参考正电压,在原理图上需要我们连接到特定的电源上,范围是1.8V到电源电压,一般连接到电源电压3.3V。VREF-是参考负电压,一般接地。
RADC是采样开关电阻,是连接到外部信号源的,外部信号通过此电阻分压到ADC的采样保持电路。
CADC是采样保持电容,在采样之后,信号源在这个电容充电,之后这个电容的电压就是ADC转换器采集到的电压。
ts采样时间,在我们按照后面的使用场景下,是3-480个cycle。这里说的cycle,就是ADCCLK的周期。我们一般不说多少毫秒,会说多少个cycle。
tCONV转换时间,按照公式,是(采样时间 + 转换精度)个cycle。如果说,使用12位精度,采样时间是3个cycle。那么转换时间是3 + 12 = 15个cycle。
1.3 ADC框图解析
STM32F407ZGT6里面内置了3个ADC外设,分别是ADC1、ADC2和ADC3。下图是ACDC1的原理图。ADC2和ADC3没有图中的温度传感器、内部参考电压VREFINT还有VBAT这三个通道。对于STM32F407来说,温度传感器内部连接到ADC1_IN16,内部参考电压VREFINT连接到ADC1_IN17,而VBAT连接到ADC1_IN18。
而对于每个ADC都有的外部的输入通道而言,引脚进来先到了“GPIO端口”模块,所以我们如果设置了某个外部通道,需要将用到的引脚设置成模拟输入模式。
框图中,我们首先看位于中间的模数转换器,它是ADC的核心。前面我们知道它是逐次逼近型的。由图中,我们得知,里面分为注入通道和规则通道。
如果需要使用ADC,首先要做的就是使能ADC,之后就是使能注入转换,或者使能规则转换。操作的寄存器如下。
这里简单讲一下这两个通道。不管是注入通道还是规则通道,都像是一张表格,这是长度不同。比如下面的示例。
1.3.1 注入转换
注入通道左边有箭头,“多达4”,就是说,最多有4路输入可以连接到此通道。而这4路输入的选项,在左边,我们看到有ADCx_IN0到ADCx_IN15(其中的x可以是1,2,3),温度传感器,内部参考电压VREFINT还有VBAT。就像前面看到的介绍,一共有19路通道可以选择。对于ADC1,有19路;对于ADC2和ADC3没有后面的三个通道。
注入通道的优先级比规则通道高,也就是说,在规则通道转换期间,一旦注入通道使能了转换,那么就立即抢占ADC转换器的使用权,先转换注入通道。
注入数据寄存器一共有4个,当ADC转换完成后,会将结果放进此寄存器。
1.3.2 规则转换
对于规则通道,有描述“多达16”,就是说,最多可以有16路输入连接到此通道。这16路的输入源与注入通道一样。ADC可以使用扫描模式,从头到尾将表格上的通道扫描地转换一遍。也可以单独让他转换某特定的通道。
规则数据寄存器一共就1个,当ADC转换完成后,会将结果存放进此寄存器。
1.3.3 形象的比喻
ADC转换器就像一个大厨师,全饭店就他一个人负责做饭。这个时候,他有两张菜单,一张写着“注入通道”,这是VIP客人的下单要吃的菜,但这份菜单只可以写4道菜,这个菜单对应这4个空盘子,也就是说,做完一道菜就可以立即放在一个空盘子,继续做第二道,期间不担心第二道菜做出来没地方放。另一张菜单是给普通客人用的,可以写16道菜,但只对应着一个空盘子,做出一道菜就要立即有人去把菜盛出来,留下空盘子给厨师继续用。一旦没有及时清理上次做的菜,厨师直接把上次做的菜倒了,放新做的菜。
所以你就明白了,不管外面多少客人要吃菜,厨师就一个人,厨师可以分时复用地给所有人提供服务。如果您明白了,倒是给我点个赞哪!哈哈……
1.3.4 中断
当转换结束,可以有转换结束中断通知应用层。对应注入通道,他单独还有一个注入转换结束中断,要不怎么说人家是VIP呢?哈哈。
还有DMA溢出中断,在使用规则通道的时候,我们可以使用DMA来自动将转换结果存储到我们的指定变量或数组中。但在参考手册讲到中断的时候,只讲到溢出中断,就是说,厨师已经做好第二道菜了,发现前面那道菜没有人拿走,那就溢出了。个人怀疑这里写DMA溢出可能写错了。
这里还有模拟看门狗,就是说,如果转换的结果超过了某个阈值,这里有上限和下限,超过了这个范围就会触发中断。
1.3.5 触发源
既然前面已经配置好了ADC需要转换的通道了,那么如何开启转换呢?可以使用定时器来触发,也可以使用软件来触发。关键看下面的配置。
1.4 转换模式
1.4.1 单次转换模式
使用单次模式还是连续转换模式,关键在于操作下面的寄存器位。还是上面提到的ADC_CR2寄存器。
该位等于0,则使用单次转换模式。
此模式下,ADC执行一次转换,就停止。当然外部触发或者软件触发,如果使用软件触发,需要在ADC_CR2寄存器中写1到相应的位。参见第三章开头部分的解释。转换结束后,如果是注入模式,数据存放在ADC_JDR1寄存器;如果是规则模式,数据存储在ADC_DR寄存器。但是这两个寄存器都是16位的,而转换结果是12位的,就涉及到到底是左对齐存放?还是右对齐存放?在下面的寄存器位上设置。还是ADC_CR2寄存器。
当转换完成,我们怎么知道已经转换完成了?
查看寄存器!
如果相应的中断使能位置位了,还会触发中断。这个时候,我们可以在中断处理函数中读取数据。
1.4.2 连续转换模式(仅适用于规则模式)
前面CONT位等于1,就会使用连续转换模式。
这种模式下,结束一次转换后会立即启动下一次新的转换。启动转换的话,可以使用外部触发或者软件触发。软件触发的话,置位SWSTRT寄存器位即可。
转换之后,EOC(转换结束标志位)等于1,另外如果EOCIE置位的话,会触发中断。
1.4.3 ADC转换时序图
每一次ADC转换,需要提前使能ADC,之后触发ADC转换,之后等到转换完成,EOC就会置位,我们去读取数据,手动清除此位,再进行下一步的转换。
ADC在使能了之后需要一段稳定时间。参见2.5章节的数据手册内容,可得知。
1.4.4 模拟看门狗
关于看门狗,请查看下面的寄存器。
这里的阈值与ALIGN位所选的对齐方式无关,对齐方式是针对转换结果的。这里的阈值只有效于低12位。只要转换的结果在这二者以内,就不会触发看门狗中断。
对于哪个通道要使用看门狗,下面的表格有注释。
参见寄存器详情。
如果使能了单一通道的模拟看门狗,那么到底哪个通道上使用呢?看AWDCH[4:0]位的值。ADC一共有19个通道,每一个通道都有一个数值与之对应。
1.4.5 扫描模式
注入转换和规则转换模式都可以使用扫描模式。还记得上面第三章讲到的表格吗?当我们在表格里填写了转换通道,就可以开启扫描模式。ADC会依次以扫描的方式,转换这些通道,并且,如果开启了连续转换模式,ADC转换完最后一个通道后会从头转换第一个通道,不会停下来。
而对于这两张表格,其实对应到了下面的寄存器。
最值得注意的是,L[2:0],代表了规则转换里放了多少个通道,也就是表格里有多少行是有效的。而这里的“SQ+数字”,就是表格上的”第几行“。而这样的寄存器还有,下面补充。
也就是说规则通道一共有SQ1-SQ16。
关于注入通道,也有类似的寄存器,但由于注入通道一共也就4个,所以一个寄存器就可以满足了。
最值得注意的是,JL[1:0],代表了注入转换里放了多少个通道,也就是表格里有多少行是有效的。而这里的“JSQ+数字”,就是表格上的”第几行“。
1.4.6 注入通道管理
1.4.6.1 触发注入
在此模式下,在规则转换期间,出现注入转换通道的启动触发,就立即停下来规则转换,将当前的转换复位。转而去执行ADC的注入转换。等注入转换完毕,再回到规则转换中,上次被打断的位置继续转换。怎么样,听起来就跟中断一样,是吧?
注意:
- 这种情况下,注入转换序列会切换为单次扫描模式。也就是说,注入转换的“表格”里面的所有通道会从头到尾转换一遍,转换完毕就结束。
- ADC_CR1寄存器的JAUTO位 = 0。
- 注入转换触发的时间间隔必须比转换一遍注入转换用时长。比如注入转换一次用时30个cycle,那么触发间隔必须大于等于31个cycle。原因很容易理解,这次都没转换完,莫非要再启动一次?不合理吧。
1.4.6.2 自动注入
这种模式下, ADC_CR1寄存器的JAUTO位 = 1。注入组的转换会排在规则组之后,也就是说,注入组的通道在规则组通道之后自动转换。这样的话,只需要触发规则转换就可以启动所有的通道的转换了。包括规则通道(最多16个)和注入通道(最多4个),一共20个通道。
注意:
- 此模式下,禁止注入模式的外部触发。
- 如果CONT和JAUTO都等于1,那么转换完注入通道还会回头转换规则通道。
1.4.7 不连续转换模式
比如说,规则转换模式下,有12个通道,需要ADC去转换。但你希望按照每3个通道为一个小组,一次转换3个通道,直到所有通道转换完成,再置位转换完成标志位EOC。那么就需要用到不连续采样模式。说到底,就是将大序列,分成小序列,逐个击破。
将上图中的寄存器位置位,就会启动规则组或者注入组的不连续转换。
注意:
- 不可以同时使用自动注入模式和不连续采样模式。
- 不可以同时对注入组和规则组使用不连续采样模式。只能有一个组使用。
1.5 数据对齐
由于精度最多也就12位,但寄存器是16位的,所以一定要选择对齐方式。可以左对齐,也可以右对齐。
操作的寄存器如下:
1.5.1 规则组数据对齐模式
对于规则转换,如果是左对齐,就低位全部补充0;如果右对齐,那就高位全部补充0 。
除非分辨率是6位的时候,如下:
1.5.2 注入组数据对齐模式
注入通道转换的结果减去用户自定义的偏移量,得到的结果放在数据寄存器中。这个偏移量存放在寄存器ADC_JOFRx。
由于是初始值 - 偏移量,所以有可能出现负数。于是需要符号位。在手册中描述为SEXT位。
1.6 各通道单独设置采样时间及转换时间公式
每个通道都可以设置采样时间,具体操作的寄存器:
总的转换时间有如下公式:
转换时间TCONV = 采样时间 + ADC分辨率
比如说,分辨率选择的是12位,而采样时间选择3个周期,那么转换时间就是 3 + 12 = 15cycle。
此时如果ADCCLK = 30Mhz,也就是周期是1/30us,乘以15,得到的就是转换时间: 1/30 * 15 = 0.5us。
1.7 外部触发和极性
不管是规则转换还是注入转换,都可以有各种外部触发来源和极性,当然这些都是外部触发!与此对立的是软件触发,就是写寄存器ADC_CR1位SWSTART=1
(规则转换)和JSWSTART=1
(注入转换)。
需要提醒一下大家,所谓的触发极性,就是上升沿还是下降沿触发,都是针对外部中断而言的(外部中断11对应规则转换触发,外部中断15对应注入转换),对于内部的定时器触发,没有触发极性一说。
看到这里,大家可以和前面ADC框图讲解中的3.5章节对应一下,理解更加容易。
1.8 16个外部通道对应的引脚
有时候我们想要使用外部通道来采样ADC,但不知道对应的引脚是哪里,这个时候就要来查看一下下面的表格。
注:上表摘自《正点原子 STM32F4 HAL库开发指南》
二、配置流程
2.1 配置ADC通道
2.2 参数配置
如果转换通道写2,那还有Rank2需要配置。配置来源是上面我们选择的通道,上面哪个打勾了,下面才可以选哪个。
注:关于偏移量,请看1.5.2 注入组对齐模式
章节。
注意:上面模拟看门狗的配置只是我用来给大家演示的,我并没有使能模拟看门狗。里面的上下限的配置不正确。
2.3 中断配置
2.4 DMA配置
三、代码实践
生成的代码中,ADC初始化是在main函数中自动调用的。
MX_ADC1_Init();
3.1 温度传感器
对于 STM32F40x 和 STM32F41x 器件,温度传感器内部连接到 ADC1_IN16 通道,而ADC1 用于将传感器输出电压转换为数字值。
我们需要做的是启动转换,并且采集数据。
char str[40];
uint16_t adcValue;
uint16_t Voltage_int_Value = 0;
uint16_t Voltage_float_Value = 0;
float Adc_Voltage = 0.0;
(void)HAL_ADC_Start(&hadc1); // 启动ADC转换
if(HAL_ADC_PollForConversion(&hadc1, 10) == HAL_OK)
{
adcValue = HAL_ADC_GetValue(&hadc1);
}
/*处理内部温度传感器的采样值*/
Adc_Voltage = (adcValue / 4096.0) *3.3;
Adc_Voltage = (Adc_Voltage - 0.76) / 0.0025 + 25;
Voltage_int_Value = (uint16_t)(Adc_Voltage);
Voltage_float_Value = (Adc_Voltage - Voltage_int_Value) * 1000;
sprintf(str, "ADC:%ld Temperature Value:%d.%d\r\n", adcValue, Voltage_int_Value, Voltage_float_Value);
HAL_UART_Transmit(&huart1, (uint8_t *)str, strlen(str), 1000);
HAL_ADC_Stop(&hadc1);
(void)HAL_ADCEx_InjectedStart_IT(&hadc1);
上面的代码启动了一次规则转换,并通过轮询的方式获取了转换结果,存储在adcValue中。之后转换成电压值,再通过公式进行换算得到温度值。
关于温度计算公式,下面给出:
参照数据手册,得到参数。
于是得出上面代码中使用的公式。
Adc_Voltage = (Adc_Voltage - 0.76) / 0.0025 + 25;
3.2 VBAT电压测试
关于VBAT电压,手册这样描述的:
由于 VBAT 电压可能高于 VDDA,因此 VBAT 引脚需要内部连接到桥接分配器,以确保 ADC 正确运行。
设置 VBATE 后,桥接器会自动使能以进行以下连接:
- 在 STM32F40xx 和 STM32F41xx 器件上将
VBAT/2
连接到 ADC1_IN18 输入通道。下面代码的Vbat_float = (VbatValue / 4096.0) *3.3*2.0;
最后乘以2,就是为了这里的VBAT/2
。
void HAL_ADCEx_InjectedConvCpltCallback(ADC_HandleTypeDef *hadc)
{
uint32_t Vbat_int_Value, Vbat_decimal_Value;
float Vbat_float;
char str[40];
uint32_t VbatValue = HAL_ADCEx_InjectedGetValue(hadc, ADC_INJECTED_RANK_1);
Vbat_float = (VbatValue / 4096.0) *3.3*2.0;
Vbat_int_Value = (uint32_t)(Vbat_float);
Vbat_decimal_Value = (uint32_t)((Vbat_float - (float)Vbat_int_Value) * 1000);
sprintf(str, "ADC:%ld Vbat: %ld.%ldV", VbatValue, Vbat_int_Value, Vbat_decimal_Value);
LCD_ShowString(30,130,200,16,16,(uint8_t *)str);
sprintf(str, "VbatVal:%ld VbatVol: %ld.%ldV\r\n", VbatValue, Vbat_int_Value, Vbat_decimal_Value);
queue_printf(str);
}
由于前面已经调用了函数:
(void)HAL_ADCEx_InjectedStart_IT(&hadc1);
所以程序会进入上面的中断回调函数采集VBAT电压。
四、踩的坑总结一下
4.1 VBAT电压采集
经过计算得到的电压只有1.4V到1.5V,严重与实际值不符合,实际是3V的电池。后阅读参考手册,明白了桥接分配器搞的鬼。
4.2 温度传感器
手册里说的比较隐晦,没有提到代码:Adc_Voltage = (adcValue / 4096.0) *3.3;
这里代码所讲的内容。
只是对下面的公式有讲解,导致得到的温度不对。
Adc_Voltage = (Adc_Voltage - 0.76) / 0.0025 + 25;
五、参考文献
《正点原子 STM32HAL库开发指南》
《STM32F4中英文参考手册》
《STM32F407数据手册》
全文结束,如果对您有帮助,欢迎点赞、收藏、转发、关注!