stm32之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定時器的應用在之前的文章有過介紹,可移步閱讀。

       分享一些關於DMA在數據傳輸方面的資料作為參考   

       stm32 如何用DMA搬運數據
       http://www.makeru.com.cn/live/detail/1484.html?s=45051


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM