HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)

  • 简单的ADC介绍

ADC的分辨率一般是12位,即把电压3.3V分成了4096(2^12)份,常见的数据对齐方式有左对齐和右对齐两种方法。

1.右对齐模式

比如10位的ADC,右对齐的时候,10位ADC的结果表示方式为ADCH:ADCL,ADCH是10bit结果的高2位,ADCL是10bit结果的低8位。

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第1张图片

2.左对齐模式

 比如是10位的ADC,左对齐的时候,ADCH是10bit结果的高8位,ADCL是10bit结果的低2位。

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第2张图片

自然,右对齐的时候ADCH和ADCL组合起来自然就直接是我们所需要的值,所以选择右对齐的时候直接读取值就ok了,但是选择左对齐时,我们读出来的值相当于左移了6位,所以要在读出来的值后/64才是我们所需要的实际值。

(当我们ADC分辨率是12位的话,左对齐相当于左移4位,如下图)

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第3张图片

一般情况下,右对齐能满足我们大多数的需要(不需要额外处理数据),左对齐一般是为了保证高八位的精度时采用的,如果有需要,直接只读取八位的数据,理论上提高处理速度。

  • ADC读取电压

常见的采集方式有两种,阻塞式和非阻塞式的AD转换,各个AD转换通道可以实现单次,连续、扫描和间断四种模式。

1.阻塞式:轮询的方式读取

2.非阻塞式:中断方式和DMA方式

这里的阻塞如何理解:轮询方式读取时,给予ADC开始的信号后,必须等它采集完成后才能进行其他任务的执行,而其他的方式主程序给予开始的信号后就不用管了,完成后会通过中断把值读取出来。

在h文件中,可以看到注释的解释:有阻塞模式:轮询等待;非阻塞模式:中断;非阻塞模式:DMA;在轮询和中断中ADC获取转化值;ADC取样控制;ADC中断和回调函数在阻塞和非阻塞模式中的应用。

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第4张图片

 使用的硬件:stm32G431RB(蓝桥杯) PB15引脚采集R37的电压

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第5张图片

查阅stm32G431RB数据手册可得,含有两个12位的逐次逼近法(中速)的模数转换器

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第6张图片

利用stm32cubemx配置ADC常见选项:(详细内容可以查阅stm32G4参考手册)

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第7张图片

 连续转换模式和间断模式(continuous conversion mode/discontinuous conversion mode):

间断模式 用来执行一个短序列的n次转换(n<=8),一个外部触发信号可以启动下一轮n次转换,直到此序列所有的转换完成为止当所有子组被转换完成,下一次触发启动第一个子组的转换。(较为复杂没用过)

连续模式,只需要使用一次HAL_ADC_Start()(中断和DMA同理),开启转换,ADC会马不停蹄的电压转换成数字量,用户只需要调用HAL_ADC_GetValue(),读取ADC原始值。continuous conversion mode如果设置为DISABLE,则是单次转换模式,只转换一次数据就停止,要再次触发转换才可以。每次需要先使用HAL_ADC_Start()(或HAL_ADC_Start_IT(),HAL_ADC_Start_DMA()中断或者DMA也都要)启动转换,需要使用HAL_ADC_PollForConversion()等待转换完成,之后HAL_ADC_GetState()获取ADC转换状态(若返回值为HAL_OK说明转换完成),转换完成后使用HAL_ADC_GetValue()读取ADC原始值,读取完成后,使用HAL_ADC_Stop()停止转换,如需再次获取ADC数据,需重复执行上述步骤。

扫描模式,如果选择了多个通道需要配置,配置Number of Conservation,并且配置好rank,决定转化的各个通道的次序。

独立模式模式下,双ADC不能同步,每个ADC接口独立工作。所以如果不需要ADC同步或者只是用了一个ADC的时候,应该设成独立模式,多个ADC同时使用时会有其他模式,如双重ADC同步模式,两个ADC同时采集一个或多个通道,可以提高采样率

对于ADC的时钟分频:通过对AHB总线的时钟信号进行分频得到的时钟即为同步时钟,直接对系统时钟进行分频得到ADC时钟即为异步时钟。众多外设都是连接在AHB、APB总线上的,比如说用TIM来触发ADC,二者都是在一个总线上的时候即为同步,但是如若ADC接到了系统时钟上,此时我们需要做时钟同步工作,为整个系统增大工程量的同时也减小了稳定性。异步的优点:首先一点分频选择更多,这是看得到的,其次按照手册上说明,系统时钟的主频相对于APB等频率是要高的,最差也是相等,那么我们可以获得更高的采样频率(当然不能超过上限)。

