DMA叫做直接存儲器訪問,用于在外設與存儲器之間與存儲器之間提供高速數據傳輸。可以在無需任何CPU操作的情況下通過DMA快速移動數據。這樣節省的CPU資源可供其他操作使用。從硬件層次上來說,DMA 控制器是獨立于 Cortex-M4內核的,有點類似 GPIO、USART 外設一般,只是 DMA的功能是可以快速移動內存數據。
要實現把外設數據寫入內存,或把內存數據送至外設,一般由CPU控制完成,可采用標記查詢或中斷的方式。利用中斷傳送數據,可大大提高CPU的利用率。但當批量傳輸數據時,由于進出中斷需保護現場和斷點,無形之中增加了CPU的中斷負載。另外,CPU的核心工作是算法處理,借用CPU進行大量數據傳輸,會降低CPU處理算法的效率。引入DMA技術,在一定程度上解放了CPU,提高了CPU的處理能力。
STM32F4xx的兩個 DMA 控制器總共有 16 個數據流(每個控制器 8 個),每一個 DMA控制器都用于管理一個或多個外設的存儲器訪問請求。每個數據流總共可以有多達 8 個通道(或稱請求)。每個通道都有一個仲裁器,用于處理 DMA 請求間的優先級。
DMA控制器結構框圖
DMA 控制器執行直接存儲器傳輸:因為采用 AHB 主總線,它可以控制 AHB 總線矩陣來啟動 AHB 事務。
它可以執行下列事務:
● 外設到存儲器的傳輸
● 存儲器到外設的傳輸
● 存儲器到存儲器的傳輸(僅DMA2數據流能實現存儲器到存儲器的傳輸)
通道選擇
每個數據流可連接8個通道,某一時刻具體使用數據流的哪個通道,需通過DMA_SxCR寄存器中的CHSEL[2:0]位控制。通道選擇如下圖所示:
兩個DMA控制器的通道(請求)與數據流的映射關系如下所示:
a.DMA1通道選擇(請求映射)
b.DMA2通道選擇(請求映射)
由表可知,STM32F4XX的每個DMA控制器有8個數據流,每個數據流有多達8個通道。
仲裁器
當有多個數據流同時請求數據傳輸時,由仲裁器裁決數據流的傳輸順序。
仲裁器為兩個 AHB 主端口(存儲器和外設端口)提供基于請求優先級的 8 個 DMA 數據流請求管理,并啟動外設/存儲器訪問序列。
優先級管理分為兩個階段:
● ** 軟件** :每個數據流優先級都可以在 DMA_SxCR 寄存器中配置。分為四個級別:
— 非常高優先級
— 高優先級
— 中優先級
— 低優先級
● 硬件 :如果兩個請求具有相同的軟件優先級,則編號低的數據流優先于編號高的數據流。例如,數據流 2 的優先級高于數據流 4。
配置好優先級,還需要指定數據傳輸的方向、指針遞增方式和循環模式。
DMA的核心設置集中在配置寄存器、指定內存地址、外設地址及數據項目。下面描述DMA的相關寄存器。
DMA 低中斷狀態寄存器 (DMA_LISR)
DMA 高中斷狀態寄存器 (DMA_HISR)
DMA控制器使用狀態寄存器LISR和HISR,來描述8個數據流的狀態標記。LISR描述低4個數據流,而HISR描述高4個數據流。每個流對應5個狀態,下面以數據流x來進行說明。
FCIFx :數據流x傳輸完成中斷標記;
HTIFx :數據流x半傳輸完成中斷標記;
TEIFx :數據流x傳輸錯誤中斷標記;
DMEIFx:數據流x直接傳輸錯誤中斷標記;
FEIFx :數據流xFIFO錯誤中斷標記。
DMA 低中斷標志清零寄存器 (DMA_LIFCR)
DMA 高中斷標志清零寄存器 (DMA_HIFCR)
由DMA_LIFR/DMA_HIFR寄存器可知,DMA的標記是只讀的,不能直接擦除。DMA控制器含有中斷標記清零寄存器,用戶通過向中斷標記清零寄存器對應位寫1,可清除對應的中斷標記。
DMA 數據流 x 配置寄存器 (DMA_SxCR) (x = 0..7)
位 27:25 CHSEL[2:0]:通道選擇 (Channel selection)
這些位將由軟件置 1 和清零。
000:選擇通道 0
001:選擇通道 1
010:選擇通道 2
011:選擇通道 3
100:選擇通道 4
101:選擇通道 5
110:選擇通道 6
111:選擇通道 7
這些位受到保護,只有 EN 為“0”時才可以寫入
位 24:23 MBURST :存儲器突發傳輸配置(Memory burst transfer configuration)
這些位將由軟件置 1 和清零。
00:單次傳輸
01:INCR4(4 個節拍的增量突發傳輸)
10:INCR8(8 個節拍的增量突發傳輸)
11:INCR16(16 個節拍的增量突發傳輸)
這些位受到保護,只有 EN 為“0”時才可以寫入
在直接模式中,當位 EN =“1”時,這些位由硬件強制置為 0x0。
位 22:21 PBURST[1:0]:外設突發傳輸配置 (Peripheral burst transfer configuration)
這些位將由軟件置 1 和清零。
00:單次傳輸
01:INCR4(4 個節拍的增量突發傳輸)
10:INCR8(8 個節拍的增量突發傳輸)
11:INCR16(16 個節拍的增量突發傳輸)
這些位受到保護,只有 EN 為“0”時才可以寫入
在直接模式下,這些位由硬件強制置為 0x0。
位 19 CT:當前目標(僅在雙緩沖區模式下)(Current target (only in double buffer mode))
此位由硬件置 1 和清零,也可由軟件寫入。
0:當前目標存儲器為存儲器 0(使用 DMA_SxM0AR 指針尋址)
1:當前目標存儲器為存儲器 1(使用 DMA_SxM1AR 指針尋址)
只有 EN 為“0”時,此位才可以寫入,以指示第一次傳輸的目標存儲區。在使能數據流后,此位相當于一個狀態標志,用于指示作為當前目標的存儲區。
位 18 DBM:雙緩沖區模式 (Double buffer mode)
此位由軟件置 1 和清零。
0:傳輸結束時不切換緩沖區
1:DMA 傳輸結束時切換目標存儲區
此位受到保護,只有 EN 為“0”時才可以寫入。
位 17:16 PL[1:0]:優先級 (Priority level)
這些位將由軟件置 1 和清零。
00:低
01:中
10:高
11:非常高
這些位受到保護,只有 EN 為“0”時才可以寫入。
位 15 PINCOS:外設增量偏移量 (Peripheral increment offset size)
此位由軟件置 1 和清零
0:用于計算外設地址的偏移量與 PSIZE 相關
1:用于計算外設地址的偏移量固定為 4(32 位對齊)。
如果位 PINC =“0”,則此位沒有意義。
此位受到保護,只有 EN 為“0”時才可以寫入。
如果選擇直接模式或者 PBURST 不等于“00”,則當使能數據流(位 EN =“1”)時,此位由硬件強制置為低電平。
位 14:13 MSIZE[1:0]:存儲器數據大小 (Memory data size)
這些位將由軟件置 1 和清零。
00:字節(8 位)
01:半字(16 位)
10:字(32 位)
11:保留
這些位受到保護,只有 EN 為“0”時才可以寫入。
在直接模式下,當位 EN =“1”時,MSIZE 位由硬件強制置為與 PSIZE 相同的值。
位 12:11 PSIZE[1:0]:外設數據大小 (Peripheral data size)
這些位將由軟件置 1 和清零。
00:字節(8 位)
01:半字(16 位)
10:字(32 位)
11:保留
這些位受到保護,只有 EN 為“0”時才可以寫入
位 10 MINC:存儲器遞增模式 (Memory increment mode)
此位由軟件置 1 和清零。
0:存儲器地址指針固定
1:每次數據傳輸后,存儲器地址指針遞增(增量為 MSIZE 值)
此位受到保護,只有 EN 為“0”時才可以寫入。
位 9 PINC:外設遞增模式 (Peripheral increment mode)
此位由軟件置 1 和清零。
0:外設地址指針固定
1:每次數據傳輸后,外設地址指針遞增(增量為 PSIZE 值)
此位受到保護,只有 EN 為“0”時才可以寫入。
位 8 CIRC:循環模式 (Circular mode)
此位由軟件置 1 和清零,并可由硬件清零。
0:禁止循環模式
1:使能循環模式
如果外設為流控制器(位 PFCTRL=1)且使能數據流(位 EN=1),此位由硬件自動強制清零。
如果 DBM 位置 1,當使能數據流(位 EN =“1”)時,此位由硬件自動強制置 1。
位 7:6 DIR[1:0]:數據傳輸方向 (Data transfer direction)
這些位將由軟件置 1 和清零。
00:外設到存儲器
01:存儲器到外設
10:存儲器到存儲器
11:保留
這些位受到保護,只有 EN 為“0”時才可以寫入。
位 5 PFCTRL:外設流控制器 (Peripheral flow controller)
此位由軟件置 1 和清零。
0:DMA 是流控制器
1:外設是流控制器
此位受到保護,只有 EN 為“0”時才可以寫入。
選擇存儲器到存儲器模式(位 DIR[1:0]=10)后,此位由硬件自動強制清零。
位 4 TCIE:傳輸完成中斷使能 (Transfer complete interrupt enable)
此位由軟件置 1 和清零。
0:禁止 TC 中斷
1:使能 TC 中斷
位 3 HTIE:半傳輸中斷使能 (Half transfer interrupt enable)
此位由軟件置 1 和清零。
0:禁止 HT 中斷
1:使能 HT 中斷
位 2 TEIE:傳輸錯誤中斷使能 (Transfer error interrupt enable)
此位由軟件置 1 和清零。
0:禁止 TE 中斷
1:使能 TE 中斷
位 1 DMEIE:直接模式錯誤中斷使能 (Direct mode error interrupt enable)
此位由軟件置 1 和清零。
0:禁止 DME 中斷
1:使能 DME 中斷
位 0 EN:數據流使能/讀作低電平時數據流就緒標志 (Stream enable / flag stream ready when read low)
此位由軟件置 1 和清零。
0:禁止數據流
1:使能數據流
以下情況下,此位可由硬件清零:
— DMA 傳輸結束時(準備好配置數據流)
— AHB 主總線出現傳輸錯誤時
— 存儲器 AHB 端口上的 FIFO 閾值與突發大小不兼容時
此位讀作 0 時,軟件可以對配置和 FIFO 位寄存器編程。EN 位讀作 1 時,禁止向這些寄存器執行寫操作。
另外,在EN位置1(啟動流前),與數據流相對應的事件標記都要清0。
DMA 數據流 x 數據項數寄存器 (DMA_SxNDTR) (x = 0..7)
位 15:0 NDT[15:0]:要傳輸的數據項數目 (Number of data items to transfer)
要傳輸的數據項數目(0 到 65535)。只有在禁止數據流時,才能向此寄存器執行寫操作。
使能數據流后,此寄存器為只讀,用于指示要傳輸的剩余數據項數。每次 DMA 傳輸后,此寄存器將遞減。
傳輸完成后,此寄存器保持為零(數據流處于正常模式時),或者在以下情況下自動以先前編程的值重載:
— 以循環模式配置數據流時。
— 通過將 EN 位置“1”來重新使能數據流時
如果該寄存器的值為零,則即使使能數據流,也無法完成任何事務。
DMA 數據流 x 外設地址寄存器 (DMA_SxPAR) (x = 0..7)
位 31:0 PAR[31:0]:外設地址 (Peripheral address)
讀/寫數據的外設數據寄存器的基址。
這些位受到寫保護,只有 DMA_SxCR 寄存器中的 EN 為“0”時才可以寫入。
DMA相當于CPU的助理,通過軟件對DMA控制器的以上寄存器進行合理配置,就可以啟動DMA來傳輸數據了。此時CPU就可以專注于運算或其它事務的處理了。
這里以串口DMA為例編寫程序
void DMA2_Uart1_TX_Init()
{
//1. 開外設時鐘(DMA2)
RCC- >AHB1ENR |= 1< 22;
//2.配置DMA控制器
//a. 先禁止DMA,然后再設置
//清狀態標記(寫1清除)
DMA2- >LIFCR = 0X0F7D0F7D;
DMA2- >HIFCR = 0X0F7D0F7D;
//禁止EN
DMA2_Stream7- >CR = 0;
while((DMA2_Stream7- >CR & (1< 0)) != 0); //等待DMA停止
//b. 配置DMA/采用單次模式
DMA2_Stream7- >CR |= 4< 25; //通道4
DMA2_Stream7- >CR |= 2< 16; //優先級:2
//c. PINCOS不偏移(不固定4字節對齊)、內存/外設大小為一個字節
//內存遞增、外設不遞增
DMA2_Stream7- >CR |= 1< 10;
//不采用循環模式、方向:內存到外設
DMA2_Stream7- >CR |= 1< 6;
}
/*
傳輸設置函數
paddr:外設地址
maddr:內存地址
cnt:數據項數目
*/
void DMA2_TransConfig(u32 paddr,u32 maddr,u16 cnt)
{
//清狀態標記(寫1清除)
DMA2- >LIFCR = 0X0F7D0F7D;
DMA2- >HIFCR = 0X0F7D0F7D;
DMA2_Stream7- >CR &=~(1< 0);
while((DMA2_Stream7- >CR & (1< 0)) != 0); //等待DMA停止
DMA2_Stream7- >PAR = paddr;
DMA2_Stream7- >M0AR = maddr;
DMA2_Stream7- >NDTR = cnt;
DMA2_Stream7- >CR |= 1< 0; //啟動傳輸
}
u8 DMA2_GetFlag() //獲取傳輸完成標記
{
if(DMA2- >HISR &(1< 27)) //判斷是否傳輸完成
return 1;
else
return 0;
}
u8 DMA2_ClearFlag()
{
DMA2- >HIFCR = 1< 27; //清傳輸完成標記
}
除了以上DMA控制器的配置外,還要開啟串口的DMA功能。
void Usart1_dma_config()
{
USART1- >CR3 |= 1< 7; //使能串口DMA發送功能
}
接著編寫主函數進行測試:
#include "stm32f4xx.h"
#include "usart.h"
#include "DMA.h"
#include "delay.h"
u8 str[]="hello,world!rn";
int main()
{
u32 i=0;
Usart1_Init(460800);
Usart1_dma_config(); //開啟串口發送DMA功能
DMA2_Uart1_TX_Init(); //DMA控制器初始化
DMA2_TransConfig((u32)&USART1- >DR,(u32)str,sizeof(str)); //開始傳輸
while(1)
{
while(DMA2_GetFlag() == 0); //判斷傳輸是否完成,完成時退出循環
DMA2_ClearFlag(); //清傳輸完成標記
DMA2_TransConfig((u32)&USART1- >DR,(u32)str,sizeof(str)); //再次開啟傳輸
Delay_ms(500);
}
}
由于DMA的傳輸速度較快,所以把串口的波特率調到最高460800,但是速度還是太快,串口接收的速度跟不上,如下圖所示:
因此在大循環的最后加上500ms的延時,就可以看到傳輸的數據被完整地打印出來了。可見DMA傳輸速度之快。
串口DMA發送數據測試成功。串口DMA發送數據是屬于內存到外設的,對于外設到內存和內存到內存的應用同樣只要根據以上寄存器的描述進行配置,就可以實現DMA的數據傳輸了。
評論
查看更多