一、什么是DMA
DMA全程Direct Memory Access,即直接存儲器訪問。簡單來講,它的功能是把數據從一個地址搬運到另一個地址。通常有三個傳輸方向,分別是內存到內存,內存到外設和外設到內存。
DMA示意圖
二、DMA有什么作用
直接存儲器存取(DMA)用來提供在外設和存儲器之間或者存儲器和存儲器之間的高速數據傳輸。無須CPU干預,數據可以通過DMA快速地移動,這就節省了CPU的資源來做其他操作。
比如在串口接收或者發送時可以直接利用DMA將接收內容直接搬運到接收數組。或者利用DMA將準備發送的數據搬運到發送的緩沖區。再或者利用DMA把數據搬運到特定的地址,或者從特定的地址利用DMA搬運數據出來。總而言之,在平時的開發過程中,DMA是非常常用的。
三、STM32的DMA
STM32F103ZET6有兩個DMA,12個通道(DMA1有7個通道,DMA2有5個通道),每個通道專門用來管理來自于一個或多個外設對存儲器訪問的請求。還有一個仲裁器來協調各個DMA請求的優先權。
STM32F103ZET6的DMA特性
3.1 DMA請求
DMA請求
如果一個外設想要通過DMA傳輸數據,必須先給DMA控制器發送DMA請求。DMA控制器收到請求后,會給外設一個應答信號。當外設收到應答信號后,也會給DMA控制器一個應答信號。當DMA控制器收到外設的應答信號后,啟動DMA傳輸。
前面介紹STM32F103ZET6有兩個DMA,12個通道,同的 DMA 控制器的通道對應著不同的外設請求。根據中文參考手冊,對應關系如下
DMA1對應外設
DMA1對應外設
DMA2對應外設
DMA2對應外設
3.2 DMA通道
DMA具有12個獨立可編程的通道,每個通道對應不同外設的DMA請求。雖然每個通道可以接收多個外設的DMA請求,但是同一時間只能接收一個。
DMA通道
3.3 仲裁器
當有多個DMA請求時,需要仲裁器來決定響應的先后順序。仲裁器決定相應順序的方法有兩種
? 軟件判定 軟件中可以通過設置DMA_CCRx寄存器來設置DMA通道的優先級。共有四個優先級可以設置,分別是非常高,高,中和低。
? 硬件判定 當遇到兩個或者多個相同優先級的DMA通道請求時,仲裁器根據DMA通道的編號來決定響應順序。DMA通道編號越低,優先級越高。另外,DMA1擁有比DMA2更高的優先級。
仲裁器四、DMA配置
4.1 DMA配置步驟
? 使能DMA時鐘
? 初始化DMA通道,包括配置通道,外設和內存地址,傳輸數據量等
? 使能外設DMA功能
? 開啟DMA通道傳輸
? 查詢DMA通道狀態
4.2 DMA結構體成員
? DMA_PeripheralBaseAddr :外設地址,外設地址,通過DMA_CPAR寄存器設置,一般設置為外設的數據寄存器地址,比如要進行串口DMA 傳輸,那么外設基地址為串口接收/發送數據存儲器USART1->DR 的地址,表示方法為&USART1->DR。如果是存儲器到存儲器模式則設置為其中一個存儲區地址。
? DMA_Memory0BaseAddr :存儲器地址,通過DMA_CMAR寄存器設置,一般設置為我們自定義存儲區的首地址,即我們存放DMA傳輸數據的內存地址。比如我們定義一個u32類型數組,直接寫數組首地址(直接使用數組名)即可,在DMA傳輸的時候就可以發送數組數據,或者把數組用來接收其他數據。
? DMA_DIR :數據傳輸方向選擇,可選擇外設到存儲器、存儲器到外設以及存儲器到存儲器。通過設定DMA_CCR寄存器的DIR[1:0]位的值決定。
? DMA_BufferSize :用來設置一次傳輸數據的大小,通過DMA_CNDTR寄存器設置。
? DMA_PeripheralInc :用來設置外設地址是遞增還是不變,通過DMA_CCR寄存器的PINC位設置,如果設置為遞增,那么下一次傳輸的時候地址加1。通常外設只有一個數據寄存器,所以一般不會使能該位,即配置為DMA_PeripheralInc_Disable。
? DMA_MemoryInc :用來設置內存地址是否遞增,通過DMA_CCR寄存器的MINC位設置。我們自定義的存儲區一般都是存放多個數據的,所以需要使能存儲器地址自動遞增功能,即配置為DMA_MemoryInc_Enable。
? DMA_PeripheralDataSize :外設數據寬度選擇,可以為字節(8位)、半字(16位)、字(32位),通過DMA_CCR寄存器的PSIZE[1:0]位設置。
? DMA_Mode :DMA傳輸模式選擇,可選擇一次傳輸或者循環傳輸,通過DMA_CCR寄存器的CIRC位來設定。比如我們要從內存(存儲器)中傳輸64個字節到串口,如果設置為循環傳輸,那么它會在64個字節傳輸完成之后繼續從內存的第一個地址傳輸,如此循環。這里我們設置為一次傳輸完成之后不循環。所以設置值為DMA_Mode_Normal。
? DMA_Priority :用來設置DMA通道的優先級,有低,中,高,超高四種級別,可通過DMA_CCR寄存器的PL[1:0]位來設定。DMA優先級只有在多個DMA數據流同時使用時才有意義。
? DMA_M2M :用來設置存儲器到存儲器模式,使用存儲器到存儲器時用到,設定DMA_CCR 的位 14 MEN2MEN 即可啟動存儲器到存儲器模式。
五、DMA配置程序
這里以配置DMA,將ADC采集到的數據搬運到內存中的某一個數組中為例,講解一下DMA的配置和使用方法。
5.1 ADC1初始化程序
ADC使用TIM4的通道4觸發,具體配置可見本系列另一篇文章STM32速成筆記—ADC。這里在之前配置的基礎上需要開啟ADC的DMA傳輸,在初始化ADC時加上下面的程序
ADC_DMACmd(ADC1,ENABLE); // 使能ADC的DMA傳輸
ADC初始化程序如下
/*
*==============================================================================
*函數名稱:ADC1_Init
*函數功能:初始化ADCx
*輸入參數:無
*返回值:無
*備 注:TIM4通道4觸發AD轉換,使能了DMA
*==============================================================================
*/
void ADC1_Init(void)
{
// 結構體定義
GPIO_InitTypeDef GPIO_InitStructure;
ADC_InitTypeDef ADC_InitStructure;
// 開啟時鐘
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1,ENABLE);
// 設置ADC分頻因子6 72M/6=12,ADC最大時間不能超過14M
RCC_ADCCLKConfig(RCC_PCLK2_Div6);
// 規則通道配置
ADC_RegularChannelConfig(ADC1, ADC_Channel_1, 1, ADC_SampleTime_239Cycles5);
// GPIO配置
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_1; //ADC1通道1
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AIN; // 模擬輸入
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);
// ADC參數配置
ADC_InitStructure.ADC_Mode = ADC_Mode_Independent; // 獨立模式
ADC_InitStructure.ADC_ScanConvMode = DISABLE; // 非掃描模式
ADC_InitStructure.ADC_ContinuousConvMode = DISABLE; // 關閉連續轉換
ADC_InitStructure.ADC_ExternalTrigConv = ADC_ExternalTrigConv_T4_CC4; // TIM2通道2觸發
ADC_InitStructure.ADC_DataAlign = ADC_DataAlign_Right; // 右對齊
ADC_InitStructure.ADC_NbrOfChannel = 1; // 1個轉換在規則序列中 也就是只轉換規則序列1
ADC_Init(ADC1, &ADC_InitStructure); // ADC初始化
// 使能外部觸發
ADC_ExternalTrigConvCmd(ADC1, ENABLE);
ADC_DMACmd(ADC1,ENABLE); // 使能ADC的DMA傳輸
ADC_Cmd(ADC1, ENABLE); // 開啟AD轉換器
// ADC校準
ADC_ResetCalibration(ADC1); // 重置指定的ADC的校準寄存器
while(ADC_GetResetCalibrationStatus(ADC1)); // 獲取ADC重置校準寄存器的狀態
ADC_StartCalibration(ADC1); // 開始指定ADC的校準狀態
while(ADC_GetCalibrationStatus(ADC1)); // 獲取指定ADC的校準程序
ADC_SoftwareStartConvCmd(ADC1, ENABLE); // 使能或者失能指定的ADC的軟件轉換啟動功能
}
5.2 DMA初始化程序
由上面的介紹可知,ADC1是DMA1的通道1,我們配置一下DMA1的通道1,使能傳輸完成中斷。
/*
*==============================================================================
*函數名稱:DMA1_Init
*函數功能:DMA1初始化
*輸入參數:souAddr:數據源地址;desAddr:數據目的地址
*返回值:無
*備 注:數據傳輸寬度為16位,外設到內存,循環傳輸,使能了傳輸完成中斷
*==============================================================================
*/
void DMA1_Init (u32 souAddr,u32 desAddr)
{
// 結構體定義
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能DMA時鐘
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1,ENABLE);
//DMA1初始化
DMA_DeInit(DMA1_Channel1);
DMA_InitStructure.DMA_PeripheralBaseAddr = souAddr; // 數據源地址
DMA_InitStructure.DMA_MemoryBaseAddr = desAddr; // 目的地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; // 傳輸方向(外設到內存)
DMA_InitStructure.DMA_BufferSize = 128; // 一次傳輸數據大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // 外設地址不自增
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; // 內存地址自增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // 外設數據寬度選擇
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // 內存數據寬度選擇
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; // DMA模式:循環傳輸
DMA_InitStructure.DMA_Priority = DMA_Priority_High; // 優先級:高
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; // 禁止內存到內存的傳輸
DMA_Init(DMA1_Channel1, &DMA_InitStructure); // 配置DMA1
// 使能傳輸完成中斷
DMA_ITConfig(DMA1_Channel1,DMA_IT_TC, ENABLE);
// NVIC配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
// 使能DMA1通道1
DMA_Cmd(DMA1_Channel1,ENABLE);
}
// DMA1中斷服務函數
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
{
DMA_Cmd(DMA1_Channel1,DISABLE);
while (1)
{}
}
// 清除中斷標志位
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
定義一個存儲AD轉換結果的數組,初始化時,程序如下
u16 gAdcAdValue[128]; // 存儲AD值
DMA1_Init((u32)(&ADC1- >DR),(u32)&gAdcAdValue); // DMA1初始化
中斷服務函數中將存儲標志位置1表示存儲完成
u8 gDmaAdcSaveFlag = 0; // ADC數據存儲標志位
// DMA1中斷服務函數
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1)!=RESET)
{
gDmaAdcSaveFlag = 1; // 存儲標志位置1,表示存儲完成
}
// 清除中斷標志位
DMA_ClearITPendingBit(DMA1_IT_TC1);
}
上面的配置就可以實現ADC采集,DMA將采集結果搬運到內存中的一個數組里面。
-
存儲器
+關注
關注
38文章
7625瀏覽量
166268 -
STM32
+關注
關注
2288文章
10999瀏覽量
362004 -
ADC采樣
+關注
關注
0文章
134瀏覽量
13115 -
DMA控制器
+關注
關注
1文章
43瀏覽量
12551 -
USART串口
+關注
關注
0文章
32瀏覽量
7015
發布評論請先 登錄
STM32實例教程-DMA實驗

STM32F103ZE_DMA筆記
STM32F1開發指南筆記32----DMA

STM32學習筆記(串口+DMA)

stm32的串口DMA空閑中斷接收不等長數據,stm32F1的usart1-DMA-IDLE收發

stm32學習筆記 DMA

評論