STM32本身十分复杂,外设非常多  但我们实际使用的时候只会用到有限的几个外设,使用任何外设都需要时钟才能启动,但并不是所有的外设都需要系统时钟那么高的频率,为了兼容不同速度的设备,有些高速,有些低速,如果都用高速时钟,势必造成浪费   并且,同一个电路,时钟越快功耗越快,同时抗电磁干扰能力也就越弱,所以较为复杂的MCU都是采用多时钟源的方法来解决这些问题。STM32时钟系统主要的目的就是给相对独立的外设模块提供时钟,也是为了降低整个芯片的耗能。

STM32为了低功耗,他将所有的外设时钟都设置为disable(不使能),用到什么外设,只要打开对应外设的时钟就可以, 其他的没用到的可以还是disable(不使能),这样耗能就会减少。  这就是为什么不管你配置什么功能都需要先打开对应的时钟的原因。不同的功能模块会有不同的时钟上限,因此提供不同的时钟,也能在一个单片机内放置更多的功能模块。

由于对ADC还不够深入学习,所以目前只演示阻塞和非阻塞方式以及上述三种模式选择的实际操作。(还需要学习ADC同步规则、同步注入、交叉模式等等)。

ADC单通道采集

1.单次转换:

  • 轮询:

while(1)
{
  HAL_ADC_Start(&hadc2);      //开始
  if(HAL_OK==HAL_ADC_PollForConversion(&hadc2,100))    //等待转换,100ms超时时间
  ADC_VALUE=HAL_ADC_GetValue(&hadc2);    //读取值	
  //打印在显示屏上
  sprintf(buf, "ADC_VALUE:%1.2f   ",(double)ADC_VALUE/4096*3.3); 
  LCD_DisplayStringLine(Line8, (uint8_t *)buf);
}

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第8张图片

 需要注意的是,HAL_ADC_Start()需要不断调用,每次转化结束后不调用HAL_ADC_Stop()也会自动停止。至于HAL_ADC_PollForConversion()函数是用来等待转化结束,但我还不清楚有什么实际作用?


2022.11.8更新:

知道HAL_ADC_Stop()函数的作用了

在stm32的低功耗模式下,如果采集完不用这个函数关掉,低功耗下ADC还是会耗电

具体见文章:stm32f103c6t6下的HAL库搭建三种低功耗模式及实战分析(stm32通用)

 


  • 中断

void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc)  //在中断回调函数中读取
{
    ADC_VALUE=HAL_ADC_GetValue(&hadc2);  //这样可以不浪费资源,当然放在主函数中读取也行
	  
}

while(1)
{
  HAL_ADC_Start_IT(&hadc2);      //中断开始
  //打印在显示屏上
  sprintf(buf, "ADC_VALUE:%1.2f   ",(double)ADC_VALUE/4096*3.3); 
  LCD_DisplayStringLine(Line8, (uint8_t *)buf);
}

需要注意的是,HAL_ADC_Start_IT()需要不断调用,每次转化结束后不调用也会自动停止,清掉启动标志。

  • DMA:Direct Memory Access(存储器直接访问),DMA传送方式是让存储器与外设、或外设与外设之间直接交换数据,不需要经过CPU的累加器中转。

Normal:正常模式
当一次DMA数据传输完后,停止DMA传送 ,也就是只传输一次

Circular: 循环模式

传输完成后又重新开始继续传输,不断循环永不停止


四种传输方向:

外设到内存 Peripheral To Memory
内存到外设 Memory To Peripheral
内存到内存 Memory To Memory
外设到外设 Peripheral To Peripheral


Priority: 优先级

最高优先级 Very Hight
高优先级 Hight
中等优先级 Medium
低优先级;Low

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第9张图片

 采样周期选择:采样周期越短,ADC 转换数据输出周期就越短但数据精度也越低,采样周期越长,ADC 转换数据输出周期就越长同时数据精度越高。转换周期太小的时候,会导致DMA触发的中断干扰CPU导致串口无法正常发送数据。同理,在实验过程中,ADC设置为2.5周期的连续转换中断模式,也是因为中断不停触发导致cpu无法正常工作。

  while (1)
  {

     HAL_ADC_Start_DMA(&hadc2, &ADC_VALUE_DMA,1);
	 sprintf(buf, "ADC_VALUE:%1.2f   ",(double)ADC_VALUE_DMA/4096*3.3);
     LCD_DisplayStringLine(Line8, (uint8_t *)buf);
  }

 需要注意的是,HAL_ADC_Start_DMA()需要不断调用,因为还是单次转换。同时这里因为只有一个数据,所以Data Width干扰不大(介绍见后文)

2.连续转换:

  • 轮询:

当下没有实现,还不清楚为什么,程序也并没有卡死,但是ADC不转化(只进行一次)。

  • 中断:

