STM32學習筆記——DMA控制器(向原子哥學習)


一、DMA簡介

  DMA,全稱為:Direct Memory Access,即直接存儲器訪問,DMA 用來提供在外設和存儲器之間或者存儲器和存儲器之間的高速數據傳輸。當 CPU 初始化這個傳輸動作,傳輸動作本身是由DMA 控制器 來實行和完成。DMA 傳輸對於高效能嵌入式系統算法和網絡是很重要的。DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場的過程,通過硬件為 RAM 與 I/O 設備開辟一條直接傳送數據的通路,能使 CPU 的效率大為提高。STM32 最多有 2 個 DMA 控制器(DMA2 僅存在大容量產品中),DMA1 有 7 個通道,DMA2 有 5個通道。每個通道專門用來管理來自於一個或多個外設對存儲器訪問的請求。還有一個仲裁器來協調各個 DMA 請求的優先權。

  從外設(TIMx、ADC、SPIx、I2Cx 和 USARTx)產生的 DMA 請求,通過邏輯或輸入到DMA 控制器,這就意味着同時只能有一個請求有效。外設的 DMA 請求,可以通過設置相應的外設寄存器中的控制位,被獨立地開啟或關閉。

表1 DMA各通道一覽表

  邏輯或是指通道 1 的幾個 DMA1 請求(ADC1、 TIM2_CH3、 TIM4_CH1),這幾個是通過邏輯或到通道 1 的,這樣我們在同一時間,就只能使用其中的一個。其他通道也是類似的。

二、DMA寄存器

  注意: 在以下列舉的所有寄存器中,所有與通道6和通道7相關的位,對DMA2都不適用,因為DMA2只有5個通道。

 1、DMA 中斷狀態寄存器(DMA_ISR)

  如果開啟了 DMA_ISR 中這些中斷,在達到條件后就會跳到中斷服務函數里面去,如果沒開啟,我們也可以通過查詢這些位來獲得當前 DMA 傳輸的狀態。這里常用的是TCIFx,即通道 DMA 傳輸完成與否的標志。注意此寄存器為只讀寄存器,所以在這些位被置位之后,只能通過其他的操作來清除。

  2、DMA 中斷標志清除寄存器(DMA_IFCR)。

 

圖2 DMA_IFCR 寄存器

  DMA_IFCR 的各位就是用來清除 DMA_ISR 的對應位的,通過寫 0 清除。在 DMA_ISR 被置位后,我們必須通過向該位寄存器對應的位寫入 0 來清除。

