使用SPI DMA在STM32上读写外部Flash

.为什么要使用DMA?

STM32硬件spi其实速度也算快,对于非大数据的读写外部flash操作,使用spi查询方式,基本就能处理很多问题,但是对于ota项目中,需要将固件写入外部flash,使用flash+dma能在减少cpu的负担。

最重要就是下面的代码 ,这是STM32F407标准库代码,HAL大家可以参考安富莱。

第一步:配置DMA通道数据流!

打开我们的stm32f407数据手册,找到DMA这一章节,

我们找到请求映射这一块,我们可以看到,通道三有SPI1rx SPI1tx,那有的人就要问了,为啥通道三上面有两对spirx,spitx呀,其实用那一对都可以,因为同一通道同一时刻只能用一个数据流,所以,我们选通道三数据流的0和3即可。

接下来就开始配置我们的数据流了

由于spi是全双工通信,那么全双通信就需要注意,你发的时候同时在在收数据,你接受数据的时候也是在发数据,那么如何保证我收的时候,不发送数据呢,我们来看这段代码。

isread 表示你是读操作还是写操作,是1表示读操作,读操作就是从SPI->DR读取数据,到内部存储器,那么我就需要把写的数据流 的DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递。

梳理一下流程:

读的时候,第一步:我需要设置好接收数据流buffer地址,设置存储器地址自增,

 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;                  //读方式下,把接收的数据传到buffer里
 DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;       

 第二步:配置发送数据流中的DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递

  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;

 写的时候:第一步:先配置接收数据流中的DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递

 DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;

 第二步:配置写数据流中的发送buffer 并且地址自增

  

  DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;

 总结:读数据时候禁止写数据流的buffer(&0x0xFFFFFFFF,不自增),写数据的时候禁止读数据流buffer(&0x0xFFFFFFFF,不自增)。

if两次判断就是这么来的: 

void flash_start_dma(uint8_t *buffer, uint32_t length, uint8_t isread)
{
    uint32_t temp = 0xffffffff;
    DMA_InitTypeDef DMA_InitStructure;
 
    
    DMA_StructInit(&DMA_InitStructure);//先初始化DMA的结构体
    DMA_DeInit(DMA2_Stream0);//Rx  读  设置数据流初始化
    DMA_DeInit(DMA2_Stream3);//TX  写  设置数据流初始化
	
    while(DMA_GetCmdStatus(DMA2_Stream0) != DISABLE);
    while(DMA_GetCmdStatus(DMA2_Stream3) != DISABLE);
 
    /* DMA1 channel 0 stream0 --- 配置 SPI3-RX DMA */
    DMA_InitStructure.DMA_Channel = DMA_Channel_3;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
    
    if (isread == 1)
    {
        /* 读方式下回读数据放到 buffer 中*/
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;                  //读方式下,把接收的数据传到buffer里
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                    //内存自增
    }
    else
    {
        /* 写方式下回读数据丢弃 
		代码健壮性:我这个通道配置的是读,如果不是读,那么我就设置我的地址不自增,不使能
		
		第一个if判断:重点:配置DMA2_Stream0,这是用于接收数据的DMA流(SPI的RX,即接收数据)。
		
		当isread为1时,
		表示执行读操作,DMA应该将数据从SPI数据寄存器(SPI1->DR)传输到内存中的buffer。因此,
		设置DMA_Memory0BaseAddr为buffer的地址,并启用内存地址递增(DMA_MemoryInc_Enable)。
		如果不是读操作(即写操作),则不需要从SPI读取数据到内存,
		此时DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递
		*/
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
    }
 
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;
    DMA_InitStructure.DMA_BufferSize = length;
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_High;
    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(DMA2_Stream0, &DMA_InitStructure);
    
    /* DMA1 channel 0 stream7 --- 配置 SPI3-TX DMA */
    DMA_StructInit(&DMA_InitStructure);
    
    DMA_InitStructure.DMA_Channel = DMA_Channel_3;
    DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI1->DR;
 
    if (isread == 1)
    {
        /* 读方式下发送 0xff
           我现在是配置写,那么你读到的数据
           此时DMA配置为将固定值(通常是0xFFFFFFFF,表示一个字节的全1)写入SPI数据寄存器,内存地址不递

		*/
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)&temp;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Disable;
    }
    else
    {
        /* 写方式下发送 buffer 数据*/
        DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)buffer;
        DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
    }
    
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
    DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral;
    DMA_InitStructure.DMA_BufferSize = length;  
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
    DMA_InitStructure.DMA_Priority = DMA_Priority_Low;
    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(DMA2_Stream3, &DMA_InitStructure);
 
    /* 1.开启 DMA 数据流传输完成中断 */
    DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, ENABLE);
    DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, ENABLE);
 
    /* 2.使能 DMA 数据流 */
    DMA_Cmd(DMA2_Stream0, ENABLE);
    DMA_Cmd(DMA2_Stream3, ENABLE);
 
    /* 3.使能 SPI DMA 请求 */
    SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx|SPI_I2S_DMAReq_Tx, ENABLE);
 
    /* 5.等待 DMA 传输完成  互锁机制  那我怎么知道你写 或者读完了呢 肯定在中断里面进行的*/
    spi_wait_tx();
    spi_wait_rx();
}

