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

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

一些簡單的動做,例如複製或發送,就能夠不透過CPU,從而減輕CPU負擔函數

因爲本人使用的是正點原子開發板,部分代碼取自裏面的範例測試

 

本篇內容大綱ui

【1】DMA初步瞭解spa

【2】導入相關的庫3d

【3】代碼流程code

 

【1】DMA初步瞭解orm

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

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

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

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

而後,每一個數據流又有8個通道(Channel)

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

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

在使用DMA以前須要作設定,例如我想用串口1的發送(USART1_TX),在DMA2裏面,『Stream = 7』『Channel = 4』 就是咱們要的了

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

也許你會發現,爲何會有兩個同樣的,例如DMA1表格裏,【Stream0、Channel0】【Stream二、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,PC十、十一、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了

觀看的人,若是能幫到你,這是個人榮幸

相關文章
相關標籤/搜索