STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64

随言:

为后面的QSPI内存映射铺垫。

为芯片内执行 (XIP) 执行代码。

 

参考例程:

C:\Users\admin\STM32Cube\Repository\STM32Cube_FW_F7_V1.16.0\Projects\STM32F723E-Discovery\Examples\QSPI\QSPI_ReadWrite

 

源码链接:

https://download.csdn.net/download/sudaroot/13092140

 

QSPI介绍:

下面内容摘自《STM32H7xx参考手册中文版.PDF》

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第1张图片

QSPI控制Flash W25Q64芯片用间接模式。

 

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第2张图片

指令阶段
这一阶段,将在 QUADSPI_CCR[7:0] 寄存器的 INSTRUCTION 字段中配置的一条 8 位指令 发送到 FLASH,指定待执行操作的类型。
尽管大多数 FLASH 从 IO0/SO 信号(单线 SPI 模式)只能以一次 1 位的方式接收指令,但指 令阶段可选择一次发送 2 位(在双线 SPI 模式中通过 IO0/IO1)或一次发送 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[9:8] 寄存器中的 IMODE[1:0] 字 段进行配置。
若 IMODE = 00,则跳过指令阶段,命令序列从地址阶段(如果存在)开始。

 

地址阶段
在地址阶段,将 1-4 字节发送到 FLASH,指示操作地址。待发送的地址字节数在
QUADSPI_CCR[13:12] 寄存器的 ADSIZE[1:0] 字段中进行配置。在间接模式和自动轮询模 式下,待发送的地址字节在 QUADSPI_AR 寄存器的 ADDRESS[31:0] 中指定。在内存映射 模式下,则通过 AXI(来自于 Cortex® 或 DMA)直接给出地址。
地址阶段可一次发送 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[11:10] 寄存器中的 ADMODE[1:0] 字段进行配置。
若 ADMODE = 00,则跳过地址阶段,命令序列直接进入下一阶段(如果存在)。

 

交替字节阶段

不常用,直接设置跳过。

ABMODE = 00,则跳过交替字节阶段,命令序列直接进入下一阶段(如果存在)。

 

空指令周期阶段
在空指令周期阶段,给定的 1-31 个周期内不发送或接收任何数据,目的是当采用更高的时钟频 率时,给 FLASH 留出准备数据阶段的时间。这一阶段中给定的周期数在 QUADSPI_CCR[22:18] 寄存器的 DCYC[4:0] 字段中指定。在 SDR 和 DDR 模式下,持续时间被指定为一定个数的全 时钟周期。
若 DCYC 为零,则跳过空指令周期阶段,命令序列直接进入数据阶段(如果存在)。
空指令周期阶段的操作模式由 DMODE 确定。
为确保数据信号从输出模式转变为输入模式有足够的“周转”时间,使用双线和四线模式从FLASH 接收数据时,至少需要指定一个空指令周期。

 

数据阶段
在数据阶段,可从 FLASH 接收或向其发送任意数量的字节。
在间接模式和自动轮询模式下,待发送/接收的字节数在 QUADSPI_DLR 寄存器中指定。
在间接写入模式下,发送到 FLASH 的数据必须写入 QUADSPI_DR 寄存器。在间接读取模 式下,通过读取 QUADSPI_DR 寄存器获得从 FLASH 接收的数据。
在内存映射模式下,读取的数据通过 AXI 直接发送回 Cortex 或 DMA。
数据阶段可一次发送/接收 1 位(在单线 SPI 模式中通过 SO)、2 位(在双线 SPI 模式中通过 IO0/IO1)或 4 位(在四线 SPI 模式中通过 IO0/IO1/IO2/IO3)。这可通过 QUADSPI_CCR[15:14] 寄存器中的 ABMODE[1:0] 字段进行配置。
若 DMODE = 00,则跳过数据阶段,命令序列在拉高 nCS 时立即完成。这一配置仅可用于 仅间接写入模式。

 

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第3张图片

 

 

STM32CubeIDE:

硬件:STM32H743IIT6 + W25Q64

1、配置时钟树,QSPI时钟是240MHz:

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第4张图片

 

2、设置QSPI参数。

Clock Prescaler:时钟分频系数。内部计算默认+1,FCLK(qspi) = Fquadspi_ker_ck / (Clock Prescaler + 1)。

上面时钟树设置的QSPI时钟是240MHz,而W25Q64芯片最大支持133MHz,我想2分频成120MHz。由于内部计算默认加1了,故分频240MHz / (2 - 1) = 120MHz.

Fifo Threshold:FIFO 阈值级别。设置FIFO阈值为4,即如果 FIFO 中存在 5 个以上空闲字节可供读或写,则 FTF 置 1。

Sample Shifting:采样移位。默认情况下,QUADSPI 在 FLASH 驱动数据后过半个 CLK 周期开始采集数据。使用该
位,可考虑外部信号延迟,推迟数据采集。0:不发生移位 ;1:移位半个周期。

Flash Size:Flash芯片大小。FSIZE+1 是对 FLASH 寻址所需的地址位数。

注:在间接模式下,FLASH 容量最高可达4GB(使用 32 位进行寻址),但在内存映射模式下的可寻址空间限制为 256MB。

W25Q64大小是64Mbit = 8MByte = 8388608Byte 即 2的23次方。但是内部默认+1,故填写23 - 1.

Chip select high time:片选高电平时间,根据W25Q64芯片手册的tSHSL参数设置,内部默认+1个周期。

由于W25Q64的tSHSL最小是50ns,故1s / 120MHz = 8.3ns。那么Chip select high time >= (50 / 8.3) - 1 , 考虑走线等因素结果向上加1.

Clock Mode:nCS 为高电平(片选释放)时,CLK 必须保持电平。对应的就是SPI协议的模式 0/模式 3 (Mode 0 / mode 3)。

根据自己芯片选择,W25Q64兼容两种时序,故选择高低电平都可行。

Flash ID:FLASH 选择。挂载在Bank1就叫 Flash ID1,只有在双QSPI芯片时操作单闪存时才有用。

Dual-flash mode:双闪存模式。该位激活双闪存模式,同时使用两个外部 FLASH 以将吞吐量和容量扩大一倍。

 

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第5张图片

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第6张图片

最后生成代码。

 

编程:

编程前,先讲一下编程思路。

1、W25Q64芯片在上电默认通讯方式是SPI,模式1或者模式3.

2、即如果我们要使用QSPI方式通讯,那么一定要先用SPI方式设置芯片下一次通讯方式为QSPI.

对此ST的HAL库代码需要用户自己先配置QSPI通讯的方式,支持单线,双线和四线通讯。

 

首先编程

第一步先把HAL_QSPI_Command()配置指令、地址、交替字节、空指令周期数和数据,以几线方式通讯。对应的就是下图。

第二步才是收发数据HAL_QSPI_Transmit()或HAL_QSPI_Receive();

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第7张图片

看看发送指令函数HAL_StatusTypeDef HAL_QSPI_Command()参数中的命令结构体QSPI_CommandTypeDef。

把结构体成员整理了一下顺序,和上图的四线模式时序一样的顺序。

其实就是上图时序几个步骤设置几个数据IO通讯或者跳过。

typedef struct
{
  uint32_t InstructionMode; 		// 指令模式,可设置使用 单线 双线 四线通讯,设0则跳过发送指令。
  uint32_t Instruction;  			// 指令值
 
  uint32_t AddressMode;        		// 地址模式,可设置使用 单线 双线 四线通讯,设0则跳过发送地址。
  uint32_t AddressSize; 			// 地址位数,可8bit 16bit 24bit 32bit      
  uint32_t Address;            		// 地址值
  
  uint32_t AlternateByteMode; 		// 交替模式,常见设0则跳过即可。
  uint32_t AlternateBytesSize;		// 交替字节位数,可8bit 16bit 24bit 32bit      
  uint32_t AlternateBytes;    		// 交替字节   
       
  uint32_t DummyCycles;             // 空指令周期
  
  uint32_t DataMode;           		// 数据模式,可设置使用 单线 双线 四线通讯,设0则跳过发送数据。
  uint32_t NbData;             		// 发送或接收数据长度
  
  uint32_t DdrMode;            		// DDR(双倍数据速率)模式,设0禁止。
  uint32_t DdrHoldHalfCycle;   		// DDR模式下数据输出延时
  
  uint32_t SIOOMode;          		// 在每个事务中发送指令或者只发一次指令
}QSPI_CommandTypeDef;

下一步就是调用QSPI收发数据函数

HAL_StatusTypeDef HAL_QSPI_Transmit (QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout);
HAL_StatusTypeDef HAL_QSPI_Receive  (QSPI_HandleTypeDef *hqspi, uint8_t *pData, uint32_t Timeout);

可以看到没有写接收的字节长度,因为长度是在HAL_QSPI_Command()参数中QSPI_CommandTypeDef的NbData已指定。

 

1、QSPI单线读取ID

QSPI单线即标准SPI协议,且W25Q64上电默认是标准的SPI协议,故无需额外设置。

// 以标准的SPI协议测试读ID指令
uint16_t W25Qx_SPI_ReadID(QSPI_HandleTypeDef *hqspi)
{
  uint8_t temp[2] = {0};
  QSPI_CommandTypeDef      s_command;

  s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;		// 指令单线传输
  s_command.Instruction       = MANUFACTURER_DEVICE_ID;			// 读ID指令0x90
  s_command.AddressMode       = QSPI_ADDRESS_1_LINE;			// 地址单线传输
  s_command.Address			  = 0;								// 地址 0
  s_command.AddressSize		  = QSPI_ADDRESS_24_BITS;			// 地址 24bit
  s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;		// 跳过交替字节模式
  s_command.DummyCycles       = 0;								// 空指令周期数
  s_command.DataMode          = QSPI_DATA_1_LINE;				// 数据单线传输
  s_command.NbData			  = 2;								// 数据接收长度
  s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;			// 禁止DDR模式
  s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;		// 禁止DDR延时
  s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;  		// 在每个事务中发送指令

  /* Send the command */
  if (HAL_QSPI_Command(hqspi, &s_command, 1000) != HAL_OK)
  {
    return HAL_ERROR;
  }

  HAL_QSPI_Receive(hqspi, temp, 1000);
  printf("ID = %X %X\r\n", temp[0], temp[1]);

  return HAL_OK;
}

