
DMA,全稱Direct Memory Access,即直接存儲器訪問。
DMA傳輸將數(shù)據(jù)從一個地址空間復制到另一個地址空間,提供在外設和存儲器之間或者存儲器和存儲器之間的高速數(shù)據(jù)傳輸。
我們知道CPU有轉(zhuǎn)移數(shù)據(jù)、計算、控制程序轉(zhuǎn)移等很多功能,系統(tǒng)運作的核心就是CPU,CPU無時不刻的在處理著大量的事務,但有些事情卻沒有那么重要,比方說數(shù)據(jù)的復制和存儲數(shù)據(jù),如果我們把這部分的CPU資源拿出來,讓CPU去處理其他的復雜計算事務,是不是能夠更好的利用CPU的資源呢?
因此:轉(zhuǎn)移數(shù)據(jù)(尤其是轉(zhuǎn)移大量數(shù)據(jù))是可以不需要CPU參與。比如希望外設A的數(shù)據(jù)拷貝到外設B,只要給兩種外設提供一條數(shù)據(jù)通路,直接讓數(shù)據(jù)由A拷貝到B 不經(jīng)過CPU的處理。

DMA就是基于以上設想設計的,它的作用就是解決大量數(shù)據(jù)轉(zhuǎn)移過度消耗CPU資源的問題。有了DMA使CPU更專注于更加實用的操作–計算、控制等。
?
DMA用來提供在外設和存儲器之間或者存儲器和存儲器之間的高速數(shù)據(jù)傳輸。無須CPU的干預,通過DMA數(shù)據(jù)可以快速地移動。這就節(jié)省了CPU的資源來做其他操作。
?
DMA的作用就是實現(xiàn)數(shù)據(jù)的直接傳輸,而去掉了傳統(tǒng)數(shù)據(jù)傳輸需要CPU寄存器參與的環(huán)節(jié),主要涉及四種情況的數(shù)據(jù)傳輸,但本質(zhì)上是一樣的,都是從內(nèi)存的某一區(qū)域傳輸?shù)絻?nèi)存的另一區(qū)域(外設的數(shù)據(jù)寄存器本質(zhì)上就是內(nèi)存的一個存儲單元)。四種情況的數(shù)據(jù)傳輸如下:
外設到內(nèi)存
內(nèi)存到外設
內(nèi)存到內(nèi)存
外設到外設
?
我們知道,數(shù)據(jù)傳輸,首先需要的是1 數(shù)據(jù)的源地址 2 數(shù)據(jù)傳輸位置的目標地址 ,3 傳遞數(shù)據(jù)多少的數(shù)據(jù)傳輸量 ,4 進行多少次傳輸?shù)膫鬏斈J?DMA所需要的核心參數(shù),便是這四個
當用戶將參數(shù)設置好,主要涉及源地址、目標地址、傳輸數(shù)據(jù)量這三個,DMA控制器就會啟動數(shù)據(jù)傳輸,當剩余傳輸數(shù)據(jù)量為0時 達到傳輸終點,結(jié)束DMA傳輸 ,當然,DMA 還有循環(huán)傳輸模式 當?shù)竭_傳輸終點時會重新啟動DMA傳輸。
也就是說只要剩余傳輸數(shù)據(jù)量不是0,而且DMA是啟動狀態(tài),那么就會發(fā)生數(shù)據(jù)傳輸。

可編程的數(shù)據(jù)傳輸數(shù)目:最大為65535。
對于大容量的STM32芯片有2個DMA控制器 兩個DMA控制器,DMA1有7個通道,DMA2有5個通道。
每個通道都可以配置一些外設的地址。
①DMA1 controller




這些在下方系統(tǒng)框圖中也可以清晰地看到。
?

?
上方的框圖,我們可以看到STM32內(nèi)核,存儲器,外設及DMA的連接,這些硬件最終通過各種各樣的線連接到總線矩陣中,硬件結(jié)構(gòu)之間的數(shù)據(jù)轉(zhuǎn)移都經(jīng)過總線矩陣的協(xié)調(diào),使各個外設和諧的使用總線來傳輸數(shù)據(jù)。
下面看有與沒有DMA的情況下,ADC采集的數(shù)據(jù)是怎樣存放到SRAM中的?

1. DMA傳輸時外設對DMA控制器發(fā)出請求。
2. DMA控制器收到請求,觸發(fā)DMA工作。
3. DMA控制器從AHB外設獲取ADC采集的數(shù)據(jù),存儲到DMA通道中

在發(fā)生一個事件后,外設向DMA控制器發(fā)送一個請求信號。DMA控制器根據(jù)通道的優(yōu)先權(quán)處理請求。當DMA控制器開始訪問發(fā)出請求的外設時,DMA控制器立即發(fā)送給它一個應答信號。當從DMA控制器得到應答信號時,外設立即釋放它的請求。一旦外設釋放了這個請求,DMA控制器同時撤銷應答信號。DMA傳輸結(jié)束,如果有更多的請求時,外設可以啟動下一個周期。
從外設數(shù)據(jù)寄存器或者從當前外設/存儲器地址寄存器指示的存儲器地址取數(shù)據(jù),第一次傳輸時的開始地址是DMA_CPARx或DMA_CMARx寄存器指定的外設基地址或存儲器單元;
存數(shù)據(jù)到外設數(shù)據(jù)寄存器或者當前外設/存儲器地址寄存器指示的存儲器地址,第一次傳輸時的開始地址是DMA_CPARx或DMA_CMARx寄存器指定的外設基地址或存儲器單元;
執(zhí)行一次DMA_CNDTRx寄存器的遞減操作,該寄存器包含未完成的操作數(shù)目。
?
當一次DMA數(shù)據(jù)傳輸完后,停止DMA傳送 ,也就是只傳輸一次
當傳輸結(jié)束時,硬件自動會將傳輸數(shù)據(jù)量寄存器進行重裝,進行下一輪的數(shù)據(jù)傳輸。也就是多次傳輸模式
?
?

仲裁器的作用是確定各個DMA傳輸?shù)膬?yōu)先級
仲裁器根據(jù)通道請求的優(yōu)先級來啟動外設/存儲器的訪問。
軟件:每個通道的優(yōu)先權(quán)可以在DMA_CCRx寄存器中設置,有4個等級:
最高優(yōu)先級
高優(yōu)先級
中等優(yōu)先級
低優(yōu)先級
注意:在大容量產(chǎn)品和互聯(lián)型產(chǎn)品中,DMA1控制器擁有高于DMA2控制器的優(yōu)先級。
?
在設置了DMA的通道之后,還要選擇通道對應外設的數(shù)據(jù)流。
● 雙緩沖區(qū)類型事務:使用存儲器的兩個存儲器指針的雙緩沖區(qū)傳輸(當 DMA 正在進行自/至緩沖區(qū)的讀/寫操作時,應用程序可以進行至/自其它緩沖區(qū)的寫/讀操作)。要傳輸?shù)臄?shù)據(jù)量(多達 65535)可以編程,并與連接到外設 AHB 端口的外設(請求 DMA 傳輸)的源寬度相關。每個事務完成后,包含要傳輸?shù)臄?shù)據(jù)項總量的寄存器都會遞減。

?
每個通道都可以在有固定地址的外設寄存器和存儲器地址之間執(zhí)行DMA傳輸。DMA傳輸?shù)臄?shù)據(jù) 量是可編程的,大達到65535。包含要傳輸?shù)臄?shù)據(jù)項數(shù)量的寄存器,在每次傳輸后遞減。
外設和存儲器的傳輸數(shù)據(jù)量可以通過DMA_CCRx寄存器中的PSIZE和MSIZE位編程。
?
根據(jù) DMA_SxCR 寄存器中 PINC 和 MINC 位的狀態(tài),外設和存儲器指針在每次傳輸后可以自動向后遞增或保持常量。當設置為增量模式時,下一個要傳輸?shù)牡刂穼⑹乔耙粋€地址加上增量值
通過單個寄存器訪問外設源或目標數(shù)據(jù)時,禁止遞增模式十分有用。
如果使能了遞增模式,則根據(jù)在 DMA_SxCR 寄存器 PSIZE 或 MSIZE 位中編程的數(shù)據(jù)寬度,下一次傳輸?shù)牡刂穼⑹乔耙淮蝹鬏數(shù)牡刂愤f增 1個數(shù)據(jù)寬度、2個數(shù)據(jù)寬度或 4個數(shù)據(jù)寬度。
?
DMA通道的操作可以在沒有外設請求的情況下進行,這種操作就是存儲器到存儲器模式。
當設置了DMA_CCRx寄存器中的MEM2MEM位之后,在軟件設置了DMA_CCRx寄存器中的EN位啟動DMA通道時,DMA傳輸將馬上開始。當DMA_CNDTRx寄存器變?yōu)?時,DMA傳輸結(jié)束。存儲器到存儲器模式不能與循環(huán)模式同時使用。
這里要注意僅 DMA2 的外設接口可以訪問存儲器,所以僅 DMA2 控制器支持存儲器到存儲器的傳輸,DMA1 不支持。
存儲器到存儲器模式不能與循環(huán)模式同時使用。
?

使沒開啟,我們也可以通過查詢這些位來獲得當前 DMA 傳輸?shù)臓顟B(tài)。這里我們常用的是 TCIFx位,即數(shù)據(jù)流 x 的 DMA 傳輸完成與否標志。
可編程的數(shù)據(jù)傳輸寬度、對齊方式和數(shù)據(jù)大小端
當PSIZE和MSIZE不相同時,DMA模塊按照下圖進行數(shù)據(jù)對齊。
注意:在大容量產(chǎn)品中, DMA2 通道 4 和 DMA2 通道 5 的中斷被映射在同一個中斷向量上。在互聯(lián)型產(chǎn)品 中, DMA2 通道 4 和 DMA2 通道 5 的中斷分別有獨立的中斷向量。所有其他的 DMA 通道都有自己的 中斷向量
?
在STM32控制器中,芯片采用Cortex-MX架構(gòu),總線結(jié)構(gòu)有了很大的優(yōu)化,DMA占用另外的地址總線,并不會與CPU的系統(tǒng)總線發(fā)生沖突。也就是說,DMA的使用不會影響CPU的運行速度。
但是要注意:
DMA 控制器和Cortex-M3核共享系統(tǒng)數(shù)據(jù)總線執(zhí)行直接存儲器數(shù)據(jù)傳輸。當CPU和DMA同時訪問相同的目標(RAM或外設)時,DMA請求可能會停止 CPU訪問系統(tǒng)總線達若干個周期,總線仲裁器執(zhí)行循環(huán)調(diào)度,以保證CPU至少可以得到一半的系統(tǒng)總線(存儲器或外設)帶寬。
?
DMA 配置部分
?
DMA配置參數(shù)包括:通道地址、優(yōu)先級、數(shù)據(jù)傳輸方向、存儲器/外設數(shù)據(jù)寬度、存儲器/外設地址是否增量、循環(huán)模式、數(shù)據(jù)傳輸量。
?

我們?nèi)绻_啟了 DMA_ISR 中這些中斷,在達到條件后就會跳到中斷服務函數(shù)里面去,即使 沒開啟,我們也可以通過查詢這些位來獲得當前 DMA 傳輸?shù)臓顟B(tài)。這里我們常用的是 TCIFx, 即通道 DMA 傳輸完成與否的標志。
注意此寄存器為只讀寄存器,所以在這些位被置位之后,只能通過其他的操作來清除。
?

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

?
該寄存器控制著 DMA 的很多相關 信息,包括數(shù)據(jù)寬度、外設及存儲器的寬度、通道優(yōu)先級、增量模式、傳輸方向、中斷允許、 使能等都是通過該寄存器來設置的。所以 DMA_CCRx 是 DMA 傳輸?shù)暮诵目刂萍拇嫫鳌?/span>
?

這個寄存器控制 DMA 通道 x 的每次 傳輸所要傳輸?shù)臄?shù)據(jù)量。其設置范圍為 0~65535。并且該寄存器的值會隨著傳輸?shù)倪M行而減少, 當該寄存器的值為 0 的時候就代表此次數(shù)據(jù)傳輸已經(jīng)全部發(fā)送完成了。所以可以通過這個寄存 器的值來知道當前 DMA 傳輸?shù)倪M度。
?

該寄存器用來存儲 STM32 外設的地 址,比如我們使用串口 1,那么該寄存器必須寫入 0x40013804(其實就是&USART1_DR)。如果使 用其他外設,就修改成相應外設的地址就行了。
?