三、庫函數下 DMA1 通道 4 的配置步驟:

  1、使能 DMA 時鍾

    RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 時鍾

  2、初始化 DMA 通道 4 參數

  DMA 通道配置參數種類比較繁多,包括內存地址,外設地址,傳輸數據長度,數據寬度,通道優先級等等。這些參數的配置在庫函數中都是在函數 DMA_Init 中完成,下面我們看看函數定義:

    void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx,DMA_InitTypeDef* DMA_InitStruct)

  函數的第一個參數是指定初始化的 DMA 通道號,主要看看第二個參數。跟其他外設一樣,同樣是通過初始化結構體成員變量值來達到初始化的目的,下面看看 DMA_InitTypeDef 結構體的定義:
  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_PeripheralBaseAddr 用來設置 DMA 傳輸的外設基地址,比如要進行串口DMA 傳輸,那么外設基地址為串口接受發送數據存儲器 USART1->DR 的地址,表示方法為&USART1->DR。

  第二個參數 DMA_MemoryBaseAddr 為內存基地址,也就是我們存放 DMA 傳輸數據的內存地址。

  第三個參數 DMA_DIR 設置數據傳輸方向,決定是從外設讀取數據到內存還送從內存讀取數據發送到外設,也就是外設是源地還是目的地,這里我們設置為從內存讀取數據發送到串口,所以外設自然就是目的地了,所以選擇值為 DMA_DIR_PeripheralDST。

  第四個參數 DMA_BufferSize 設置一次傳輸數據量的大小,這個很容易理解。

  第五個參數 DMA_PeripheralInc 設置傳輸數據的時候外設地址是不變還是遞增。如果設置為遞增,那么下一次傳輸的時候地址加 1,這里因為我們是一直往固定外設地址&USART1->DR發送數據,所以地址不遞增,值為 DMA_PeripheralInc_Disable;

  第六個參數 DMA_MemoryInc 設置傳輸數據時候內存地址是否遞增。 這個參數和DMA_PeripheralInc 意思接近,只不過針對的是內存。這里我們的場景是將內存中連續存儲單元的數據發送到串口,毫無疑問內存地址是需要遞增的,所以值為 DMA_MemoryInc_Enable。

  第七個參數 DMA_PeripheralDataSize 用來設置外設的數據長度是為字節傳輸(8bits) ,半字傳輸 (16bits) 還是字傳輸 (32bits) ,這里我們是 8 位字節傳輸,所以 值設置為DMA_PeripheralDataSize_Byte。

  第八個參數 DMA_MemoryDataSize 是用來設置內存的數據長度,和第七個參數意思接近,這里我們同樣設置為字節傳輸 DMA_MemoryDataSize_Byte。

  第九個參數 DMA_Mode 用來設置 DMA 模式是否循環采集,也就是說,比如我們要從內存中采集 64 個字節發送到串口,如果設置為重復采集,那么它會在 64 個字節采集完成之后繼續從內存的第一個地址采集,如此循環。這里我們設置為一次連續采集完成之后不循環。所以設置值為 DMA_Mode_Normal。在我們下面的實驗中,如果設置此參數為循環采集,那么你會看到串口不停的打印數據,不會中斷,大家在實驗中可以修改這個參數測試一下。

  第十個參數是設置 DMA 通道的優先級,有低,中,高,超高三種模式,這個在前面講解過,這里我們設置優先級別為中級,所以值為 DMA_Priority_Medium。如果要開啟多個通道,那么這個值就非常有意義。


  第十一個參數 DMA_M2M 設置是否是存儲器到存儲器模式傳輸,這里我們選擇DMA_M2M_Disable。

  這里給出上面場景的實例代碼:
  DMA_InitTypeDef DMA_InitStructure;
  DMA_InitStructure.DMA_PeripheralBaseAddr = &USART1->DR; //DMA 外設 ADC 基地址
  DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA 內存基地址
  DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //從內存讀取發送到外設
  DMA_InitStructure.DMA_BufferSize = 64; //DMA 通道的 DMA 緩存的大小
  DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外設地址不變
  DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址遞增
  DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //8 位
  DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; // 8 位
  DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常緩存模式
  DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA 通道 x 擁有中優先級
  DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非內存到內存傳輸
  DMA_Init(DMA_CHx, &DMA_InitStructure); //根據指定的參數初始化
  

 3、使能串口 DMA 發送

  進行 DMA 配置之后,我們就要開啟串口的 DMA 發送功能,使用的函數是:
    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
  如果是要使能串口 DMA 接受,那么第二個參數修改為 USART_DMAReq_Rx 即可。

 4、使能 DMA1 通道 4,啟動傳輸。

  使能串口 DMA 發送之后,我們接着就要使能 DMA 傳輸通道:
    DMA_Cmd(DMA_CHx, ENABLE);
  通過以上 3 步設置,我們就可以啟動一次 USART1 的 DMA 傳輸了。

 5、查詢 DMA 傳輸狀態

  在 DMA 傳輸過程中,我們要查詢 DMA 傳輸通道的狀態,使用的函數是:
    FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
  比如我們要查詢 DMA 通道 4 傳輸是否完成,方法是:
    DMA_GetFlagStatus(DMA2_FLAG_TC4);
  這里還有一個比較重要的函數就是獲取當前剩余數據量大小的函數:
    uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)
  比如我們要獲取 DMA 通道 4 還有多少個數據沒有傳輸,方法是:
    DMA_GetCurrDataCounter(DMA1_Channel4);
  DMA 相關的庫函數更詳細的資料可以查看固件庫中文手冊。

 


免責聲明!

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



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