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

目录

[一、 工程配置](#一、 工程配置)

[1、时钟、DEBUG、GPIO、USART6、Code Generator](#1、时钟、DEBUG、GPIO、USART6、Code Generator)

2、SPI2

3、NVIC

二、软件设计

[1、 FALSH、KEY_LED](#1、 FALSH、KEY_LED)

[2、 spi.h](#2、 spi.h)

[3、 spi.c](#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

cpp 复制代码
/* 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

cpp 复制代码
/* 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 */
cpp 复制代码
/* 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

cpp 复制代码
/* USER CODE BEGIN Includes */
#include "keyled.h"
#include "w25flash.h"
#include <stdio.h>
/* USER CODE END Includes */
cpp 复制代码
 /* 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()发送数据。

cpp 复制代码
    /* 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通信过程。然后显示了从数据缓冲区内随意两个位置读出的数,以验证写入和读出的数据是否一致。

cpp 复制代码
/* 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写入数据后再读出,会看到读出的数据与写入的数据一致,说明功能正确。

相关推荐
weixin_452600692 小时前
串行时钟保持芯片D1380/D1381,低功耗工作方式自带秒、分、时、日、日期、月、年的串行时钟保持芯片,每个月多少天以及闰年能自动调节
科技·单片机·嵌入式硬件·时钟·白色家电电源·微机串行时钟
森旺电子5 小时前
51单片机仿真摇号抽奖机源程序 12864液晶显示
单片机·嵌入式硬件·51单片机
不过四级不改名6777 小时前
蓝桥杯嵌入式备赛教程(1、led,2、lcd,3、key)
stm32·嵌入式硬件·蓝桥杯
小A1597 小时前
STM32完全学习——SPI接口的FLASH(DMA模式)
stm32·嵌入式硬件·学习
Rorsion7 小时前
各种电机原理介绍
单片机·嵌入式硬件
善 .10 小时前
单片机的内存是指RAM还是ROM
单片机·嵌入式硬件
超级码农ProMax10 小时前
STM32——“SPI Flash”
stm32·单片机·嵌入式硬件
Asa31911 小时前
stm32点灯Hal库
stm32·单片机·嵌入式硬件
end_SJ13 小时前
初学stm32 --- 外部中断
stm32·单片机·嵌入式硬件
gantengsheng13 小时前
基于51单片机和OLED12864的小游戏《贪吃蛇》
单片机·嵌入式硬件·游戏·51单片机