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

随言:

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

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

参考例程:

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

源码链接:

H743_QSPI_W25Q64.rar-嵌入式文档类资源-CSDN下载

QSPI介绍:

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

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

指令阶段
这一阶段,将在 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:

硬件:STM32H743IIT6 + W25Q64

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

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 以将吞吐量和容量扩大一倍。

最后生成代码。

编程:

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

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

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

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

首先编程

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

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

看看发送指令函数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、看手册写驱动

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

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

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

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

/*
 * w25qxx_qspi.c
 *
 *  Created on: 2020年10月19日
 *      Author: sudaroot
 */
#include <stdio.h>
#include <w25qx_qspi.h>
#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, &reg, 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, &reg, 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_ */

进入内存映射模式:

手册中提醒了,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)
  {

  }
}

现象:

退出内存映射模式:

ST官网有篇帖子说了怎么退出,不过我没测试。

https://community.st.com/s/question/0D50X00009XkaJuSAJ/stm32f7-qspi-exit-memory-mapped-mode

  全篇完。

本人是一个嵌入式未入门小白,博客仅仅代表我个人主观见解,记录成长笔记。
笔记是以最简单的方式,只展示最核心的原理。
若有与 大神大大 见解有歧义,我绝对坚信 大神大大 见解是对的,我的是错的。
若无积分等无法下载源码,可加入QQ群657407920下载交流经验。感谢~!

 

  • 34
    点赞
  • 143
    收藏
    觉得还不错? 一键收藏
  • 17
    评论
根据引用和引用的内容,可以总结出STM32h750 QSPI W25Q64驱动的一些关键信息。 首先,W25Q64是一种SPI NOR Flash芯片,它被连接到STM32h750的QSPI(Quad SPI)接口上。W25Q64的引脚连接为PB2、PB6、PF6、PF7、PF8和PF9。 在设置QSPI时,一些关键的配置参数需要注意。首先是时钟预分频器(clock prescaler),根据W25Q256的最高时钟频率为104MHz,因此需要将分频设置为2。其次是闪存大小(FLASH SIZE),W25Q64的大小为8MB,所以需要将设置为2的(22-1)次方。时钟模式(Clock Mode)应设置为Low,表示CLK空闲时为低电平。芯片选择(Chip Select)需要设置为High Time为5,以确保高电平持续时间大于50ns。 另外,为了保证正常的工作,所有的QSPI引脚都应该设置为very high,而NCS脚(PB6)必须设置为PULL-UP。关于为什么要设置为PULL-UP,具体原因在引用中没有提及。 最后,需要注意W25Q64与W25Q256之间的一些区别。首先是地址位数,W25Q64只支持24位地址,而W25Q256支持24位和32位地址。其次是读写状态寄存器的不同,W25Q64的读状态寄存器为05h和35h,而W25Q256的为05h、35h和15h。写状态寄存器也有所不同,W25Q64的为01h,而W25Q256的为01h、31h和11h。 综上所述,STM32h750的QSPI可以通过相应的配置来驱动W25Q64芯片。需要注意的是,具体的配置参数和引脚连接可能还取决于具体的硬件设计和应用需求。<span class="em">1</span><span class="em">2</span> #### 引用[.reference_title] - *1* [STM32H750 QSPI间接模式 W25Q64](https://blog.csdn.net/smallerlang/article/details/127921384)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [STM32H753 STM32H743 STM32H750 QSPI W25Q256 下载算法](https://blog.csdn.net/c101028/article/details/132073746)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值