HAL库STM32常用外设教程(七)—— ADC


前言

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-1 ADC框图

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 模拟部分的供电

1.2.2 输入通道(对应图6-2中的②)

  每个ADC单元有16个外部输入引脚,对应与16个ADC输入复用引脚,即图6-1中的ADCx_IN0至ADCx_IN15。每个ADC单元的模拟输入复用引脚如图1-3所示。
在这里插入图片描述
                图1-3 ADC通道表
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进行理解。
在这里插入图片描述               图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(不同芯片的频率不同)。
在这里插入图片描述                图1-5 STM32F407 时钟树

在这里插入图片描述                图1-6 ADC Prescalers分频

  我们可以设置在N个ADCCLK周期内对信号进行采样,N的值最小为3,最大为480(见图1-7),在允许的情况下,尽量选大一点的会使ADC 更稳定、更精确

在这里插入图片描述                图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-8 ADC数据对齐方式

在这里插入图片描述
                  图1-9 ADC数据对齐示意图

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转换的外部触发信号源,图 中列出了所有可选的信号源,是一些定时器的Trigger Out event或Capture Compare event,这里选择Timer 3 Trigger Out evernt,也就是TIM3 的TRGO x信号。
  • External Trigger Conversion Edge,用于设置触发转换的跳变沿,可选上跳沿,下跳沿或双边沿都触发。这里选择上跳沿,因为TRGO是一个短时正脉冲信号。

使用定时器的TRGO信号周期性地启动ADC转换时,应该开启ADC的全局中断,在转换完成事件(ADC_IT_EOC)中断里读取转换结果。因此,在NVIC Settings页面开启ADC1的全局中断,并设置ADC1中断的抢占优先级1,ADC中端配置的结果如图 所示。
注:ADC1、ADC2、ADC3共用一个中断号。

在这里插入图片描述
                  图4-1 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-2 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参数配置页中文注解


  生活不能等别人来安排,要自己去争取和奋斗;而不论结果是喜是悲,但能够慰藉的是,你总不枉在这世界上活了一场。
        ——《人生》

  • 28
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
STM32F103C8T6是一款高性能的ARM Cortex-M3核心的微控制器,具有丰富的外设和强大的运算能力。在这款开发板上,我们可以使用HAL库来实现DMA(Direct Memory Access)和ADC(Analog to Digital Converter)的多通道功能。 首先,DMA是一种直接内存访问技术,它允许外设通过DMA控制器直接访问存储器,而不需要CPU的干预。这样可以提高外设数据传输的效率,减轻CPU的负担。STM32F103C8T6的HAL库提供了简化的API来配置和控制DMA。 对于ADC多通道的实现,首先我们需要设置ADC的时钟源和采样时间。然后,我们可以使用HAL库提供的函数来配置ADC的通道和触发源。在HAL库中,我们可以使用HAL_ADC_ConfigChannel()函数来配置ADC通道,使用HAL_ADC_ConfigInjectedChannel()函数来配置注入通道。 在配置完ADC通道后,我们可以使用HAL_ADC_Start_DMA()函数来启动DMA传输,将ADC的转换结果存储到内存缓冲区中。需要注意的是,我们还需要配置DMA的通道和传输方向。在配置完DMA传输后,我们可以使用中断回调函数来处理转换完成的数据。 总结来说,使用HAL库可以简化DMA和ADC多通道的配置和控制。我们只需调用适当的API函数来完成配置和启动即可。此外,还需要合理设置时钟源、采样时间和触发源,以满足我们的需求。这些功能使得我们能够有效地实现DMA ADC多通道在STM32F103C8T6开发板中的应用。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值