可以实现,因为会不断转化,只需要将中断开启放在main函数初始化中即可。

  •  DMA:

需要开启DMA的连续转化请求,才能实现连续转化。与中断同理,HAL_ADC_Start_DMA(&hadc2, &ADC_VALUE_DMA,1);放在main中初始化即可。

ADC多通道采集

在IO悬空的时候,ADC会串扰, 采集转换的数据会一端正常一端串扰,故IO不能悬空。意思就是其他的ADC采集的值会串扰那个悬空的IO。

  • 轮询查询(单次转换)

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第10张图片

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第11张图片

事实证明,不写等待转换完成函数是会有错误的,等待带来的是有序。

  •  中断采集(单次转换)

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第12张图片

while循环中:

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第13张图片

 忙猜每个通道中都会触发中断,所以会顺序进入中断,验证猜想正确。

  • DMA多通道采集(连续模式)

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第14张图片

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第15张图片

Increment Address:地址指针递增(上方有介绍)。

左侧Src Memory 表示外设地址寄存器

功能:设置传输数据的时候外设地址是不变还是递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,

右侧Dst Memory 表示内存地址寄存器

功能:设置传输数据时候内存地址是否递增。如果设置 为递增,那么下一次传输的时候地址加 Data Width个字节,

我的理解是,我存放ADC的值是采用的int,32bits,对应的地址每次应该加一个word的大小

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第16张图片

至于其他的单次和连续模式在单通道、多通道的组合就不一一列举了,道理都一样,也是可行的。

还有其他许多问题需要解决,还有很多不会的,以后慢慢加深理解叭,如果有大佬,欢迎评论区中指教。

引用来自:ADC左对齐与右对齐的数据读取问题

                  单/多通道ADC读取电压

                  ADC时钟分频因子

------------------------------------------------------------更新对ADC采样频率的理解:

在做第十一届蓝桥杯嵌入式的时候发现:

在连续转换的时候如果采用以下配置: 

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第17张图片

 采样周期选的非常小,会导致程序运行异常,一旦开启了ADC转化后,   HAL_ADC_Start_IT(&hadc2);它之后的初始化都完成不了,而在更改采样频率为640.5后就可以了,下面是我的理解:

ADC转换就是输入模拟的信号量,单片机转换成数字量。读取数字量必须等转换完成后,完成一个通道的读取叫做采样周期。采样周期一般来说=转换时间+读取时间。而转换时间=采样时间+12.5个时钟周期。采样时间是你通过寄存器告诉STM32采样模拟量的时间,设置越长越精确。

而这里设置为640.5的话,ADC采样频率是多少呢?

时钟树:

HAL库(STM32CubeMX)——ADC学习总结(包含单次/连续模式下的轮询/中断/DMA)(蓝桥杯STM32G431RBT6)_第18张图片

 而程序中,我用了异步二分频,所以应该是80M/2/(640.5+12.5)=61255.

我在程序中每次ADC的中断进行计数,输出1S内的进入中断次数:是61125.

可以发现采样频率很高,大概和计算结果相近叭,还是不太明白。

所以2.5采样时间确实太快了。


5.16更新:关于ADC采集校正

HAL_ADCEx_Calibration_Start(ADC_HandleTypeDef *hadc, uint32_t SingleDiff)

ADC校正主要利用这个函数,第二个参数是选择采集模式。

#define IS_ADC_SINGLE_DIFFERENTIAL(__SING_DIFF__) (((__SING_DIFF__) == ADC_SINGLE_ENDED)      || \
                                                   ((__SING_DIFF__) == ADC_DIFFERENTIAL_ENDED)  )      

ADC_SINGLE_ENDED是单端模式。

ADC_DIFFERENTIAL_ENDED是差分模式。

可以参考这篇文章:单端和差分的不同

函数注释说明调用的情况:Perform an ADC automatic self-calibration ​Calibration prerequisite: ADC must be disabled (execute this function before HAL_ADC_Start() or after HAL_ADC_Stop() )。

执行ADC自动自我校准 校准的先决条件。ADC必须被禁用(在HAL_ADC_Start()之前或HAL_ADC_Stop()之后执行此函数)。

    MX_ADC2_Init();
	HAL_ADCEx_Calibration_Start(&hadc2,ADC_SINGLE_ENDED);

我直接加在了初始化的后面

uint16_t getADC(void)
{
    uint16_t adc = 0;
		
    HAL_ADC_Start(&hadc2);
    adc = HAL_ADC_GetValue(&hadc2);

    return adc;
}

   普通的读取ADC的值,然后发现函数确实发挥了作用,本来采集不到4096,现在能由4039到接近4083。

你可能感兴趣的:(stm32,学习,单片机,c语言,arm)