STM32 ADC DMA通道数减小引发的异常问题及解决

659 阅读9分钟

本文已参与 [新人创作礼] 活动,一起开启掘金创作之路。​

一、基础知识

在讲具体问题之前先列出STM32系列单片机ADC部分的一些知识:

ADC控制器

STM32一共有3个ADC控制器:ADC1、ADC2、ADC3。

18个通道

STM32的ADC多达18个通道:16个外部通道和2个内部信号源。

  • 16个外部通道:芯片上有16个引脚是能够接到模拟电压上进行电压值检测的。
  • 2个内部信号源 :一个是内部温度传感器,一个是内部参考电压。

一共支持23个引脚支持ADC,包括21个外部和2个内部信号源。

STM32F10x系列芯片ADC通道和引脚对应关系

如下图所示:

ADC的转换模式

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

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

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

ADC单通道

  1. 只进行一次ADC转换:配置为“单次转换模式”,扫描模式关闭。ADC通道转换一次后,就中止转换,等待再次使能后才会从新转换;
  2. 进行连续ADC转换:配置为“连续转换模式”,扫描模式关闭。ADC通道转换一次后,接着进行下一次转换,不断连续。

ADC多通道

  1. 只进行一次ADC转换:配置为“单次转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,就中止转换,等待再次使能后才会从新转换;
  2. 进行连续ADC转换:配置为“连续转换模式”,扫描模式使能。ADC的多个通道,按照配置的顺序依次转换一次后,接着进行下一次转换,不断连续。

也就是说:多通道必须使能扫描模式。

左对齐或右对齐

由于ADC获得的数据是12位精度的,而数据存储在16位数据寄存器中,因此ADC的存储结果能够分为左对齐右对齐方式(12位),如下表所示:

ADC输入通道

ADCx_INT0-ADCx_INT15对应三个ADC的16个外部通道,进行模拟信号转换。此外,还有两个内部通道:温度检测内部电压检测。选择对应通道以后,便会选择对应GPIO引脚,相关的引脚定义和描述可参见数据手册。

注入通道,规则通道

在选择了ADC的相关通道引脚以后,在模拟至数字转换器中有两个通道:注入通道、规则通道。
规则通道至多16个,注入通道至多4个。

规则通道

规则通道是关于你正常运行的程序,看它的名字就能够知道,很规矩,就是正常执行程序。

注入通道

注入通道能够打断规则通道,听它的名字就知道不安分,若是在规则通道转换过程当中,有注入通道进行转换,那么就要先转换完注入通道,等注入通道转换完成后,再回到规则通道的转换流程。

DMA

ADC还支持DMA触发,规则和注入通道转换结束后会产生DMA请求,用于将转换好的数据传输到内存。

注意,只有ADC1和ADC3能够产生DMA请求。

CubeMX中ADC配置项

  • Data Alignment数据对齐方式)

对应Init.DataAlign。设置数据的对齐方式。

  • Scan Conversion Mode(扫描模式)

对应Init.ScanConvMode。是否开启扫描模式。若是只是用了一个通道的话,DISABLE就能够了(也只能DISABLE);若是使用了多个通道的话,会自动设置为ENABLE

  • Continuous Conversion Mode(连续转换模式)

对应Init.Continuous。设置为ENABLE,即连续转换;若是设置为DISABLE,则是单次转换。二者的区别在于连续转换直到全部的数据转换完成后才中止转换,而单次转换则只转换一次数据就中止,要再次触发转换才能够进行转换。

  • Discontinuous Conversion Mode(间断模式)

对应Init.DiscontinuousConvMode。

  • Number OF Conversion(转换通道数)

对应Init.NbrOfConversion。用到多少个通道就设置为多少。

  • Extenal Trigger Conversion Source (外部触发转换源)

对应Init.ExternalTrigConv。设定ADC的触发方式。

  • Rank(转换顺序)

对应sConfig.Rank。多个通道时会有多个Rank,能够设定每一个通道的转换顺序。

  • Sampling Time(通道采样时间)

对应sConfig.SamplingTime。

ADC相关接口函数

