整理:MilerShao 某日,一深圳客戶在用STM32F0芯片開發(fā)新產(chǎn)品,其中用到7個ADC通道,并將AD轉(zhuǎn)換的值通過DMA傳輸?shù)揭粋內(nèi)存數(shù)組里。他反映如果單通道ADC并啟用DMA,數(shù)據(jù)傳輸都很正確,但當(dāng)啟用多通道ADC并啟用DMA傳輸時,發(fā)現(xiàn)數(shù)據(jù)亂了套,結(jié)果是第一個數(shù)據(jù)特別大,后面的數(shù)據(jù)多數(shù)為0。后來通過網(wǎng)上了解到,貌似不少人栽在這里,這里盡力分享交流下。
在聊這個問題前,不妨插入兩個小話題。 一、 記得有一次有個工程師在用STM8S芯片的ADC,跟我說ADC的值當(dāng)輸入電壓較低時數(shù)據(jù)很準(zhǔn)很正確,可當(dāng)輸入電壓高到一定范圍時,數(shù)據(jù)反而變小,似乎并無章法。查看其代碼,其AD使用的是10bit,可他ADC處理函數(shù)返回的數(shù)據(jù)卻8bit的。既然這樣,當(dāng)ADC數(shù)據(jù)大過255時要正確就怪了。 二、 還記得某工程師用STM32F1的芯片開發(fā)產(chǎn)品時問我,是不是用HSI的話,UART波特率就上不了115200。我告知他一般來說,輕松能上。后來細(xì)查其代碼,他不知何時把那個存放UART波特率的數(shù)據(jù)變量定義為16位寬度了,既然這樣最高波特率就過不了64K。
好,回到今天的多通道ADC的DMA傳輸話題。
其實,關(guān)于stm32 多通道ADC的DMA傳輸,ST官方在其傳統(tǒng)外設(shè)固件庫或CUBE工程固件庫里都有現(xiàn)存的項目工程。兩個庫的例程我用基于STM32F072的?桶濉綨UCLEO】做了測試,都可以正常使用。出現(xiàn)上面工程師提到的問題,是因為其有關(guān)數(shù)據(jù)寬度配置不一致導(dǎo)致的誤解和誤判。 因為反映該問題的客戶是基于ST官方的CUBE庫做的。這里就基于cube工程示例項目交流。相關(guān)工程位置如下: \STM32Cube\Repository\STM32Cube_FW_F0_V1.3.0\Projects\STM32F072RB-Nucleo\Examples\ADC\ADC_Sequencer
例程項目用到3個AD通道。在有關(guān)ADC配置的地方,可以看出其ADC轉(zhuǎn)換的數(shù)據(jù)分辨率為12位,數(shù)據(jù)右對齊,多通道ADC的掃描方向是從小往大,即FORWARD方向掃描。 
從有關(guān)DMA配置代碼可以看出,DMA拾取、送達(dá)兩端的數(shù)據(jù)對齊寬度均為半字即16bit;數(shù)據(jù)從外設(shè)搬到存儲器;內(nèi)存地址遞增方式; 
從官方例程里可以看到一個3元素的數(shù)組用來存儲3個AD通道轉(zhuǎn)換值,數(shù)組元素的數(shù)據(jù)寬度為16位,即半字uint16_t 。 #define ADCCONVERTEDVALUES_BUFFER_SIZE ((uint32_t) 3) /* Variable containing ADC conversions results */ __IO uint16_t aADCxConvertedValues[ADCCONVERTEDVALUES_BUFFER_SIZE];

