【STM32】使用DMA+SPI傳輸數據


DMA(Direct Memory Access):直接存儲器訪問

一些簡單的動作,例如復制或發送,就可以不透過CPU,從而減輕CPU負擔

由於本人使用的是正點原子開發板,部分代碼取自里面的范例

 

本篇內容大綱

【1】DMA初步了解

【2】導入相關的庫

【3】代碼流程

 

【1】DMA初步了解

DMA可以設定三種傳輸方式:『外設到存儲器』『存儲器到外設』『存儲器到存儲器』(第三種方式僅DMA2能執行)

本篇測試的是『存儲器到外設』,下面繼續介紹DMA

STM32F4兩個DMA控制器(DMA1、DMA2)

每個控制器8個數據流(Stream)

然后,每個數據流又有8個通道(Channel)

下面兩張張表格,來說明『DMA控制器』『數據流』『通道』所對應的DMA請求映射(request mapping)

以下這圖是針對STM32F4的,其他芯片,例如STM32F1,應該要找各自的說明書,也許表格會有出入

在使用DMA之前需要做設定,例如我想用串口1的發送(USART1_TX),在DMA2里面,『Stream = 7』『Channel = 4』 就是我們要的了

/* ---------------------------------------------------------------------- 題外話 ----------------------------------------------------------------------------------- */

也許你會發現,為什么會有兩個一樣的,例如DMA1表格里,【Stream0、Channel0】【Stream2、Channel0】對應的都是SPI3_RX

在網上問人后,對方是和我說,因為有兩個DMA控制器,這部分后續有時間再研究

/* ------------------------------------------------------------------------------------------------------------------------------------------------------------------- */

 

【2】導入相關的庫

因為本篇測試的是『存儲器到外設』

先看看有沒有所需外設的文件,例如stm32f4xx_usart.c,沒有的話參考下面的圖片來導入,以本篇來說,需要導入的外設是stm32f4xx_spi.c

接下來,由於我們要使用DMA,所以也要導入stm32f4xx_dma.c

 

導入完成后,我們先打開 stm32f4xx_dma.h 這個頭文件,可以看到一些設定的函數,例如初始化之類的

 

基本上要設置時,所要調用的函數就在這里了,而下方紅框是中斷和標志相關的函數

為什么說基本上?那是因為還有一小部分的設定,要在別的地方找

假設我們要使用USART(上面已經添加庫了:stm32f4xx_usart.c)

找一下stm32f4xx_USART.h這個頭文件,通過搜尋dmacmd,就會找到使能函數(USART_DMACmd)

因為本篇使用SPI,但由於我懶得改圖了,只要找到stm32f4xx_SPI.h這個頭文件

就會發現關於DMA的函數,void SPI_I2S_DMACmd(SPI_TypeDef* SPIx, uint16_t SPI_I2S_DMAReq, FunctionalState NewState)

 

【3】代碼流程

在main里,大致的流程是這樣的

(1)首先初始化外設,這里以SPI為例(spi3_init)

(2)執行DMA的初始化(MYDMA_Config)

(3)在你需要執行傳送數據的地方,執行數據的傳送,這里是直接寫在while(1)里面了

(4)做完一次的DMA,要把相關的標志清0

int main(void)
{
     SPI3_Init(); // 串口初始化
     MYDMA_Config(DMA1_Stream5,DMA_Channel_0,(u32)&SPI3->DR,(u32)SendBuff,SEND_BUF_SIZE); // DMA初始化
     while(1)
     {
          SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE); // 使能DMA發送
          MYDMA_Enable(DMA1_Stream5,SEND_BUF_SIZE); // 執行一次的DMA發送
          if(DMA_GetFlagStatus(DMA1_Stream5,DMA_FLAG_TCIF5)!=RESET)) //等待DMA傳輸完成
              DMA_ClearFlag(DMA1_Stream5,DMA_FLAG_TCIF5); // 清除標志
     }    
}   

先不要在意里面的參數,下面會詳解,DMA的使用,大致的流程就是這樣

下面詳解這5個函數的內容,判斷式就不解釋了

 

SPI3_Init()

void SPI3_Init(void)
{	 
  GPIO_InitTypeDef  GPIO_InitStructure;
  SPI_InitTypeDef  SPI_InitStructure;
	
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC, ENABLE);//使能GPIOC時鍾
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI3, ENABLE);//使能SPI3時鍾
 
  // PC10:SPI3_SCK
  // PC11:SPI3_MISO
  // PC12:SPI3_MOSI
  // PB3:SPI1_SCK、SPI3_SCK
  // PB4:SPI1_MISO、SPI3_MISO
  // PB5:SPI1_MOSI、SPI3_MOSI
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12;//PC10~12復用功能輸出	
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//復用功能
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽輸出
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;//上拉
  GPIO_Init(GPIOC, &GPIO_InitStructure);//初始化
	
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource10,GPIO_AF_SPI3); //PC10復用為 SPI3
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource11,GPIO_AF_SPI3); //PC11復用為 SPI3
  GPIO_PinAFConfig(GPIOC,GPIO_PinSource12,GPIO_AF_SPI3); //PC12復用為 SPI3

  SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;  //設置SPI單向或者雙向的數據模式:SPI設置為雙線雙向全雙工
  SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//設置SPI工作模式:設置為主SPI
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;		//設置SPI的數據大小:SPI發送接收8位幀結構
  SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;		//串行同步時鍾的空閑狀態為高電平
  SPI_InitStructure.SPI_CPHA = SPI_CPHA_2Edge;	//串行同步時鍾的第二個跳變沿(上升或下降)數據被采樣
  SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;		//NSS信號由硬件(NSS管腳)還是軟件(使用SSI位)管理:內部NSS信號有SSI位控制
  SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;		//定義波特率預分頻的值:波特率預分頻值為8
  SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;	//指定數據傳輸從MSB位還是LSB位開始:數據傳輸從MSB位開始
  SPI_InitStructure.SPI_CRCPolynomial = 7;	//CRC值計算的多項式
  SPI_Init(SPI3, &SPI_InitStructure);  //根據SPI_InitStruct中指定的參數初始化外設SPIx寄存器
 
  SPI_Cmd(SPI3, ENABLE); //使能SPI外設	 
}  

