什么是DMA
DMA,全稱直接存儲器訪問(Direct Memory Access),是一種允許硬件子系統直接讀寫系統內存的技術,無需中央處理單元(CPU)的介入。下面是DMA的工作原理概述:
數據傳輸觸發:DMA傳輸可以由CPU指令觸發,也可以由硬件事件(如一個外設準備好數據)自動觸發。
外設和內存之間的數據交換:一旦DMA傳輸開始,DMA控制器會將數據從源地址直接傳送到目的地址。源地址常常是外設的數據寄存器,目的地址通常是內存中的一個緩沖區;反之亦然。
CPU解放:在沒有DMA的情況下,CPU需要執行多個指令來完成數據的傳送,包括數據的讀取和寫入操作。當使用DMA時,CPU只需初始化傳輸,之后DMA控制器將自動處理數據傳送,CPU則可以執行其他任務。
傳輸完成中斷:當所有數據都被傳輸至目的地之后,DMA控制器會向CPU發送一個中斷信號。這個中斷告知CPU數據已經成功傳輸完畢,CPU隨后可以進行后續處理,比如停止DMA,處理數據,或重新初始化另一次DMA傳輸。
優先級和通道:DMA控制器可能有多個通道,每個通道可以獨立配置并與特定的外設關聯。在多通道DMA系統中,可能會有優先級設置,以決定哪個DMA請求被優先處理。
DMA是提高數據傳輸效率、減少CPU負荷、優化系統性能的有效手段,特別是在處理高速數據流或頻繁數據傳輸時。在嵌入式系統和計算機架構中,有了DMA,CPU就可以更有效地處理邏輯計算和數據處理任務,而不是花費大量時間在數據移動上。
DMA可以用于哪些數據傳輸方式?
DMA 可以用于多種數據傳輸方式,包括:
1、內存到內存:數據可以直接從一個內存地址復制到另一個內存地址,無需CPU的介入。
2、外設到內存:數據從外設(如ADC,UART接收緩沖區,SPI等)直接傳輸到內存。這通常用于從外設讀取數據時。
3、內存到外設:數據從內存直接傳輸到外設(如DAC,UART發送緩沖區,SPI等)。這通常在向外設寫入數據時使用。
4、外設到外設:雖然不是所有DMA控制器都支持這個功能,但有些高級系統允許直接從一個外設到另一個外設的數據傳輸,不經過內存。
這些傳輸方式中,最常見的使用場景是外設到內存和內存到外設。因為這可以大幅度減輕CPU的工作負擔,尤其是在數據流量較大時,如從網絡接口接收數據包,或向圖形處理器發送圖像數據。?
DMA初始化的步驟有哪些?
DMA初始化的步驟通常如下所示:
1、DMA控制器時鐘使能:
通過 RCC(Reset and Clock Control)模塊使能 DMA 控制器的時鐘。
2、DMA通道配置:
? ? ? ? a、為目標 DMA 通道配置傳輸方向(從內存到外設、從外設到內存或者內存到內存)。
? ? ? ? b、設置源地址和目的地址及其增量模式(地址在傳輸后是否遞增)。
? ? ? ? c、配置數據大小(傳輸的數據寬度,一般為8位、16位或32位)。
? ? ? ? d、設定傳輸數據的數量(數據塊大小)。
3、外設配置:
如果外設(如 ADC, UART, SPI)使用 DMA 進行數據傳輸,需要在外設的配置中使能相應的 DMA 傳輸請求。
4、中斷配置(可選):
根據實際需要,可以配置并使能傳輸完成、半傳輸完成及傳輸錯誤中斷。
5、DMA流控制(如果適用):
在某些情況下,特別是在使用雙緩沖或循環模式時,可能需要配置 DMA 流控制。
6、啟動DMA傳輸:
使用相應的庫函數或直接通過控制寄存器啟動 DMA 傳輸。
重要寄存器
STM32微控制器中的DMA(直接存儲器訪問)寄存器包括幾組關鍵的控制和狀態寄存器,用于管理和監控DMA的操作。具體的寄存器會根據STM32的不同系列及其內部架構的差異而有所不同,但一般而言,一個DMA通道會涉及以下幾個主要寄存器:
1、DMA控制寄存器 (DMA_CCRx):
控制傳輸的基本配置,包括傳輸方向、傳輸模式(正常或循環模式)、優先級、內存和外設大小、內存增量模式、外設增量模式等。
2、DMA數量寄存器 (DMA_CNDTRx):
包含被傳輸數據的數量。一次DMA操作開始前,此寄存器需要被加載。
3、DMA外設地址寄存器 (DMA_CPARx):
保存外部外設的基地址。
4、DMA內存地址寄存器 (DMA_CMARx):
保存內存的基地址。這是數據要被傳輸到或從中傳輸出的內存位置。
5、DMA狀態寄存器 (DMA_ISR):
表示每個通道的狀態,包括傳輸完成、半傳輸、傳輸錯誤和全局中斷標志。
6、DMA標志清除寄存器 (DMA_IFCR):
用于清除特定通道的中斷標志位。
DMA的狀態寄存器具體表示了哪些通道的狀態?
DMA的狀態寄存器提供了關于各通道傳輸狀態的具體信息,主要包括:
1、傳輸完成(TC):指示相應DMA通道是否已經完成所配置的數據傳輸任務。
2、半傳輸(HT):指示相應DMA通道是否已經完成一半的數據傳輸(對于某些應用可能需要在傳輸一半時進行處理)。
3、傳輸錯誤(TE):如果DMA通道在傳輸過程中遇到錯誤,該標志會被置位。
這些狀態可以用來觸發中斷服務程序(Interrupt Service Routines, ISRs), 允許軟件響應DMA傳輸的完成或錯誤。開發者通常會在對應的中斷處理函數中檢查這些狀態,以確定傳輸是否成功,或者是否需要采取措施來處理錯誤情況。
?
DMA的中斷服務程序是如何觸發的?
DMA的中斷服務程序(ISR)是通過設置DMA控制寄存器中的中斷使能位來觸發的。具體觸發步驟如下:
1、使能中斷:在初始化DMA時,你需要在DMA通道的控制寄存器(DMA_CCRx)中使能傳輸完成中斷、半完成中斷和/或傳輸錯誤中斷。
2、配置NVIC:在嵌套向量中斷控制器(NVIC)中,你需要為對應的DMA通道中斷配置優先級,并使能中斷。
3、中斷發生:
? ? ? ? a、當DMA傳輸完指定數量的數據后,如果啟用了傳輸完成中斷,DMA控制器將設置狀態寄存器(DMA_ISR)的傳輸完成標志(TC)。
? ? ? ?b、 如果啟用了半傳輸中斷,當傳輸完成一半的數據量時,DMA控制器將設置狀態寄存器的半傳輸完成標志(HT)。
? ? ? ? c、如果在傳輸過程中發生任何錯誤,如FIFO溢出或者總線錯誤等,DMA控制器將設置狀態寄存器的傳輸錯誤標志(TE)。
4、中斷處理:一旦相應的標志位被置位,如果NVIC中對應的中斷已使能,CPU將暫停當前任務,跳轉到對應的中斷服務程序執行中斷處理代碼。
5、中斷標志清除:在中斷服務程序中,需要手動清除DMA的狀態寄存器中的中斷標志位,以防止重復進入中斷服務程序。
通過以上步驟,STM32 中的DMA中斷服務程序可以在特定的事件發生時被觸發,以便進行如數據處理、錯誤處理等操作。
代碼演示
下面是一個簡單的STM32程序例子,用于演示如何使用DMA將數據從一個數組復制到另一個數組(內存到內存的DMA傳輸)
#include "stm32f4xx.h"
// 定義數組大小
#define ARRAYSIZE 5
// 定義源數組和目標數組
uint32_t srcArray[ARRAY_SIZE] = {1, 2, 3, 4, 5};
uint32_t destArray[ARRAY_SIZE] = {0, 0, 0, 0, 0};
void DMA_Config(void)
{// 手動使能DMA1時鐘RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE);// 創建DMA初始化結構體并填入配置DMA_InitTypeDef DMA_InitStructure;DMA_DeInit(DMA1_Stream0); // 將DMA Stream恢復到初始狀態DMA_InitStructure.DMA_Channel = DMA_Channel_0; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&srcArray; // 設置DMA源地址DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&destArray; // 設置DMA目標地址DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToMemory; // 設置內存到內存模式DMA_InitStructure.DMA_BufferSize = ARRAY_SIZE; // 傳輸的數據大小DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Enable; // 外設地址遞增DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 內存地址遞增DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Word; // 32位數據寬度DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Word; // 32位數據寬度DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; // 設置為正常模式DMA_InitStructure.DMA_Priority = DMA_Priority_Low; // 設置優先級為低DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; // FIFO模式禁用DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_HalfFull; // FIFO閾值設置DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; // 單次突發模式DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; // 單次突發模式// 初始化DMADMA_Init(DMA1_Stream0, &DMA_InitStructure);// 使能DMA流DMA_Cmd(DMA1_Stream0, ENABLE);// 如果都是定期的規律的檢查傳輸狀態,可以留空這里// 如果你需要傳輸完成的通知,可能需要使用中斷和相應的服務函數
}
int main(void)
{// 系統初始化SystemInit();// 配置DMADMA_Config();// 在這里,我們假定系統只是簡單地執行一次內存到內存的復制// 復制完后,可以添加代碼檢查destArray數組中的值// 這里以簡單的檢查第一個元素是否等于1為例if (destArray[0] == 1){// 如果傳輸成功,將會進入到這里}// 主循環,程序可以繼續執行或者進入睡眠while (1){}
}