首页 最新 热门 推荐

  • 首页
  • 最新
  • 热门
  • 推荐

HAL库STM32常用外设-ADC

  • 25-02-21 19:41
  • 4410
  • 13715
blog.csdn.net

一、ADC概述

1.1 ADC的特性

ADC 即模拟数字转换器(Analog-to-digital converter,ADC),可以将外部的模拟信号转换为数字信号,是模拟信号数字化的必要器件。STM32F407有3个ADC,最高12位分辨率,最多16个外部通道,ADC1还有3个内部测量通道,可以测量内部温度、参考电压和备用电池电压。
STM32F407的ADC 主要特性可总结为一下几条:
● 可配置 12 位、10 位、8 位或 6 位分辨率
● 在转换结束、注入转换结束以及发生模拟看门狗或溢出事件时产生中断
● 单次和连续转换模式
● 自校准
● 用于自动将通道 0 转换为通道“n”的扫描模式
● 数据对齐以保持内置数据一致性
● 可独立设置各通道采样时间
● 外部触发器选项,可为规则转换和注入转换配置极性
● 不连续采样模式
● 双重/三重模式(具有 2 个或更多 ADC 的器件提供)
● 双重/三重 ADC 模式下可配置的 DMA 数据存储
● 双重/三重交替模式下可配置的转换间延迟
● ADC 转换类型(参见数据手册)
● ADC 电源要求:全速运行时为 2.4 V 到 3.6 V,慢速运行时为 1.8 V
● ADC 输入范围:VREF- ~VREF+
● 规则通道转换期间可产生 DMA 请求

1.2 ADC的工作原理

STM32F407的ADC的运行框图如下所示:

图1-1ADC 框图

1.2.1 ADC的供电部分(图1-1中①):

其引脚的解释如下:
在这里插入图片描述

图1-2 ADC供电引脚解释

  • VDDA:VDDA是模拟部分工作电源。如果要保证ADC精度,可以使用独立的模拟电源输入,但是VDDA最高不能超过4V。
  • VSSA:VSSA是模拟部分接地工作电源。与数字电源共地。
  • VREF+:VREF+ 是ADC转换的正参考电压。如果要保证ADC精度,可以使用单独的参考电压芯片输出的精密参考电压, VREF+不能超过VDDA,最低值为1.8V。一般情况下,将VREF+与VDDA连接。
  • VREF-:VREF-是将ADC的负参考电压,必须与VSSA连接,也就是必须和数字电源共地。

需要注意的是:ADC转换电压的输入范围是在VERF-与 VERF+之间。因为VREF-必须与VSS连接,也就是VREF-总是0,所以STM32F407的片上ADC只能转换正电压,这与某些独立的ADC芯片可转换正负范围的电压是不同的。
典型的供电方案如图1-3所示。
在这里插入图片描述

图1-3:ADC供电方案

1.2.2输入通道(图1-1中②)

每个ADC单元有16个外部输入引脚,对应与16个ADC输入复用引脚,即图1中的ADCx_IN0至ADCx_IN15。每个ADC单元的模拟输入复用引脚如图3所示。
在这里插入图片描述

图1-4:ADC通道对应引脚图

ADC1单元还有以下3个内部输入使用ADC1_IN16、ADC1_IN17、ADC1_IN18。

  • ADC1_IN16(温度传感器):芯片内部温度传感器,测温范围为 -40摄氏度-125摄氏度,精度为 ±1.5°。
  • ADC1_IN17(VERFINT):内部参考电压,实际连接内部1.2V调压器的输出电压。
  • ADC1_IN18(VBAT):备用电源电压,因为VBAT电压可能高于VDDA,内部有桥梁分压器,实际测量的电压是VBAT/2。

1.2.3注入通道和规则通道(图1-1中③)

当任意 ADCx 多个通道以任意顺序进行一系列转换就诞生了成组转换,这里就有两种成组转换类型:规则组和注入组。规则组就是图上的规则通道,注入组也就是图上的注入通道。规则组允许最多 16 个输入通道进行转换,而注入组允许最多 4 个输入通道进行转换。这里讲解一下规则组和注入组。
规则组(规则通道),按字面理解,“规则”就是按照一定的顺序,相当于正常运行的程序,平常用到最多也是规则组。
注入组(注入通道),按字面理解,“注入”就是打破原来的状态,相当于中断。当程序执行的时候,中断是可以打断程序的执行。同这个类似,注入组转换可以打断规则组的转换。假如在规则组转换过程中,注入组启动,那么注入组被转换完成之后,规则组才得以继续转换。

