细说STM32F407单片机DMA方式读写SPI FLASH W25Q16BV

目录

一、 工程配置

1、时钟、DEBUG、GPIO、USART6、Code Generator

2、SPI2

3、NVIC

二、软件设计

1、 FALSH、KEY_LED

2、 spi.h

3、 spi.c

4、main.c

三、下载、运行


        SPI接口具有发送和接收两个DMA请求,在大数据量传输时,使用DMA效率更高,比如,一次写入一个扇区的数据。

        参考本文作者写的其他文章:细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV-CSDN博客  https://wenchm.blog.csdn.net/article/details/144587209

        本文目的:对照参考文章的需求,把读、写FLASH操作修改为通过DMA方式。 其它不变。

        硬件开发板同参考文章。工程的核心用途:

  • KeyUp键按下时,调用函数Flash_EraseChip()擦除整个芯片。
  • KeyDown键按下时,调用函数Flash_EraseSector()擦除扇区0,一个扇区有16个页。
  • KeyLeft键按下时,调用函数Flash_TestWriteDMA()以DMA方式向Page 3写入数据。
  • KeyRight键按下时,调用函数Flash_TestReadDMA()以DMA方式从Page 3读取数据。Flash_TestWriteDMA()和Flash_TestReadDMA()是在文件spi.h中新定义的两个函数,用于测试DMA方式的SPI数据读写功能。
  • 一、 工程配置

    1、时钟、DEBUG、GPIO、USART6、Code Generator

            与参考文章相同。

    2、SPI2

            MCU的SPI2基本参数和管脚设置与参考文章相同。

            SPI2的DMA设置如下图:

     

            为DMA请求SPI2_TX和SPI2_RX分别配置DMA流。SPI2_TX的DMA传输方向是存储器到外设,SPI2_RX的DMA传输方向是外设到存储器。注意,两个DMA流的Mode(工作模式)一定要设置为正常(Normal),因为每次的DMA发送或接收只执行一次,不需要循环执行。外设和存储器的数据宽度(Data Width)都是字节,开启存储器的地址自增(Increment Address)功能。         

    3、NVIC

            DMA中断默认开启的,优先级设置为1,其它中断设置与参考文章相同。 

            DMA流的中断会自动打开,设置两个DMA流中断的抢占优先级为1,因为DMA流中断的回调函数里会间接用到延时函数HAL_Delay()。不要打开SPI2的全局中断。 

    二、软件设计

    1、 FALSH、KEY_LED

            外部文件,与参考文章相同。

            FLASH文件夹里的W25Q16驱动程序的底层SPI传输,采用的是阻塞式传输方式,没必要进行改写,对于一般的操作指令,用阻塞式SPI传输实现更容易。DMA方式适用于需要传输大块数据的场合,例如,一次写入或读取几个页的数据。 

    2、 spi.h

    /* USER CODE BEGIN Prototypes */
    void Flash_TestReadStatus(void);
    void Flash_TestReadDMA(void); 	//以DMA方式读取1个Page
    void Flash_TestWriteDMA();  	//以DMA方式写入1个Page
    /* USER CODE END Prototypes */

            main()函数中调用的3个W25Q16测试函数都在文件spi.h中定义,函数Flash_TestReadStatus()与参考文章完全相同。函数Flash_TestWriteDMA()和Flash_TestReadDMA()需要在文件spi.h中声明函数原型。这两个函数和相关回调函数的代码在文件spi.c中。

    3、 spi.c

    /* USER CODE BEGIN 0 */
    #include "w25flash.h"
    #include <stdio.h>
    
    uint8_t bufPageRead[FLASH_PAGE_SIZE];	//接收1个Page数据的缓存区
    uint8_t bufPageWrite[FLASH_PAGE_SIZE];	//发送1个Page数据的缓存区
    /* USER CODE END 0 */
    /* USER CODE BEGIN 1 */
    //读取器件ID,状态寄存器 SR1和SR2
    void Flash_TestReadStatus(void)
    {
    //1. 读DeviceID,SR1,SR2
    	uint16_t devID;
    	devID = Flash_ReadID();	//读取器件ID
    	printf("Device ID= %X\r\n",devID);
    	printf("The chip is: ");
    	switch (devID)
    	{
    	case 0xEF14:
    		printf("W25Q16\r\n");
    		break;
    	case 0xEF16:
    		printf("W25Q64\r\n");
    		break;
    	case 0xEF17:
    		printf("W25Q128\r\n");
    		break;
    	case 0xC817:
    		printf("GD25Q128\r\n");
    		break;
    	case 0x1C17:
    		printf("EN25Q128\r\n");
    		break;
    	case 0x2018:
    		printf("N25Q128\r\n");
    		break;
    	case 0x2017:
    		printf("XM25QH128\r\n");
    		break;
    	case 0xA117:
    		printf("FM25Q128\r\n");
    		break;
    	default:
    		printf("Unknown type\r\n");
    	}
    
    	uint8_t SR1=Flash_ReadSR1();		//读寄存SR1
    	printf("Status Reg1= %X\r\n",SR1);	//Hex显示
    
    	uint8_t SR2=Flash_ReadSR2();		//读寄存器SR2
    	printf("Status Reg2= %X\r\n",SR2);	//Hex显示
    }
    
    
    void Flash_TestWriteDMA()  				//以DMA方式写入1个Page
    {
    	uint8_t	blockNo=0;
    	uint16_t sectorNo=0;
    	uint32_t memAddress=0;
    
    	for(uint16_t i=0;i<FLASH_PAGE_SIZE;i++)
    		bufPageWrite[i]=i;				//准备数据
    	uint16_t pageNo=3;
    	memAddress=Flash_Addr_byBlockSectorPage(blockNo,sectorNo,pageNo);
    	uint8_t byte2, byte3, byte4;
    	Flash_SpliteAddr(memAddress,&byte2,&byte3,&byte4);
    
    	Flash_Write_Enable();   	//写能
     	Flash_Wait_Busy();
    	__Select_Flash();			//CS=0
    	SPI_TransmitOneByte(0x02);  //Command=0x02: 对一个Page编程写入
    	SPI_TransmitOneByte(byte2);	//Transmit 24bit address
    	SPI_TransmitOneByte(byte3);
    	SPI_TransmitOneByte(byte4);
    //以DMA方式连续写入256字节
    	printf("Writing Page3 in DMA mode.\r\n");
    	HAL_SPI_Transmit_DMA(&hspi2,bufPageWrite,FLASH_PAGE_SIZE);
    }
    
    //DMA transmit completion interrupt callback
    void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
    {
    	__Deselect_Flash();		//CS=1, 结束SPI传输过程
    	Flash_Wait_Busy();
    	printf("DMA Writing complete.\r\n");
    	printf("** Reselect menu or reset **\r\n");
    }
    
    void Flash_TestReadDMA(void) //以DMA方式读取1个Page
    {
    	uint8_t	blockNo=0;
    	uint16_t sectorNo=0;
    	uint16_t pageNo=3;
    	uint32_t memAddress;
    	memAddress=Flash_Addr_byBlockSectorPage(blockNo,sectorNo,pageNo);
    	uint8_t byte2, byte3, byte4;
    	Flash_SpliteAddr(memAddress,&byte2,&byte3,&byte4);	//地址分解
    
    	__Select_Flash();				//CS=0
    	SPI_TransmitOneByte(0x03);      //Command=0x03, read data
    	SPI_TransmitOneByte(byte2);		//transmit 24bit address
    	SPI_TransmitOneByte(byte3);
    	SPI_TransmitOneByte(byte4);
    	//DMA方式连续接收256个字��?
    	printf("Reading Page3 in DMA mode.\r\n");
    	HAL_SPI_Receive_DMA(&hspi2,bufPageRead,FLASH_PAGE_SIZE);
    }
    
    //DMA接收完成中断回调函数
    void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi)
    {
    	__Deselect_Flash();		//CS=1, 结束SPI传输过程
    	Flash_Wait_Busy();
    	printf("DMA reading complete.\r\n");
    	printf("Page3[26] = %d\r\n",bufPageRead[26]);
    	printf("DMA reading complete.\r\n");
    	printf("Page3[205] = %d\r\n",bufPageRead[205]);
    	printf("** Reselect menu or reset **\r\n");
    }
    /* USER CODE END 1 */

    4、main.c

    /* USER CODE BEGIN Includes */
    #include "keyled.h"
    #include "w25flash.h"
    #include <stdio.h>
    /* USER CODE END Includes */
     /* USER CODE BEGIN 2 */
      printf("Test:SPI-DMA Read/Write: \r");
      printf("16Mbit Flash Memory;\r\n");
      Flash_TestReadStatus(); //读取DeviceID, SR1,SR2
    
      //显示菜单
      printf("[S2]KeyUp   = Erase Chip;\r\n");
      printf("[S3]KeyDown = Erase Sector0;\r\n");
      printf("[S4]KeyLeft = Write Page3;\r\n");
      printf("[S5]KeyRight= Read Page3;\r\n");
    
      // MCU output low level LED light is on
      LED1_OFF();
      LED2_OFF();
      LED3_OFF();
      LED4_OFF();
      /* USER CODE END 2 */

            定义了两个256字节的数组bufPageRead和bufPageWrite,分别用作接收和发送数据的缓冲区,用于读取或写入一个页的数据。因为要在不同的函数里使用,所以定义为全局变量。函数Flash_TestWriteDMA()以DMA方式写入一个页共256字节的数据。它实际上是重新实现了“页编程”指令,在发送了指令码0x02和3字节的地址数据后,发送后面的256字节的写入数据时,使用了HAL_SPI_Transmit_DMA(),而不是像函数Flash_WriteInPage()里那样,使用SPI阻塞式传输函数HAL_SPI_Transmit()发送数据。 

        /* USER CODE BEGIN 3 */
    	  KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);
    
    	  switch(curKey)
    	  {
    	  	case KEY_UP:	//S2
    	  		printf("Erasing chip, about 30sec...\r\n");
    	  		Flash_EraseChip();				//擦除整个芯片
    	  		printf("Chip is erased.\r\n");
    	  		printf("** Reselect menu or reset **\r\n");
    		  	LED1_ON();
    		  	LED2_OFF();
    		  	LED3_OFF();
    		  	LED4_OFF();
    	  		break;
    
    	  	case KEY_DOWN:	//S3
    	  		printf("Erasing Sector 0(16 pages)...\r\n");
    	  		uint32_t globalAddr=0;
    	  		Flash_EraseSector(globalAddr);	//擦除扇区0
    	  		printf("Sector 0 is erased.\r\n");
    	  		printf("** Reselect menu or reset **\r\n");
    		  	LED1_OFF();
    		  	LED2_ON();
    		  	LED3_OFF();
    		  	LED4_OFF();
    	  		break;
    
    	  	case KEY_LEFT:	//S4
    	  		Flash_TestWriteDMA();			//测试写入Page 3
    		  	LED1_OFF();
    		  	LED2_OFF();
    		  	LED3_ON();
    		  	LED4_OFF();
    	  		break;
    
    	  	case KEY_RIGHT://S5
    	  		Flash_TestReadDMA();			//测试读取Page 3
    	  		LED1_OFF();
    	  		LED2_OFF();
    	  		LED3_OFF();
    	  		LED4_ON();
    	  		break;
    	  	default:
    	  		LED1_OFF();
    	  		LED2_OFF();
    	  		LED3_OFF();
    	  		LED4_OFF();
    	  		break;
    	  }
    
    	  HAL_Delay(500);	//延时500,消除按键后抖动
      }
      /* USER CODE END 3 */

            函数Flash_TestWriteDMA()里启动DMA传输后就退出了,由DMA去完成后面的数据传输。传输完成后会产生DMA传输完成事件中断,关联的回调函数是HAL_SPI_TxCpltCallback()。重新实现此回调函数,在此函数里,首先要将W25Q16的片选信号CS置1,以结束SPI通信过程,然后需要等待BUSY位变为0。这就是以DMA方式实现指令0x02的页编程数据写入过程。函数Flash_TestReadDMA()以DMA方式读取一个页的256字节的数据。在发送了指令码0x03和3字节的地址数据后,调用函数HAL_SPI_Receive_DMA()以DMA方式读取后续256字节的数据。

            函数Flash_TestReadDMA()启动DMA传输后就退出了,由DMA去完成后面的数据传输。接收完256字节数据后,产生DMA传输完成事件中断,关联的回调函数是HAL_SPI_RxCpltCallback(),在此函数里,首先将W25Q16的片选信号CS置1,以结束SPI通信过程。然后显示了从数据缓冲区内随意两个位置读出的数,以验证写入和读出的数据是否一致。

    /* USER CODE BEGIN 4 */
    //串口打印
    int __io_putchar(int ch)
    {
    	HAL_UART_Transmit(&huart6, (uint8_t*)&ch, 1, 0xFFFF);
    	return ch;
    }
    /* USER CODE END 4 */

            函数Flash_TestWriteDMA()里启动DMA传输后就退出了,由DMA去完成后面的数据传输。传输完成后会产生DMA传输完成事件中断,关联的回调函数是HAL_SPI_TxCpltCallback()。重新实现此回调函数,在此函数里,首先要将W25Q16的片选信号CS置1,以结束SPI通信过程,然后需要等待BUSY位变为0。这就是以DMA方式实现指令0x02的页编程数据写入过程。函数Flash_TestReadDMA()以DMA方式读取一个页的256字节的数据。在发送了指令码0x03和3字节的地址数据后,调用函数HAL_SPI_Receive_DMA()以DMA方式读取后续256字节的数据。

            函数Flash_TestReadDMA()启动DMA传输后就退出了,由DMA去完成后面的数据传输。接收完256字节数据后,产生DMA传输完成事件中断,关联的回调函数是HAL_SPI_RxCpltCallback(),在此函数里,首先将W25Q16的片选信号CS置1,以结束SPI通信过程。然后显示了从数据缓冲区内随意两个位置读出的数,以验证写入和读出的数据是否一致。

    三、下载、运行

            可以先擦除扇区0。如果擦除后读取Page 3的数据,读出的数据都是255,即擦除后的状态。向Page 3写入数据后再读出,会看到读出的数据与写入的数据一致,说明功能正确。

    作者:wenchm

    物联沃分享整理
    物联沃-IOTWORD物联网 » 细说STM32F407单片机DMA方式读写SPI FLASH W25Q16BV

    发表回复