簡介
DMA(Direct Memory Access,直接存儲區訪問)為實現數據高速在外設寄存器與存儲器之間或者存儲器與存儲器之間傳輸提供了高效的方法。之所以稱之為高效,是因為 DMA 傳輸實現高速數據移動過程無需任何 CPU 操作控制。從硬件層次上來說, DMA 控制器是獨立於 Cortex-M4 內核的,有點類似 GPIO、 USART 外設一般,只是 DMA 的功能是可以快速移動內存數據。
STM32F4xx 系列的 DMA 功能齊全,工作模式眾多,適合不同編程環境要求。STM32F4xx 系列的 DMA 支持外設到存儲器傳輸、存儲器到外設傳輸和存儲器到存儲器傳輸三種傳輸模式。這里的外設一般指外設的數據寄存器,比如 ADC、 SPI、 I2C、 DCMI 等等外設的數據寄存器,存儲器一般是指片內 SRAM、外部存儲器、片內 Flash 等等。
外設到存儲器傳輸就是把外設數據寄存器內容轉移到指定的內存空間。比如進行 ADC采集時我們可以利用 DMA 傳輸把 AD 轉換數據轉移到我們定義的存儲區中,這樣對於多通道采集、采樣頻率高、連續輸出數據的 AD 采集是非常高效的處理方法。
存儲區到外設傳輸就是把特定存儲區內容轉移至外設的數據寄存器中,這種多用於外設的發送通信。
存儲器到存儲器傳輸就是把一個指定的存儲區內容拷貝到另一個存儲區空間。功能類似於 C 語言內存拷貝函數 memcpy,利用 DMA 傳輸可以達到更高的傳輸效率,特別是DMA 傳輸是不占用 CPU 的,可以節省很多 CPU 資源。
DMA功能框圖
STM32F4xx 系列的 DMA 可以實現外設寄存器與存儲器之間或者存儲器與存儲器之間傳輸三種模式,這要得益於 DMA 控制器是采樣 AHB 主總線的,可以控制 AHB 總線矩陣來啟動 AHB 事務。
1、外設通道選擇
STM32F4xx 系列資源豐富,具有兩個 DMA 控制器,同時外設繁多,為實現正常傳輸,DMA 需要通道選擇控制。每個 DMA 控制器具有 8 個數據流,每個數據流對應 8 個外設請求。在實現 DMA 傳輸之前, DMA 控制器會通過 DMA 數據流 x 配置寄存器 DMA_SxCR(x為 0~7,對應 8 個 DMA 數據流)的 CHSEL[2:0]位選擇對應的通道作為該數據流的目標外設。外設通道選擇要解決的主要問題是決定哪一個外設作為該數據流的源地址或者目標地址。
DMA 請求映射情況參考表 21-1 和表 21-2。
DMA1請求映射
DMA2請求映射
每個外設請求都占用一個數據流通道,相同外設請求可以占用不同數據流通道。比如SPI3_RX 請求,即 SPI3 數據接收請求,占用 DMA1 的數據流 0 的通道 0,因此當我們使用該請求時,我們需要在把 DMA_S0CR 寄存器的 CHSEL[2:0]設置為“000”,此時相同數據流的其他通道不被選擇,處於不可用狀態,比如此時不能使用數據流 0 的通道 1 即I2C1_RX 請求等等。
2、仲裁器
一個 DMA 控制器對應 8 個數據流,數據流包含要傳輸數據的源地址、目標地址、數據等等信息。如果我們需要同時使用同一個 DMA 控制器(DMA1 或 DMA2)多個外設請求時,那必然需要同時使用多個數據流,那究竟哪一個數據流具有優先傳輸的權利呢?這就需要仲裁器來管理判斷了。
仲裁器管理數據流方法分為兩個階段。第一階段屬於軟件階段,我們在配置數據流時可以通過寄存器設定它的優先級別,具體配置 DMA_SxCR 寄存器 PL[1:0]位,可以設置為非常高、高、中和低四個級別。第二階段屬於硬件階段,如果兩個或以上數據流軟件設置優先級一樣,則他們優先級取決於數據流編號,編號越低越具有優先權,比如數據流 2 優先級高於數據流 3。
3、FIFO
每個數據流都獨立擁有四級 32 位 FIFO(先進先出存儲器緩沖區)。 DMA 傳輸具有 FIFO模式和直接模式。
直接模式在每個外設請求都立即啟動對存儲器傳輸。在直接模式下,如果 DMA 配置為存儲器到外設傳輸那 DMA 會將一個數據存放在 FIFO 內,如果外設啟動 DMA 傳輸請求就可以馬上將數據傳輸過去。
FIFO 用於在源數據傳輸到目標地址之前臨時存放這些數據。可以通過 DMA 數據流xFIFO 控制寄存器 DMA_SxFCR 的 FTH[1:0]位來控制 FIFO 的閾值,分別為 1/4、 1/2、 3/4和滿。如果數據存儲量達到閾值級別時, FIFO 內容將傳輸到目標中。
FIFO 對於要求源地址和目標地址數據寬度不同時非常有用,比如源數據是源源不斷的字節數據,而目標地址要求輸出字寬度的數據,即在實現數據傳輸時同時把原來 4 個 8 位字節的數據拼湊成一個 32 位字數據。此時使用 FIFO 功能先把數據緩存起來,分別根據需要輸出數據。
FIFO 另外一個作用使用於突發(burst)傳輸。
4、存儲器端口、外設端口
DMA 控制器實現雙 AHB 主接口,更好利用總線矩陣和並行傳輸。 DMA 控制器通過存儲器端口和外設端口與存儲器和外設進行數據傳輸,關系見圖 21-2。 DMA 控制器的功能是快速轉移內存數據,需要一個連接至源數據地址的端口和一個連接至目標地址的端口。
DMA2(DMA 控制器 2)的存儲器端口和外設端口都是連接到 AHB 總線矩陣,可以使用AHB 總線矩陣功能。 DMA2 存儲器和外設端口可以訪問相關的內存地址,包括有內部Flash、內部 SRAM、 AHB1 外設、 AHB2 外設、 APB2 外設和外部存儲器空間。
DMA1 的存儲區端口相比 DMA2 的要減少 AHB2 外設的訪問權,同時 DMA1 外設端口是沒有連接至總線矩陣的,只有連接到 APB1 外設,所以 DMA1 不能實現存儲器到存儲器傳輸。
5、編程端口
AHB 從器件編程端口是連接至 AHB2 外設的。 AHB2 外設在使用 DMA 傳輸時需要相關控制信號。
DMA數據配置
1、DMA傳輸模式
DMA2 支持全部三種傳輸模式,而 DMA1 只有外設到存儲器和存儲器到外設兩種模式。模式選擇可以通過 DMA_SxCR 寄存器的 DIR[1:0]位控制,進而將 DMA_SxCR 寄存器的EN 位置 1 就可以使能 DMA 傳輸。
在 DMA_SxCR 寄存器的 PSIZE[1:0]和 MSIZE[1:0]位分別指定外設和存儲器數據寬度大小,可以指定為字節(8 位)、半字(16 位)和字(32 位),我們可以根據實際情況設置。直接模式要求外設和存儲器數據寬度大小一樣,實際上在這種模式下 DMA 數據流直接使用PSIZE, MSIZE 不被使用。
2、源地址和目標地址
DMA 數據流 x 外設地址 DMA_SxPAR(x 為 0~7)寄存器用來指定外設地址,它是一個32 位數據有效寄存器。 DMA 數據流 x 存儲器 0 地址 DMA_SxM0AR(x 為 0~7) 寄存器和DMA 數據流 x 存儲器 1 地址 DMA_SxM1AR(x 為 0~7) 寄存器用來存放存儲器地址,其中DMA_SxM1AR 只用於雙緩沖模式, DMA_SxM0AR 和 DMA_SxM1AR 都是 32 位數據有效的。
當選擇外設到存儲器模式時,即設置 DMA_SxCR 寄存器的 DIR[1:0] 位為“00”,DMA_SxPAR 寄存器為外設地址,也是傳輸的源地址, DMA_SxM0AR 寄存器為存儲器地址,也是傳輸的目標地址。對於存儲器到存儲器傳輸模式,即設置 DIR[1:0] 位為“10”時,采用與外設到存儲器模式相同配置。而對於存儲器到外設,即設置 DIR[1:0]位為“01”時,DMA_SxM0AR 寄存器作為為源地址, MA_SxPAR 寄存器作為目標地址。
3、流控制器
流控制器主要涉及到一個控制 DMA 傳輸停止問題。 DMA 傳輸在 DMA_SxCR 寄存器的 EN 位被置 1 后就進入准備傳輸狀態,如果有外設請求 DMA 傳輸就可以進行數據傳輸。很多情況下,我們明確知道傳輸數據的數目,比如要傳 1000 個或者 2000 個數據,這樣我們就可以在傳輸之前設置DMA_SxNDTR 寄存器為要傳輸數目值, DMA 控制器在傳輸完這么多數目數據后就可以控制 DMA 停止傳輸。
DMA 數據流 x 數據項數 DMA_SxNDTR(x 為 0~7)寄存器用來記錄當前仍需要傳輸數目,它是一個 16 位數據有效寄存器,即最大值為 65535,這個值在程序設計是非常有用也是需要注意的地方。我們在編程時一般都會明確指定一個傳輸數量,在完成一次數目傳輸后DMA_SxNDTR 計數值就會自減,當達到零時就說明傳輸完成。如果某些情況下在傳輸之前我們無法確定數據的數目,那 DMA 就無法自動控制傳輸停止了,此時需要外設通過硬件通信向 DMA 控制器發送停止傳輸信號。這里有一個大前提就是外設必須是可以發出這個停止傳輸信號,只有 SDIO 才有這個功能,其他外設不具備此功能。
4、循環模式
循環模式相對應於一次模式。一次模式就是傳輸一次就停止傳輸,下一次傳輸需要手動控制,而循環模式在傳輸一次后會自動按照相同配置重新傳輸,周而復始直至被控制停止或傳輸發生錯誤。
通過 DMA_SxCR 寄存器的 CIRC 位可以使能循環模式。
5、傳輸類型
DMA 傳輸類型有單次(Single)傳輸和突發(Burst)傳輸。突發傳輸就是用非常短時間結合非常高數據信號率傳輸數據,相對正常傳輸速度,突發傳輸就是在傳輸階段把速度瞬間提高,實現高速傳輸,在數據傳輸完成后恢復正常速度,有點類似達到數據塊“秒傳”效果。為達到這個效果突發傳輸過程要占用 AHB 總線,保證要求每個數據項在傳輸過程不被分割,這樣一次性把數據全部傳輸完才釋放 AHB 總線;而單次傳輸時必須通過 AHB 的總線仲裁多次控制才傳輸完成。
單次和突發傳輸數據使用具體情況參考表 21-4。其中 PBURST[1:0]和 MBURST[1:0]位是位於 DMA_SxCR 寄存器中的,用於分別設置外設和存儲器不同節拍數的突發傳輸,對應為單次傳輸、 4 個節拍增量傳輸、 8 個節拍增量傳輸和 16 個節拍增量傳輸。 PINC 位和MINC 位是寄存器 DMA_SxCR 寄存器的第 9 和第 10 位,如果位被置 1 則在每次數據傳輸后數據地址指針自動遞增,其增量由 PSIZE 和 MSIZE 值決定,比如,設置 PSIZE 為半字大小,那么下一次傳輸地址將是前一次地址遞增 2。
突發傳輸與 FIFO 密切相關,突發傳輸需要結合 FIFO 使用,具體要求 FIFO 閾值一定要是內存突發傳輸數據量的整數倍。 FIFO 閾值選擇和存儲器突發大小必須配合使用,具體參考表 21-5。
6、直接模式
默認情況下, DMA 工作在直接模式,不使能 FIFO 閾值級別。
直接模式在每個外設請求都立即啟動對存儲器傳輸的單次傳輸。直接模式要求源地址和目標地址的數據寬度必須一致,所以只有 PSIZE 控制,而 MSIZE 值被忽略。突發傳輸是基於 FIFO 的所以直接模式不被支持。另外直接模式不能用於存儲器到存儲器傳輸。
在直接模式下,如果 DMA 配置為存儲器到外設傳輸那 DMA 會將一個數據存放在FIFO 內,如果外設啟動 DMA 傳輸請求就可以馬上將數據傳輸過去。
7、雙緩沖模式
設置 DMA_SxCR 寄存器的 DBM 位為 1 可啟動雙緩沖傳輸模式,並自動激活循環模式。雙緩沖不應用與存儲器到存儲器的傳輸。雙緩沖模式下,兩個存儲器地址指針都有效,即DMA_SxM1AR 寄存器將被激活使用。開始傳輸使用 DMA_SxM0AR 寄存器的地址指針所對應的存儲區,當這個存儲區數據傳輸完 DMA 控制器會自動切換至 DMA_SxM1AR 寄存器的地址指針所對應的另一塊存儲區,如果這一塊也傳輸完成就再切換至 DMA_SxM0AR寄存器的地址指針所對應的存儲區,這樣循環調用。
當其中一個存儲區傳輸完成時都會把傳輸完成中斷標志 TCIF 位置 1,如果我們使能了DMA_SxCR 寄存器的傳輸完成中斷,則可以產生中斷信號,這個對我們編程非常有用。另外一個非常有用的信息是 DMA_SxCR 寄存器的 CT 位,當 DMA 控制器是在訪問使用DMA_SxM0AR 時 CT=0,此時 CPU 不能訪問 DMA_SxM0AR,但可以向 DMA_SxM1AR填充或者讀取數據;當 DMA 控制器是在訪問使用 DMA_SxM1AR 時 CT=1,此時 CPU 不能訪問 DMA_SxM1AR,但可以向 DMA_SxM0AR 填充或者讀取數據。另外在未使能DMA 數據流傳輸時,可以直接寫 CT 位,改變開始傳輸的目標存儲區。
雙緩沖模式應用在需要解碼程序的地方是非常有效的。比如 MP3 格式音頻解碼播放,MP3 是被壓縮的文件格式,我們需要特定的解碼庫程序來解碼文件才能得到可以播放的PCM 信號,解碼需要一定的實際,按照常規方法是讀取一段原始數據到緩沖區,然后對緩沖區內容進行解碼,解碼后才輸出到音頻播放電路,這種流程對 CPU 運算速度要求高,很容易出現播放不流暢現象。如果我們使用 DMA 雙緩沖模式傳輸數據就可以非常好的解決這個問題,達到解碼和輸出音頻數據到音頻電路同步進行的效果。
8、DMA中斷
每個 DMA 數據流可以在發送以下事件時產生中斷:
-
達到半傳輸: DMA 數據傳輸達到一半時 HTIF 標志位被置 1,如果使能 HTIE 中斷控制位將產生達到半傳輸中斷;
-
傳輸完成: DMA 數據傳輸完成時 TCIF 標志位被置 1,如果使能 TCIE 中斷控制位將產生傳輸完成中斷;
-
傳輸錯誤: DMA 訪問總線發生錯誤或者在雙緩沖模式下試圖訪問“受限”存儲器地址寄存器時 TEIF 標志位被置 1,如果使能 TEIE 中斷控制位將產生傳輸錯誤中斷;
-
FIFO 錯誤:發生 FIFO 下溢或者上溢時 FEIF 標志位被置 1,如果使能 FEIE 中斷控制位將產生 FIFO 錯誤中斷;
-
直接模式錯誤:在外設到存儲器的直接模式下,因為存儲器總線沒得到授權,使得先前數據沒有完成被傳輸到存儲器空間上, 此時 DMEIF 標志位被置 1,如果使能 DMEIE 中斷控制位將產生直接模式錯誤中斷。
DMA初始化結構體詳解
DMA_ InitTypeDef 初始化結構體
typedef struct {
uint32_t DMA_Channel; //通道選擇
uint32_t DMA_PeripheralBaseAddr; //外設地址
uint32_t DMA_Memory0BaseAddr; //存儲器 0 地址
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_FIFOMode; //FIFO 模式
uint32_t DMA_FIFOThreshold; //FIFO 閾值
uint32_t DMA_MemoryBurst; //存儲器突發傳輸
uint32_t DMA_PeripheralBurst; //外設突發傳輸
} DMA_InitTypeDef;
-
DMA_Channel: DMA 請求通道選擇,可選通道 0 至通道 7,每個外設對應固定的通道,具體設置值需要查表 21-1 和表 21-2;它設 定 DMA_SxCR 寄存器 的CHSEL[2:0]位的值。例如,我們使用模擬數字轉換器 ADC3 規則采集 4 個輸入通道的電壓數據,查表 21-2 可知使用通道 2。
-
DMA_PeripheralBaseAddr:外設地址,設定 DMA_SxPAR 寄存器的值;一般設置為外設的數據寄存器地址,如果是存儲器到存儲器模式則設置為其中一個存儲區地址。 ADC3 的數據寄存器ADC_DR 地址為((uint32_t)ADC3+0x4C)。
-
DMA_Memory0BaseAddr:存儲器 0 地址,設定 DMA_SxM0AR 寄存器值;一般設置為我們自定義存儲區的首地址。我們程序先自定義一個 16 位無符號整形數組ADC_ConvertedValue[4]用來存放每個通道的 ADC 值,所以把數組首地址(直接使用數組名即可)賦值給 DMA_Memory0BaseAddr。
-
DMA_DIR:傳輸方向選擇,可選外設到存儲器、存儲器到外設以及存儲器到存儲器。它設定 DMA_SxCR 寄存器的 DIR[1:0]位的值。 ADC 采集顯然使用外設到存儲器模式。
-
DMA_BufferSize:設定待傳輸數據數目,初始化設定 DMA_SxNDTR 寄存器的值。這里 ADC 是采集 4 個通道數據,所以待傳輸數目也就是 4。
-
DMA_PeripheralInc:如果配置為 DMA_PeripheralInc_Enable,使能外設地址自動遞增功能,它設定 DMA_SxCR 寄存器的 PINC 位的值;一般外設都是只有一個數據寄存器,所以一般不會使能該位。 ADC3 的數據寄存器地址是固定並且只有一個所以不使能外設地址遞增。
-
DMA_MemoryInc:如果配置為 DMA_MemoryInc_Enable,使能存儲器地址自動遞增功能,它設定 DMA_SxCR 寄存器的 MINC 位的值;我們自定義的存儲區一般都是存放多個數據的,所以使能存儲器地址自動遞增功能。我們之前已經定義了一個包含 4 個元素的數字用來存放數據,使能存儲區地址遞增功能,自動把每個通道數據存放到對應數組元素內。
-
DMA_PeripheralDataSize:外設數據寬度,可選字節(8 位)、半字(16 位)和字(32位),它設定 DMA_SxCR 寄存器的 PSIZE[1:0]位的值。 ADC 數據寄存器只有低 16位數據有效,使用半字數據寬度。
-
DMA_MemoryDataSize:存儲器數據寬度,可選字節(8 位)、半字(16 位)和字(32位),它設定 DMA_SxCR 寄存器的 MSIZE[1:0]位的值。保存 ADC 轉換數據也要使用半字數據寬度,這跟我們定義的數組是相對應的。
-
DMA_Mode : DMA 傳 輸 模 式 選 擇 , 可 選 一 次 傳 輸 或 者 循 環 傳 輸 , 它 設 定DMA_SxCR 寄存器的 CIRC 位的值。我們希望 ADC 采集是持續循環進行的,所以使用循環傳輸模式。
-
DMA_Priority:軟件設置數據流的優先級,有 4 個可選優先級分別為非常高、高、中和低,它設定 DMA_SxCR 寄存器的 PL[1:0]位的值。 DMA 優先級只有在多個DMA 數據流同時使用時才有意義,這里我們設置為非常高優先級就可以了。
-
DMA_FIFOMode: FIFO 模式使能,如果設置為 DMA_FIFOMode_Enable 表示使能 FIFO 模式功能;它設定 DMA_SxFCR 寄存器的 DMDIS 位。 ADC 采集傳輸使用直接傳輸模式即可,不需要使用 FIFO 模式。
-
DMA_FIFOThreshold: FIFO 閾值選擇,可選 4 種狀態分別為 FIFO 容量的 1/4、1/2、 3/4 和滿;它設定 DMA_SxFCR 寄存器的 FTH[1:0]位;DMA_FIFOMode 設置為DMA_FIFOMode_Disable,那 DMA_FIFOThreshold 值無效。 ADC 采集傳輸不使用 FIFO 模式,設置改值無效。
-
DMA_MemoryBurst:存儲器突發模式選擇,可選單次模式、 4 節拍的增量突發模式、 8 節拍的增量突發模式或 16 節拍的增量突發模式,它設定 DMA_SxCR 寄存器的 MBURST[1:0]位的值。ADC 采集傳輸是直接模式,要求使用單次模式。
-
DMA_PeripheralBurst:外設突發模式選擇,可選單次模式、 4 節拍的增量突發模式、 8 節拍的增量突發模式或 16 節拍的增量突發模式,它設定 DMA_SxCR 寄存器的 PBURST[1:0]位的值。 ADC 采集傳輸是直接模式,要求使用單次模式。
DMA存儲器到存儲器模式實驗
存儲器到存儲器模式可以實現數據在兩個內存的快速拷貝。我們先定義一個靜態的源數據,然后使用 DMA 傳輸把源數據拷貝到目標地址上,最后對比源數據和目標地址的數據,看看是否傳輸准確。
參考引用:
- 野火---《零死角玩轉STM32-F429挑戰者》
- 《STM32F4xx中文參考手冊》
- 《Cortex-M4內核編程手冊》