在这里插入图片描述
图1-5:规则通道和注入通道对比图。

规则序列是允许 16 个通道进行转换的,那么就需要安排通道转换的次序即规则序列。规则序列寄存器有 3 个,分别为 SQR1、SQR2 和 SQR3。SQR3 控制规则序列中的第 1 个到第 6 个转换的通道;SQR2 控制规则序列中第 7 个到第 12 个转换的通道;SQR1 控制规则序列中第 13 个到第 16 个转换的通道。如图1-6所示:
在这里插入图片描述
图1-6:规则序列寄存器ADC_SQRX表
其中,在ADC_SQR1寄存器中位 23:20 L[3:0]:定义规则通道序列的长度,表示需要转换的通道的数量。在STM32CUBEMAX中的设置如下。
在这里插入图片描述

图1-7:规则通道设置
注入序列,跟规则序列差不多,都是有顺序的安排。由于注入组最大允许 4 个通道输入,所以这里就使用了一个寄存器 JSQR。如图1-8所示:
在这里插入图片描述

图1-8:注入序列寄存器ADC_JSQR表

1.2.4触发源(图1-1中④)

规则通道和注入通道由单独的触发源,有以下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作为规则组或注入组的外部触发源。
    在STM32CUBEMAX中的规则通道的触发源设置如下图。
    在这里插入图片描述
    图1-9:规则通道的触发源设置

规则通道和注入通道的触发源由ADC 控制寄存器 2 (ADC_CR2)位[29:16]所设置。其中:

  • 位 30 SWSTART:为规则组选择软件触发;
  • 位 29:28 EXTEN:规则通道的外部触发使能(如:上升沿上的触发检测);
  • 位 27:24 EXTSEL[3:0]:为规则组选择外部事件(如:定时器事件、外部中断事件);
  • 位 22 JSWSTART:为注入组选择软件触发;
  • 位 21:20 JEXTEN:注入通道的外部触发使能(如:上升沿上的触发检测);
  • 位 19:16 JEXTSEL[3:0]:为注入组选择外部事件(如:定时器事件、外部中断事件);
  • 位 0 ADON:A/D 转换器开启/关闭;

1.2.5 ADC时钟与转换时间(图1-1中的⑤)

ADC转换需要时钟信号ADCCLK驱动,ADCCLK由PCLK2经过分频产生,最少2分频,最多8分频。STM32F407的PCLK2的最高频率为84MHz,所以ADCCLK的最高频率为42MHz(不同芯片的频率不同)。

在这里插入图片描述

图1-10:STM32F407时钟树
在这里插入图片描述

图1-11:ADC Prescalers分频
ADC Prescalers分频设置对应的寄存器为ADC 通用控制寄存器 (ADC_CCR);
其中:位 17:16 ADCPRE:ADC 预分频器置 1 和清零,以选择 ADC 的分频模式。该时钟为所有 ADC 所共用。

可以设置在N个ADCCLK周期内对信号进行采样,N的值最小为3,最大为480(如图1-12), 在允许的情况下,尽量选大一点的会使ADC 更稳定、更精确。
在这里插入图片描述

图1-12:ADC采样周期配置图
ADC 总转换时间的计算公式如下:

TCONV = 采样时间 + 12 个周期

以STM32F407的ADC1的通道1为例,如果 Clock Prescaler设置成“PCLK2 divided by 4”,Sampling Time设置成15 Cycles,那么ADC时钟频率为:

84MHz / 4 = 21MHz

则一个周期所需的时间为:

1/21MHz=0.047us

ADC1_IN1通道的转换时间为:

TCONV=(15+12)*0.047us

TCONV = 采样时间 + 12 个周期