按照上面的條件進(jìn)行編譯調(diào)試,查看數(shù)組里的轉(zhuǎn)換結(jié)果,并無發(fā)現(xiàn)異常。既沒有第一個數(shù)據(jù)特別大,也沒有后面數(shù)據(jù)為0的異象。分別是3個16位數(shù)據(jù)0x0803,0x06b2,0x05e5,每個數(shù)組變量對應(yīng)一個AD通道的轉(zhuǎn)換值。【后面也會用到這幾個數(shù)據(jù),因為是實時調(diào)試截圖,數(shù)據(jù)可能些差異,先行忽略】 如果把上面存放ADC數(shù)據(jù)的數(shù)組變量數(shù)據(jù)寬度由16位改為32位,即U16改為U32,其它不變,再來看看結(jié)果。如下圖所示:
 呵呵,貌似異象出現(xiàn)了。 數(shù)組里的數(shù)據(jù)出現(xiàn)跟第一種情況明顯不同的布局,數(shù)組第一個數(shù)據(jù)的確是特別大,數(shù)組第2個數(shù)據(jù)擺放的似乎并非程序猿所希望的。結(jié)合上面的測試結(jié)果,大致可以看出該數(shù)組的第2個數(shù)據(jù)是處在第3個轉(zhuǎn)換次序的AD通道的轉(zhuǎn)換值,第3個數(shù)組數(shù)據(jù)里空空如也,是0。通過兩次實驗的比較不難發(fā)現(xiàn),第二種情形下的半字?jǐn)?shù)據(jù),除了0值外,跟第一種情形里的數(shù)據(jù)是一樣的,只是在數(shù)組元素的位置有變動。
看到這里估計有人已經(jīng)明白怎么回事了。DMA傳輸?shù)臄?shù)據(jù)寬度跟數(shù)組定義的存儲寬度不一致導(dǎo)致誤會。其實各通道ADC的值并沒有錯【從上面實驗也可以看出】,第2種情況只是把兩個16位寬的ADC值放到一個32位寬的數(shù)組元素里。如果此時簡單地把每個數(shù)組變量里的數(shù)據(jù)當(dāng)做單個通道轉(zhuǎn)換過來的值就是天大的誤解了,因為每個數(shù)組變量存放的數(shù)據(jù)跟單個通道的ADC值不存在一一對應(yīng)關(guān)系了。
上面客戶的問題就是出在這里。為了避免類似誤解和麻煩,DMA配置過程中在定義內(nèi)存數(shù)據(jù)對齊寬度時最好與存放AD轉(zhuǎn)換值的存儲變量用同類型的數(shù)據(jù)寬度。 細(xì)心的人還可以發(fā)現(xiàn),在ST CUBE庫例程代碼里的DMA相關(guān)配置代碼后面還特意跟了一句注釋:

/* Transfer to memory by half-word to match with buffer variable type: half-word */ 這樣做的目的主要是方便后面對ADC轉(zhuǎn)換數(shù)據(jù)的讀取及后續(xù)計算,并不是說數(shù)組存儲變量的數(shù)據(jù)寬度定義跟DMA傳輸數(shù)據(jù)寬度不一致就一定錯了。 對于DMA而言,它只關(guān)心數(shù)據(jù)要存放的起始地址、自己每次搬運數(shù)據(jù)的寬度、單輪循環(huán)搬運的次數(shù)就行。至于緩沖區(qū)存放數(shù)據(jù)的變量類型怎么樣它并不關(guān)心。 下面是我將存放數(shù)據(jù)的數(shù)組變量的數(shù)據(jù)寬度改為8位、數(shù)組元素改為6,其它不動的測試結(jié)果。【高位地址對應(yīng)高位數(shù)據(jù)字節(jié)】 
顯然,當(dāng)把數(shù)組變量類型定義為U8時,DMA將來自AD轉(zhuǎn)換來的每個16位源數(shù)據(jù)分別放在2個8位數(shù)組變量空間。跟上面第二種情形一樣,每個數(shù)組變量并不對應(yīng)一個AD轉(zhuǎn)換值,而是每個AD值分兩個數(shù)組元素擺放。但從AD變換或DMA傳輸而言,并沒有出錯,結(jié)果都是正確的。 下圖是直接給DMA一個目標(biāo)地址,然后在內(nèi)存區(qū)查看ADC轉(zhuǎn)換出來的數(shù)據(jù)。【高位地址對應(yīng)高位數(shù)據(jù)字節(jié)】結(jié)果也是正常的。

最后順便提下,STM32F0的多通道AD掃描有兩個方向,一般默認(rèn)為FORWARD方向,也可以設(shè)置為BACKWARD方向。有時忽略了也可能給開發(fā)者帶來混亂或困惑,因為有時多通道,換個方向后AD值可能跟預(yù)估的大相徑庭而又無規(guī)律。
本文中的話題,包括開頭中插入的兩個話題,看似跟AD/DMA等有關(guān),實質(zhì)上感覺跟C語言或其它基礎(chǔ)更為密切。
【拋磚引玉 旨在交流,如有錯疏 歡迎賜教】 |