当前位置: 首页 > article >正文

细说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写入数据后再读出,会看到读出的数据与写入的数据一致,说明功能正确。


http://www.kler.cn/a/449170.html

相关文章:

  • HTML基础学习(1)
  • 03.01、三合一
  • (2024.12)Ubuntu20.04安装openMVS<成功>.colmap<成功>和openMVG<失败>记录
  • 设计模式之【观察者模式】
  • C++算法第十二天
  • 深入了解Java在人工智能领域的最新应用
  • Python从0到100(七十九):神经网络-从0开始搭建过拟合和防过拟合模型
  • DINO对比去噪训练代码分析
  • 范德蒙矩阵(Vandermonde 矩阵)简介:意义、用途及编程应用
  • 图学习新突破:一个统一框架连接空域和频域
  • 《开启微服务之旅:Spring Boot 从入门到实践》(一)
  • 短视频矩阵源码开发部署全解析
  • CentOS修改hostname,导致无法连接(网络不工作)
  • 动手学深度学习-深度学习计算-1层和块
  • 如何实现圆形头像功能
  • 【IC】TSMC先进工艺发展历程--从N5到A16
  • 统信UOS(1070)系统如何进入root用户模式下操作
  • Java 实现日志文件大小限制及管理——以 Python Logging 为启示
  • redis编译安装(版本6.2.6)
  • 练14:DFS基础
  • [python SQLAlchemy数据库操作入门]-03.为行情设计数据库模型
  • 华为云语音交互SIS的使用案例(文字转语音-详细教程)
  • 【多线程进阶】重要!!!
  • 音视频学习(二十四):hls协议
  • 如何理解TCP/IP协议?如何理解TCP/IP协议是什么?
  • Unable to create data directory /var/lib/zookeeper/log/version-2