采样时间可通过ADC 采样时间寄存器1((ADC_SMPR1) 和ADC 采样时间寄存器2( ADC_SMPR2 )中的 SMP[2:0]位编程,ADC_SMPR2控制的是通道 0-9,ADC_SMPR1 控制的是通道 10~18。

1.2.6数据寄存器(图1-1中的⑥)

ADC完成转换后将结果存放进数据寄存器,ADC 规则数据寄存器(ADC_DR),只有低16位有效,在多通道转换时,如果前一通道转换结束后,ADC_DR的数据未被及时读出,下一个转换通道的转换结果就会覆盖上一次结果的数据。所以,在多通道转换时,一般EOC中断里及时读取数据或通过DMA将数据传输到内存里。
注入通道有4个数据寄存器:ADC 注入数据寄存器 x (ADC_JDRx) (x= 1…4)分别对应4个注入通道的转换结果。低16位有效。
因为注入数据寄存器和ADC 规则数据寄存器是16位有效,与12位的ADC分辨率相比,多出4位。因此可通过ADC 控制寄存器 2 (ADC_CR2)位 11 ALIGN来设置数据左对齐或右对齐,一般使用右对齐。在二进制中左移n位,数据扩大2的n次方倍,所以左对齐直接读出的数值会比实际值大16倍。
同时在STM32CUBEMX也可以设置左对齐或者右对齐:
在这里插入图片描述

图1-13:ADC数据对齐方式

1.2.7中断(图1-1中的⑦)

规则和注入组转换结束时能产生中断,当模拟看门狗状态位被设置时也能产生中断,它们在 ADC_SR 中都有独立的中断使能位,后面讲解 ADC_SR 寄存器时再进行展开。这也是本文的重点。这里讲解一下,模拟看门狗中断以及 DMA 请求。

  • 模拟看门狗中断:首先通过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) / 2^12 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主要函数功能描述图

2.2软件启动转换方式

函数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位数据
  • 1
  • 2
  • 3
  • 4

其中,参数hadc是ADC外设对象指针,Timeout是超时等待时间(单位是ms)。

  • HAL_ADC_Start():其本质就是将ADC 控制寄存器 2 (ADC_CR2)中的位 0 ADON置1;
  • HAL_ADC_Stop();其本质就是将ADC 控制寄存器 2 (ADC_CR2)中的位 0 ADON置0;
  • HAL_ADC_PollForConversion():其本质是读取判断ADC 状态寄存器 (ADC_SR)中的位 1 EOC(规则通道转换结束)是否置1;
  • HAL_ADC_GetValue():其本质是读取ADC 规则数据寄存器(ADC_DR)的值。

2.3中断转换方式

当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);
  • 1
  • 2

其中:

  • HAL_ADC_Start_IT():本质是在HAL_ADC_Start()的基础上将ADC 控制寄存器 1 (ADC_CR1)的位 5 EOCIE(EOC 中断使能)与位 26 OVRIE(溢出中断使能)置1;
  • HAL_ADC_Stop_IT():本质是在HAL_ADC_Stop()的基础上将ADC 控制寄存器 1 (ADC_CR1)的位 5 EOCIE(EOC 中断使能)与位 26 OVRIE(溢出中断使能)置0;

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)  //数据溢出事件、即转换结果未被及时读出
  • 1
  • 2
  • 3
  • 4

ADC中断通用处理函数是HAL_ADC_IRQHandler(),它内部会判断中断事件类型,并调用相应的回调函数。ADC的4个中断事件类型及其对应的回调函数如图所示。

中断事件类型中断事件回掉函数
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()

图2-2中断事件对应回调函数

我们可以设置为在转换完一个通道后就产生EOC事件,也可以设置为转换完规则通道组的所有通道之后产生EOC事件。但是规则组只有一个转换结果寄存器,如果有多个转换通道,设置为转换完规则组的所有通道之后产生EOC,会导致数据溢出。一般设置为在转换外一个通道后就产生EOC事件如下图所示。
其原理就是通过将ADC 控制寄存器 1 (ADC_CR1)的位 8 SCAN(扫描模式)置0或者置1来控制什么时候产生EOC事件,所以,中断方式转换适用于单通道或采样频率不高的场合。
在这里插入图片描述

图2-3规则通道转换结果结束(EOC)事件

2.4 DMA方式转换

通过2.3节可知将ADC 控制寄存器 1 (ADC_CR1)的位 8 SCAN(扫描模式)置1,控制器就会在转换完规则组的所有通道之后产生EOC中断。但ADC 规则数据寄存器 (ADC_DR)只有一个,规则序列中的通道超过一个数据就会溢出。数据寄存器只能保存规则序列中最后一个通道的数据,如何避免数据溢出又能采集到规则通道中所有的数据。这里就需要DMA。
从图1的③和⑥中可以看到,每当ADC的通道转换结束时,就会产生一个DMA请求。如何让ADC与DMA联动起来呢?HAL库提供了一个函数:

HAL_StatusTypeDef HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length);
  • 1