打印出“ID = EF 16”, W25Q64读取ID正确。

 

2、QSPI四线读取ID

1、先用QSPI单线(标准SPI)协议把W25Q64通讯设成四线QSPI。

先检查一下寄存器2的QE标志位是否为1,然后发送QPI 模式(0x38)指令启动QSPI。

我的芯片默认寄存器2的QE标志位是为1,故直接设置QPI.再读ID.

uint16_t W25Qx_QSPI_ReadID( QSPI_HandleTypeDef *hqspi )
{
  uint8_t temp[2] = {0};
  QSPI_CommandTypeDef      s_command = {0};

  // SPI 启动QPI模式
  s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;		// 指令QSPI单线传输
  s_command.Instruction       = ENTER_QPI_MODE;					// 指令,0x38使能QPI模式
  s_command.AddressMode       = QSPI_ADDRESS_NONE;				// 地址跳过
  s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;		// 交替字节模式跳过
  s_command.DummyCycles       = 0;								// 空指令周期数
  s_command.DataMode          = QSPI_DATA_NONE;					// 数据模式跳过
  s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;			// 禁止DDR模式
  s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;		// 禁止DDR模式数据延时
  s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;  		// 在每个事务中发送指令

  /* Send the command */
  if (HAL_QSPI_Command(hqspi, &s_command, 1000) != HAL_OK)
  {
    return HAL_ERROR;
  }

  // QSPI 读ID
  s_command.InstructionMode   = QSPI_INSTRUCTION_4_LINES;		// 指令QSPI四线传输
  s_command.Instruction       = MANUFACTURER_DEVICE_ID;			// 指令,0x90读ID
  s_command.AddressMode       = QSPI_ADDRESS_4_LINES;			// 地址QSPI四线传输
  s_command.Address			  = 0;								// 地址 0
  s_command.AddressSize		  = QSPI_ADDRESS_24_BITS;			// 地址 24bit
  s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;		// 交替字节模式跳过
  s_command.DummyCycles       = 0;								// 空指令周期数
  s_command.DataMode          = QSPI_DATA_4_LINES;				// 数据QSPI四线传输
  s_command.NbData			  = 2;								// 数据长度 1
  s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;			// 禁止DDR模式
  s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;		// 禁止DDR模式数据延时
  s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;  		// 在每个事务中发送指令

  /* Send the command */
  if (HAL_QSPI_Command(hqspi, &s_command, 1000) != HAL_OK)
  {
	  return HAL_ERROR;
  }

  HAL_QSPI_Receive(hqspi, temp, 1000);
  printf("QSPI, ID = %X %X\r\n", temp[0], temp[1]);

  return HAL_OK;
}

打印出“ QSPI,  = EF 16 ”, W25Q64读取ID正确。

 

至于后面的后面的读写和标准SPI编程一样,微微改动即可,就不写了,直接上代码了。

 

3、状态轮询模式

状态轮询模式实际上就是读取某一W25Q64寄存器的值,进行and 或者 or 运算,与匹配值对比。

若一致运算结果与结果一致就退出,HAL_QSPI_AutoPolling()返回0表正确。

 

下面是官方例程代码。

typedef struct
{
  uint32_t Match;           // 匹配值    
  uint32_t Mask;            // 掩码    
  uint32_t Interval;        // 两次读操作间CLK周期数。
  uint32_t StatusBytesSize; // 指定接收到的状态字节的大小
  uint32_t MatchMode;       // 用于确定匹配项的方法,AND或者OR运算   
  uint32_t AutomaticStop;   // 指定匹配后是否停止自动轮询 
}QSPI_AutoPollingTypeDef;
  /* Configure automatic polling mode to wait the QUADEN bit=1 and WIP bit=0 */
  s_config.Match           = QSPI_SR_QUADEN;
  s_config.Mask            = QSPI_SR_QUADEN|QSPI_SR_WIP;
  s_config.MatchMode       = QSPI_MATCH_MODE_AND;
  s_config.StatusBytesSize = 1;
  s_config.Interval        = 0x10;
  s_config.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;

  s_command.InstructionMode   = QSPI_INSTRUCTION_4_LINES;
  s_command.Instruction       = READ_STATUS_REG_CMD;
  s_command.DataMode          = QSPI_DATA_4_LINES;

  if (HAL_QSPI_AutoPolling(hqspi, &s_command, &s_config, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
  {
    return HAL_ERROR;
  }

  return HAL_OK;

过程大概是:

s_command写好读取的命令和协议,发送。

读取到的W25Q64某一寄存器的值,与Mask进行运算,运算方式由MatchMode指定,

若运算结果和Match一致则匹配成功。

 

 

4、看手册写驱动

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第8张图片

 

看手册会发现有很多读指令。每个读指令用法都不一样,看红框圈出来的内容,

如(1-4-4)代表的是传输的三个步骤使用几线传输数据,指令用单线传输,地址和数据都是四线传输。

现在看读指令Fast Read Quad I/O (EBh),在地址传输完成后,要等3个字节,每个字节传输需要2个时钟周期,即6个时钟周期才能接收到数据。

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第9张图片

 

 

 

 

下面是完整的驱动代码,我移植野火的QSPI驱动。

/*
 * w25qxx_qspi.c
 *
 *  Created on: 2020年10月19日
 *      Author: sudaroot
 */
#include 
#include 
#include "stm32h7xx_hal_qspi.h"

extern QSPI_HandleTypeDef hqspi;

static void W25Qx_QSPI_Delay(uint32_t ms);
static uint8_t W25Qx_QSPI_Addr_Mode_Init(void);
static uint8_t W25Qx_QSPI_ResetMemory (void);
static uint8_t W25Qx_QSPI_WriteEnable (void);
static uint8_t W25Qx_QSPI_AutoPollingMemReady  (uint32_t Timeout);

/**
 * @brief  初始化QSPI存储器
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_Init(void)
{
	QSPI_CommandTypeDef s_command;
	uint8_t value = W25QxJV_FSR_QE;

	/* QSPI存储器复位 */
	if (W25Qx_QSPI_ResetMemory() != QSPI_OK)
	{
		return QSPI_NOT_SUPPORTED;
	}

	/* 使能写操作 */
	if (W25Qx_QSPI_WriteEnable() != QSPI_OK)
	{
		return QSPI_ERROR;
	}
	/* 设置四路使能的状态寄存器,使能四通道IO2和IO3引脚 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = WRITE_STATUS_REG2_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.DummyCycles = 0;
	s_command.NbData = 1;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
	/* 配置命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	/* 传输数据 */
	if (HAL_QSPI_Transmit(&hqspi, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	/* 自动轮询模式等待存储器就绪 */
	if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK)
	{
		return QSPI_ERROR;
	}

	/* 配置地址模式为 4 字节 , 非W25Q256直接跳过*/
	if (sFLASH_ID != 0XEF4019)
		return QSPI_OK;

	if (W25Qx_QSPI_Addr_Mode_Init() != QSPI_OK)
	{
		return QSPI_ERROR;
	}

	return QSPI_OK;
}

/**
 * @brief  检查地址模式不是4字节地址,配置为4字节
 * @retval QSPI存储器状态
 */
static uint8_t W25Qx_QSPI_Addr_Mode_Init(void)
{
	QSPI_CommandTypeDef s_command;
	uint8_t reg;
	/* 初始化读取状态寄存器命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = READ_STATUS_REG3_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.DummyCycles = 0;
	s_command.NbData = 1;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 配置命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	/* 接收数据 */
	if (HAL_QSPI_Receive(&hqspi, ®, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 检查寄存器的值 */
	if ((reg & W25Q256FV_FSR_4ByteAddrMode) == 1)    // 4字节模式
	{
		return QSPI_OK;
	}
	else    // 3字节模式
	{
		/* 配置进入 4 字节地址模式命令 */
		s_command.Instruction = Enter_4Byte_Addr_Mode_CMD;
		s_command.DataMode = QSPI_DATA_NONE;

		/* 配置并发送命令 */
		if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
				!= HAL_OK)
		{
			return QSPI_ERROR;
		}

		/* 自动轮询模式等待存储器就绪 */
		if (W25Qx_QSPI_AutoPollingMemReady(
				W25QxJV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK)
		{
			return QSPI_ERROR;
		}

		return QSPI_OK;
	}
}

/**
 * @brief  从QSPI存储器中读取大量数据.
 * @param  pData: 指向要读取的数据的指针
 * @param  ReadAddr: 读取起始地址
 * @param  Size: 要读取的数据大小
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_FastRead(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
	QSPI_CommandTypeDef s_command;

	if(Size == 0)	return QSPI_OK;

	/* 初始化读命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = QUAD_INOUT_FAST_READ_CMD;
	s_command.AddressMode = QSPI_ADDRESS_4_LINES;
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;
	s_command.Address = ReadAddr;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_4_LINES;
	s_command.DummyCycles = 0;
	s_command.NbData = Size;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 配置命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 接收数据 */
	if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  从QSPI存储器中读取大量数据.
 * @note   改指令只能使用在50MHz一下,本配置下不好用
 * @param  pData: 指向要读取的数据的指针
 * @param  ReadAddr: 读取起始地址
 * @param  Size: 要读取的数据大小
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_Read(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
	QSPI_CommandTypeDef s_command;
	/* 初始化读命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = READ_CMD;    //READ_CMD;
	s_command.AddressMode = QSPI_ADDRESS_1_LINE;
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;
	s_command.Address = ReadAddr;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.DummyCycles = 0;
	s_command.NbData = Size;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 配置命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 接收数据 */
	if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  将大量数据写入QSPI存储器
 * @param  pData: 指向要写入数据的指针
 * @param  WriteAddr: 写起始地址
 * @param  Size: 要写入的数据大小
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_Write(uint8_t *pData, uint32_t WriteAddr, uint32_t Size)
{
	QSPI_CommandTypeDef s_command;
	uint32_t end_addr, current_size, current_addr;
	/* 计算写入地址和页面末尾之间的大小 */
	current_addr = 0;

	while (current_addr <= WriteAddr)
	{
		current_addr += W25QxJV_PAGE_SIZE;
	}
	current_size = current_addr - WriteAddr;

	/* 检查数据的大小是否小于页面中的剩余位置 */
	if (current_size > Size)
	{
		current_size = Size;
	}

	/* 初始化地址变量 */
	current_addr = WriteAddr;
	end_addr = WriteAddr + Size;

	/* 初始化程序命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = QUAD_INPUT_PAGE_PROG_CMD;
	s_command.AddressMode = QSPI_ADDRESS_1_LINE;
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_4_LINES;
	s_command.DummyCycles = 0;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 逐页执行写入 */
	do
	{
		s_command.Address = current_addr;
		s_command.NbData = current_size;

		/* 启用写操作 */
		if (W25Qx_QSPI_WriteEnable() != QSPI_OK)
		{
			return QSPI_ERROR;
		}

		/* 配置命令 */
		if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
				!= HAL_OK)
		{
			return QSPI_ERROR;
		}

		/* 传输数据 */
		if (HAL_QSPI_Transmit(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
				!= HAL_OK)
		{
			return QSPI_ERROR;
		}

		/* 配置自动轮询模式等待程序结束 */
		if (W25Qx_QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)
		{
			return QSPI_ERROR;
		}

		/* 更新下一页编程的地址和大小变量 */
		current_addr += current_size;
		pData += current_size;
		current_size =
				((current_addr + W25QxJV_PAGE_SIZE) > end_addr) ?
						(end_addr - current_addr) : W25QxJV_PAGE_SIZE;
	} while (current_addr < end_addr);
	return QSPI_OK;
}

/**
 * @brief  擦除QSPI存储器的指定块
 * @param  BlockAddress: 需要擦除的块地址
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_Erase_Block(uint32_t BlockAddress)
{
	QSPI_CommandTypeDef s_command;
	/* 初始化擦除命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = SECTOR_ERASE_CMD;
	s_command.AddressMode = QSPI_ADDRESS_1_LINE;
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;
	s_command.Address = BlockAddress;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_NONE;
	s_command.DummyCycles = 0;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 启用写操作 */
	if (W25Qx_QSPI_WriteEnable() != QSPI_OK)
	{
		return QSPI_ERROR;
	}

	/* 发送命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 配置自动轮询模式等待擦除结束 */
	if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  擦除整个QSPI存储器
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_Erase_Chip(void)
{
	QSPI_CommandTypeDef s_command;
	/* 初始化擦除命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = CHIP_ERASE_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_NONE;
	s_command.DummyCycles = 0;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 启用写操作 */
	if (W25Qx_QSPI_WriteEnable() != QSPI_OK)
	{
		return QSPI_ERROR;
	}
	/* 发送命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	/* 配置自动轮询模式等待擦除结束 */
	if (W25Qx_QSPI_AutoPollingMemReady(W25QxJV_BULK_ERASE_MAX_TIME) != QSPI_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  读取QSPI存储器的当前状态
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_GetStatus(void)
{
	QSPI_CommandTypeDef s_command;
	uint8_t reg;
	/* 初始化读取状态寄存器命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = READ_STATUS_REG1_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.DummyCycles = 0;
	s_command.NbData = 1;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 配置命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	/* 接收数据 */
	if (HAL_QSPI_Receive(&hqspi, ®, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}
	/* 检查寄存器的值 */
	if ((reg & W25QxJV_FSR_BUSY) != 0)
	{
		return QSPI_BUSY;
	}
	else
	{
		return QSPI_OK;
	}
}

/**
 * @brief  返回QSPI存储器的配置
 * @param  pInfo: 在配置结构上的指针
 * @retval QSPI存储器状态
 */
uint8_t W25Qx_QSPI_GetInfo(QSPI_Info *pInfo)
{
	/* 配置存储器配置结构 */
	pInfo->FlashSize = W25QxJV_FLASH_SIZE;
	pInfo->EraseSectorSize = W25QxJV_SUBSECTOR_SIZE;
	pInfo->EraseSectorsNumber = (W25QxJV_FLASH_SIZE / W25QxJV_SUBSECTOR_SIZE);
	pInfo->ProgPageSize = W25QxJV_PAGE_SIZE;
	pInfo->ProgPagesNumber = (W25QxJV_FLASH_SIZE / W25QxJV_PAGE_SIZE);
	return QSPI_OK;
}

/**
 * @brief  复位QSPI存储器。
 * @param  hqspi: QSPI句柄
 * @retval 无
 */
static uint8_t W25Qx_QSPI_ResetMemory()
{
	QSPI_CommandTypeDef s_command;
	/* 初始化复位使能命令 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = RESET_ENABLE_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_NONE;
	s_command.DummyCycles = 0;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	/* 发送命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 发送复位存储器命令 */
	s_command.Instruction = RESET_MEMORY_CMD;
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return QSPI_ERROR;
	}

	s_command.InstructionMode = QSPI_INSTRUCTION_4_LINES;
	s_command.Instruction = RESET_ENABLE_CMD;

	/* 发送命令 */
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 发送复位存储器命令 */
	s_command.Instruction = RESET_MEMORY_CMD;
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return QSPI_ERROR;
	}

	W25Qx_QSPI_Delay(1);

	/* 配置自动轮询模式等待存储器就绪 */
	if (W25Qx_QSPI_AutoPollingMemReady(HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != QSPI_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  发送写入使能,等待它有效.
 * @param  hqspi: QSPI句柄
 * @retval 无
 */
static uint8_t W25Qx_QSPI_WriteEnable()
{
	QSPI_CommandTypeDef s_command;
	QSPI_AutoPollingTypeDef s_config;
	/* 启用写操作 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = WRITE_ENABLE_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_NONE;
	s_command.DummyCycles = 0;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		return QSPI_ERROR;
	}

	/* 配置自动轮询模式等待写启用 */
	s_config.Match = W25QxJV_FSR_WREN;
	s_config.Mask = W25QxJV_FSR_WREN;
	s_config.MatchMode = QSPI_MATCH_MODE_AND;
	s_config.StatusBytesSize = 1;
	s_config.Interval = 0x10;
	s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

	s_command.Instruction = READ_STATUS_REG1_CMD;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.NbData = 1;

	if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config,
			HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  读取存储器的SR并等待EOP
 * @param  hqspi: QSPI句柄
 * @param  Timeout 超时
 * @retval 无
 */
static uint8_t W25Qx_QSPI_AutoPollingMemReady(uint32_t Timeout)
{
	QSPI_CommandTypeDef s_command;
	QSPI_AutoPollingTypeDef s_config;
	/* 配置自动轮询模式等待存储器准备就绪 */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = READ_STATUS_REG1_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.DummyCycles = 0;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	s_config.Match = 0x00;
	s_config.Mask = W25QxJV_FSR_BUSY;
	s_config.MatchMode = QSPI_MATCH_MODE_AND;
	s_config.StatusBytesSize = 1;
	s_config.Interval = 0x10;
	s_config.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;

	if (HAL_QSPI_AutoPolling(&hqspi, &s_command, &s_config, Timeout) != HAL_OK)
	{
		return QSPI_ERROR;
	}
	return QSPI_OK;
}

/**
 * @brief  读取FLASH ID
 * @param 	无
 * @retval FLASH ID
 */
uint32_t W25Qx_QSPI_FLASH_ReadID(void)
{
	QSPI_CommandTypeDef s_command;
	uint32_t Temp = 0;
	uint8_t pData[3];
	/* 读取JEDEC ID */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = READ_JEDEC_ID_CMD;
	s_command.AddressMode = QSPI_ADDRESS_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DummyCycles = 0;
	s_command.NbData = 3;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		printf("QSPI_FLASH_ReadID ERROR!!!....\r\n");
		/* 用户可以在这里添加一些代码来处理这个错误 */
		while (1)
		{

		}
	}
	if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)
			!= HAL_OK)
	{
		printf("QSPI_FLASH_ReadID ERROR!!!....\r\n");
		/* 用户可以在这里添加一些代码来处理这个错误 */
		while (1)
		{

		}
	}

	Temp = (pData[2] | pData[1] << 8) | (pData[0] << 16);

	return Temp;
}

/**
 * @brief  读取FLASH Device ID
 * @param 	无
 * @retval FLASH Device ID
 */
uint32_t W25Qx_QSPI_FLASH_ReadDeviceID(void)
{
	QSPI_CommandTypeDef s_command;
	uint32_t Temp = 0;
	uint8_t pData[3];
	/*##-2-读取设备ID测试    ###########################################*/
	/* 读取制造/设备 ID */
	s_command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
	s_command.Instruction = READ_ID_CMD;
	s_command.AddressMode = QSPI_ADDRESS_1_LINE;
	s_command.AddressSize = QSPI_ADDRESS_24_BITS;
	s_command.Address = 0x000000;
	s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
	s_command.DataMode = QSPI_DATA_1_LINE;
	s_command.DummyCycles = 0;
	s_command.NbData = 2;
	s_command.DdrMode = QSPI_DDR_MODE_DISABLE;
	s_command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
	s_command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

	if (HAL_QSPI_Command(&hqspi, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
	{
		printf("QSPI_FLASH_ReadDeviceID ERROR!!!....\r\n");
		/* 用户可以在这里添加一些代码来处理这个错误 */
		while (1)
		{

		}
	}
	if (HAL_QSPI_Receive(&hqspi, pData, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)	!= HAL_OK)
	{
		printf("QSPI_FLASH_ReadDeviceID ERROR!!! ....\r\n");
		/* 用户可以在这里添加一些代码来处理这个错误 */
		while (1)
		{

		}
	}

	Temp = pData[1] | (pData[0] << 8);

	return Temp;
}

static void W25Qx_QSPI_Delay(uint32_t ms)
{
	HAL_Delay(ms);
}

 

/*
 * w25qxx_qspi.h
 *
 *  Created on: 2020年10月19日
 *      Author: sudaroot
 */

#ifndef INC_W25QX_QSPI_H_
#define INC_W25QX_QSPI_H_

#include "main.h"


/* Private typedef -----------------------------------------------------------*/
//#define  sFLASH_ID                       0xEF3015     //W25X16
//#define  sFLASH_ID                       0xEF4015	    //W25Q16
#define  sFLASH_ID                         0XEF4017     //W25Q64
//#define  sFLASH_ID                       0XEF4018     //W25Q128
//#define  sFLASH_ID                       0XEF4019     //W25Q256

/* QSPI Error codes */
#define QSPI_OK            ((uint8_t)0x00)
#define QSPI_ERROR         ((uint8_t)0x01)
#define QSPI_BUSY          ((uint8_t)0x02)
#define QSPI_NOT_SUPPORTED ((uint8_t)0x04)
#define QSPI_SUSPENDED     ((uint8_t)0x08)


/* W25QxJV Micron memory */
/* Size of the flash */
#define QSPI_FLASH_SIZE            24     /* 地址总线宽度访问整个内存空间 */
#define QSPI_PAGE_SIZE             256

/* QSPI Info */
typedef struct {
  uint32_t FlashSize;          /*!< 闪存大小 */
  uint32_t EraseSectorSize;    /*!< 擦除操作的扇区大小 */
  uint32_t EraseSectorsNumber; /*!< 擦除操作的扇区数 */
  uint32_t ProgPageSize;       /*!< 编程操作的页面大小 */
  uint32_t ProgPagesNumber;    /*!< 编程操作的页面数 */
} QSPI_Info;

/* Private define ------------------------------------------------------------*/
/*命令定义-开头*******************************/
/**
  * @brief  W25QxJV配置
  */
#define W25QxJV_FLASH_SIZE                  0x800000  	/* 64 MBits => 8MBytes */
#define W25QxJV_SECTOR_SIZE                 0x10000   	/* 128 sectors of 64KBytes */
#define W25QxJV_SUBSECTOR_SIZE              0x1000    	/* 2048 subsectors of 4kBytes */
#define W25QxJV_PAGE_SIZE                   0x100     	/* 65536 pages of 256 bytes */

#define W25QxJV_DUMMY_CYCLES_READ           4
#define W25QxJV_DUMMY_CYCLES_READ_QUAD      10

#define W25QxJV_BULK_ERASE_MAX_TIME         250000
#define W25QxJV_SECTOR_ERASE_MAX_TIME       3000
#define W25QxJV_SUBSECTOR_ERASE_MAX_TIME    800

/**
  * @brief  W25QxJV 指令
  */
/* 复位操作 */
#define RESET_ENABLE_CMD                     0x66
#define RESET_MEMORY_CMD                     0x99

#define ENTER_QPI_MODE_CMD                   0x38
#define EXIT_QPI_MODE_CMD                    0xFF

/* 识别操作 */
#define READ_ID_CMD                          0x90
#define DUAL_READ_ID_CMD                     0x92
#define QUAD_READ_ID_CMD                     0x94
#define READ_JEDEC_ID_CMD                    0x9F

/* 读操作 */
#define READ_CMD                             0x03
#define FAST_READ_CMD                        0x0B
#define DUAL_OUT_FAST_READ_CMD               0x3B
#define DUAL_INOUT_FAST_READ_CMD             0xBB
#define QUAD_OUT_FAST_READ_CMD               0x6B
#define QUAD_INOUT_FAST_READ_CMD             0xEB

/* 写操作 */
#define WRITE_ENABLE_CMD                     0x06
#define WRITE_DISABLE_CMD                    0x04

/* 寄存器操作 */
#define READ_STATUS_REG1_CMD                  0x05
#define READ_STATUS_REG2_CMD                  0x35
#define READ_STATUS_REG3_CMD                  0x15

#define WRITE_STATUS_REG1_CMD                 0x01
#define WRITE_STATUS_REG2_CMD                 0x31
#define WRITE_STATUS_REG3_CMD                 0x11


/* 编程操作 */
#define PAGE_PROG_CMD                        0x02
#define QUAD_INPUT_PAGE_PROG_CMD             0x32
#define EXT_QUAD_IN_FAST_PROG_CMD            0x12
#define Enter_4Byte_Addr_Mode_CMD            0xB7

/* 擦除操作 */
#define SECTOR_ERASE_CMD                     0x20    //0xD8擦:64K    0x52擦:32K     0x20擦:4K
#define CHIP_ERASE_CMD                       0xC7

#define PROG_ERASE_RESUME_CMD                0x7A
#define PROG_ERASE_SUSPEND_CMD               0x75


/* 状态寄存器标志 */
#define W25QxJV_FSR_BUSY                    ((uint8_t)0x01)    /*!< busy */
#define W25QxJV_FSR_WREN                    ((uint8_t)0x02)    /*!< write enable */
#define W25QxJV_FSR_QE                      ((uint8_t)0x02)    /*!< quad enable */
#define W25Q256FV_FSR_4ByteAddrMode         ((uint8_t)0x01)    /*!< 4字节地址模式 */

/*命令定义-结尾*******************************/


uint8_t W25Qx_QSPI_Init(void);
uint8_t W25Qx_QSPI_Erase_Block(uint32_t BlockAddress);
uint8_t W25Qx_QSPI_FastRead(uint8_t* pData, uint32_t ReadAddr, uint32_t Size);
uint8_t W25Qx_QSPI_Read(uint8_t* pData, uint32_t ReadAddr, uint32_t Size);
uint8_t W25Qx_QSPI_Write(uint8_t* pData, uint32_t WriteAddr, uint32_t Size);

uint32_t W25Qx_QSPI_FLASH_ReadDeviceID(void);
uint32_t W25Qx_QSPI_FLASH_ReadID(void);

#endif /* INC_W25QX_QSPI_H_ */

 

内存映射模式:

STM32CubeIDE QSPI间接模式和内存映射模式 读写W25Q64_第10张图片

手册中提醒了,QSPI FLASH寻址空间不能大于256MB,但是QSPI FLASH芯片可以大于256MB。

 

QSPI Flash映射到内存地址是0x9000 0000,芯片内部flash地址是0x0800 0000,别搞混了。HAL库有定义:

内存映射需要调用HAL的HAL_QSPI_MemoryMapped()函数,配置函数,调用。

只要你访问的地址是0x9000 0000,那么芯片自动去QSPI flash读取0地址的数据。

下面函数参数s_command配置的是使用W25Q64 qspi 的读指令,这个指令一般会使用读取速度最快的,所以是四线读取指令。

而s_mem_mapped_cfg参数,设置是用CS超时值和超时是否释放CS片选。

/**
  * @brief  Configure the QSPI in memory-mapped mode
  * @retval QSPI memory status
  */
static uint32_t QSPI_EnableMemoryMappedMode(QSPI_HandleTypeDef *QSPIHandle)
{
  QSPI_CommandTypeDef      s_command;
  QSPI_MemoryMappedTypeDef s_mem_mapped_cfg;

  /* Configure the command for the read instruction */
  s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
  s_command.Instruction       = QUAD_INOUT_FAST_READ_CMD;
  s_command.AddressMode       = QSPI_ADDRESS_4_LINES;
  s_command.AddressSize       = QSPI_ADDRESS_24_BITS;
  s_command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  s_command.DataMode          = QSPI_DATA_4_LINES;
  s_command.DummyCycles       = 6;
  s_command.DdrMode           = QSPI_DDR_MODE_DISABLE;
  s_command.DdrHoldHalfCycle  = QSPI_DDR_HHC_HALF_CLK_DELAY;
  s_command.SIOOMode          = QSPI_SIOO_INST_EVERY_CMD;

  /* Configure the memory mapped mode */
  s_mem_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
  s_mem_mapped_cfg.TimeOutPeriod     = 0;

  return HAL_QSPI_MemoryMapped(QSPIHandle, &s_command, &s_mem_mapped_cfg);
}

 

测试程序。main函数

int main(void)
{
	uint8_t temp1[50] = "hello sudaroot\r\n";
	uint8_t temp2[50] = {0};
	uint8_t *temp3 = (uint8_t*)QSPI_BASE;

  SCB_EnableICache();
  SCB_EnableDCache();
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_QUADSPI_Init();
  MX_USART1_UART_Init();
  W25Qx_QSPI_Init();
  W25Qx_QSPI_Erase_Block(0);
  W25Qx_QSPI_Write(temp1, 0, 15);
  W25Qx_QSPI_FastRead(temp2, 0, 15);
  printf("1: %s\r\n", temp2);

  QSPI_EnableMemoryMappedMode(&hqspi);
  memset(temp2, 0, 50);
  memcpy(temp2, temp3, 15);
  printf("2: %s\r\n", temp2);

  while (1)
  {

  }
}

现象:

 

全篇完。

本人博客仅仅代表我个人见解方便记录成长笔记。

若有与 看官老爷见解有冲突,我坚信看官老爷见解是对的,我的是错的。

感谢~!

 

 

 

 

 

 

 

 

你可能感兴趣的:(STM32)