HAL库STM32常用外设教程(七)—— ADC
2、STM32CubeMx软件3、keil5内容简述:通篇文章将涉及以下内容,如有错误,欢迎指出1、ADC基本原理2、轮询、中断、DMA方式下的ADC采集ADC 即模拟数字转换器,英文详称 Analog-to-digital converter,可以将外部的模拟信号转换为数字信号,是模拟信号数字化的必要器件。STM32F407有3个ADC,最高12位分辨率,最多16个外部通道,ADC1还有3个内部
前言
1、STM32F407ZGT6
2、STM32CubeMx软件
3、keil5
内容简述:
通篇文章将涉及以下内容,如有错误,欢迎指出:
1、ADC基本原理
2、轮询、中断、DMA方式下的ADC采集
一、ADC功能概述
1. 1 ADC的特性
ADC 即模拟数字转换器,英文详称 Analog-to-digital converter,可以将外部的模拟信号转换为数字信号,是模拟信号数字化的必要器件。STM32F407有3个ADC,最高12位分辨率,最多16个外部通道,ADC1还有3个内部测量通道,可以测量内部温度、参考电压和备用电池电压。
STM32F407 的 ADC 主要特性我们可以总结为以下几条:
- 可配置 12 位、 10 位、 8 位或 6 位分辨率
- 转换结束、注入转换结束和发生模拟看门狗事件时产生中断
- 有单次和连续转换模式
- 自校准
- 带内嵌数据一致性的数据对齐
- 多通道输入时,具有从通道0到通道n的扫描模式
- 具有内部和外部触发选项,可由定时器触发或外部中断触发
- 具有模拟看门狗功能,可以监测电压范围
- 采样间隔可以按通道分别编程
- 规则转换和注入转换均有外部触发选项
- 有间断模式
- 双重模式(带 2 个或以上 ADC 的器件)
- ADC 转换时间: 最大转换速率为 2.4MHz,转换时间为 0.41us
- ADC 供电要求: 2.4V 到 3.6V
- ADC 输入范围: VREF– ≤ VIN ≤ VREF+
- 规则通道转换期间有 DMA 请求产生
1.2 ADC的工作原理
单个ADC的内部功能结构如图1-1所示,核心是图中的“模数转换器”。
1.2.1 模拟部分供电(对应图6-1中的①)
ADC是MCU上的模拟部分,模拟部分的供电有4个引脚,典型的供电方案如图1-2所示。
(1) VDDA是模拟部分工作电源。如果要保证ADC精度,可以使用独立的模拟电源输入,但是VDDA最高不能超过4V。如图6-2所示。
注:VREF+为3.3V,那么ADC的输入电压不应该超过这个值。
(2)VSS是模拟地电源,与数字电源共地。
(3)VREF+ 是ADC转换的正参考电压。如果要保证ADC精度,可以使用单独的参考电压芯片输出的精密参考电压, VREF+不能超过VDDA,最低值为1.8V。一般情况下,将VREF+与VDDA连接。
注:
在测量电压时,如果VDDA接的是3.3V,那么VERF+一定注意不能接到大于3.3V上。
(4) VREF-是将ADC的负参考电压,必须与VSSA连接。
ADC转换电压的输入范围是在VERF-与 VERF+之间,因为VREF-必须与VSS连接,也就是VREF-总是0,所以STM32F407的片上ADC只能转换正电压,这与某些独立的ADC芯片可转换正负范围的电压是不同的。
1.2.2 输入通道(对应图6-2中的②)
每个ADC单元有16个外部输入引脚,对应与16个ADC输入复用引脚,即图6-1中的ADCx_IN0至ADCx_IN15。每个ADC单元的模拟输入复用引脚如图1-3所示。
ADC1单元还有以下3个内部输入使用通道16-18。
- 温度传感器:芯片内部温度传感器,测温范围为 -40摄氏度-125摄氏度,精度为 ±1.5°。
- VERFINT:内部参考电压,实际连接内部1.2V调压器的输出电压。
- VBAT:备用电源电压,因为VBAT电压可能高于VDDA,内部有桥梁分压器,实际测量的电压是VBAT/2。
一个ADC单元可以选择多个输入通道,通过模拟复用器进行多路ADC转换。
1.2.3 规则通道和注入通道(对应图6-2中的③)
选择的多个模拟输入通道可以分为两组:规则通道和注入通道。每个组的通道构成一个转换序列。
规则转换序列最多可设置16个通道,一个规则转换序列规定了多路复用转换时的顺序。例如,选择了IN0、IN1、IN2共三个通道作为规则通道,定义的规则转换序列可以是IN0、IN1、IN2,也可以是IN0、IN3、IN2,甚至是IN0、IN31、IN0。
注入通道就是可以在规则通道转换过程中插入进行转换的通道,类似于中断的现象,可以通过图1-4进行理解。
注入转换序列最多可以设置成4个注入通道,也可以像规则转换序列那样设置转换顺序。每个注入通道还可以设置一个数据的偏移量,每次转换结果自动减去这个偏移量,所以转换结果可以是负数。例如,设置偏移量为信号的直流分量,每次转换自动减去直流分量。
1.2.4 触发源(对应图6-2中的④)
规则通道和注入通道由单独的触发源,有以下3类启动或触发转换的方式。
- 软件启动:可以通过软件命令直接触发ADC转换,而不需要外部触发源。直接将控制器寄存器ADC_CR2的ADON位 置1启动ADC转换,写入0时停止ADC转换,这种方式常用与轮询的ADC转换。
- 内部定时器触发:可以使用定时器的输出比较事件或更新事件来触发ADC转换,选择某个定时器的触发输出信号(TRGO)或输入捕获信号作为触发源。例如,选择TIM2_TRGO信号作为启动触发信号,而TIM2的TRGO设置为UEV事件信号,这样的定时器TIM2每次定时溢出时就启动一次转换。这种方式可用于周期性ADC转换。
- 外部IO触发:可以使用外部引脚的信号来触发ADC转换。可以选择外部中断线EXTI_11或EXTI_15作为规则组或注入组的外部触发源。
1.2.5 ADC时钟与转换时间(对应图6-2中的⑤)
ADC转换需要时钟信号ADCCLK驱动,ADCCLK由PCLK2经过分频产生(见图1-5),最少2分频,最多8分频(见图1-6)。STM32F407的PCLK2的最高频率为84MHz,所以ADCCLK的最高频率为42MHz(不同芯片的频率不同)。
我们可以设置在N个ADCCLK周期内对信号进行采样,N的值最小为3,最大为480(见图1-7), 在允许的情况下,尽量选大一点的会使ADC 更稳定、更精确。
ADC 总转换时间的计算公式如下:
TCONV = 采样时间 + 12 个周期
注:之所以要加12个周期,是因为ADC在开始精确转换之前,需要一段稳定时间(个人理解,如理解有误还望指出)
以STM32F407的ADC1的通道1为例,如果 Clock Prescaler设置成“PCLK2 divided by 4”,Sampling Time设置成15 Cycles,那么ADC时钟频率为
42MHz / 4 = 10.5MHz
一个周期转换所需要的时间为频率的倒数,即此时的频率为 1 / 10.5 MHz 。
转换周期为
15 + 12 = 27 周期
转换时间为
一次转换需要的总周期数 * 一个周期转换所需时间 = 27 * ( 1 / 10.5 Mhz ) = 2.57 微秒
其他情况,以此推算。
1.2.6 数据寄存器(对应图6-2中的⑥)
ADC完成转换后将结果存放进数据寄存器,规则通道和注入通道有不同的数据寄存器。规则通道只有一个数据寄存器ADC_DR,只有低16位有效,在多通道转换时,如果前一通道转换结束后,ADC_DR的数据未被及时读出,下一个转换通道的转换结果就会覆盖上一次结果的数据。所以,在多通道转换时,一般EOC中断里及时读取数据或通过DMA将数据传输到内存里。
注入通道有4个数据寄存器,分别对应4个注入通道的转换结果。 规则数据寄存器和注入数据寄存器都是低16位有效,因为转换结果数据对多12位有效。可以设置数据左对齐或右对齐,一般使用右对齐,如图1-8。
注:如图1-9所示, 在二进制中左移n位,数据扩大2的n次方倍,所以左对齐直接读出的数值会比实际值大16倍(左移4位,2的4次方)。
1.2.7 中断(对应图6-2中的⑦)
规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断,它们在 ADC_SR 中都有独立的中断使能位。
- 模拟看门狗中断
模拟看门狗中断发生条件:首先通过ADC_LTR和ADC_HTR寄存器设置低阈值和高阈值,然后开启了模拟看门狗中断后,当被 ADC 转换的模拟电压低于低阈值或者高于高阈值时,就会产生中断。例如我们设置高阈值是 3.0V,那么模拟电压超过 3.0V 的时候,就会产生模拟看门狗中断,低阈值的情况类似。 - DMA 请求
规则组和注入组的转换结束后,除了产生中断外,还可以产生 DMA 请求,把转换好的数据存储在内存里面,防止读取不及时数据被覆盖。
1.3 ADC转换结果电压计算
ADC转换的结果是一个数字量,与实际的模拟电压之间的计算关系 由VERF+和转换精度位数确定。例如,转换精度位12位,VERF+ 为3.3V,ADC转换结果位12位数字量对应的整数为X,则实际电压为
Voltage = (3.3 * X) / 4096 V(该公式必须理解)
比如ADC采集电压采集到的数值为2000,则实际对应的电压应该为
Voltage = (3.3 * 2000)/ 4096 = 1.61 V
1.4 多重ADC模式
STM32F407有3个ADC。这三个ADC可以独立工作,也可以组成双重或三种工作模式。在多重工作模式下,ADC1是主器件,是必须使用的;双重模式就是使用的ADC1和ADC2,不能使用ADC1和ADC3;三种模式就是3个ADC都使用。
多重模式就是使用住期间ADC1的触发信号去交替触发或同步触发其他ADC启动转换。例如,对于三分量模拟输出的振动传感器,需要对X、Y、Z这3个方向的振动信号同步采集,以合成一个三维空间中的振动矢量,这时就需要使用3个ADC对3路信号同步采集,而不能使用一个ADC对3路信号通过多路复用方式进行采集。
多重ADC有多种工作模式,可以交替触发,也可以同步触发。为避免过于复杂,我们仅以双重ADC同步触发为例,说明多重ADC的工作原理和使用方法。 三种ADC和其他工作模式的原理参见STM32F407参考手册。
设置ADC1和ADC2双重工作模式,为ADC1设置的触发源同时也触发ADC2,以实现两个ADC同步转换。在多重模式下,有一个专门的32位数据寄存器ADC_CDR,用于存储多重模式下的转换结果数据。在多重模式下,ADC_CDR的高16位存储ADC2的规则转数据,ADC_CDR的低16位存储ADC1的规则转换结果数据。
在多重模式下,使用DMA进行数据传输有3种模式,其中DMA模式2适用于双重ADC的数据传输。双重ADC是,DMA模式2的工作特点是:每发送一个DMA请求,就以字的形式传输 表示ADC2和ADC1转换结果的32位数据,其中高16位是ADC2的转换结果,低16位是ADC1的转换结果,相当于将ADC_CDR的数据在一个DMA请求时传输出去。
二、ADC的HAL库驱动程序
2.1 常规通道
ADC的驱动程序有两个头文件:头文件stm32f4xx_hal_adc.h是ADC模块总体设置和常规通道相关的函数和定义;文件stm32f4xx_hal_adc_ex.h是注入通道和多重ADC模式相关的函数和定义。表2-1 是文件stm32f4xx_hal_adc.h中的一些主要函数
表2-1 文件stm32f4xx_hal_adc.h中的一些主要的函数
分组 | 函数名 | 功能描述 |
---|---|---|
初始化和配置 | HAL_ADC_Init() | ADC的初始化,设置ADC的总体参数 |
初始化和配置 | HAL_ADC_MspInit() | ADC初始化的MSP弱函数,在HAL_ADC_Init()里面被调用 |
初始化和配置 | HAL_ADC_ConfigChannel() | ADC常规通道配置,一次配置一个通道 |
初始化和配置 | HAL_ADC_AnalogWDGConfig() | 模拟看门狗配置 |
初始化和配置 | HAL_ADC_GetState() | 返回ADC的当前状态 |
初始化和配置 | HAL_ADC_GetError() | 返回ADC的错误码 |
软件启动和转换 | HAL_ADC_Start() | 启动ADC,开始常规通道的转换 |
软件启动和转换 | HAL_ADC_Stop() | 停止常规通道的转换,并停止ADC |
软件启动和转换 | HAL_ADC_PollForConversion() | 轮询方式等待ADC常规通道转换完成 |
软件启动和转换 | HAL_TIM_Base_GetState() | 获取基础定时器的当前状态 |
中断方式转换 | HAL_ADC_Start_IT() | 开启中断,开始ADC常规通道的转换 |
软件启动和转换 | HAL_ADC_Stop_IT() | 关闭中断,停止ADC常规通道的转换 |
软件启动和转换 | HAL_ADC_IRQHandler() | ADC中断ISR里调用的ADC中断处理通用处理函数 |
中断方式转换 | HAL_ADC_Start_DMA() | 开启ADC的DMA请求,开始ADC常规通道的转换 |
软件启动和转换 | HAL_ADC_Stop_DMA() | 停止ADC的DMA请求,停止ADC常规通道的转换 |
下面介绍ADC的三种启动方式,应熟练掌握。
2.1.1 软件启动转换方式(轮询)
函数HAL_ADC_Start()用于以软件方式启动ADC常规通道的转换,软件启动转换后,需要调用函数HAL_ADC_PollForConversion()查询转换是否完成,转换完成后可用函数HAL_ADC_GetValue()读出常规转换结果寄存器的32位数据。若要再次转换,需要再次使用这3个函数启动转换、查询转换是否完成、读出转换结果。使用函数HAL_ADC_Stop()停止ADC常规通道转换。
这种软件启动转换的模式适用于单通道、低采样率的ADC转换。这几个函数原型定义如下:
HAL_StatusTypeDef HAL_ADC_Start(ADC_HandleTypeDef* hadc); //软件启动转换
HAL_StatusTypeDef HAL_ADC_Stop(ADC_HandleTypeDef* hadc); //停止转换
HAL_StatusTypeDef HAL_ADC_PollForConversion(ADC_HandleTypeDef* hadc, uint32_t Timeout);
uint32_t HAL_ADC_GetValue(ADC_HandleTypeDef* hadc); //读取转换结果寄存器的32位数据
其中,参数hadc是ADC外设对象指针,Timeout是超时等待时间(单位是ms)。
2.1.2 中断转换方式
当ADC设置为用定时器或外部信号触发转换时,函数HAL_ADC_Start_IT()用于启动转换,这会开启ADC的中断。当ADC转换完成时会触发中断,在中断服务程序里,可以用HAL_ADC_GetValue()读取转换结果寄存器里的数据。函数HAL_ADC_Stop_IT()可以关闭中断,停止ADC转换。开启和停止ADC中断方式转换的两个函数的原型定义如下:
HAL_StatusTypeDef HAL_ADC_Start_IT(ADC_HandleTypeDef* hadc);
HAL_StatusTypeDef HAL_ADC_Stop_IT(ADC_HandleTypeDef* hadc);
ADC1、ADC2和ADC3共用一个中断号,ISR名称是ADC_IRQHandler()。ADC有4个中断事件源,中断事件类型的宏定义如下(在stm32f4xx_hal_adc.h中):
#define ADC_IT_EOC ((uint32_t)ADC_CR1_EOCIE) //规则通道转换结果结束(EOC)事件
#define ADC_IT_AWD ((uint32_t)ADC_CR1_AWDIE) //模拟看门狗触发事件
#define ADC_IT_JEOC ((uint32_t)ADC_CR1_JEOCIE) //注入通道转换结束事件
#define ADC_IT_OVR ((uint32_t)ADC_CR1_OVRIE) //数据溢出事件、即转换结果未被及时读出
ADC中断通用处理函数是HAL_ADC_IRQHandler(),它内部会判断中断事件类型,并调用相应的回调函数。ADC的4个中断事件类型及其对应的回调函数如表6-2所示。
表6-2 ADC的中断时间类型及其对应的回调函数
中断事件类型 | 中断事件 | 回调函数 |
---|---|---|
ADC_IT_EOC | 规则通道转换结果结束(EOC)事件 | HAL_ADC_ConvCpltCallback() |
ADC_IT_AWD | 模拟看门狗触发事件 | HAL_ADC_LevelOutOfWindowCallback() |
ADC_IT_JEOC | 转入通道转换结束事件 | HAL_ADCEx_InjectedConvCpltCallback() |
ADC_IT_OVR | 数据溢出事件、即转换结果未被及时读出 | HAL_ADC_ErrorCallback() |
用户可以设置为在转换完一个通道后就产生EOC事件,也可以设置为转换完规则通道组的所有通道之后产生EOC事件。但是规则组只有一个转换结果寄存器,如果有多个转换通道,设置为 转换完规则组的所有通道之后产生EOC,会导致数据溢出。一般设置为在转换外一个通道后就产生EOC事件(如图2-1所示)。所以,中断方式转换适用于单通道或采样频率不高的场合。
图2-1 规则通道转换结果结束(EOC)事件
2.1.3 DMA方式转换
ADC只有一个DMA请求,方向是外设到存储器。DMA在ADC中是非常有用,它可以处理多通道、高采样频率的情况。函数HAL_ADC_Start_DMA()以DM方式启动ADC,其原型定义如下
HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
其中,参数hadc是ADC外设对象指针;参数pData是uint32_t类型缓冲区指针,因为ADC转换结果寄存器是32位的,所以DMA数据宽度是32位;参数Length是缓冲区长度,单位是字(4字节)。
停止DMA方式采集的函数是HAL_ADC_Stop_DMA();其原型定义如下:
HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
DMA流的主要中断事件与ADC的回调函数之间的关系如表6-3所示。一个外设使用DMA传输方式时,DMA流的事件中断一般使用外设的事件中断回调函数。
表6-3 DMA流中断事件类型和关联的回调函数*
DMA流中断事件类型宏 | DMA流中断事件类型 | 关联的回调函数名称 |
---|---|---|
DMA_IT_TC | 传输完成中断 | HAL_ADC_ConvCpltCallback() |
DMA_IT_HT | 传输半完成中断 | HAL_ADC_ConvHalfCpltCallback() |
ADC_IT_JEOC | 传输错误中断 | HAL_ADC_ErrorCallback() |
注:注入通道没有DMA方式。
三、软件启动ADC转换
在该示例中,使用ADC2的IN5通道采集电位器的电压,通过串口进行显示,采用软件方式启动ADC转换,在mian()函数的while循环,每隔约500ms转换一次。
3.1 CubeMx项目设置
1、ADC配置
ADC1的模式设置界面如图3-1 所示(本示例用到的是ADC2,但是考虑到ADC1功能更全,故 部分是按ADC1进行讲解,ADC2同样适用),各个复选框的意义如下。
- IN0 至 IN15,是1ADC1的16个外部通道。本示例中输出连接的是ADC2的IN5通道,所以只勾选IN5.
- Temperature Sensor Channel,内部的温度传感器通道,连接ADC1的IN16通道。
- Vrefint Channel,内部参考电压通道,连接ADC1的IN17通道。
- Vbat Channel,备用电源VBAT的通道,连接ADC1的IN8通道。
- Enternal-Trigger-for-Injected-conversion ,为注入转换使用外部触发。
- Enternal-Trigger-for-Regular-conversion,为规则转换使用外部触发。
图3-1 ADC的通道选择
在图3-2所示的界面中设置ADC1的参数,参数分为多个组。
图3-2 ADC2的参数设置
(1) ADCs_Common_Settings组,具体包括如下参数。
Mode:模式。只启用了一个ADC时,只能选择Independent mode(独立模式)。如果启用2个或3个ADC,会出现双重或三重工作模式的选项。
(2) ADC_Settings组,具体包括如下参数。
- ADC_SettingsClock Prescalar: 时钟分频。由PCLK2分频产生ADCCLK时钟,可选2、4、6、8分频。
- Resolution: 分辨率。可选12位、10位、8位、6位,选项中还显示了使用3次采样时的单次转换时钟周期个数,即单次转换最少周期个数。
- Data Alignment:数据对齐方式。可选择右对齐(Right alignment)或左对齐(Left alignment)。
- Scan Conversion Mode:是否使用扫描转换模式。扫描模式用于一组输入通道的转换,如果启用扫描转换模式,则转换完一个通道后,会自动转换组内下一个通道,直到一组通道都转换完成。如果同时启动·了连续转换模式,会立即从组内第一个通过通道再开始转换。
- Continuous Conversion Mode:连续转换模式。启用连续转换模式后,ADC结束一个转换后立即启动一个新的转换。
- Discontinuous Conversion Mode:非连续转换模式,这种模式一般用于外部触发时,将一组输入通道分为多个短的序列,分批次转换。例如,一组规则转换通道为0、1、3、6、7、8、9,如果设置非连续转换通道数为3,则每次触发时的转换通道序列如下。
①第一次触发:转换序列0、1、3.
②第二次触发:转换序列5、6、7。
③第三次触发:转换序列8、9,并生成EOC事件。
④第四次触发:转换序列0、1、3。 - DMA Continuous Requests: 是否连续产生DMA请求,用于设置控制寄存器ADC_CR2的DDS位。如果设置为Disabled,则在最后一个传输后不发出新的DMA请求,如果设置为Enable,只要发生数据转换且使用了DMA,就发出DMA请求。
- End of Conversion Selection:EOC标志产生方式,有以下2种选项。
① EOC flag at the end of single channel conversion:在每个通道转换完成后产生EOC标志。
② EOC flag at the end of all conversion:在一组的所有通道转换完成后产生EOC标志。
(3)ADC_Regular_ConversionMode组,具体包括如下参数。
- Number of Conversion:规则转换序列的转换个数,最多16个,每个转换作为一个Rank(级)。这个数值不必等于输入模拟信号通道数,例如,本例只有一个IN5输入通道,但是转换个数也可以设置为2,每个转换通道都选择IN5.
- External Trigger Conversion Source:外部触发转换的信号源。本例选择为软件启动常规转换(Regular Conbversion launched by software)。周期性采集时,一般选择定时器TRGO信号或捕获比较事件信号作为触发信号,还可以选择外部中断线信号作为触发信号(需要先在图3-3中启用外部触发中断)。
图3-3 外部触发中断设置
- External Trigger Conversion Edge:外部触发转换时间使用的信号边沿,可选择上跳沿、下跳沿,或双边都触发。本例中未使用外部触发,所以设置为None。
- Rank:规则组内每一个转换对应一个Rank,一个Rank需要设置输入通道(Channel) 和 采样时间(Samping Time)。一个规则组中有多个Rank时,Rank的设置顺序就规定了转换通道的序列。每个Rank的采样时间可以单独设置。采样时间的单位是ACCLK的时钟周期数,采样时间越大,转换结果越准确。
(4) ADC_Injected_ConversionMode组。该组用于设置注入转换序列的参数。注入转换个数最多为4个,每个转换也就是一个Rank,注入转换的Rank多了一个offset参数,可以设置0 ~ 4095中的一个数作为偏移量,转换的结果数据是ADC转换结果减掉这个偏移量的值。
(5) WatchDog组。如果开启了模拟看门狗,可以对一个通道或对个通道的模拟电压进行监测。需要设置一个阈值上限和一个阈值下限,阈值用0 ~ 4095中的数表示(设置ADC分辨率为12位时),应根据监测的电压换算成阈值数值,可以开启模拟看门狗中断,在检测的电压超过上限或下限是,会产生模拟看门狗事件中断。模拟看门狗参数设置界面如图 所示。本示例不使用模拟看门狗,所以应该取消图3-4 中Enable Analog WatchDog Mode 的选择。
图3-4 ADC2 WatchDog设置
2、串口设置
本实例中用到的是USART1,STM32CubeMx的配置如下。
图3-5 串口设置
3.2 程序设置
因为此时用到了串口将数据发打印出来,所以要先进行“串口重定向”,之所以这样做是将它们的出流重定向到其他设备或接口,如串口、LCD 显示屏等。这样可以通过串口或其他方式输出调试信息、变量值等。
/* 重定义fputc函数(需包含 stdio.h 头文件) */
int fputc(int ch, FILE*f)
{
while (!(USART1->SR & (1 << 7)));
USART1->DR = ch;
return ch;
}
注:如果使用串口2,只需要将 “串口重定向” 程序中 “ USART1” 改为 “ USART2 ”,其他不变。
在该示例中,采用软件起启动方式进行ADC转换,所以在while循环中每隔约500ms进行一次轮换。使用HAL_ADC_Start(&hadc2)启动转换,然后在函数HAL_ADC_PollForConversion()轮询转换是否完成,再用函数HAL_ADC_GetValue()读出转换结果数据,返回一个32位的值val(因为本示例中设置的数据寄存器为右对齐,所以只有低12位有效,可以直接用)。通过printf将val、转换后的电压打印出来,然后用HAL_ADC_Stop()函数停止ADC转换。后面通过HAL_Delay()函数实现500毫秒阻塞式延时。
注:
为了便于观察,程序中加了一行HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10); 翻转LED灯的代码。
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
HAL_ADC_Start(&hadc2); /* 开启ADC转换 */
if(HAL_ADC_PollForConversion(&hadc2,200)==HAL_OK){ /* 判断ADC转换是否完成 */
uint32_t val=HAL_ADC_GetValue(&hadc2); /* 获取ADC采集到的数值 */
printf("val = %-7d V = %5.2f\n",val, val*3.3/4096); /* 将数值和转换出的电压打印出来 */
}
HAL_ADC_Stop(&hadc2); /* 关闭ADC转换 */
HAL_GPIO_TogglePin(GPIOF,GPIO_PIN_10); /* 翻转LED灯 */
HAL_Delay(500); /* 延时500ms */
}
注:
(1)函数HAL_ADC_Stop()停止ADC的规则通道转换。没有必要每次转换结束后调用此函数停止ADC,因为停止后再启动ADC需要经过一段稳定时间才能开始精确转换。
(2)在实际应用时,如果需要一直转换通常不采用上述模式。因为本次没有选择 Continuous Conversion Mode (连续转换模式),所以用HAL_ADC_Start()函数开启ADC后只转换一次,需要将HAL_ADC_Start()放入while(1)循环里不断开启才能实现连续转换。
连续转换时 将Continuous Conversion Mode 使能,然后在初始化的时候用HAL_ADC_Start()开启一次ADC转换(此时不需要调用HAL_ADC_Stop()关闭ADC转换)。在回调函数里HAL_ADC_PollForConversion()里进行读取数值,即可实现连续转换。
3.3 转换结果
用一根杜邦线将开发板上的PA5引脚引出,将其分别放到GND和3.3V上(注意,测量电压不要超过3.3V!原理看前文VERF+的讲解),转换结果如下,通过图3-6的实际操作和图3-7的串口检测,能够看到不断是将杜邦线接到3.3V上还是将杜邦线接到GND上,串口都能及时的反馈出数值。
图3-6 接线图
图3-7 串口显示图
四、定时器触发ADC转换
4.1 STM32CubeMx配置
前面的示例使用软件触发方式进行ADC转换,每次转换之后延时500ms,采样周期大约是500ms。如果要求精确周期性进行ADC转换,这种方式的周期肯定是不够精确的。ADC可以使用定时器的触发输出(TRGO)信号或捕获比较事件作为ADC转换启动信号,而TRGO信号可以设置为定时器的更新事件(UEV)信号,也就是定时溢出信号,这样,每次ADC的采样间隔就是精确的。
在本次示例中,我们使用TIM3的TRGO信号作为ADC1的外部触发信号,TIM3的定时周期为500ms,ADC2以中断模式启动转换,在ADC的转换完成中断里读取转换结果数据。
1、ADC2的设置
ADC1的输入通道仍然只选择IN5,参数设置部分只需要修改外部触发源,如图4-1所示,主要有两个参数的设置。
- External Trigger Conversion Source,用于设置启动ADC转换的外部触发信号源,图3-3中列出了所有可选的信号源,是一些定时器的Trigger Out event或Capture Compare event,这里选择Timer 3 Trigger Out evernt,也就是TIM3 的TRGO x信号。
- External Trigger Conversion Edge,用于设置触发转换的跳变沿,可选上跳沿,下跳沿或双边沿都触发。这里选择上跳沿,因为TRGO是一个短时正脉冲信号。
图4-1 External Trigger Conversion Source(外部触发源)选项
使用定时器的TRGO信号周期性地启动ADC转换时,应该开启ADC的全局中断,在转换完成事件(ADC_IT_EOC)中断里读取转换结果。因此,在NVIC Settings页面开启ADC1的全局中断,并设置ADC1中断的抢占优先级1,ADC中端配置的结果如图 所示。
注:ADC1、ADC2、ADC3共用一个中断号。
图4-2 ADC全局中断设置
2、TIM3的设置
STM32F407ZGT6的TIM3在总线APB1上,定时器时钟信号频率为84MHz,TIM3的模式和配置界面如图4-2所示。模式设置部分只需设置Clock Source 为Internal Clock,启动TIM3即可。
Trigger Output(TRGO)Parameters组用于设置TRGO信号,主/从模式(Master/Slave Mode)设置为Disable,即禁用主/从模式。触发事件选择(Trigger Event Selection)设置为Updata Event,也让就是以UEV事件信号作为TRGOx信号。
这样,ADC1在TIM3的TRGO信号的每个上跳沿启动一次ADC转换,就可以实现周期性的ADC转换,转换周期由TIM3的定时周期决定。无需开启TIM3的全局中断,TRGO信号也是正常输出的。
关于定时器定时不了解的可以看这篇文章 HAL库STM32常用外设教程(四)—— 定时器 基本定时
图4-3 TIM3设置
4.2 程序设置
(1)因为本示例中用到了“串口重定向”,具体程序见3.2中的代码,此处不再赘述。
(2)以中断方式启动了ADC2的模数转换过程,启动了定时器TIM3的基本定时功能。
HAL_ADC_Start_IT(&hadc2); /* 开启ADC转换,中断模式 */
HAL_TIM_Base_Start(&htim3); /* 启动定时器 */
(3)HAL_ADC_ConvCpltCallback() 函数是HAL库提供的ADC转换完成的回调函数,ADC_HandleTypeDef* hadc 是传递给回调函数的ADC句柄,其中包含有关ADC配置和状态的信息。
- if (hadc->Instance == ADC2) 用于检查回调的是哪个ADC实例,这里只处理ADC2。
- uint32_t val = HAL_ADC_GetValue(&hadc2); 获取ADC2的转换数值,即获取ADC采集到的原始数据。
- printf(“val = %-7d V = %5.2f\n”, val, val * 3.3 / 4096); 将获取到的数值和转换出的电压通过printf函数打印出来。这里假设ADC的分辨率是12位(4096),电压范围是0到3.3V。
注:
“%-7d”用法
①%7d:表示输出的整数字段宽度为7个字符,如果实际整数的宽度不足7个字符,将在左侧用空格填充,使得输出整数右对齐。
② %-7d:表示输出的整数字段宽度为7个字符,如果实际整数的宽度不足7个字符,将在右侧用空格填充,使得输出整数左对齐。
%5.2f中的“5.2”表示总的输出宽度为5个字符,其中有2位小数。
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
if(hadc->Instance == ADC2){
uint32_t val=HAL_ADC_GetValue(&hadc2); /* 获取ADC采集到的数值 */
printf("val = %-7d V = %5.2f\n",val, val*3.3/4096); /* 将数值和转换出的电压打印出来 */
}
}
4.3 示例结果
在开发板上进行测试,将图 在串口里进行打印,从打印的时间可以看出,每间隔500ms就会进行一次打印,采集到的电压通过串口观察也是正确的。
图4-3 结果显示
五、多通道和DMA传输
5.1 示例功能
前面两个示例都只有一个通道,在转换结束后可以计时读出结果数据寄存器的内容。当规则转换组有多个通道时,应该使用扫描转换模式(Scan Conversion Mode),ADC在转换完一个通道后立即转换下一个通道,知道规则组内的通道序列转换完。规则转换只有一个转换结果数据寄存器,虽然可以设置在每个通道转换完之后就产生EOC事件中断,但是在多通道情况下,在EOC事件中断里读取转换结果数据可能是来不及的,更谈不上对数据进行显示和处理。
如果规则转换组有多个输入通道,应该使用DMA,使转换结果数据通过DMA传输自动保存到缓存中,在一个规则组转换结束后在对数据进行处理,或者在采集多次数据后再处理。
本次示例中用到了三个规则组输入通道,使用扫描模式,通过DMA方式传输ADC转换结果数据。和第四节一样依然通过定时器TIM3定时500ms 去触发。此处不再赘述。
5.2 CubeMx项目设置
1、ADC1通道配置
在ADC1的模式设置中,选择3个输入通道,如图5-1所示。本次示例使用了3个内部输入通道, Temperature Sensor Channel是内部的温度传感器通道, Vrefint Channel是内部参考电压通道, Vbat Channel,备用电源VBAT的通道
在ADC_Settings参数组,开启扫描转换模式(Scan Conversion Mode)和DMA连续请求(DMA Continous Requests)。这两个参数的意义见3.1节的解释。
图5-1 ADC1参数设置(1)
在ADC_Regular_ConversionMode参数组设置转换个数为3,下面会自动生成3个Rank的设置,分别设置每个Rank的输入通道和采样时间——每个通道的采样时间可以不一样,3个Rank里面模拟通道的出现顺序就是规则组转换的顺序,如图5-2所示。
注意,ADC_Setting组里面的参数End of Conversion Selection的设定置不变,仍然是在每个通道转换完成后产生EOC信号。
图5-2 ADC1参数设置(2)
ADC只有一个DMA请求,为这个DMA请求配置DMA流DMA2 Stream 0,设置DMA传输属性参数,设置界面如图5-3 所示。DMA传输方向自动设置为Peripheral To Memory(外设到存储器)。在DMA Request Setting 组中将Mode(工作模式)设置为Circular(循环模式),将外设和存储器的数据宽度都设置为Word——因为ADC转换结果数据寄存器是32位的,存储器设置为地址自增加。
图5-3 ADC1的DMA设置
5.3 程序设计
(1)因为本示例中用到了“串口重定向”,具体程序见3.2中的代码,此处不再赘述。
(2)相关参数定义,此处定义了一个数组去存储ADC采集到的数值。
uint32_t dmaDataBuffer[3]; /* ADC数据存放数组 */
(3)开启DMA转换并且开启定时器3。
- HAL_ADC_Start_DMA(&hadc1, dmaDataBuffer, 3);该函数用于以DMA方式开启ADC转换。第一个参数是指向ADC_HandleTypeDef结构体的指针,表示要操作的ADC模块(在这里是hadc1)。第二个参数是一个指向存储转换结果的数据缓冲区的指针(在这里是dmaDataBuffer)。第三个参数是要转换的通道数量(在这里是3)。调用该函数后,ADC模块将会以DMA方式进行模拟信号的转换,并将结果存储在dmaDataBuffer数组中。
- HAL_TIM_Base_Start(&htim3);该函数用于启动定时器TIM3的基本定时器功能。第一个参数是指向TIM_HandleTypeDef结构体的指针,表示要操作的定时器(在这里是htim3)。调用该函数后,定时器TIM3将开始计数,并触发相关的定时器中断(如果已使能)
HAL_ADC_Start_DMA(&hadc1,dmaDataBuffer,3); /* 以DMA方式开启ADC转换 */
HAL_TIM_Base_Start(&htim3); /* 启动定时器3 */
(4)在回调函数里面读取参数。
- void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc),这是一个由HAL库提供的回调函数,当ADC转换完成时,将自动调用该函数。它接受一个指向ADC_HandleTypeDef结构体的指针参数hadc,用于标识触发回调的ADC模块。
- float adcValue[2] = {0}; 定义了一个数组adcValue,用于存放计算后的ADC转换结果。
- 循环处理:通过循环遍历,对前两个通道的转换结果进行处理。循环变量i从0到1,即执行两次循环。
- 转换结果计算:adcValue[i] = (3.3 * dmaDataBuffer[i])/4096; 该行代码计算了第i个通道的电压值。该计算使用了转换结果(存储在dmaDataBuffer数组中)与参考电压(此处假设为3.3V)之间的比例关系进行计算。
- 打印结果:printf(“Vrefint = %-5.2f Vbat = %-5.2f T_value = %d \n”,adcValue[0],adcValue[1],dmaDataBuffer[2]);
该行代码使用printf函数打印出计算后的结果。其中,adcValue[0]表示第一个通道的计算结果(为参考电压值),adcValue[1]表示第二个通道的计算结果(为备用电源VBAT的电压值),dmaDataBuffer[2]表示存放在DMA缓冲区中的第三个通道的原始转换结果(是温度采集到的AD值,此处没有进行转换,直接打印出来的原数值)。
5.4 示例结果
在开发板上进行测试,将图5在串口里进行打印,从打印的时间可以看出,每间隔500ms就会进行一次打印,可以采集到两路电压值和一路ADC采集的原始数据。
六、 总结
本章介绍了ADC的原理,讲解了ADC采集的三种方式,这三种方式在其他外设中也是同样适用的,一种是轮询、一种是中断、一种是DMA方式,三种方式应该熟练掌握并知道其中区别。本人在实际应用中用到的DMA的方式比较多。
参考书籍和文章:
1、《STM32Cube高效开发教程(基础篇)》王维波
2、《STM32F4xx中文参考手册》
3、《STM32F407 探索者开发指南》
4、STM32对HAL库的ADC(多通道DMA)
5、STM32使用ADC获取内部温度传感器数据输出(直接读取/DMA两种方式实现)
6、STM32CubeMX ADC参数配置页中文注解
生活不能等别人来安排,要自己去争取和奋斗;而不论结果是喜是悲,但能够慰藉的是,你总不枉在这世界上活了一场。
——《人生》
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)