記錄一下,方便以後翻閱~
主要內容:
1) DMA基本原理;
2) 相關寄存器及庫函數介紹;
3) 相關實驗代碼解讀。
實驗功能:系統啓動後,通過按鍵KEY0控制串口1以DMA方式發送數據,按下KEY0,就開始DMA傳送,同時,串口調試助手可以收到DMA發送的內容。
官方資料:《STM32中文參考手冊V10》第10章——DMA控制器
1. DMA(Direct MemoryAccess-直接存儲器訪問)基本原理
1.1 DMA傳輸將數據從一個地址空間複製到另一個地址空間。當CPU初始化這個傳輸動作,傳輸動作本身是由DMA控制器來實現和完成的。
DMA傳輸方式無需CPU直接控制傳輸,也沒有中斷處理方式那樣保留現場和恢復現場過程,通過硬件爲RAM和IO設備開闢一條直接傳輸數據的通道,使得CPU的效率大大提高。
簡要總結:提高數據傳輸的速度,爲CPU減負。
1.2 STM32最多有2個DMA控制器(DMA2僅存在大容量產品中),DMA1有7個通道。DMA2有5個通道。每個通道專門用來管理來自於一個或多個外設對存儲器訪問的請求。
還有一個仲裁起來協調各個DMA請求的優先權。
1.3 DMA框圖如下所示
DMA1有7個通道,DMA2有5個通道,仲裁器來處理優先級,DMA1和DMA2的請求來自APB1和APB2的外設。DMA總線用來訪問存儲器。
1.4 DMA特點
1.4.1 每個通道都直接連接專用的硬件DMA請求,都支持軟件觸發,這些通過軟件來配置;
1.4.2 在七個請求間的優先權可以通過軟件編程設置(共有四級:很高、高、中等和低),假如在相等優先權時由硬件決定(請求0優先於請求1,依此類推) ;
1.4.3 獨立的源和目標數據區的傳輸寬度(字節、半字、全字),模擬打包和拆包的過程。源和目標地址必須按數據傳輸寬度對齊;
1.4.4 支持循環的緩衝器管理;
1.4.5 每個通道都有3個事件標誌(DMA半傳輸,DMA傳輸完成和DMA傳輸出錯),這3個事件標誌邏輯或成爲一個單獨的中斷請求;
1.4.6 外設和存儲器,存儲器和外設的傳輸 ,存儲器和存儲器間的傳輸;
1.4.7 閃存、SRAM、外設的SRAM、APB1 APB2和AHB外設均可作爲訪問的源和目標;
1.4.8 可編程的數據傳輸數目:最大爲65536。
2. DMA控制器
3. DMA處理
針對DMA_CCRx寄存器,位4,數據傳輸方向:
4. 仲裁器
針對DMA_CCRx寄存器,位[12~13],PL通道優先級:
5. 通道
6. 指針增量
7. 循環模式
8. 通道傳輸數據量
9. 中斷
10. 通道配置過程
11. DMA配置參數
1.1 通道;
1.2 優先級;
1.3 數據傳輸方向;
1.4 存儲器/外設,數據寬度;
1.5 存儲器/外設,地址是否增量;
1.6 循環模式;
1.7 數據傳輸量。
12. 相關庫函數
12.1 官方庫函數爲stm32f10x_dma.c和tm32f10x_dma.h;
12.2 常用庫函數
1) void DMA_Init(DMA_Channel_TypeDef* DMAy_Channelx, DMA_InitTypeDef* DMA_InitStruct);
2) void DMA_Cmd(DMA_Channel_TypeDef* DMAy_Channelx, FunctionalState NewState);
3) void DMA_ITConfig(DMA_Channel_TypeDef* DMAy_Channelx, uint32_t DMA_IT, FunctionalState NewState);
4) void DMA_SetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx, uint16_t DataNumber);
5) uint16_t DMA_GetCurrDataCounter(DMA_Channel_TypeDef* DMAy_Channelx);
6) FlagStatus DMA_GetFlagStatus(uint32_t DMAy_FLAG);
7) void DMA_ClearFlag(uint32_t DMAy_FLAG);
8) ITStatus DMA_GetITStatus(uint32_t DMAy_IT);
9) void DMA_ClearITPendingBit(uint32_t DMAy_IT);
12.3 常用的外設DMA使能庫函數
1) void USART_DMACmd(USART_TypeDef* USARTx, uint16_t USART_DMAReq, FunctionalState NewState);
2) void ADC_DMACmd(ADC_TypeDef* ADCx, FunctionalState NewState);
3) void DAC_DMACmd(uint32_t DAC_Channel, FunctionalState NewState);
4) void I2C_DMACmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
5) void SDIO_DMACmd(FunctionalState NewState);
6) void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState);
7) void TIM_DMAConfig(TIM_TypeDef* TIMx, uint16_t TIM_DMABase, uint16_t TIM_DMABurstLength);
8) void TIM_DMACmd(TIM_TypeDef* TIMx, uint16_t TIM_DMASource, FunctionalState NewState);
12.4 void DMA_Init(DMA_CHx, &DMA_InitStructure)函數第二個入口參數結構體解讀
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;
13. DMA程序配置過程
13.1 使能DMA時鐘:
RCC_AHBPeriphClockCmd();
13.2 初始化DMA通道參數:
DMA_Init();
13.3 使能串口DMA發送,串口DMA使能函數:
USART_DMACmd();
13.4 使能DMA1通道,啓動傳輸:
DMA_Cmd();
13.5 查詢DMA傳輸狀態:
DMA_GetFlagStatus();
13.6 獲取/設置通道當前剩餘數據量:
DMA_GetCurrDataCounter();
DMA_SetCurrDataCounter();
14. 相關實驗代碼解讀
14.1 dma.h頭文件代碼解讀
#ifndef __DMA_H
#define __DMA_H
#include "sys.h"
//申明兩個函數//
void MYDMA_Config(DMA_Channel_TypeDef*DMA_CHx,u32 cpar,u32 cmar,u16 cndtr); //配置DMA1_CHx,4個入口參數//
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx); //使能DMA1_CHx//
#endif
14.2 dma.c文件代碼解讀
#include "dma.h"
//申明一個結構體//
DMA_InitTypeDef DMA_InitStructure;
u16 DMA1_MEM_LEN; //保存DMA每次數據傳送的長度//
//編寫MYDMA_Config函數,4個入口參數,包括:DMA_CHx:DMA通道CHx;cpar:外設地址;cmar:存儲器地址;cndtr:數據傳輸量//
//該函數固定的部分包括:從存儲器->外設模式;8位數據寬度;存儲器增量模式(外設地址不增量)//
void MYDMA_Config(DMA_Channel_TypeDef* DMA_CHx,u32 cpar,u32 cmar,u16 cndtr)
{
//第一步,使能DMA傳輸//
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA_CHx); //將DMA的通道1寄存器重設爲缺省值//
DMA1_MEM_LEN=cndtr;
//第二步,初始化DMA_Init函數//
DMA_InitStructure.DMA_PeripheralBaseAddr = cpar; //DMA外設基地址,其中一個入口參數//
DMA_InitStructure.DMA_MemoryBaseAddr = cmar; //DMA內存基地址,其中一個入口參數//
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST; //數據傳輸方向,從內存讀取發送到外設,設爲固定//
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; //數據寬度爲8位,設爲固定//
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //數據寬度爲8位,設爲固定//
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //不執行循環模式,設爲固定//
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //中優先級,隨便設,設爲固定//
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //非存儲器到存儲器模式,設爲固定//
DMA_Init(DMA_CHx, &DMA_InitStructure);
}
//開啓一次DMA傳輸
void MYDMA_Enable(DMA_Channel_TypeDef*DMA_CHx)
{
DMA_Cmd(DMA_CHx, DISABLE ); //關閉USART1 TX DMA1 所指示的通道//
DMA_SetCurrDataCounter(DMA_CHx,DMA1_MEM_LEN); //DMA通道的DMA緩存的大小//
DMA_Cmd(DMA_CHx, ENABLE); //使能USART1 TX DMA1 所指示的通道//
}
14.3 main.c文件代碼解讀
#include "led.h"
#include "key.h"
#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "dma.h"
#define SEND_BUF_SIZE 8200 //發送數據長度,最好等於sizeof(TEXT_TO_SEND)+2的整數倍.
u8 SendBuff[SEND_BUF_SIZE]; //發送數據緩衝區
const u8 TEXT_TO_SEND[]={"ALIENTEK WarShip STM32F1 DMA 串口實驗"};
int main(void)
{
u16 i;
u8 t=0;
u8 j,mask=0;
// float pro=0; //進度,用不到//
delay_init(); //延時函數初始化//
uart_init(115200); //串口初始化爲115200//
LED_Init(); //初始化與LED連接的硬件接口//
KEY_Init(); //按鍵初始化//
//DMA1通道4,外設爲串口1,針對USART_DR寄存器,存儲器爲SendBuff,長度SEND_BUF_SIZE//
MYDMA_Config(DMA1_Channel4,(u32)&USART1->DR,(u32)SendBuff,SEND_BUF_SIZE);
//顯示提示信息
j=sizeof(TEXT_TO_SEND); //計算"ALIENTEK WarShip STM32F1 DMA 串口實驗"這段字符串的字節長度,遠小於8200//
//填充數據到SendBuff//
for(i=0;i<SEND_BUF_SIZE;i++) //當0≤i<8200時,執行//
{
if(t>=j) //當t≥j時,執行//
{
if(mask)
{
SendBuff[i]=0x0a; //如果mask=1,則SendBuff[i]=0x0a,換行的意思//
t=0;
}else
{
SendBuff[i]=0x0d; //如果mask=0,則SendBuff[i]=0x0d,回車的意思//
mask++;
}
}else //當t<j時//
{
mask=0;
SendBuff[i]=TEXT_TO_SEND[t]; //複製TEXT_TO_SEND語句//
printf("\r\nDMA DATA:%d\r\n",SendBuff[i]);
t++;
}
}
i=0; //i至0//
while(1)
{
t=KEY_Scan(0);
if(t==KEY0_PRES) //如果KEY0按下,執行//
{
printf("\r\nDMA DATA:\r\n");
USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE); //使能串口1的DMA發送//
MYDMA_Enable(DMA1_Channel4); //開始一次DMA傳輸//
//實際應用中,傳輸數據期間,可以執行另外的任務//
while(1)
{
if(DMA_GetFlagStatus(DMA1_FLAG_TC4)!=RESET) //判斷通道4傳輸是否完成//
{
LED1=1;
DMA_ClearFlag(DMA1_FLAG_TC4); //清除通道4傳輸完成標誌//
break; //推出當前循環//
}
//pro=DMA_GetCurrDataCounter(DMA1_Channel4); //得到當前還剩餘多少個數據//
//pro=1-pro/SEND_BUF_SIZE; //得到百分比//
//pro*=100; //擴大100倍//
LED1=!LED1;
}
}
i++;
delay_ms(10);
if(i==20)
{
LED0=!LED0; //提示系統正在運行
i=0;
}
}
}
實驗結果
按一次KEY0鍵,一次導入如下字符串數據至串口調試助手上:
舊知識點
1)複習如何新建工程模板,可參考STM32學習心得二:新建工程模板;
2)複習基於庫函數的初始化函數的一般格式,可參考STM32學習心得三:GPIO實驗-基於庫函數;
3)複習寄存器地址,可參考STM32學習心得四:GPIO實驗-基於寄存器;
4)複習位操作,可參考STM32學習心得五:GPIO實驗-基於位操作;
5)複習寄存器地址名稱映射,可參考STM32學習心得六:相關C語言學習及寄存器地址名稱映射解讀;
6)複習時鐘系統框圖,可參考STM32學習心得七:STM32時鐘系統框圖解讀及相關函數;
7)複習延遲函數,可參考STM32學習心得九:Systick滴答定時器和延時函數解讀;
8)複習ST-LINK仿真器的參數配置,可參考STM32學習心得十:在Keil MDK軟件中配置ST-LINK仿真器;
9)複習ST-LINK調試方法,可參考STM32學習心得十一:ST-LINK調試原理+軟硬件仿真調試方法;
10)複習串口通信相關知識,可參考STM32學習心得十四:串口通信相關知識及配置方法。