
這里選用STM32H563Nucleo開發板,使用USART3外設演示DMA鏈表模式的實現過程。大致過程是這樣的:
STM32H563芯片上的USART3與板載STLINK的VCP相連,通過PC端串口助手軟件先后發送3串字符給MCU,MCU的USART3通過DMA以鏈表模式依次接收并存到相應內存,同時在相應的調試界面的觀察窗口加以顯示。接收過程對應3個DMA接收節點,他們構成1個DMA接收隊列。當MCU接收到3串字符后,也以DMA 鏈表模式依次發送3串字符到PC端的串口助手并顯示出來。同樣,USART3的DMA 發送也用到3個節點以組成1個DMA發送隊列。
(資料圖片僅供參考)
GPDMA1的2個通道CH0,CH1,配置在鏈表模式。其中CH0用于USART3 RX DMA隊列,CH1用于USART3 TX DMA隊列。這個通道安排可以在用戶代碼里調整。
下面使用STM32CbueMx圖形化工具進行基本配置后創建測試工程。【注:手機模式下文中圖片可放大查看】
3個USART3 DMA RX節點的配置如下【3個配置類似,只是源地址、目標地址有差異】:
3個USART3 DMA TX節點的配置如下【3個配置類似,只是源地址、目標地址有差異】:
相關的用戶參考代碼如下:
#define RXBUFFERSIZE (8)#define TXBUFFERSIZE (22)volatileuint8_tRxComplete=0;//DMA接收完成標志ALIGN_32BYTES (uint8_t aTxBuffer0[])="STM32H5_DMA_LIST_0";ALIGN_32BYTES (uint8_t aTxBuffer1[])="STM32H5_DMA_LIST_1";ALIGN_32BYTES (uint8_t aTxBuffer2[])="STM32H5_DMA_LIST_2";ALIGN_32BYTES (uint8_t aRxBuffer0[RXBUFFERSIZE]);ALIGN_32BYTES (uint8_t aRxBuffer1[RXBUFFERSIZE]);ALIGN_32BYTES (uint8_t aRxBuffer2[RXBUFFERSIZE]);DMA_HandleTypeDef handle_GPDMA1_Channel1;DMA_HandleTypeDef handle_GPDMA1_Channel0;UART_HandleTypeDef huart3;int main(void){/* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* Configure the system clock */SystemClock_Config();/* Initialize all configured peripherals */ MX_GPIO_Init(); MX_GPDMA1_Init();//MX_ADC1_Init(); MX_ICACHE_Init(); MX_USART3_UART_Init();/* USER CODE BEGIN 2 */ MX_Uart_tx_queue_Config(); MX_Uart_rx_queue_Config();/* Link UART queue to DMA channel */ HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel0, &Uart_tx_queue);/* Associate the initialized GPDMA handle to the UART handle */ __HAL_LINKDMA(&huart3, hdmatx, handle_GPDMA1_Channel0);/* Link UART queue to DMA channel */ HAL_DMAEx_List_LinkQ(&handle_GPDMA1_Channel1, &Uart_rx_queue);/* Associate the initialized GPDMA handle to the UART handle */ __HAL_LINKDMA(&huart3, hdmarx, handle_GPDMA1_Channel1);HAL_UART_Receive_DMA(&huart3, (uint8_t *)aRxBuffer0, RXBUFFERSIZE);/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1) {if(RxComplete)//checkuartRxDMAtransfercomplete ornot { RxComplete=0; huart3.gState = HAL_UART_STATE_READY; HAL_UART_Transmit_DMA(&huart3, (uint8_t*)aTxBuffer0, TXBUFFERSIZE); } }}/*UART3DMA接收完成中斷回調函數*/void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart){RxComplete++;//DMAtransfercompleted}
下面開始運行程序以驗證結果。
PC串口終端先發送三串字符被MCU USART3通過DMA接收,然后MCU以DMA鏈表的形式發送三串字符到串口終端。下圖中右邊觀察串口的字符為USART3通過DMA以鏈表形式接收的來自串口助手的數據。下圖中綠色方框內三串字符為STM32H563 USART3以DMA鏈表形式3次發送出來的。
在上面測試中,關于DMA傳輸完成事件的配置,我選擇的針對整個DMA傳輸鏈完成傳輸后才產生完成事件。以上面的USART3 RX DMA接收為例,當3個節點都完成DMA接收時才觸發DMA傳輸完成事件。當然,我們也可以基于每個DMA傳輸節點來產生DMA傳輸完成事件,比如按下面的配置修改。
如果這樣修改之后,其它地方不動,這時USART3 通過DMA每收到一串數據都會觸發傳輸完成事件,并在相應中斷里設置完成事件標志【RxComplete】,進而啟動USART3發送的DMA 鏈式傳輸。見下圖結果【褐紫色字符是USART3通過DMA接收到的數據】:
其實,在前面驗證過程中,發生了點小波折也順便分享出來。
當我做完基本的配置,創建工程、添加用戶代碼。編譯、除錯后,開始運行程序時并不順利。
我先使用串口助手發送字符串給MCU,可是在USART3的DMA接收隊列里,只有第一個節點有反應。通過開發環境的觀察窗口看到的接收數據跟我發出去的字符大相徑庭,其它2個DMA接收節點不論我發送多少數據就根本沒反應。因為我設計的就是只有當接收隊列即3個接收節點都正常接收后才會啟動USART3的DMA發送隊列。所以此時USART3也就沒有任何數據發送出來。檢查配置和用戶代碼,但沒有很快發現問題原因。
后來在檢查USART3的基本通信配置時,無意中發現了一點異樣。就是有關接收數據是否需要翻轉的配置項不知何時變成Enable了。
顯然,這個地方不能翻轉,不然發送的數據跟接收的數據肯定不一樣了。我清楚地記得這個地方我是沒動的,不知是不是某個時候不小心碰著而改動了。立即將其Disable掉后進行測試驗證。
測試結果發現第一個DMA接收節點的數據正常了。但奇怪的是第2個、第3個接收節點仍然接收不到數據。又是一番配置和代碼核對,無果。
后來,先干脆不管接收隊列,而試著直接啟動USART3的DMA發送隊列。慶幸的是,發送很正常。看來,USART3 TX DMA發送隊列的配置是靠譜的。其實USART3 RX DMA接收隊列的配置跟發送隊列配置很相似,只是請求源、傳輸端地址的配置差異。再次逐個核對DMA接收節點2和3的配置。功夫不負有心人,果真發現烏龍了。在DMA接收節點2的DMA請求配置那里,我竟然不小心選成USART1的了,一字之差,謬以千里。節點3的配置沒有問題。看來問題就出在接收節點2的那個誤選項。立馬驗證,結果一切正常了。
為什么只是節點2配置有問題,節點3也不能接收數據呢?因為這3個節點構成了一條關于DMA傳輸的時序、事件配合鏈,節點2沒有正常完成,節點3也沒法正常啟動。
另外,還解釋一個變量。我在代碼里設置了RxComplete變量,是用來標記DMA接收完成事件的,在主程序和中斷里都用到了。記得定義它時加上volatile,不然當優化等級調至較高時,可能出現程序運行異常情況。
OK,今天的DMA 鏈表功能的應用演示就到分享這里,下次再聊。
審核編輯:湯梓紅
標簽: