整理: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語言或其它基礎更為密切。
【拋磚引玉 旨在交流,如有錯疏 歡迎賜教】 |