DMA,全稱為:Direct Memory Access,即直接存儲器訪問,DMA 傳輸將數(shù)據(jù)從一個地址空間復制到另外一個地址空間。當 CPU 初始化這個傳輸動作,傳輸動作本身是由DMA 控制器 來實行和完成。典型的例子就是移動一個外部內存的區(qū)塊到芯片內部更快的內存區(qū)。像是這樣的操作并沒有讓處理器工作拖延,反而可以被重新排程去處理其他的工
作。DMA 傳輸對于高效能嵌入式系統(tǒng)算法和網絡是很重要的。DMA 傳輸方式無需 CPU 直接控制傳輸,也沒有中斷處理方式那樣保留現(xiàn)場和恢復現(xiàn)場的過程,通過硬件為 RAM 與 I/O 設備開辟一條直接傳送數(shù)據(jù)的通路,能使 CPU 的效率大為提高。DMA 是個非常好的功能,它不但能減輕 CPU 負擔,還能提高數(shù)據(jù)傳輸速度
STM32 最多有 2 個 DMA 控制器(DMA2 僅存在大容量產品中),DMA1 有 7 個通道。DMA2 有 5個通道。每個通道專門用來管理來自于一個或多個外設對存儲器訪問的請求。還有一個仲裁起來協(xié)調各個 DMA 請求的優(yōu)先權。
STM32 的 DMA 有以下一些特性:
●每個通道都直接連接專用的硬件 DMA 請求,每個通道都同樣支持軟件觸發(fā)。這些功能通過軟件來配置。
●在七個請求間的優(yōu)先權可以通過軟件編程設置(共有四級:很高、高、中等和低),假如在相等優(yōu)先權時由硬件決定(請求 0 優(yōu)先于請求 1,依此類推) 。
●獨立的源和目標數(shù)據(jù)區(qū)的傳輸寬度(字節(jié)、半字、全字),模擬打包和拆包的過程。源和目標地址必須按數(shù)據(jù)傳輸寬度對齊。
●支持循環(huán)的緩沖器管理
●每個通道都有 3 個事件標志(DMA 半傳輸,DMA 傳輸完成和 DMA 傳輸出錯),這 3 個事件標志邏輯或成為一個單獨的中斷請求。
●存儲器和存儲器間的傳輸
●外設和存儲器,存儲器和外設的傳輸
●閃存、SRAM、外設的 SRAM、APB1 APB2 和 AHB 外設均可作為訪問的源和目標。
●可編程的數(shù)據(jù)傳輸數(shù)目:最大為 65536
庫函數(shù)下 DMA1 通道 4 的配置步驟
1)使能 DMA 時鐘
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能 DMA 時鐘
2)初始化 DMA 通道 4 參數(shù)
DMA 通道配置參數(shù)種類比較繁多,包括內存地址,外設地址,傳輸數(shù)據(jù)長度,數(shù)據(jù)寬度,通道優(yōu)先級等等。這些參數(shù)的配置在庫函數(shù)中都是在函數(shù) DMA_Init 中完成,下面我們看看函數(shù)定義:
void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct)
函數(shù)的第一個參數(shù)是指定初始化的 DMA 通道號,這個很容易理解,下面我們主要看看第二個參數(shù)。跟其他外設一樣,同樣是通過初始化結構體成員變量值來達到初始化的目的,下面我們來看看 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;
第一個參數(shù) DMA_PeripheralBaseAddr 用來設置 DMA 傳輸?shù)耐庠O基地址,比如要進行串口DMA 傳輸,那么外設基地址為串口接受發(fā)送數(shù)據(jù)存儲器 USART1->DR 的地址,表示方法為&USART1->DR。
第二個參數(shù) DMA_MemoryBaseAddr 為內存基地址,也就是我們存放 DMA 傳輸數(shù)據(jù)的內存地址。
第三個參數(shù) DMA_DIR 設置數(shù)據(jù)傳輸方向,決定是從外設讀取數(shù)據(jù)到內存還送從內存讀取數(shù)據(jù)發(fā)送到外設,也就是外設是源地還是目的地,這里我們設置為從內存讀取數(shù)據(jù)發(fā)送到串口,所以外設自然就是目的地了,所以選擇值為 DMA_DIR_PeripheralDST。
第四個參數(shù) DMA_BufferSize 設置一次傳輸數(shù)據(jù)量的大小
第五個參數(shù) DMA_PeripheralInc 設置傳輸數(shù)據(jù)的時候外設地址是不變還是遞增。如果設置為遞增,那么下一次傳輸?shù)臅r候地址加 1,這里因為我們是一直往固定外設地址&USART1->DR發(fā)送數(shù)據(jù),所以地址不遞增,值為 DMA_PeripheralInc_Disable;
第六個參 數(shù) DMA_MemoryInc 設置傳輸數(shù)據(jù)時候內存地址是否遞增。 這個參數(shù) 和DMA_PeripheralInc 意思接近,只不過針對的是內存。這里我們的場景是將內存中連續(xù)存儲單元的數(shù)據(jù)發(fā)送到串口,毫無疑問內存地址是需要遞增的,所以值為 DMA_MemoryInc_Enable。
第七個參數(shù) DMA_PeripheralDataSize 用來設置外設的數(shù)據(jù)長度是為字節(jié)傳輸(8bits) ,半字傳輸 (16bits) 還 是 字 傳 輸 (32bits) , 這 里 我 們 是 8 位 字 節(jié) 傳 輸 , 所 以 值 設 置 為DMA_PeripheralDataSize_Byte。
第八個參數(shù) DMA_MemoryDataSize 是用來設置內存的數(shù)據(jù)長度,和第七個參數(shù)意思接近,這里我們同樣設置為字節(jié)傳輸 DMA_MemoryDataSize_Byte。
第九個參數(shù) DMA_Mode 用來設置 DMA 模式是否循環(huán)采集,也就是說,比如我們要從內存中采集 64 個字節(jié)發(fā)送到串口,如果設置為重復采集,那么它會在 64 個字節(jié)采集完成之后繼續(xù)從內存的第一個地址采集,如此循環(huán)。這里我們設置為一次連續(xù)采集完成之后不循環(huán)。所以設置值為 DMA_Mode_Normal。在我們下面的實驗中,如果設置此參數(shù)為循環(huán)采集,那么你會看到串口不停的打印數(shù)據(jù),不會中斷,大家在實驗中可以修改這個參數(shù)測試一下。
第十個參數(shù)是設置 DMA 通道的優(yōu)先級,有低,中,高,超高三種模式,這里我們設置優(yōu)先級別為中級,所以值為 DMA_Priority_Medium。如果要開啟多個通道,那么這個值就非常有意義。
第 十 一 個 參 數(shù) 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; //從內存讀取發(fā)送到外設
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 擁有中優(yōu)先級
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非內存到內存?zhèn)鬏?/span>
DMA_Init(DMA_CHx, &DMA_InitStructure); //根據(jù)指定的參數(shù)初始化
3)使能串口 DMA 發(fā)送
進行 DMA 配置之后,我們就要開啟串口的 DMA 發(fā)送功能,使用的函數(shù)是:
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);
如果是要使能串口 DMA 接受,那么第二個參數(shù)修改為 USART_DMAReq_Rx 即可。
4)使能 DMA1 通道 4,啟動傳輸。
使能串口 DMA 發(fā)送之后,我們接著就要使能 DMA 傳輸通道:
DMA_Cmd(DMA_CHx, ENABLE);
通過以上 3 步設置,我們就可以啟動一次 USART1 的 DMA 傳輸了。
5)查詢 DMA 傳輸狀態(tài)
在 DMA 傳輸過程中,我們要查詢 DMA 傳輸通道的狀態(tài),使用的函數(shù)是:
FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG)
比如我們要查詢 DMA 通道 4 傳輸是否完成,方法是:
DMA_GetFlagStatus(DMA2_FLAG_TC4);
這里還有一個比較重要的函數(shù)就是獲取當前剩余數(shù)據(jù)量大小的函數(shù):
uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx)
比如我們要獲取 DMA 通道 4 還有多少個數(shù)據(jù)沒有傳輸,方法是:
DMA_GetCurrDataCounter(DMA1_Channel4);
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN;//保存DMA每次數(shù)據(jù)傳送的長度 //DMA1的各通道配置 //這里的傳輸形式是固定的,這點要根據(jù)不同的情況來修改 //從存儲器->外設模式/8位數(shù)據(jù)寬度/存儲器增量模式 //DMA_CHx:DMA通道CHx //cpar:外設地址 //cmar:存儲器地址 //cndtr:數(shù)據(jù)傳輸量
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr) { RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA傳輸 DMA_DeInit(DMA_CHx); //將DMA的通道1寄存器重設為缺省值 DMA1_MEM_LEN=cndtr; DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外設ADC基地址 DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA內存基地址 DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //數(shù)據(jù)傳輸方向,從內存讀取發(fā)送到外設 DMA_InitStructure.DMA_BufferSize = cndtr; //DMA通道的DMA緩存的大小 DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外設地址寄存器不變 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //內存地址寄存器遞增 DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //數(shù)據(jù)寬度為8位 DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //數(shù)據(jù)寬度為8位 DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常緩存模式 DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x擁有中優(yōu)先級 DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x沒有設置為內存到內存?zhèn)鬏?br> DMA_Init(DMA_CHx, &DMA_InitStructure); //根據(jù)DMA_InitStruct中指定的參數(shù)初始化DMA的通道USART1_Tx_DMA_Channel所標識的寄存器 }
//開啟一次DMA傳輸 void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx) { DMA_Cmd(DMA_CHx, DISABLE ); //關閉USART1 TX DMA1 所指示的通道 DMA_SetCurrDataCounter(DMA1_Channel4,DMA1_MEM_LEN);//DMA通道的DMA緩存的大小 DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道 }
u8 SendBuff[5200]; MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,5168);//DMA1通道4,外設為串口1,存儲器為SendBuff,長度5168.
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA發(fā)送 MYDMA_Enable(DMA1_Channel4);//開始一次DMA傳輸! if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判斷通道4傳輸完成 { DMA_ClearFlag(DMA1_FLAG_TC4);//清除通道4傳輸完成標志 break; } pro=DMA_GetCurrDataCounter(DMA1_Channel4);
|