SPI一開始要使能的就是時鍾

其次,找到SPI能復用的引腳,這里用的是SPI3,PC10、11、12

后續一堆的SPI_InitStructure開頭的,就是在做SPI相關的初始化

這部分就不詳解了,SPI的知識網上有很多介紹的,例如什么是CPOL,什么又是CPHA,這些都是重點

倒數第二行執行SPI_Init來初始化

最后一行使能外設

 

MYDMA_Config(DMA1_Stream5,DMA_Channel_0,(u32)&SPI3->DR,(u32)SendBuff,SEND_BUF_SIZE)

void MYDMA_Config(DMA_Stream_TypeDef *DMA_Streamx,u32 chx,u32 par,u32 mar,u16 ndtr)
{ 
 
 DMA_InitTypeDef  DMA_InitStructure;
	
 if((u32)DMA_Streamx>(u32)DMA2)//得到當前stream是屬於DMA2還是DMA1
 {
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2時鍾使能 	
 }
 else  {     RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE);//DMA1時鍾使能  } DMA_DeInit(DMA_Streamx);  while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}//等待DMA可配置 /* 配置 DMA Stream */ DMA_InitStructure.DMA_Channel = chx; //通道選擇 DMA_InitStructure.DMA_PeripheralBaseAddr = par;//DMA外設地址 DMA_InitStructure.DMA_Memory0BaseAddr = mar;//DMA 存儲器0地址 DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;//存儲器到外設模式 DMA_InitStructure.DMA_BufferSize = ndtr;//數據傳輸量 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_FIFOMode = DMA_FIFOMode_Disable; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;//存儲器突發單次傳輸 DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;//外設突發單次傳輸 DMA_Init(DMA_Streamx, &DMA_InitStructure);//初始化DMA Stream }

 這個函數需要給5個參數

(1)DMA數據流,參照文章一開始的表格,這里使用的是SPI3_TX,對應的是DMA1的數據流5(DMA1_Stream5)

(2)通道,參照文章一開始的表格,這里使用的是SPI3_TX,對應的是DMA1的數據流5的通道0(DMA_Channel_0)

(3)外設地址,使用的是SPI發送(SPI3->DR)

(4)存儲器地址,自己定義的一個變量

            #define SEND_BUF_SIZE 500

            u8 SendBuff[SEND_BUF_SIZE];

(5)傳輸的數據量,第4點的宏定義,當然,也可以看你要傳多少

 

函數的內容差不多也就那樣,都是一些初始化的設定,也就傳輸方式、優先級、單次傳輸還是循環之類的

while(1)之前的兩個初始化介紹完了,接下來就是while(1)內部的幾個函數

 

SPI_I2S_DMACmd(SPI3, SPI_I2S_DMAReq_Tx, ENABLE)

結果到頭來,還是要截這張圖。。。這個是庫函數,不是我自己寫的

參數1:SPI3,因為我測試用的就是SPI3

參數2:發送或是接收,我是發送,所以是SPI_I2S_DMAReq_Tx

參數3:使能請求

 

MYDMA_Enable(DMA1_Stream5,SEND_BUF_SIZE)

void MYDMA_Enable(DMA_Stream_TypeDef *DMA_Streamx,u16 ndtr)
{
 
	DMA_Cmd(DMA_Streamx, DISABLE);                      //關閉DMA傳輸 
	
	while (DMA_GetCmdStatus(DMA_Streamx) != DISABLE){}	//確保DMA可以被設置  
		
	DMA_SetCurrDataCounter(DMA_Streamx,ndtr);          //數據傳輸量  
 
	DMA_Cmd(DMA_Streamx, ENABLE);                      //開啟DMA傳輸 
}	  

參數1:哪個DMA控制器的哪個數據流,這里是DMA1數據流5(DMA1_Stream5)

參數2:數據量

 

DMA_ClearFlag(DMA1_Stream5,DMA_FLAG_TCIF5)

參數1:哪個DMA控制器的哪個數據流,這里是DMA1數據流5(DMA1_Stream5)

參數2:圖片的1068行,說明了可以用0~7的數據流,我使用的是數據流5,所以要清除的也是數據流5(DMA_FLAG_TCIF5)

第1063~1067行的解釋

DMA_FLAG_TCIFx:『數據流x』傳輸完成標志

DMA_FLAG_HTIFx:『數據流x』半傳輸完成標志

DMA_FLAG_TEIFx:『數據流x』傳輸錯誤標志

DMA_FLAG_DMEIFx:『數據流x』直接模式錯誤標志

DMA_FLAG_FEIFx:『數據流x』FIFO錯誤標志

選定自己需要的來清除即可

然后就能實現DMA+SPI了

觀看的人,如果能幫到你,這是我的榮幸

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM