DMA簡介
DMA(Direct Memory Access)——直接存儲器存取,就像其名稱一樣,DMA的主要作用是搬數(shù)據(jù),DMA可以把數(shù)據(jù)從存儲器搬到外設(shè)铃在、從外設(shè)搬到存儲器、從存儲器搬到存儲器碍遍。DMA的特殊之處就是搬運(yùn)數(shù)據(jù)==不需要占用CPU==,DMA控制器包含了DMA1和DMA2阳液,其中DMA1由7個通道怕敬,DMA2有5個通道。
DMA框圖
了解外設(shè)先要理解其工作框圖:
功能框圖主要分為三部分:
1.DMA請求
外設(shè)如果想要通過DMA傳輸數(shù)據(jù)帘皿,必先給DMA控制器發(fā)送DMA請求东跪,DMA收到請求信號之后會傳回給外設(shè)一個應(yīng)答信號,當(dāng)外設(shè)應(yīng)答后且DMA控制器收到應(yīng)答信號后鹰溜,就會啟動DMA的傳輸虽填,直至傳輸完畢。但DMA有2個DMA控制器曹动,15條通道斋日,不同的通道對應(yīng)著不同的外設(shè)請求,所以必須對通道和外設(shè)請求進(jìn)行一一對口墓陈,各個通道對應(yīng)的外設(shè)如下:
2.通道
要注意的是DMA共有12個獨(dú)立可編程的通道恶守,DMA1有7個第献、DMA2有5個,每個通道對應(yīng)著不同的外設(shè)的請求兔港,但同一時間只能接收一個庸毫。
3.仲裁器
當(dāng)多個通道同時請求時,就要處理先后響應(yīng)的問題衫樊,就像中斷里的優(yōu)先級分組一樣飒赃。仲裁器管理DMA通道請求時主要依據(jù)倆點(diǎn):
第一判斷:在DMA_CCRx 寄存器中設(shè)置有 4 個等級:非常高、高科侈、中和低载佳,先根據(jù)此優(yōu)先級判斷響應(yīng)的先后,如果優(yōu)先級都一樣兑徘,進(jìn)行第二判斷刚盈。
第二判斷:比較通道的編號,編號越低優(yōu)先權(quán)越高挂脑。
DMA傳輸數(shù)據(jù)分析
使用DMA藕漱,核心技術(shù)就是配置數(shù)據(jù)的傳輸崭闲,主要分為三點(diǎn):
==1.傳輸?shù)姆较?br>
2.傳輸?shù)臄?shù)量
3.傳輸?shù)哪J?=
1.傳輸?shù)姆较?/h3>
DMA有三種數(shù)據(jù)傳輸方向:
一:存儲器——>外設(shè)
當(dāng)我們使用從存儲器到外設(shè)傳輸時刁俭,以串口向電腦端發(fā)送數(shù)據(jù)為例牍戚。 DMA 外設(shè)寄存器的地址對應(yīng)的就是串口數(shù)據(jù)寄存器的地址, DMA 存儲器的地址就是我們自定義的變量(相當(dāng)于一個緩沖區(qū)宪哩,用來存儲通過串口發(fā)送到電腦的數(shù)據(jù))的地址锁孟。方向我們設(shè)置外設(shè)為目
標(biāo)地址茁瘦。
二:外設(shè)——>存儲器
當(dāng)我們使用從外設(shè)到存儲器傳輸時甜熔,以 ADC 采集為例纺非。 DMA 外設(shè)寄存器的地址對應(yīng)的就是 ADC 數(shù)據(jù)寄存器的地址赘方, DMA 存儲器的地址就是我們自定義的變量(用來接收存儲 AD 采集的數(shù)據(jù))的地址窄陡。方向我們設(shè)置外設(shè)為源地址跳夭。
三:存儲器——>存儲器
當(dāng)我們使用從存儲器到存儲器傳輸時币叹,以內(nèi)部 FLASH 向內(nèi)部 SRAM 復(fù)制數(shù)據(jù)為例颈抚。DMA 外設(shè)寄存器的地址對應(yīng)的就是內(nèi)部 FLASH(我們這里把內(nèi)部 FALSH 當(dāng)作一個外設(shè)來看)的地址贩汉, DMA 存儲器的地址就是我們自定義的變量(相當(dāng)于一個緩沖區(qū)锚赤,用來存儲來自內(nèi)部 FLASH 的數(shù)據(jù))的地址线脚。方向我們設(shè)置外設(shè)(即內(nèi)部 FLASH)為源地址浑侥。跟上面兩個不一樣的是,這里需要把 DMA_CCR 位 14: MEM2MEM:存儲器到存儲器模式配
置為 1蠢莺,啟動 M2M 模式。
2.傳輸?shù)臄?shù)量
以串口向電腦發(fā)送數(shù)據(jù)為例考蕾,我們可以一次性給電腦發(fā)送很多數(shù)據(jù)肖卧,具體多少由==DMA_CNDTR== 配置掸鹅,這是一個 32 位的寄存器,一次最多只能傳輸 65535 個數(shù)據(jù)荷鼠。而且源和目標(biāo)的數(shù)據(jù)寬度必須一致榔幸,數(shù)據(jù)寬度可以設(shè)置為8/16/32位削咆。
除此之外拨齐,還要設(shè)置源和目標(biāo)倆邊==數(shù)據(jù)指針的增量模式== ,即傳輸完一個數(shù)據(jù)后數(shù)據(jù)指針的移動模式厦滤,是加一馁害?還是不變蹂匹?以串口向電腦發(fā)送數(shù)據(jù)為例限寞,要發(fā)送的數(shù)據(jù)很多履植,每發(fā)送完一個玫霎,那么存儲器的地址指針就應(yīng)該加 1庶近,而串口數(shù)據(jù)寄存器只有一個,那么外設(shè)的地址指針就固定不變反番。
3.傳輸?shù)哪J?/h3>
數(shù)據(jù)傳輸?shù)那闆r可以通過查詢標(biāo)志位或者通過中斷來鑒別篙贸,每個DMA通道在DMA傳輸過半爵川、傳輸完成雁芙、傳輸錯誤時都會有相應(yīng)的標(biāo)志位兔甘,如果使能相關(guān)的中斷后還會產(chǎn)生中斷洞焙。一次數(shù)據(jù)傳輸完成后還分倆種模式:是一次傳輸還是循環(huán)傳輸澡匪。
一次傳輸: 傳輸一次后就停止褒链,要想再傳的話甸鸟,必須關(guān)閉DMA使能后重新配置后方能繼續(xù)傳輸兵迅。
循環(huán)傳輸: 一次傳輸完成后又恢復(fù)第一次傳輸時的配置循環(huán)傳輸抢韭,不斷重復(fù)。
代碼部分
編程要點(diǎn):
1.配置USART通信功能
2.設(shè)置DMA工作參數(shù)
3.使能DMA
DMA初始化結(jié)構(gòu)體
固件庫編程中對一個外設(shè)的操作主要通過配置外設(shè)的初始化結(jié)構(gòu)體來完成恍箭,雖然最終操作的是寄存器刻恭,但相比較寄存器,還是采用庫函數(shù)比較方便扯夭。
typedef struct
{
uint32_t DMA_PeripheralBaseAddr; // 外設(shè)地址
uint32_t DMA_MemoryBaseAddr; // 存儲器地址
uint32_t DMA_DIR; // 傳輸方向
uint32_t DMA_BufferSize; // 傳輸數(shù)目
uint32_t DMA_PeripheralInc; // 外設(shè)地址增量模式
uint32_t DMA_MemoryInc; // 存儲器地址增量模式
uint32_t DMA_PeripheralDataSize; // 外設(shè)數(shù)據(jù)寬度
uint32_t DMA_MemoryDataSize; // 存儲器數(shù)據(jù)寬度
uint32_t DMA_Mode; // 模式選擇
uint32_t DMA_Priority; // 通道優(yōu)先級
uint32_t DMA_M2M; // 存儲器到存儲器模式
} DMA_InitTypeDef;
下面利用《零死角玩轉(zhuǎn) STM32F103》的話介紹一下結(jié)構(gòu)體成員:
==1.DMA_PeripheralBaseAddr==:外設(shè)地址鳍贾,設(shè)定 DMA_CPAR 寄存器的值;一般設(shè)置為外設(shè)的數(shù)據(jù)寄存器地址交洗,如果是存儲器到存儲器模式則設(shè)置為其中一個存儲器地址贾漏。
== 2.DMA_Memory0BaseAddr == : 存儲器地址,設(shè)定 DMA_CMAR 寄存器值藕筋;一般設(shè)置為我們自定義存儲區(qū)的首地址。零死角玩轉(zhuǎn) STM32F103—MINI
==3.DMA_DIR==:傳輸方向選擇,可選外設(shè)到存儲器隐圾、存儲器到外設(shè)。它設(shè)定
DMA_CCR 寄存器的 DIR[1:0]位的值。這里并沒有存儲器到存儲器的方向選擇,
當(dāng)使用存儲器到存儲器時,只需要把其中一個存儲器當(dāng)作外設(shè)使用即可。
==4. DMA_BufferSize==:設(shè)定待傳輸數(shù)據(jù)數(shù)目,初始化設(shè)定 DMA_CNDTR 寄存器的值。
==5. DMA_PeripheralInc==:如果配置為 DMA_PeripheralInc_Enable,使能外設(shè)地址自動遞增功能,它設(shè)定 DMA_CCR 寄存器的 PINC 位的值;一般外設(shè)都是只有一個數(shù)據(jù)寄存器,所以一般不會使能該位。
==6. DMA_MemoryInc==:如果配置為 DMA_MemoryInc_Enable,使能存儲器地址自動遞增功能,它設(shè)定 DMA_CCR 寄存器的 MINC 位的值;我們自定義的存儲區(qū)一般都是存放多個數(shù)據(jù)的,所以要使能存儲器地址自動遞增功能。
==7.DMA_PeripheralDataSize==:外設(shè)數(shù)據(jù)寬度哥捕,可選字節(jié)(8 位)凫佛、半字(16 位)和字(32位),它設(shè)定 DMA_CCR 寄存器的 PSIZE[1:0]位的值瓮栗。
==8. DMA_MemoryDataSize==:存儲器數(shù)據(jù)寬度,可選字節(jié)(8 位)以蕴、半字(16 位)和字(32位),它設(shè)定 DMA_CCR 寄存器的 MSIZE[1:0]位的值。當(dāng)外設(shè)和存儲器之間傳數(shù)據(jù)時搞疗,兩邊的數(shù)據(jù)寬度應(yīng)該設(shè)置為一致大小泄隔。
==9. DMA_Mode==: DMA 傳輸模式選擇暖呕,可選一次傳輸或者循環(huán)傳輸湾揽,它設(shè)定
DMA_CCR 寄存器的 CIRC 位的值。例程我們的 ADC 采集是持續(xù)循環(huán)進(jìn)行的葱淳,所
以使用循環(huán)傳輸模式钝腺。
==10.DMA_Priority==:軟件設(shè)置通道的優(yōu)先級毫目,有 4 個可選優(yōu)先級分別為非常高、高、中和低掂名,它設(shè)定 DMA_CCR 寄存器的 PL[1:0]位的值。 DMA 通道優(yōu)先級只有在多個 DMA 通道同時使用時才有意義雳窟,如果是單個通道尊浪,優(yōu)先級可以隨便設(shè)置。
==11. DMA_M2M == :存 儲器 到存 儲器 模式 封救,使 用存儲 器到 存儲 器時 用到拇涤, 設(shè)定DMA_CCR 的位 14 MEN2MEN 即可啟動存儲器到存儲器模式。
USART配置函數(shù)
此代碼結(jié)合串口通信章節(jié)內(nèi)容理解更佳誉结,此處不多于講解鹅士。USART配置詳解
void DEBUG_UART_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
/* 第一步:初始化GPIO */
// 打開串口GPIO的時鐘
DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
// 將USART Tx的GPIO配置為推挽復(fù)用模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_TX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DEBUG_USART_TX_GPIO_PORT, &GPIO_InitStructure);
// 將USART Rx的GPIO配置為浮空輸入模式
GPIO_InitStructure.GPIO_Pin = DEBUG_USART_RX_GPIO_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DEBUG_USART_RX_GPIO_PORT, &GPIO_InitStructure);
/* 第二步:配置串口的初始化結(jié)構(gòu)體 */
// 打開串口外設(shè)的時鐘
DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK, ENABLE);
// 配置串口的工作參數(shù)
// 配置波特率
USART_InitStructure.USART_BaudRate = DEBUG_USART_BAUDRATE;
// 配置 針數(shù)據(jù)字長
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
// 配置停止位
USART_InitStructure.USART_StopBits = USART_StopBits_1;
// 配置校驗位
USART_InitStructure.USART_Parity = USART_Parity_No ;
// 配置硬件流控制
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
// 配置工作模式,收發(fā)一起
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
// 完成串口的初始化配置
USART_Init(DEBUG_USARTx, &USART_InitStructure);
/* 第三步:使能串口 */
// 使能串口
USART_Cmd(DEBUG_USARTx, ENABLE);
}
DMA配置函數(shù)
此函數(shù)主要是打開==DMA外設(shè)時鐘惩坑、配置DMA初始化結(jié)構(gòu)體掉盅、使能DMA通道==
// 串口對應(yīng)的DMA請求通道
#define USART_TX_DMA_CHANNEL DMA1_Channel4
// 外設(shè)寄存器地址也拜,USART1的數(shù)據(jù)寄存器地址(數(shù)據(jù)寄存器包含USART將發(fā)送或接收的數(shù)據(jù))
#define USART_DR_ADDRESS (USART1_BASE+0x04)
// 一次發(fā)送的數(shù)據(jù)量
#define SENDBUFF_SIZE 5000
uint8_t SendBuff[SENDBUFF_SIZE];
/* 定義的數(shù)組就存儲在存儲器SRAM中 */
void USARTx_DMA_Config(void)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
// 設(shè)置DMA源地址:串口數(shù)據(jù)寄存器地址*/
DMA_InitStructure.DMA_PeripheralBaseAddr = USART_DR_ADDRESS;
// 內(nèi)存地址(要傳輸?shù)淖兞康闹羔?
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)SendBuff;
// 方向:從內(nèi)存到外設(shè)
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
// 傳輸大小
DMA_InitStructure.DMA_BufferSize = SENDBUFF_SIZE;
// 外設(shè)地址不增
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
// 內(nèi)存地址自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
// 外設(shè)數(shù)據(jù)單位
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
// 內(nèi)存數(shù)據(jù)單位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
// DMA模式,一次或者循環(huán)模式
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal ;
//DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;
// 優(yōu)先級:中
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium;
// 禁止內(nèi)存到內(nèi)存的傳輸
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
// 配置DMA通道
DMA_Init(USART_TX_DMA_CHANNEL, &DMA_InitStructure);
// 使能DMA
DMA_Cmd (USART_TX_DMA_CHANNEL,ENABLE);
}
主函數(shù)
int main(void)
{
uint32_t i;
DEBUG_UART_Config();
USARTx_DMA_Config();
/*填充將要發(fā)送的數(shù)據(jù)*/
for(i=0;i<SENDBUFF_SIZE;i++)
{
SendBuff[i] = 'P';
}
/* USART1向DMA發(fā)出請求 */
USART_DMACmd(DEBUG_USARTx, USART_DMAReq_Tx, ENABLE);
}