开启(启动)ADC

开启(启动)ADC有3种模式,分别是:轮询模式、中断模式、DMA模式。

  • 轮询模式开启ADC

HAL_ADC_Start

  • 中断轮询模式开启ADC

HAL_ADC_Start_IT

  • DMA模式开启ADC

HAL_ADC_Start_DMA

关闭(停止)ADC

相应地,关闭(停止)ADC也有3中模式,分别是:轮询模式、中断模式和DMA模式。

  • 轮询模式关闭ADC

HAL_ADC_Stop

  • 中断模式关闭ADC

HAL_ADC_Stop_IT

  • DMA模式关闭ADC

HAL_ADC_Stop_DMA

ADC校准

HAL_ADCEx_Calibration_Start

注:F4系列不支持此接口

读取ADC转换值

HAL_ADC_GetValue

等待转换结束函数

HAL_ADC_PollForConversion

ADC中断回调函数

HAL_ADC_ConvCpltCallback

注:转换完成后回调,DMA模式下DMA传输完成后调用

规则通道配置

HAL_ADC_ConfigChannel

看门狗配置

HAL_ADC_AnalogWDGConfig

二、所遇问题

项目中原来使用的ADC通道数为16,新的项目只使用了4路AD,因此通道数缩减为4。针对于次变化,对旧有程序做了相关的修改,主要涉及AD部分以及GPIO部分。但是,进行修改后出现了问题:原本运行正常的程序,变得响应非常慢,甚至无响应;而且读的AD值也不对,大多数数据本来有值,但一段时间后会无故变为0。仔细检查程序,并没有发现由老版本程序到新版本程序所做出的改变有什么问题和错误。进一步地,发现最基本的memset函数变得非常慢,而老版本程序中则无此问题。

正在百思不得其解、没有头绪的时候,一片博客文章如久旱逢甘雨般地出现了,地址如下:【STM32+cubemx】0008 HAL库开发:ADC的四种用法:轮询、中断、DMA、定时器触发_xiaobaibai_2021的博客-CSDN博客_adc中断触发方式

其中有这样一段内容,描述和解决的正是笔者所遇到的问题:

注意这里的HAL_ADC_Start_DMA(&hadc1, ADC_Value, 16); 最后这个参数16,表示的是DMA搬移数据的次数;向ADC_Value及其之后的地址搬移16个数据;我们这里设置了2个通道,所以从首地址开始填入的是交替的两个通道的数据:ADC0、ADC1、ADC0、ADC1…一共16个数。

这里设置为16,是因为太小的数值很快就会执行完一个循环,产生DMA传输中断,时间太短的话会频繁产生中断,导致一直在中断中执行,没有时间执行主循环中的语句。这里是增加搬移数据的次数;也可以设置AD采样时间长一些,那么每次的转换时间变长,也不会产生过多的中断。

另外这里的校准函数一定要放在启动ADC、DMA之前,否则会占用一个通道,之后的AD通道顺序会有问题。

由于老板的通道数为16,DMA传输中断并不十分频繁,因此没有出现问题;而新版本中由于通道数大幅缩减至4,因此DMA传输中断频繁发生,导致移植在中断中执行,宏观上就表现为程序反应慢或者干脆无反应,以及其它一些匪夷所思的问题。

按照上面文章给出的建议,有两种解决方案:

(1)将通道数增大,增加循环搬移数据的次数。这样会有一些冗余数据,如:将通道数设置为8,虽然问题能够解决,但是数据将是这样:ADC_CH0、ADC_CH1、ADC_CH2、ADC_CH3、ADC_CH0、ADC_CH1、ADC_CH2、ADC_CH3;而实际上只需要一次ADC_CH0、ADC_CH1、ADC_CH2、ADC_CH3。

(2)将AD采样时间设置长一些。这样每次的转换时间会变长。

最终笔者选用了第二种解决方案,将sConfig.SamplingTime由ADC_SAMPLETIME_1CYCLE_5改为了ADC_SAMPLETIME_7CYCLE_5。

经过测试,之前的问题现象都消失了,宏观上和老版本程序的表现一致了。