STM32學習心得二十八:DMA基本原理及相關實驗

記錄一下,方便以後翻閱~
主要內容:
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學習心得十四:串口通信相關知識及配置方法

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章