HAL库学STM32 关于ADC的几个问题

编者按: 在学ADC的途中遇到了几个自我感觉比较难以理解的点,在这里做一个记录。

学习参考资料:
正点原子:STM32F1开发指南、STM32F1中文参考手册
Z小璇博客:【STM32】HAL库 STM32CubeMX教程九—ADC

1. ADC的转换模式

1。单次转换模式:ADC只执行一次转换;
2。连续转换模式:转换结束之后马上开始新的转换;

Stm32
ADC的单次模式和连续模式。这两中模式的概念是相对应的。这里的单次模式并不是指一个通道。假如你同时开了ch0,ch1,ch4,ch5这四个通道。单次模式转换模式下会把这四个通道采集一边就停止了。而连续模式就是这四个通道转换完以后再循环过来再从ch0开始。
——————————
该部分来自Derrick

3。 扫描模式:ADC扫描被规则通道和注入通道选中的所有通道,在每个组的每个通道上执行单次转换。在每个转换结束时,这一组的下一个通道被自动转换。如果设置了CONT位(开启了连续 转换模式),转换不会在选择组的最后一个通道上停止,而是再次从选择组的第一个通道继续转换。
4。间断模式:触发一次,转换一个通道,在触发,在转换。在所选转换通道循环,由触发信号启动新一轮的转换,直到转换完成为止。

扫描模式简单的说是一次对所有所选中的通道进行转换,比如开了ch0,ch1,ch4,ch5。  ch0转换完以后就会自动转换通道1,4,5直到转换完这个过程不能被打断。如果开启了连续转换模式,则会在转换完ch5之后开始新一轮的转换。

这就引入了间断模式,可以说是对扫描模式的一种补充。它可以把0,1,4,5这四个通道进行分组。可以分成0,1一组,4,5一组。也可以每个通道单独配置为一组。这样每一组转换之前都需要先触发一次。

ADC单通道:

只进行一次ADC转换:配置为“单次转换模式”,扫描模式关闭。ADC通道转换一次后,就停止转换。等待再次使能后才会重新转换

进行连续ADC转换:配置为“连续转换模式”,扫描模式关闭。ADC通道转换一次后,接着进行下一次转换,不断连续。

ADC多通道:

只进行一次ADC转换:配置为“单次转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,就停止转换。等待再次使能后才会重新转换

进行连续ADC转换:配置为“连续转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,接着进行下一次转换,不断连续。

也就是:多通道必须使能扫描模式
————————————————
版权声明:以上部分来自CSDN博主「Z小旋」的原创文章

Stm32 ADC的单次模式和连续模式。这两中模式的概念是相对应的。这里的单次模式并不是指一个通道。假如你同时开了ch0,ch1,ch4,ch5这四个通道。单次模式转换模式下会把这四个通道采集一边就停止了。而连续模式就是这四个通道转换完以后再循环过来再从ch0开始。
HAL库学STM32 关于ADC的几个问题_第1张图片

2. 多通道的配置(无DMA)

以下部分参考文章:Stm32cubeMx配置ADC多通道采集

“目前经过我的测试,要想用非dma和中断模式只有这样配置可以正确进行多通道转换:扫描模式+单次转换模式+间断转换模式(每个间断组一个通道)。

分析配置成这样的模式,扫描模式是在配置为多个通道必须打开的,stm32cubeMX上也默认好了,只能enable。单次转换模式是我不需要不停的去采集每个通道值,而是把四个通道采集完以后就让它停止。这里间断配置是关键,间断模式可以让扫描的四个通道进行分成四个组,stm32cubeMX参数里面number of Discontinous Conversions是配置间断组每个组有几个通道的,这里必须配置为1(否则在获取ad值得时候只能读取到每个间断组最后一个通道)。

参照上文配置如下:

HAL库学STM32 关于ADC的几个问题_第2张图片
代码如下:

while (1)
  {
		for(i=0;i<2;i++){
			HAL_ADC_Start(&hadc1);	//开启ADC转换
			HAL_ADC_PollForConversion(&hadc1, 50); //等待转换结束   
			if(HAL_IS_BIT_SET(HAL_ADC_GetState(&hadc1), HAL_ADC_STATE_REG_EOC))
			{
				ADC_Value[i] = HAL_ADC_GetValue(&hadc1);   
				printf("PA%d Reading : %d \r\n",i,ADC_Value[i]);
			}
		}
		printf("hello ADC\r\n");
		HAL_Delay(1000);
  }

用自己的话总结一下,像下面这张图,假设开启的是3个通道,都放在规则组里面,那扫描+单次模式就是按黑色的从start到end的直线执行,此时利用HAL_ADC_GetValue(&hadc1)去获取转化后的数据,无法确定这个数据是哪一个通道的(最有可能是最后一个通道的,因为按照顺序转换,后面的数据会覆盖前面的)。间断模式就是蓝线的部分,它给整个流程加上了暂停键,在每转换一个通道后就暂停一次,在暂停期间去读数据,就能够确保这个数据就是这个通道的。HAL库学STM32 关于ADC的几个问题_第3张图片
“在STM32的手册中,我们发现,不论是单次采集还是多次采集,转换完成的数据都会放在同一个地方。
HAL库学STM32 关于ADC的几个问题_第4张图片

