zoukankan      html  css  js  c++  java
  • 【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,应该要找各自的说明书,也许表格会有出入

    找出我想实现的功能,例如我想用串口1的发送(USART1_TX),在DMA2里面,『Stream = 7』『Channel = 4』 就是我们要的了

    /* ------------- 题外话 ------------- */

    也许你会发现,为什么会有两个一样的,例如DMA1表格里,【Stream0、Channel0】【Stream2、Channel0】对应的都是SPI3_RX

    在网上问人后,对方是和我说,这是解决唯一拥有的情况,例如只有DMA2SDIO功能,如果你同时又要使用SPI,那么可以用DMA1来配合SPI

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

    【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了

    观看的人,如果能帮到你,这是我的荣幸

  • 相关阅读:
    HDU 1010 Tempter of the Bone
    HDU 4421 Bit Magic(奇葩式解法)
    HDU 2614 Beat 深搜DFS
    HDU 1495 非常可乐 BFS 搜索
    Road to Cinema
    Sea Battle
    Interview with Oleg
    Spotlights
    Substring
    Dominating Patterns
  • 原文地址:https://www.cnblogs.com/PureHeart/p/11218076.html
Copyright © 2011-2022 走看看