其中,参数hadc是ADC外设对象指针;参数pData是uint32_t类型缓冲区指针,因为ADC转换结果寄存器是32位的,所以DMA数据宽度是32位;参数Length是缓冲区长度,单位是字(4字节)。
本质就是在HAL_ADC_Start()函数的基础上将ADC 控制寄存器1(ADC_CR1)的位 26 OVRIE(溢出中断使能)置1,将ADC 控制寄存器 2(ADC_CR2)的位 8 DMA(直接存储器访问模式)置1(使能 DMA 模式),通过HAL_DMA_Start_IT()函数将数据寄存器 (ADC_DR)与RAM内存地址联系起来并开启DMA中断;
同时,也要在STM32CUBEMX中提前设置DMA,如下图
在这里插入图片描述

图2-4 设置DMA
当ADC 控制寄存器 2(ADC_CR2)的位 9 DDS(DMA 禁止选择)置1(只要发生数据转换且 DMA = 1,便会发出 DAM 请求),也可在STM32CUBEMAX直接设置如下图。
在这里插入图片描述

图2-5 DMA 禁止选择模式设置
停止DMA方式采集的函数是HAL_ADC_Stop_DMA();其原型定义如下:

HAL_StatusTypeDef HAL_ADC_Stop_DMA(ADC_HandleTypeDef* hadc);
  • 1

本质就是在HAL_ADC_Stop()函数的基础上将ADC 控制寄存器1(ADC_CR1)的位 26 OVRIE(溢出中断使能)置0,将ADC 控制寄存器 2(ADC_CR2)的位 8 DMA(直接存储器访问模式)置0。
DMA流的主要中断事件与ADC的回调函数之间的关系如图所示。一个外设使用DMA传输方式时,DMA流的事件中断一般使用外设的事件中断回调函数。
在这里插入图片描述

图2-6 DMA流中断事件类型和关联的回调函数
注:注入通道没有DMA方式。

2.5 规则通道采样模式

对于ADC通道采集,采样模式的选择又很多内容,很容易犯迷糊。如下图所示。
在这里插入图片描述

图2-7 ADC参数示意图
图中有几个参数需要选择,如:

  • Scan Conversion Mode:扫描转换模式,设置为Enabled(规则序列中的多通道连续转换,转换完一个通道后,会自动转换组内下一个通道,直到一组通道都转换完成);设置为Disabled(只能采集到第一的通道的数据,后续的通道扫描不到)。
  • Continuous Conversion Mode:连续转换模式。设置为Enabled(ADC结束规则序列中所有通道的转换后立即启动一个新的转换);设置为Disabled(ADC结束规则序列中所有通道的转换后需要HAL_ADC_Start启动新的转换);
  • Discontinuous Conversion Mode:非连续转换模式(间断模式),设置为Enabled(ADC的转换序列被分割成多个小序列。每个小序列可以包含一个或多个通道,具体数量由Number Of Discontinuous Conversions参数决定,在每个小序列转换完成后,ADC会停止转换,并等待外部触发或软件指令来开始下一个小序列的转换)。通常情况下设置为Disabled;
  • DMA Continuous Requests: 是否连续产生DMA请求,设置为Disabled(在最后一个传输后不发出新的DMA请求);设置为Enable(只要发生数据转换且使用了DMA,就发出DMA请求)。
  • EOC标志产生方式,有以下2种选项。
    ① EOC flag at the end of single channel conversion:在每个通道转换完成后产生EOC标志。
    ② EOC flag at the end of all conversion:在规则序列的所有通道转换完成后产生EOC标志。
    寄存器在ADC_CR2位 10 EOCS(结束转换选择)
  • ADC_Regular_ConversionMode组,具体包括如下参数。
    ① Number of Conversion:规则转换序列的转换个数,最多16个,每个转换作为一个Rank(级)。这个数值不必等于输入模拟信号通道数。
    ② External Trigger Conversion Source:外部触发转换的信号源。本例选择为软件启动常规转换(Regular Conbversion launched by software)。周期性采集时,一般选择定时器TRGO信号或捕获比较事件信号作为触发信号,还可以选择外部中断线信号作为触发信号。
    ③External Trigger Conversion Edge:外部触发转换时间使用的信号边沿,可选择上跳沿、下跳沿,或双边都触发。本例中未使用外部触发,所以设置为None。
    ④Rank:规则组内每一个转换对应一个Rank,一个Rank需要设置输入通道(Channel) 和 采样时间(Samping Time)。一个规则组中有多个Rank时,Rank的设置顺序就规定了转换通道的序列。每个Rank的采样时间可以单独设置。采样时间的单位是ACCLK的时钟周期数,采样时间越大,转换结果越准确。

