標籤:

DMA數據傳輸

從字面意思上看,DMA即為「直接內存讀取」的意思,換句話說DMA就是用來傳輸數據的,它也屬於一個外設。只是在傳輸數據時,無需佔用CPU。

DMA請求

某個外設在通過DMA傳輸數據前,必須先給DMA控制器發送請求,控制器會返回一個應答信號給外設,外設應答後並且DMA控制器收到外設應答信號後,便會啟動DMA傳輸。這個過程類似於TCP的「三次握手」。

DMA有DMA1和DMA2兩個控制器,每個控制器都有不同的通道,每個通道對應不同的外設請求。如圖12-1為DMA1的通道請求、圖12-2為DMA2的通道請求。

圖12-1

圖12-2

如以上兩圖所示,DMA1有7個通道,DMA2有5個通道,每個通道都對應著不同的外設請求。既然這樣,就很有可能出現多個外設同時請求同一通道的情況。這響應先後順序該如何處理是好?那麼,這就涉及到仲裁器管理了,用仲裁器來處理請求響應先後的問題。需要分兩個階段,第一階段是在DMA_CCRx寄存器中設置通道優先順序,第二階段則需要判斷其通道編號,編號越低優先順序越高。還有一點,DMA1優先順序要高於DMA2。

DMA傳輸方向

DMA傳輸方向有三個:外設到內存,內存到外設,內存到內存。

外設到內存。即從外設讀取數據到內存。例如ADC採集數據到內存,ADC寄存器地址為源地址,內存地址為目標地址。

內存到外設。即從內存讀取數據到外設。例如串口向電腦發送數據,內存地址為源地址,串口數據寄存器地址為目標地址。此時內存存儲了需要發送的變數數據。

內存到內存。以內部flash向內部sram傳輸數據為例,此時內部flash地址即為源地址,內部sram地址即為目標地址。同時,需要將DMA_CCRx寄存器的MEM2MEM置位。

傳輸配置

我們需要確定數據每次傳輸的量,這個參數由DMA_CNDTRx寄存器配置。

再者,還有一個源地址和目標地址數據寬度的參數配置。由DMA_CCRx的PSIZE位和MSIZE位配置。可配置為8位、16位、32位。源地址和目標地址的數據寬度需要一致才可傳輸。

此外,數據想有序地傳輸,還需要配置源和目標數據指針的增量模式。由DMA_CCRx寄存器的PINC位和MINC位配置。例如串口向電腦發送數據,內存中的地址指針應該遞增的發送數據,而串口外設只有一個,所以外設的地址指針不變,無遞增。

傳輸狀態標識

可以通過查詢DMA_ISR寄存器的相應位的值來判斷傳輸狀態。如果在DMA_CCRx寄存器的相應位使能了相應中斷,則會產生中斷。

另外,傳輸完成還分成一次傳輸完成和循環傳輸完成。DMA在傳輸完成後,需要失能DMA後重新配置才能繼續傳輸。具體配置由DMA_CCRx寄存器的CIRC位完成。

DMA_InitTypeDef

/** * @brief DMA Init structure definition */typedef struct{ uint32_t DMA_PeripheralBaseAddr; // 外設地址 uint32_t DMA_MemoryBaseAddr; // 內存地址 uint32_t DMA_DIR; // 傳輸方向 uint32_t DMA_BufferSize; // 傳輸數量 uint32_t DMA_PeripheralInc; // 外設地址增量模式 uint32_t DMA_MemoryInc; // 內存地址增量模式 uint32_t DMA_PeripheralDataSize; // 外設數據寬度 uint32_t DMA_MemoryDataSize; // 內存數據寬度 uint32_t DMA_Mode; // 模式選擇 uint32_t DMA_Priority; // 通道優先順序 uint32_t DMA_M2M; // 內存到內存模式} DMA_InitTypeDef;

以上結構體代碼來自庫函數,我大概在每個成員後做了簡單注釋,在進行DMA編程時,需要對結構體成員進行配置。

內存到外設的DMA數據傳輸實驗

既然之前寫過串口通訊編程的相關文章,那乾脆用串口外設來和內存進行數據傳輸吧。這裡簡單講解一個從內存讀取數據到外設的DMA傳輸實驗,並且在實驗里用led燈驗證DMA傳輸時不佔用CPU。

這裡關於USART的配置就不繼續贅述,之前的文章有詳細的介紹,可移步閱讀。直接開始DMA配置。

#define SENDBUFF_SIZE 5000 //傳輸的數據量uint8_t SendBuff[SENDBUFF_SIZE]; //內存里等待傳輸數據的數組void USART_DMA_Config(void){ DMA_InitTypeDef DMA_InitStructure; RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // 串口外設為目標地址 DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)USART1_BASE + 0x04; // 內存為源地址 DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)SendBuff; // 傳輸方向,即從內存讀取數據到串口外設 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; // 數據傳輸量,初始化DMA_CNDTRx寄存器 DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE; // 外設地址不遞增 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 內存地址遞增 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 外設數據寬度 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; // 內存數據寬度 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 一次循環模式 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 通道優先順序 DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 不使用內存到內存模式 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // DMA通道配置 DMA_Init(DMA1_Channel4, &DMA_InitStructure); // 清除傳輸完成標誌位,避免產生不必要干擾 DMA_ClearFlag(DMA1_FLAG_TC4); DMA_Cmd(DMA1_Channel4, ENABLE);}

在main函數里調用USART、DMA、LED等的配置函數,用循環的方式往內存的數組裡填充SENDBUFF_SIZE(5000)數量的字元作為等待傳輸的數據。然後調用庫函數USART_DMACmd()向DMA發出USART_DMAReq_Tx請求。同時,可以設置led頻閃狀態作為在DMA傳輸時佔用CPU的進程,以驗證DMA傳輸不佔用CPU。

程序編譯完成燒寫到開發板後,能看到在DMA傳輸過程中led同時也在頻閃,說明DMA傳輸過程確實不佔用CPU資源,可以邊傳輸邊運行其他任務。

PS:有關led閃爍的延時控制,可用普通的軟體延時也可用SysTick定時器來完成,也很簡單。有關SysTick定時器的應用在之前的文章有過介紹,可移步閱讀。

-----------------------------------------------------------------------

文章首發於知乎專欄 - stm32,轉載請私信,並註明原文出處。


推薦閱讀:

可不可以用動態圖像(動態二維碼)傳輸數據,具體怎麼實現?
一台電腦要向n台電腦傳文件,假設傳輸50G給他們,n台電腦可以相互傳輸,一次只能向一台電腦傳,怎麼做?

TAG:数据传输 |