該寄存器和 DMA_CPARx 差不多, 但是是用來放存儲器的地址的。比如我們使用 SendBuf[5200]數(shù)組來做存儲器,那么我們在 DMA_CMARx 中寫入&SendBuff 就可以了。
?
在DMA_CPARx寄存器中設置外設寄存器的地址。發(fā)生外設數(shù)據(jù)傳輸請求時,這個地址將 是數(shù)據(jù)傳輸?shù)脑椿蚰繕恕?/span>
在DMA_CMARx寄存器中設置數(shù)據(jù)存儲器的地址。發(fā)生外設數(shù)據(jù)傳輸請求時,傳輸?shù)臄?shù) 據(jù)將從這個地址讀出或?qū)懭脒@個地址。
在DMA_CNDTRx寄存器中設置要傳輸?shù)臄?shù)據(jù)量。在每個數(shù)據(jù)傳輸后,這個數(shù)值遞減。
在DMA_CCRx寄存器的PL[1:0]位中設置通道的優(yōu)先級。
在DMA_CCRx寄存器中設置數(shù)據(jù)傳輸?shù)姆较颉⒀h(huán)模式、外設和存儲器的增量模式、外 設和存儲器的數(shù)據(jù)寬度、傳輸一半產(chǎn)生中斷或傳輸完成產(chǎn)生中斷。
設置DMA_CCRx寄存器的ENABLE位,啟動該通道。
一旦啟動了DMA通道,它既可響應連到該通道上的外設的DMA請求。當傳輸一半的數(shù)據(jù)后,半傳輸標志(HTIF)被置1,當設置了允許半傳輸中斷位(HTIE)時,將產(chǎn)生 一個中斷請求。在數(shù)據(jù)傳輸結(jié)束后,傳輸完成標志(TCIF)被置1,當設置了允許傳輸完成中斷位 (TCIE)時,將產(chǎn)生一個中斷請求。
?
1. DMA初始化函數(shù)
DMA_DeInit(DMAX_ChannelX);功能:將DMAyChannelx寄存器的初始化為其默認值
注釋:RCC_ResetCmd中對DMA無定義,因此采用的直接操縱DMA寄存器的方式
void?DMA_Init(DMA_Channel_TypeDef*?DMAy_Channelx,??DMA_InitTypeDef*?DMA_InitStruct)?功能:設置要開啟的通道,還有一些參數(shù),包括外設基地址,存儲器基地址,傳輸?shù)臄?shù)據(jù)量,增量模式,數(shù)據(jù)寬度等。
具體看下方結(jié)構(gòu)體代碼介紹:
typedef?struct?{??
???uint32_t?DMA_PeripheralBaseAddr;???
???/*設置DMA源地址*/
???uint32_t?DMA_MemoryBaseAddr;???????
???/*設置DMA目的地址*/
???uint32_t?DMA_DIR;?
???/*?設置數(shù)據(jù)傳輸方向,決定是從外設讀取數(shù)據(jù)到內(nèi)存還送從內(nèi)存讀取數(shù)?據(jù)發(fā)送到外設,也就是外設是源地還是目的地*/???????????????????????
???uint32_t?DMA_BufferSize;??????
???/*設置傳輸大小*/????
???uint32_t?DMA_PeripheralInc;???????
???/*設置ReceiveBuff地址是否自增*/??????
???uint32_t?DMA_MemoryInc;?
???/*設置傳輸數(shù)據(jù)時候內(nèi)存地址是否遞,需要開啟*/???????
???uint32_t?DMA_PeripheralDataSize;???
???/*外設的數(shù)據(jù)長度是為字節(jié)傳輸(8bits),半?字傳輸(16bits)?還是字傳輸(32bits)?*/????
???uint32_t?DMA_MemoryDataSize;????
???/*設置內(nèi)存的數(shù)據(jù)長度*/
???uint32_t?DMA_Mode;??????
???/*設置DMA的模式,正常模式/循環(huán)模式??是否循環(huán)發(fā)送*/????????
???uint32_t?DMA_Priority;?
???/*設置?DMA?通道的優(yōu)先級,有低,中,高,超高四種模式*/????????
???uint32_t?DMA_M2M;????
???/*設置是否是存儲器到存儲器模式傳輸,*/???????
}DMA_InitTypeDef;?2. DMA使能函數(shù)
void?DMA_Cmd(DMA_Channel_TypeDef*?DMAy_Channelx,?FunctionalState??NewState);功能:使能或者失能DMA外設
例如:DMA_Cmd(DMA1_Channel1 , ENABLE);
3. DMA中斷使能函數(shù)
void?DMA_ITConfig(DMA_Channel_TypeDef*?DMAy_Channelx,?uint32_t?DMA_IT,??FunctionalState?NewState);功能:配置指定的DMAy通道x的中斷
注釋:DMA_IT_TC:傳輸完成 DMA_IT_HT:傳輸一半 DMA_IT_TE:傳輸錯誤
例如:DMA_ITConfig(DMA1_Channel1 , DMA_IT_TC , ENABLE);
4. 設置CNDTRx和讀CNDTRx函數(shù) (通道傳輸數(shù)據(jù)量)
void?DMA_SetCurrDataCounter(DMA_Channel_TypeDef*?DMAy_Channelx,?uint16_t??DataNumber);
uint16_t?DMA_GetCurrDataCounter(DMA_Channel_TypeDef*?DMAy_Channelx);作用:前者設置DMA通道的傳輸數(shù)據(jù)量(DMA處于關閉狀態(tài));后者獲取當前DMA通道傳輸剩余數(shù)據(jù)量(DMA處于開啟狀態(tài))。
?
獲取當前剩余數(shù)據(jù)量大小函數(shù):DMA_GetCurrDataCounter(DMA1_Channel4);
?
以UART為例,如果要接收數(shù)據(jù),會觸發(fā)UART中斷,然后CPU介入,在中斷中通過CPU將UART輸入寄存器的值讀出來,存放到內(nèi)存中;
而DMA方式,產(chǎn)生UART中斷后,DMA直接參與,把UART輸入寄存器的值搬運到內(nèi)存中,CPU只需要在去檢查內(nèi)存的值就好了,這樣提高了CPU的效率。
?
① DMA初始化配置
void?dma_init()
{
??DMA_InitTypeDef?DMA_InitStructure;
??RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
??/*DMA配置*/
??DMA_InitStructure.DMA_PeripheralBaseAddr?=?USART1_DR_Base;//串口數(shù)據(jù)寄存器地址
??DMA_InitStructure.DMA_MemoryBaseAddr?=?(uint32_t)SendBuff;?//內(nèi)存地址(要傳輸?shù)淖兞康闹羔?
??DMA_InitStructure.DMA_DIR?=?DMA_DIR_PeripheralDST;?//方向(從內(nèi)存到外設)
??DMA_InitStructure.DMA_BufferSize?=?500;?//傳輸內(nèi)容的大小
??DMA_InitStructure.DMA_PeripheralInc?=?DMA_PeripheralInc_Disable;?//外設地址不增
??DMA_InitStructure.DMA_MemoryInc?=?DMA_MemoryInc_Enable;?//內(nèi)存地址自增
??DMA_InitStructure.DMA_PeripheralDataSize?=
??DMA_PeripheralDataSize_Byte?;?//外設數(shù)據(jù)單位
??DMA_InitStructure.DMA_MemoryDataSize?=
??DMA_MemoryDataSize_Byte?;?//內(nèi)存數(shù)據(jù)單位
??DMA_InitStructure.DMA_Mode?=?DMA_Mode_Normal?;?//DMA模式:一次傳輸,循環(huán)
??DMA_InitStructure.DMA_Priority?=?DMA_Priority_Medium?;?//優(yōu)先級:高
??DMA_InitStructure.DMA_M2M?=?DMA_M2M_Disable;?//禁止內(nèi)存到內(nèi)存的傳輸
?
??DMA_Init(DMA1_Channel4,?&DMA_InitStructure);?//配置DMA1的4通道
??DMA_Cmd(DMA1_Channel4,ENABLE);
??DMA_SetCurrDataCounter(DMA_CH4,DMA1_MEM_LEN);//DMA通道的DMA緩存的大小
??DMA_ITConfig(DMA1_Channel4,DMA_IT_TC,ENABLE);//配置DMA發(fā)送完成后產(chǎn)生中斷
}DMA中斷
void?DMA1_Channel4_IRQHandler(void)
{
????if(DMA_GetFlagStatus(DMA1_FLAG_TC4)==SET)
????{?
????????DMA_ClearFlag(DMA1_FLAG_TC4);
????}
}main函數(shù)
#define?SEND_BUF_SIZE?500?//發(fā)送數(shù)據(jù)長度,最好等于sizeof(TEXT_TO_SEND)+2的整數(shù)倍.
?
u8?SendBuff[SEND_BUF_SIZE];?//發(fā)送數(shù)據(jù)緩沖區(qū)
const?u8?TEXT_TO_SEND[]={"STM32F1?DMA?串口實驗"};
?uint16_t?i;
int?main(void)
{????
????uart_init(115200);???//串口初始化為115200
????????
????for(i=0;i<500;i++)
????{
???????SendBuff[i]?=?0xaf;
????}?
????USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);??//使能串口dma傳輸?
????
????while(1);
}原文:http://t.csdn.cn/RpJOa
本文系網(wǎng)絡轉(zhuǎn)載,版權(quán)歸原作者所有,如有侵權(quán),請聯(lián)系刪除。

往期推薦