三、定时器+ADC多通道+DMA中断读取

当只有一个通道,在转换结束后可以读出结果数据寄存器的内容。当规则转换组有多个通道时,应该使用扫描转换模式(Scan Conversion Mode),ADC在转换完一个通道后立即转换下一个通道,知道规则组内的通道序列转换完。规则转换只有一个转换结果数据寄存器,虽然可以设置在每个通道转换完之后就产生EOC事件中断,但是在多通道情况下,在EOC事件中断里读取转换结果数据可能是来不及的,更谈不上对数据进行显示和处理。
如果规则转换组有多个输入通道,应该使用DMA,使转换结果数据通过DMA传输自动保存到缓存中,在一个规则组转换结束后在对数据进行处理,或者在采集多次数据后再处理。
本次示例中用到了三个规则组输入通道,使用扫描模式,通过DMA方式传输ADC转换结果数据。通过定时器TIM2定时500ms 去触发。

3.1设置TIM2

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信号也是正常输出的。
在这里插入图片描述

3.2 ADC1设置

在ADC_Settings参数组,开启扫描转换模式(Scan Conversion Mode)、DMA连续请求(DMA Continous Requests)、在规则序列的所有通道转换完成后产生EOC标志(EOC flag at the end of all conversion)。
在ADC_Regular_ConversionMode参数组设置转换个数为3,下面会自动生成3个Rank的设置,分别设置每个Rank的输入通道和采样时间——每个通道的采样时间可以不一样,3个Rank里面模拟通道的出现顺序就是规则组转换的顺序.
External Trigger Conversion Source,用于设置启动ADC转换的外部触发信号源,选择Timer 2 Trigger Out evernt,也就是TIM2 的TRGO x信号。
External Trigger Conversion Edge,用于设置触发转换的跳变沿,可选上跳沿,下跳沿或双边沿都触发。这里选择上跳沿,因为TRGO是一个短时正脉冲信号。
配置如下图所示:
在这里插入图片描述

3.3 DMA设置

ADC只有一个DMA请求,为这个DMA请求配置DMA流DMA2 Stream 0,设置DMA传输属性参数,设置界面如图所示。DMA传输方向自动设置为Peripheral To Memory(外设到存储器)。在DMA Request Setting 组中将Mode(工作模式)设置为Circular(循环模式),将外设和存储器的数据宽度都设置为Word——因为ADC转换结果数据寄存器是32位的,存储器设置为地址自增加。
在这里插入图片描述

3.4 uart1设置

在这里插入图片描述

3.5 程序设计

在生成代码后的基础上添加以下内容:
usart.c:
在这里插入图片描述

#include "usart.h"

/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

UART_HandleTypeDef huart1;

/* USART1 init function */

void MX_USART1_UART_Init(void)
{

  /* USER CODE BEGIN USART1_Init 0 */

  /* USER CODE END USART1_Init 0 */

  /* USER CODE BEGIN USART1_Init 1 */

  /* USER CODE END USART1_Init 1 */
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART1_Init 2 */

  /* USER CODE END USART1_Init 2 */

}

void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{

  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();

    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
}

void HAL_UART_MspDeInit(UART_HandleTypeDef* uartHandle)
{

  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspDeInit 0 */

  /* USER CODE END USART1_MspDeInit 0 */
    /* Peripheral clock disable */
    __HAL_RCC_USART1_CLK_DISABLE();

    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    HAL_GPIO_DeInit(GPIOA, GPIO_PIN_9|GPIO_PIN_10);

  /* USER CODE BEGIN USART1_MspDeInit 1 */

  /* USER CODE END USART1_MspDeInit 1 */
  }
}

/* USER CODE BEGIN 1 */
int _write(int file, char *ptr, int len)
{
    /* send data to serial huart1 */
    HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 1000);
    return len;
}
/* USER CODE END 1 */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

