整理:MilerShao 某日,一深圳客户在用STM32F0芯片开发新产品,其中用到7个ADC通道,并将AD转换的值通过DMA传输到一个内存数组里。他反映如果单通道ADC并启用DMA,数据传输都很正确,但当启用多通道ADC并启用DMA传输时,发现数据乱了套,结果是第一个数据特别大,后面的数据多数为0。后来通过网上了解到,貌似不少人栽在这里,这里尽力分享交流下。
在聊这个问题前,不妨插入两个小话题。 一、 记得有一次有个工程师在用STM8S芯片的ADC,跟我说ADC的值当输入电压较低时数据很准很正确,可当输入电压高到一定范围时,数据反而变小,似乎并无章法。查看其代码,其AD使用的是10bit,可他ADC处理函数返回的数据却8bit的。既然这样,当ADC数据大过255时要正确就怪了。 二、 还记得某工程师用STM32F1的芯片开发产品时问我,是不是用HSI的话,UART波特率就上不了115200。我告知他一般来说,轻松能上。后来细查其代码,他不知何时把那个存放UART波特率的数据变量定义为16位宽度了,既然这样最高波特率就过不了64K。
好,回到今天的多通道ADC的DMA传输话题。
其实,关于stm32 多通道ADC的DMA传输,ST官方在其传统外设固件库或CUBE工程固件库里都有现存的项目工程。两个库的例程我用基于STM32F072的牛客板【NUCLEO】做了测试,都可以正常使用。出现上面工程师提到的问题,是因为其有关数据宽度配置不一致导致的误解和误判。 因为反映该问题的客户是基于ST官方的CUBE库做的。这里就基于cube工程示例项目交流。相关工程位置如下: \STM32Cube\Repository\STM32Cube_FW_F0_V1.3.0\Projects\STM32F072RB-Nucleo\Examples\ADC\ADC_Sequencer
例程项目用到3个AD通道。在有关ADC配置的地方,可以看出其ADC转换的数据分辨率为12位,数据右对齐,多通道ADC的扫描方向是从小往大,即FORWARD方向扫描。
从有关DMA配置代码可以看出,DMA拾取、送达两端的数据对齐宽度均为半字即16bit;数据从外设搬到存储器;内存地址递增方式;
从官方例程里可以看到一个3元素的数组用来存储3个AD通道转换值,数组元素的数据宽度为16位,即半字uint16_t 。 #define ADCCONVERTEDVALUES_BUFFER_SIZE ((uint32_t) 3) /* Variable containing ADC conversions results */ __IO uint16_t aADCxConvertedValues[ADCCONVERTEDVALUES_BUFFER_SIZE];
按照上面的条件进行编译调试,查看数组里的转换结果,并无发现异常。既没有第一个数据特别大,也没有后面数据为0的异象。分别是3个16位数据0x0803,0x06b2,0x05e5,每个数组变量对应一个AD通道的转换值。【后面也会用到这几个数据,因为是实时调试截图,数据可能些差异,先行忽略】 如果把上面存放ADC数据的数组变量数据宽度由16位改为32位,即U16改为U32,其它不变,再来看看结果。如下图所示:
呵呵,貌似异象出现了。 数组里的数据出现跟第一种情况明显不同的布局,数组第一个数据的确是特别大,数组第2个数据摆放的似乎并非程序猿所希望的。结合上面的测试结果,大致可以看出该数组的第2个数据是处在第3个转换次序的AD通道的转换值,第3个数组数据里空空如也,是0。通过两次实验的比较不难发现,第二种情形下的半字数据,除了0值外,跟第一种情形里的数据是一样的,只是在数组元素的位置有变动。
看到这里估计有人已经明白怎么回事了。DMA传输的数据宽度跟数组定义的存储宽度不一致导致误会。其实各通道ADC的值并没有错【从上面实验也可以看出】,第2种情况只是把两个16位宽的ADC值放到一个32位宽的数组元素里。如果此时简单地把每个数组变量里的数据当做单个通道转换过来的值就是天大的误解了,因为每个数组变量存放的数据跟单个通道的ADC值不存在一一对应关系了。
上面客户的问题就是出在这里。为了避免类似误解和麻烦,DMA配置过程中在定义内存数据对齐宽度时最好与存放AD转换值的存储变量用同类型的数据宽度。 细心的人还可以发现,在ST CUBE库例程代码里的DMA相关配置代码后面还特意跟了一句注释:
/* Transfer to memory by half-word to match with buffer variable type: half-word */ 这样做的目的主要是方便后面对ADC转换数据的读取及后续计算,并不是说数组存储变量的数据宽度定义跟DMA传输数据宽度不一致就一定错了。 对于DMA而言,它只关心数据要存放的起始地址、自己每次搬运数据的宽度、单轮循环搬运的次数就行。至于缓冲区存放数据的变量类型怎么样它并不关心。 下面是我将存放数据的数组变量的数据宽度改为8位、数组元素改为6,其它不动的测试结果。【高位地址对应高位数据字节】
显然,当把数组变量类型定义为U8时,DMA将来自AD转换来的每个16位源数据分别放在2个8位数组变量空间。跟上面第二种情形一样,每个数组变量并不对应一个AD转换值,而是每个AD值分两个数组元素摆放。但从AD变换或DMA传输而言,并没有出错,结果都是正确的。 下图是直接给DMA一个目标地址,然后在内存区查看ADC转换出来的数据。【高位地址对应高位数据字节】结果也是正常的。
最后顺便提下,STM32F0的多通道AD扫描有两个方向,一般默认为FORWARD方向,也可以设置为BACKWARD方向。有时忽略了也可能给开发者带来混乱或困惑,因为有时多通道,换个方向后AD值可能跟预估的大相径庭而又无规律。
本文中的话题,包括开头中插入的两个话题,看似跟AD/DMA等有关,实质上感觉跟C语言或其它基础更为密切。
【抛砖引玉 旨在交流,如有错疏 欢迎赐教】 |