第二步:DMA中断都做了啥? 

开启两个中断服务函数

void DMA2_Stream3_IRQHandler(void);(SPI1TX)

void DMA2_Stream0_IRQHandler(void);(SPI1RX)

主要任务:1.清除中断标志位

                  2.关闭中断;

                  3.禁止SPI请求;

                  4.禁止DMA数据流;

                  5.指标志位;

        低功耗模式:在某些低功耗应用中,
        一旦DMA传输完成,系统可能希望尽快进入低功耗状态。
        禁用中断和DMA流有助于减少系统的活动,从而降低功耗。
 

void DMA2_Stream3_IRQHandler(void) //写中断
{
    if (DMA_GetFlagStatus(DMA2_Stream3, DMA_FLAG_TCIF3) != RESET)
    {
        /* 1.清中断标志 */
        DMA_ClearFlag(DMA2_Stream3, DMA_FLAG_TCIF3);
        /* 2.禁能 DMA   传输完成中断 */
        DMA_ITConfig(DMA2_Stream3, DMA_IT_TC, DISABLE);//写完马上关掉中断
        /* 3.禁能 SPI TX DMA   请求 */
        SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Tx, DISABLE);
        /* 4.禁能 DMA Stream */
        DMA_Cmd(DMA2_Stream3, DISABLE);
        /* 5.通知应用程序 TX 传输完成 */
        spi_post_tx();
    }
}
 
void DMA2_Stream0_IRQHandler(void)
{
    if (DMA_GetFlagStatus(DMA2_Stream0, DMA_FLAG_TCIF0) != RESET)
    {
        /* 1.清中断标志 */
        DMA_ClearFlag(DMA2_Stream0, DMA_FLAG_TCIF0);
        /* 2.禁能 DMA   传输完成中断 */
        DMA_ITConfig(DMA2_Stream0, DMA_IT_TC, DISABLE);
        /* 3.禁能 SPI TX DMA   请求 */
        SPI_I2S_DMACmd(SPI1, SPI_I2S_DMAReq_Rx, DISABLE);
        /* 4.禁能 DMA Stream */
        DMA_Cmd(DMA2_Stream0, DISABLE); 
        /* 6.通知应用程序 RX 传输完成 */
        spi_post_rx();
    }
}
 

第三步:调用flash_start_dma,启动DMA

步骤:先使用轮询方式发送指令,在通过DMA进行搬运数据,

void flash_write_page(uint8_t* buffer, uint32_t addr)
{
//	u8 i;
  flash_enable_write();
  
  spi_chip_cs(1);
  spi_byte_transfer(0x02);
  spi_byte_transfer((addr >> 16) & 0xFF);
  spi_byte_transfer((addr >>  8) & 0xFF);
  spi_byte_transfer((addr >>  0) & 0xFF);
 
  flash_start_dma(buffer, FLASH_PAGE_SIZE, 0);

  spi_chip_cs(0);
//    for( i= 0;i<strlen(buffer);i++)
//	{
//		addr = addr+1;
//	}
  flash_wait_write();
	
}

总结:

以上就是SPI+DMA读取flash的内容

作者:苏锦编程

物联沃分享整理
物联沃-IOTWORD物联网 » 使用SPI DMA在STM32上读写外部Flash

发表回复