由于DR寄存器不是一个数组,而是一个字节,所以只能保存最新的转换结果。例如,通道1和通道2都使用,通道1的转换结果放在DR寄存器。通道2转换完毕以后,就会覆盖通道1的结果。
  程序里,当然可以通过一些处理,让通道1的结果在被覆盖之前就保存好。不过,运用STM32的DMA功能,可以更好地解决结果被覆盖的问题。”
————————————————
版权声明:以上部分来自CSDN博主「geek_yatao」的原创文章
原文链接:https://blog.csdn.net/geek_monkey/article/details/89326729

3. 多通道的配置(DMA)

此部分参考:
HAL库教程12:ADC与DMA采集多路AD值
基于DMA的ADC多通道采集_亲测
STM32F的利用HAL库ADC转换DMA方式多通道采样调试总结

“DMA和外设之间大致的工作流程,就拿ADC来说,当启动了ADC_DMACmd()(这个是ADC的配置寄存器可以自己查看数据手册)这个函数后,DMA和ADC之间就建立了链接。在发生一个事件后,外设向DMA控制器发送一个请求信号。DMA控制器根据通道的优先权处理请求。当DMA控制器开始访问发出请求的外设时,DMA控制器立即发送给它一个应答信号。当从DMA控制器得到应答信号时,外设立即释放它的请求。一旦外设释放了这个请求,DMA控制器同时撤销应答信号。如果有更多的请求时,外设可以启动下一个周期。”

循环模式用于处理循环缓冲区和连续的数据传输(如ADC的扫描模式)。在DMA_CCRx寄存器中的CIRC位用于开启这一功能。当启动了循环模式,数据传输的数目变为0时,将会自动地被恢复成配置通道时设置的初值,DMA操作将会继续进行。说一下这个循环缓冲区,DMA占用的也是内核的系统数据总线,而这个总线想必大家都知道那可不是在总线上待着玩呢,都是瞬时完成的。每当ADC转化一次数据完成之后(也就是CPU处理了数据你可以理解为你的电压值),DMA说,大哥你只管计算数据就啦,剩下的交给我吧,即DMA占用一下数据总线(查的手册为占用一半的资源),把数据发送到你定义的二维数组地址下的存储空间。假设你定义了10组两个通道大小的AD值,那么ADC会一直转换直到20组,那么在这个过程当中,CPU只管计算(比如采样、编码、量化等,这些可查看ADC)之后DMA尽快的去把数据送到二维数组当中。在这个DMA送数据的连续过程当中就为循环缓冲区。"
————————————————
版权声明:以上部分为CSDN博主「Lipyoung」的原创文章
原文链接:https://blog.csdn.net/lipengyu1363658871/article/details/102774852

大概捋一捋自己的理解:
ADC开启DMA功能,正如上文所说,就是将ADC和DMA链接了起来,当转换完一个数据的时候,就立马向DMA发送请求,DMA响应之后立马把DR寄存器中的数据移走放在内存的一个数组中,如此一来转换的数据就不会被覆盖,也不用开启间断模式停止扫描移走数据。

HAL_ADC_Start_DMA(&hadc1, (uint32_t*) DMA_BUF, 2);
  while (1)
  {
		printf("count = %d\r\n",count);
  		printf("hello ADC\r\n");
		printf("DMA0 = %d\r\n",DMA_BUF[0]);
		printf("DMA1 = %d\r\n",DMA_BUF[1]);
		if(HAL_DMA_GetState(&hdma_adc1) != HAL_DMA_STATE_BUSY) {
			printf("DMA0 = %d\r\n",DMA_BUF[0]);
			printf("DMA1 = %d\r\n",DMA_BUF[1]);
			printf("count = %d\r\n",count);
		}
		HAL_Delay(1000);
  }

在main.c中添加主函数如上。
在此DMA开启的是循环模式,就是说每一次传输完2个数据之后,又重新开始传输这两个数据。在此模式下,ADC必须是扫描连续模式,才能一直有数据被传送。
有意思的是,在代码中if(HAL_DMA_GetState(&hdma_adc1) != HAL_DMA_STATE_BUSY)这个条件永远是不成立的,大概是因为循环模式下DMA是不会自己停止的吧。

【2020.7.22】补充

HAL_ADC_Start_DMA(&hadc1, (uint32_t*) DMA_BUF, 2);
  while (1)
  {
//		printf("count = %d\r\n",count);
//  	printf("hello ADC\r\n");
		printf("DMA0 = %d\r\n",DMA_BUF[0]);
		printf("DMA1 = %d\r\n",DMA_BUF[1]);
		arr[0] = arr[0]+DMA_BUF[0];
		arr[1] = arr[1]+DMA_BUF[0];
		i++;
		if(i%10 == 0) {
			printf("\r\narr0 = %d\r\n",arr[0]/10);
			printf("arr1 = %d\r\n",arr[1]/10);
			printf("count = %d\r\n",count);
			printf("i = %d\r\n\r\n",i);
			arr[0] = 0;
			arr[1] = 0;
		}
		HAL_Delay(1000);
	}

“我看到的现象是,DMA_CNT大约是15W,每次两个数据,也就是DMA1秒钟搬运了30W个字节。可以想象,如果不是AD转换速度限制,DMA还可以更快一点.”

又花了一些时间(fine,不止一些),再捋一下自己的理解。
DMA的最大好处就是解放了CPU,自主利用数据总线进行数据传输。就是说, HAL_ADC_Start_DMA(&hadc1, (uint32_t*) DMA_BUF, 2); 这个语句之后,不论CPU是在做什么,DMA都在勤勤恳恳地将ADC数据寄存器中的数据搬运到DMA_BUF数组里面,每次DMA循环都会重新覆盖,就是说IN0通道的数据永远放在DMA_BUF[0]。用printf函数打印出来的DMA_BUF[0]就是刚好那个时刻的数据而已。

另外还要注意的点:
“1,规则模式下,用户内存缓冲区的数据宽度需要和DMA设置的数据宽度一致。
2 ,多通道顺序参数rank,在配置rank时,如果配置的rank在开启的通道中不连续,DMA无法将转换的数据传到对应的缓冲区,也就和无法读取到该通道的数据。”

3,HAL_ADC_Start_DMA(ADC_HandleTypeDef* hadc, uint32_t* pData, uint32_t Length) 中参数Length为多通道的通道数。

【2020.7.23】补充
此部分参考:
[已解决] HAL库 配置DMA_ADC工作后主循环里的函数不工作,求指导
stm32cubemx教程之ADC采集通过DMA传输,听说能省很多CPU时间做其他事

这个问题说来有意思,在开启HAL_ADC_Start_DMA之后,不论主循环的打印都无法显示出来。不知道程序是卡在了HAL_ADC_Start_DMA函数里面还是怎么样,无法,很迷。
查了挺久的资料,看到以上两篇文章,稍稍有点拨开迷雾的意思。

弄清这个问题还挺难,还得从HAL_ADC_Start_DMA这个函数做了什么事情说起。
HAL库学STM32 关于ADC的几个问题_第5张图片
图片来源:STM32Cube

重点在于DMA传输完成后会自动调用DMA中断函数,也就是为什么一旦配置了DAM模式,MX就一定会开启DMA中断的原因。
上图中可以看到,“DMA传输完成后自动调用名字为ADC_DMAConvCplt()的函数”,是如何看出来的?调用的不是DMA中断函数吗?那就去看看DMA中断函数好了。HAL库学STM32 关于ADC的几个问题_第6张图片
HAL库学STM32 关于ADC的几个问题_第7张图片
在这里插入图片描述
一步一步,找到了DMA传输完成的回调函数,可是这个函数看起来很不正常,甚至看不明白……
其实是这样的(此部分得以理解需得感谢豌豆。)

问:为什么DMA传输完成的回调函数与ADC转换完成的回调函数都是HAL_ADC_ConvCpltCallback,可是在DMA中断函数那里却找不到?且ADC转换完成后对DMA发起的请求,在代码中哪里有体现?

首先,两者的回调函数确实是同一个。
其次,DMA中断函数里的回调函数是一个函数指针,它指向的是“返回值为空且参数为一个结构体”的函数(定义如下)。因此我们的问题就变为了找到该指针指向的函数。

void (* XferCpltCallback)( struct __DMA_HandleTypeDef * hdma); 
 /*!< DMA transfer complete callback*/

在下面的附中,博主指出该函数指针的指向是在下列的开启ADC的函数HAL_ADC_Start_DMA中有表明。

/* Set the DMA transfer complete callback */
hadc->DMA_Handle->XferCpltCallback = ADC_DMAConvCplt;

但是两者进入中断的规则有所不同,ADC是在“组”完成转换后进入中断,DMA是在"每个通道"完成ADC后向DMA发起请求、DMA完成该通道数据的传输后进入中断。
最后,ADC转换完成后对DMA发起的请求是在每个通道转换完成都发一次,但是在STM32Cube生成的函数中找不到。
————————
以上部分来自 豌豆。的学习笔记,她的博客: https://me.csdn.net/Wandou____

也就是说,在HAL_ADC_Start_DMA这个函数里面,HAL_ADC_ConvCpltCallback告诉了DMA中断回调函数说,嘿!你就是我哦,你要调用的函数就是我哦!

下面就更有意思了。明明说DMA是解放了CPU,但是DMA请求和响应以及DMA的中断都要用到CPU,DMA传输数据的速度极快,就是进入中断十分频繁,所以会出现CPU一直在处理DMA中断而回不到主函数的情况,也就是本次问题所在。

解决方案:

  1. 最简单的就是提高ADC的采样周期,这样DMA中断函数开启的频率相应地就会降低,CPU就有时间回到主循环里面去了。
  2. 第二种是下面博主的方法
    HAL库学STM32 关于ADC的几个问题_第8张图片
  3. 碎碎念:不知道DMA模式下开启ADC中断是有个啥子用咯~

你可能感兴趣的:(STM32)