int _write(int fd, char* ptr, int len) 函数是stdio.h中printf函数的重定义,可通过printf实现串口数据的打印。
main.c:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "adc.h"
#include "dma.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
uint32_t adc_buffer[3];
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
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 */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_ADC1_Init();
  MX_USART1_UART_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  HAL_ADC_Start_DMA(&hadc1,adc_buffer, 3);
  HAL_TIM_Base_Start(&htim2);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Configure the main internal regulator output voltage
  */
  __HAL_RCC_PWR_CLK_ENABLE();
  __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSI;
  RCC_OscInitStruct.PLL.PLLM = 16;
  RCC_OscInitStruct.PLL.PLLN = 336;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = 4;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
    if(hadc->Instance==ADC1)
    {
        float adc_value[3];
        for(uint8_t i=0;i<3;i++)
        {
            adc_value[i]=(adc_buffer[i]*3.3)/4096;
        }
        printf("NVIC ADC IN1 = %fV  ADC IN2 = %fV  ADC IN3 = %fV \r\n",adc_value[0],adc_value[1],adc_value[2]);
    }
}
/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187

其中:

  • uint32_t adc_buffer[3];:定义了一个数组去存储ADC采集到的数值。也是DMA从ADC_DR提取数据的存储位置。
  • HAL_ADC_Start_DMA(&hadc1,adc_buffer, sizeof(adc_buffer));:该函数用于以DMA方式开启ADC转换。第一个参数是指向ADC_HandleTypeDef结构体的指针,表示要操作的ADC模块(在这里是hadc1)。第二个参数是一个指向存储转换结果的数据缓冲区的指针(在这里是dmaDataBuffer)。第三个参数是要转换的通道数量(在这里是3)。调用该函数后,ADC模块将会以DMA方式进行模拟信号的转换,并将结果存储在dmaDataBuffer数组中。
  • HAL_TIM_Base_Start(&htim2);:该函数用于启动定时器TIM2的基本定时器功能。第一个参数是指向TIM_HandleTypeDef结构体的指针,表示要操作的定时器(在这里是htim2)。调用该函数后,定时器TIM2将开始计数,并触发相关的定时器中断(如果已使能)
  • void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc):这是一个由HAL库提供的回调函数,当ADC转换完成时,将自动调用该函数。它接受一个指向ADC_HandleTypeDef结构体的指针参数hadc,用于标识触发回调的ADC模块。
  • static float adc_value[3];:定义了一个数组adc_value,用于存放计算后的ADC转换结果。

循环处理:通过循环遍历,对前两个通道的转换结果进行处理。循环变量i从0到2,即执行三次循环。
转换结果计算: adc_value[i]=adc_buffer[i]*3.3/4096; 该行代码计算了第i个通道的电压值。
打印结果: printf("NVIC ADC IN1 = %fV ADC IN2 = %fV ADC IN3 = %fV \r\n",adc_value[0],adc_value[1],adc_value[2]);
** 3.5 示例结果
在这里插入图片描述
软件触发,模拟看门狗,多重ADC采集,注入通道后续更新。。。
如有错误,欢迎指出,不胜感激。
参考书籍和文章:
《STM32F4xx中文参考手册》
https://openatomworkshop.csdn.net/6673ddaba1e8811a9781de12.html
https://blog.csdn.net/Pretender_1205/article/details/136379247

注:本文转载自blog.csdn.net的小欧不要怂的文章"https://blog.csdn.net/qq_38630013/article/details/145099442"。版权归原作者所有,此博客不拥有其著作权,亦不承担相应法律责任。如有侵权,请联系我们删除。
复制链接
复制链接
相关推荐
发表评论
登录后才能发表评论和回复 注册

/ 登录

评论记录:

未查询到任何数据!
回复评论:

分类栏目

后端 (14832) 前端 (14280) 移动开发 (3760) 编程语言 (3851) Java (3904) Python (3298) 人工智能 (10119) AIGC (2810) 大数据 (3499) 数据库 (3945) 数据结构与算法 (3757) 音视频 (2669) 云原生 (3145) 云平台 (2965) 前沿技术 (2993) 开源 (2160) 小程序 (2860) 运维 (2533) 服务器 (2698) 操作系统 (2325) 硬件开发 (2491) 嵌入式 (2955) 微软技术 (2769) 软件工程 (2056) 测试 (2865) 网络空间安全 (2948) 网络与通信 (2797) 用户体验设计 (2592) 学习和成长 (2593) 搜索 (2744) 开发工具 (7108) 游戏 (2829) HarmonyOS (2935) 区块链 (2782) 数学 (3112) 3C硬件 (2759) 资讯 (2909) Android (4709) iOS (1850) 代码人生 (3043) 阅读 (2841)

热门文章

124
嵌入式
关于我们 隐私政策 免责声明 联系我们
Copyright © 2020-2025 蚁人论坛 (iYenn.com) All Rights Reserved.
Scroll to Top