STM32F4 ADC 深度解析:从原理到实战,告别踩坑指南

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外设具有以下主要特点:

  1. 高精度转换:12位分辨率,转换结果可左对齐或右对齐存储
  2. 多通道支持:最多支持19个外部通道,可测量来自 16 个外部源、2个内部源和 VBAT 通道的信号。这些通道的 A/D 转换可在单次、连续、扫描或不连续采样模式下进行。 ADC 的结果存储在一个左对齐或右对齐的 16 位数据寄存器中。
  3. 灵活的工作模式:支持单次、连续、扫描和间断转换模式
  4. 高性能:最高可达2.4MHz的转换速率
  5. 低功耗特性:支持多种低功耗模式下的转换操作
  6. 丰富的触发源:支持定时器触发、外部触发等多种触发方式
  7. 模式多样:可以将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外设提供两种通道类型:

  1. 规则通道:常规转换通道,最多16个
  2. 注入通道:可中断规则通道转换的高优先级通道(最多4个)

1.2.3 工作模式

  1. 单次模式:执行单次转换后停止
  2. 连续模式:自动重复启动转换
  3. 扫描模式:自动按顺序转换多个通道
  4. 不连续模式:将长序列分成多个短序列转换

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的注入转换。等注入转换完毕,再回到规则转换中,上次被打断的位置继续转换。怎么样,听起来就跟中断一样,是吧?
注意

  1. 这种情况下,注入转换序列会切换为单次扫描模式。也就是说,注入转换的“表格”里面的所有通道会从头到尾转换一遍,转换完毕就结束。
  2. ADC_CR1寄存器的JAUTO位 = 0。
    在这里插入图片描述
  3. 注入转换触发的时间间隔必须比转换一遍注入转换用时长。比如注入转换一次用时30个cycle,那么触发间隔必须大于等于31个cycle。原因很容易理解,这次都没转换完,莫非要再启动一次?不合理吧。
1.4.6.2 自动注入

这种模式下, ADC_CR1寄存器的JAUTO位 = 1。注入组的转换会排在规则组之后,也就是说,注入组的通道在规则组通道之后自动转换。这样的话,只需要触发规则转换就可以启动所有的通道的转换了。包括规则通道(最多16个)和注入通道(最多4个),一共20个通道。
注意:

  1. 此模式下,禁止注入模式的外部触发。
  2. 如果CONT和JAUTO都等于1,那么转换完注入通道还会回头转换规则通道。

1.4.7 不连续转换模式

比如说,规则转换模式下,有12个通道,需要ADC去转换。但你希望按照每3个通道为一个小组,一次转换3个通道,直到所有通道转换完成,再置位转换完成标志位EOC。那么就需要用到不连续采样模式。说到底,就是将大序列,分成小序列,逐个击破。

在这里插入图片描述
将上图中的寄存器位置位,就会启动规则组或者注入组的不连续转换。
注意:

  1. 不可以同时使用自动注入模式和不连续采样模式。
  2. 不可以同时对注入组和规则组使用不连续采样模式。只能有一个组使用。

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数据手册》


全文结束,如果对您有帮助,欢迎点赞、收藏、转发、关注!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值