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

STM32 QSPI接口驱动GD/W25Qxx配置简要

STM32 QSPI接口GD/W25Qxx配置简要


  • 🥕阅读推荐有关介绍QSPI协议内容比较全面和详细的文章:《STM32F7QSPI学习笔记——读写N25Q128》
  • 📝本篇会具体涉及介绍Winbond(华邦)和GD(兆易创新) NOR flash相关型号指令差异。由于网络上可以搜索到很多相关QSPI相关知识内容,不对QSPI通讯内容做深度解析。
  • 🔖首先确保所使用的STM32型号,硬件上是否支持QSPI功能。
  • 由于GD25QXX和对应的W25Qxx 芯片型号较多,芯片出厂,默认状态寄存器关键位存在差异,在没有进行状态寄存器关键位做配置的情况下。两者的驱动代码不能互为通用,代码上可以通过型号辨别,以及对状态寄存器关键位的使能配置,可以整合为一套通用的驱动代码。
  • 🌿QSPI支持三种工作模式:间接模式、状态轮询模式和内存映射模式:
  • 间接模式:使用QSPI寄存器执行全部操作。
  • 状态轮询模式:周期性读取外部FLASH状态寄存器,当标志位置1时会产生中断(如擦除或烧写完成,产生中断)。
  • 内存映射模式:外部FLASH映射到微控制器地址空间,从而系统将其视作内部存储器进行访问。

在间接模式下也可以切换为内存映射模式进行访问,切换过程需要重新对QSPI外设进行复位和重新初始化QSPI操作,代码操作比较麻烦,好处就是提升了读取访问速度。如果需要进行擦除或编程操作,还需要切换为间接模式下进行。

  • QUADSPI 功能框图(见STM32H750参考手册)
    在这里插入图片描述
    在这里插入图片描述

图中BK2只有在双闪存模式下才需要。

  • 4线SPI结构框图
    在这里插入图片描述

  • 🧲QSPI通讯序列

  • QUADSPI 通过命令与 FLASH 通信 每条命令包括指令、地址、交替字节、空指令和数据这 五个阶段 任一阶段均可跳过,但至少要包含指令、地址、交替字节或数据阶段之一。
  • nCS 在每条指令开始前下降,在每条指令完成后再次上升
    在这里插入图片描述
  • 🌟对于这5个阶段的通讯内容,从STM32软件代码配置上的来说,包含了对QUADSPI 通信配置寄存器 (QUADSPI_CCR)的配置方法和要素。

  • STM32H7 QUADSPI 通信配置寄存器 (QUADSPI_CCR)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 📄对应的相关初始化代码:

static HAL_StatusTypeDef QSPI_SendCommand(uint32_t instruction,
                                          uint32_t address,
                                          uint32_t addressMode,
                                          uint32_t addressSize, 
                                          uint32_t dummyCycles, 
                                          uint32_t nbData, 
                                          uint32_t dataMode,
                                          uint32_t siooMode)
{
	QSPI_CommandTypeDef     sCommand = {0};
    sCommand.Instruction       = instruction;//指令段:填写指令
    	/*
QSPI_INSTRUCTION_NONE
QSPI_INSTRUCTION_1_LINE
QSPI_INSTRUCTION_2_LINES
QSPI_INSTRUCTION_4_LINES
*/
    sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//指令模式:在指令段使用几条线传输该指令参数:0 - 4线

	sCommand.Address           = address;//地址段:填写地址
    /*
QSPI_ADDRESS_NONE
QSPI_ADDRESS_1_LINE
QSPI_ADDRESS_2_LINES
QSPI_ADDRESS_4_LINES
*/
    sCommand.AddressMode       = addressMode;//地址模式:在地址段使用几条数据线传输该地址参数:0-4线
    /*
   小于或等于16MB容量选择QSPI_ADDRESS_24_BITS,大于16MB选择QSPI_ADDRESS_32_BITS 
    QSPI_ADDRESS_8_BITS
   QSPI_ADDRESS_16_BITS
    QSPI_ADDRESS_24_BITS
    QSPI_ADDRESS_32_BITS
*/
	sCommand.AddressSize       = addressSize;//地址长度
    sCommand.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;//交替字节模式,一般不使用
    /*
GD25Q64型号:
Quad Output Fast Read (6BH)指令:8个空指令周期
Quad I/O Fast Read (EBH)指令: 6个空指令周期
具体参数查看对应Nor flash手册
*/
    sCommand.DummyCycles       = dummyCycles;//空指令周期,具体参数查看对应Nor flash手册
	sCommand.NbData            = nbData;//数据长度
	/*
QSPI_DATA_NONE
QSPI_DATA_1_LINE
QSPI_DATA_2_LINES
QSPI_DATA_4_LINES
具体使用,查看对应Nor flash手册,对应指令
*/
    sCommand.DataMode          = dataMode;//数据模式,在数据传输段,用几条数据线传输数据
    sCommand.DdrMode           = QSPI_DDR_MODE_DISABLE;//SDR单闪存模式,不启用。
    sCommand.DdrHoldHalfCycle  = QSPI_DDR_HHC_ANALOG_DELAY;
    /*
    若 DMODE = 00,对应上面的sCommand.DataMode=QSPI_DATA_NONE则跳过数据阶段。
   QSPI_SIOO_INST_EVERY_CMD 在每个事务中发送指令
   QSPI_SIOO_INST_ONLY_FIRST_CMD 仅为第一条命令发送指令
    
*/
    sCommand.SIOOMode          = siooMode;	//在DMODE = 00,该参数设置无效。
	Status = HAL_QSPI_Command(&hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE);	
	return Status;
}

  • 🌿指令段:指令模式,传输指令一般为: QSPI_INSTRUCTION_1_LINE。也就是使用一条数据线来传输指令
  sCommand.InstructionMode   = QSPI_INSTRUCTION_1_LINE;//需要用一条数据线来传输指令
  • 🌿地址段:地址模式,传输地址指令一般为:QSPI_ADDRESS_NONE(不需要地址)、QSPI_ADDRESS_1_LINE(需要使用一条数据线用来传输地址指令)两种。
sCommand.AddressMode = QSPI_ADDRESS_NONE;//无需地址
sCommand.AddressMode = QSPI_ADDRESS_1_LINE;//需要一条数据线来传输地址指令
  • QSPI_ADDRESS_NONE(不需要地址)情况:读写Nor flash内部状态寄存器操作。
    在这里插入图片描述
    在这里插入图片描述
  • 🌿地址段:地址长度一般为:QSPI_ADDRESS_24_BITS(访问低于或等于16MB容量地址上的所有数据)、QSPI_ADDRESS_32_BITS(间接模式下可访问等于或大于32MB容量的地址所有数据:范围2^32 = 4GB。需要注意QUADSPI 采用内存映射模式下,即使 FLASH 容量更大,寻址空间也无法超过 256MB。)
  • 🔖32 位寻址下, Flash 容量最高可达 4GB,在内存映射模式下的可寻址空间限制为 256MB。 如果 DFM = 1,DDR双闪存模式,FSIZE 表示两个 Flash 容量的总和:512MB。
    在这里插入图片描述

s_command.AddressSize = QSPI_ADDRESS_24_BITS;            // 24位地址
s_command.AddressSize = QSPI_ADDRESS_32_BITS;            // 32位地址

对于通讯的整个过程,只需要留意3个状态:WIP(BUSY)用于查看擦除/编程操作是否正在进行、QE用于使能4线模式(Quad)和WREN写使能位其实和WIP位对应,该位决定接下来是否可以进行编程、擦写操作。

  • 📜GD25Q64读写测试:
    在这里插入图片描述
  • 📜W25Q64读写测试:
    在这里插入图片描述
  • 基于QSPI片外flash下载算法实现的片外启动APP程序:
    在这里插入图片描述

📗 NOR FLASH 存储器介绍

  • 🍁Winbond(华邦)W25Q64存储器功能控制结构图
    在这里插入图片描述
  • GD(兆易创新)GD25Q64
    在这里插入图片描述
  • 内存空间结构: 一页(Page)256字节,4K(4096 字节)为一个扇区(Sector),16个扇区为1块(Block),容量为8M字节,共有128个块,2048 个扇区。
  • 支持页编程。每页大小由256字节组成,每页的256字节用一次页编程指令即可完成。
  • 擦除指令通用: 16页(1个扇区4K)、32K、64K、全片擦除操作。

两者常规操作指令互为通用。在QE位都配置为使能状态情况下,除了在读取厂商ID有差异外,读取数据功能函数通用。测试了基于W25QXX制作的片外QSPI下载算法文件都可以兼容GD25XX使用。(前提是下载算法中没有对应闪存厂商ID校验,芯片相关状态寄存器位没有写保护)

🌼Nor Flash操作流程
  • 写入操作时:

写入操作前,必须先进行写使能位.
每个数据位只能由1改写为0,不能由0改写为1
写入数据前必须先擦除,擦除后,所有数据位变为1。.(删除目录实际上是填充0xff)
擦除必须按最小擦除单元进行(一个扇区:4kb)
连续写入多字节时,最多写入一页(256字节)的数据,超过页尾位置的数据,会回到页首覆盖写入
写入操作结束后,芯片进入忙状态,不响应新的读写操作

  • 读取操作时:

直接调用读取时序,快速读取(Fast Read Quad I/O (EBh)),需要QE=1,可以不需要使能Write Enable (WREN) (06H),没有页的限制,读取操作结束后不会进入忙状态,但不能在忙状态时读取.

📘Nor flash状态寄存器介绍

  • 🌿winbond(华邦)W25QXX状态寄存器访问指令
    在这里插入图片描述
  • 📄具体状态寄存器位说明
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
📘 GD25QXX状态寄存器
  • 🌿GD(兆易创新)GD25QXX状态寄存器总表:

在这里插入图片描述

🔰状态寄存器差异化说明

  • 🌿QE位:该位属于非易失性(non-volatile),数据在断电后仍然能够保持的特性。一旦该位配置位1,将一直保持其状态,不会因断电丢失。
  • 🔖winbond(华邦)W25QXX,该位出厂默认状态是根据芯片具体型号决定该位默认状态是否为1
  • When the QE bit is set to a 0 state (factory default for part numbers with ordering options “IM”&“JM”).
  • When the QE bit is set to a 1 (factory fixed default for part numbers with ordering options “IQ” & “JQ”),
  • 🔖GD(兆易创新)GD25Qxx,该位出厂默认状态是0,如需要使用QSPI 模式,必须先将该位配置为1才行。
  • ✨QE位的使能,对于QSPI 4线Quad访问方式尤为重要。

📘指令介绍

  • 🌿winbond(华邦)W25Q64指令:
    在这里插入图片描述
    在这里插入图片描述

  • 🌿GD(兆易创新)GD25Q64指令集

在这里插入图片描述

在这里插入图片描述

  • 🌾不同容量的nor flash型号,指令数量会有所不同,容量超过16MB型号,其指令会有所增加。

🛠状态寄存器配置

  • 🌿状态寄存器1,QE位配置流程说明

👉针对GD(兆易创新)GD25Q64,经测试,如需修改QE位,需要通过使用读取状态寄存器1(0x01)的命令,然后写2个字节数据(S0-S7、S8-S16),进行修改。

  • 🔖个人测试时,如果单独写状态寄存器2(0x31)的QE位,会写入不成功。目前不知道是什么原因。相同的函数针对W25Q64单独写QE位使能没有问题。
  • QE位是否配置成功,可以通过读状态寄存器(0x35),查询状态寄存器2的QE位。
  • Write Status Register (WRSR) (01H or 31H or 11H)
    The Write Status Register (WRSR) command allows new values to be written to the Status Register. Before it can be accepted, a Write Enable (WREN) command must previously have been executed. After the Write Enable (WREN)
    command has been decoded and executed, the device sets the Write Enable Latch (WEL).
    The Write Status Register (WRSR) command has no effect on S15, S10, S1 and S0 of the Status Register. For command code of “01H” / “31H” / “11H”, the Status Register bits S7~S0 / S15~S8 / S23~S16 would be written. CS# must be driven high after the eighth bit of the data byte has been latched in. Otherwise, the Write Status Register (WRSR) command is not executed. As soon as CS# is driven high, the self-timed Write Status Register cycle (whose duration is tW) is initiated. While the Write Status Register cycle is in progress, the Status Register may still be read to check the value of the Write In Progress (WIP) bit. The Write In Progress (WIP) bit is 1 during the self-timed Write Status Register cycle, and is 0 when it is completed. When the cycle is completed, the Write Enable Latch (WEL) is reset.
    The Write Status Register (WRSR) command allows the user to change the values of the Block Protect (BP4, BP3, BP2, BP1, and BP0) bits, to define the size of the area that is to be treated as read-only. The Write Status Register (WRSR) command also allows the user to set or reset the Status Register Protect (SRP1 and SRP0) bits in accordance with the Write Protect (WP#) signal. The Status Register Protect (SRP1 and SRP0) bits and Write Protect (WP#) signal allow the device to be put in the Hardware Protected Mode. The Write Status Register (WRSR) command is not executed once the Hardware Protected Mode is entered

  • 🧬 时序图:
    在这里插入图片描述

  • 🌿读状态寄存器代码实现:

int qspi_flash_read_status1(uint8_t *status) {//读状态寄存器1
  QSPI_CommandTypeDef command;

  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  command.AddressSize = QSPI_ADDRESS_24_BITS;
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

  command.Instruction = READ_STATUS_REG_CMD; //0x05,读取:S7-S0
  command.AddressMode = QSPI_ADDRESS_NONE;
  command.DataMode = QSPI_DATA_1_LINE;
  command.DummyCycles = 0;
  command.NbData = 1;//1个字节

  if (HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

  if (HAL_QSPI_Receive(&hqspi, status, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }
  return HAL_OK;
}

int qspi_flash_read_status2(uint8_t *status) {读状态寄存器2
  QSPI_CommandTypeDef command= {0};

  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  command.AddressSize = QSPI_ADDRESS_24_BITS;
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

  command.Instruction = READ_STATUS2_REG_CMD;//0x35读取:S15-S8
  command.AddressMode = QSPI_ADDRESS_NONE;
  command.DataMode = QSPI_DATA_1_LINE;
  command.DummyCycles = 0;
  command.NbData = 1;
 
  if (HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

  if (HAL_QSPI_Receive(&hqspi, status, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }
  return HAL_OK;
}
  • 🌿写状态寄存器代码实现:
int qspi_flash_write_status(uint8_t status1_val, uint8_t status2_val) {写状态寄存器1-2
	
  QSPI_CommandTypeDef command = {0};
  uint8_t buf[2];
  TxCplt = 0;
  qspi_flash_write_enable();//使能写状态寄存器命令0x06

  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  command.AddressSize = QSPI_ADDRESS_24_BITS;
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD;

  command.Instruction = WRITE_STATUS_REG_CMD;
  command.DummyCycles = 0;
  command.AddressMode = QSPI_ADDRESS_NONE;
  command.DataMode = QSPI_DATA_1_LINE;
  command.NbData = 2;//2个字节(S0-S7,S7-S16)
  buf[0] = status1_val;
  buf[1] = status2_val;

  if (HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

  if (HAL_QSPI_Transmit(&hqspi, buf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

  if (qspi_flash_atuo_polling_mem_ready() != HAL_OK) {
    return HAL_ERROR;
  }
  return HAL_OK;
}


📘读数据操作

  • 主要介绍4线QUAD操作指令。
  • 🌿Quad I/O Fast Read (EBH)
    Quad I/O快速读取命令可以通过在输入3字节地址(A23-A0)之后设置“连续读取模式”位(M7-0),从而进一步降低命令开销。如果“连续读模式”位(M5-4)=(1,0),则下一个四I/O快速读命令(在CS#升降之后)不需要EBH命令代码。如果“连续读模式”位(M5-4)=(1,0),下一个命令需要该命令代码,从而恢复正常操作。重置命令之前也可以用来在发出正常命令前重置(M7-0)。

在这里插入图片描述
在这里插入图片描述

⏰Dummy Cycle空指令周期
  • 🌿在STM32H7参考手册上,QUADSPI 通信配置寄存器 (QUADSPI_CCR),指定 CLK 周期数范围一般: 0 - 31。

对于stm32H7一般在快速读取数据时,默认设定的是6个Dummy Cycle,适用的最大工作频率是104MHz.验证这个参数是否设定合理,验证方法,读写数据页内容多次进行查看,验证数据是否正确即可。如果该参数设置不对,读取的数据和写入的数据对应不上,或者会出全部是0、或者是一个其他的uint8_t类型任意的固定或不固定的数值,也可能出现读取出来的数据和写入的数据和对应的地址错位的情况。

  • Winbond(华邦)W25Q64:没有找到相关描述
  • GD(兆易创新)GD25Q64
    在这里插入图片描述
  • 🌿读数据实现
/**
 * @brief   读NOR Flash
 * @note    从指定地址开始读取指定长度的数据
 * @param   pbuf   : 读取到数据保存的地址
 * @param   pbuf   : 指定开始读取的地址
 * @param   datalen: 指定读取数据的字节数
 * @retval  无
 */
void norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    QSPI_CommandTypeDef qspi_command_struct = {0};
//	qspi_flash_write_enable();//使能WEL位
    qspi_command_struct.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    qspi_command_struct.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD;//Quad I/O Fast Read EBH
    qspi_command_struct.AddressMode = QSPI_ADDRESS_4_LINES;
    qspi_command_struct.AddressSize = QSPI_ADDRESS_24_BITS;
    qspi_command_struct.Address = addr;
    qspi_command_struct.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
    qspi_command_struct.DummyCycles = 6; //此处必须要有空闲等待时间,否则数据和地址会出现错位
    qspi_command_struct.DataMode = QSPI_DATA_4_LINES;
    qspi_command_struct.NbData = datalen;
    qspi_command_struct.DdrMode = QSPI_DDR_MODE_DISABLE;
    qspi_command_struct.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    qspi_command_struct.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD;

    HAL_QSPI_Command(&hqspi, &qspi_command_struct, HAL_QSPI_TIMEOUT_DEFAULT_VALUE);

    HAL_QSPI_Receive(&hqspi, pbuf, HAL_QSPI_TIMEOUT_DEFAULT_VALUE);
}

📗擦除和写入操作

之所以将擦除和写入放在一起说明,是因为nor flash写操作前,需要先对当前要写入数据的地址扇区进行擦除操作,目的是为了将要写入的区域,保证为空(0xff),才能保证写入的数据的准确性。需要注意的是,最小擦除大小为扇区单位(4K)。

  • 📝扇区擦除操作:
int qspi_flash_erase_sector(uint32_t address) {

	uint32_t EraseStartAddress =  address;
	  EraseStartAddress &=0x0FFFFFFF; //最大可寻址范围
  EraseStartAddress = EraseStartAddress -  EraseStartAddress % 0x1000;//起始地址-扇区地址
  QSPI_CommandTypeDef command = {0};
  qspi_flash_write_enable();//写使能WEL
  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  command.AddressSize = QSPI_ADDRESS_24_BITS;//24位地址,可寻址范围小于或等于16MB
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

  command.Instruction = SUBSECTOR_ERASE_4_BYTE_ADDR_CMD;//擦除指令0x20
  command.AddressMode = QSPI_ADDRESS_1_LINE;
  command.Address = EraseStartAddress;
  command.DataMode = QSPI_DATA_NONE;
  command.DummyCycles = 0;

  if (HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

  if (qspi_flash_atuo_polling_mem_ready() != HAL_OK) {
    return HAL_ERROR;
  }

  return HAL_OK;
}

  • 📑写操作
/* 扇区缓存 */
static uint8_t g_norflash_buf[4096];

/**
 * @brief   写NOR Flash
 * @note    在指定地址开始写入指定长度的数据,该函数带擦除操作
 *          NOR Flash一般是:256个字节为一个Page,4096个字节为一个Sector,16个Sector为1个Block
 *          擦除的最小单位为Sector
 * @param   pbuf   : 待写入数据的起始地址
 * @param   addr   : 指定开始写入数据的地址
 * @param   datalen: 指定写入数据的字节数
 * @retval  无
 */
void norflash_write(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t *norflash_buf;

    norflash_buf = g_norflash_buf;                                      /* 扇区缓存 */
    secpos = addr / 4096;                                               /* 扇区索引 */
    secoff = addr % 4096;                                               /* 写入起始地址在当前扇区的偏移量 */
    secremain = 4096 - secoff;                                          /* 计算当扇区剩余的字节数 */
    if (datalen <= secremain)                                           /* 不大于4096个字节 */
    {
        secremain = datalen;
    }

    while (1)
    {
        norflash_read(norflash_buf, secpos * 4096, 4096);               /* 读出当前整个扇区的内容,用于擦除扇区后恢复非写入区域的数据 */

        for (i=0; i<secremain; i++)                                     /* 校验数据 */
        {
            if (norflash_buf[secoff + i] != 0xFF)                       /* 写入区域中有非0xFF的数据,就需要擦除 */
            {
                break;
            }
        }

        if (i < secremain)                                              /* 需要擦除 */
        {
//                            /* 擦除整个扇区 */
        qspi_flash_erase_sector(secpos) ;
            for (i=0; i<secremain; i++)                                 /* 将待写入的数据,先写入扇区缓存 */
            {
                norflash_buf[i + secoff] = pbuf[i];
            }

            norflash_write_nocheck(norflash_buf, secpos * 4096, 4096);  /* 写入整个扇区 */
        }
        else                                                            /* 不需要擦除,可以直接写入 */
        {
            norflash_write_nocheck(pbuf, addr, secremain);              /* 直接写扇区剩余的空间 */
        }

        if (datalen == secremain)                                       /* 写入结束 */
        {
            break;
        }
        else                                                            /* 写入未结束 */
        {
            secpos++;                                                   /* 扇区索引增1 */
            secoff = 0;                                                 /* 扇区偏移位置为0 */
            pbuf += secremain;                                          /* 偏移pbuf指针地址secremain个已写入的字节数 */
            addr += secremain;                                          /* 偏移写入地址secremain个已写入的字节数 */
            datalen -= secremain;                                       /* 计算剩余写入的字节数 */
            if (datalen > 4096)                                         /* 剩余数据量超过4096,只能先写入一个扇区数据 */
            {
                secremain = 4096;
            }
            else                                                        /* 剩余数据量不超过4096,可一次性写入 */
            {
                secremain = datalen;
            }
        }
    }
}

📒QSPI读取数据操作

  • 🔬测试以下读取数据指令操作:Quad I/O Fast Read (EBH)Quad Output Fast Read (6BH)Read Data Bytes (READ) (03H)发现,在使用Quad I/O Fast Read (EBH)快速读取数据Nor flash,如果每次读取的起始地址发生改变,函数中对应的DummyCycles空闲等待周期就不同,有时DummyCycles空闲等待周期需要6个,有时DummyCycles空闲等待周期需要14个,才能将读取的数据和地址位对应的上。(使用该指令读取数据,目前还没有找到原因和规律) 导致这个原因是,由于在配置工程时,QUADSPI_BK1_IO2引脚,输出速度模式,忘记配置为GPIO_SPEED_FREQ_VERY_HIGH导致,在使用所有QUAD 4线快速操作指令时出问题。
  • 而同样是快速读取指令Quad Output Fast Read (6BH)模式只是使用的是单线传输模式,函数中对应的DummyCycles空闲等待周期设定为固定的值为8,测试了几组从不同地址段开始读数据,没有数据和地址错位的问题。
  • 普通的数据读取指令Read Data Bytes (READ) (03H),测试正常。
/**
 * @brief   快速读取NOR Flash
 * @note    从指定地址开始读取指定长度的数据
 * @param   pbuf   : 读取到数据保存的地址
 * @param   address   : 指定开始读取的地址
 * @param   len   : 指定读取数据的字节数
 * @retval  0: 成功; 1: 失败
 **/
int qspi_flash_read_buffer(uint8_t *data, uint32_t address, uint32_t len) {
  QSPI_CommandTypeDef command = {0};
	 if(qspi_flash_atuo_polling_mem_ready() != HAL_OK)//使能QE位
    {
        return HAL_ERROR;
    }
  RxCplt = 0;
  if (memory_mapped) {
    qspi_flash_quit_memory_mapped();
  }

  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,单线传输
  command.AddressSize = QSPI_ADDRESS_24_BITS;
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD; //QSPI_SIOO_INST_ONLY_FIRST_CMD  QSPI_SIOO_INST_EVERY_CMD

  command.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD;//0xeb
  command.DummyCycles = 14;//6
  command.AddressMode = QSPI_ADDRESS_4_LINES;
  command.DataMode = QSPI_DATA_4_LINES;
  command.NbData = len;
  command.Address = address;

  if (HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

#ifdef QSPI_USE_DMA
if (HAL_QSPI_Receive_DMA(&hqspi, data) != HAL_OK) {
  return HAL_ERROR;
}
while (RxCplt == 0){};
  RxCplt = 0;
#else
  if (HAL_QSPI_Receive(&hqspi, data, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
	return HAL_ERROR;
  }
   if(qspi_flash_atuo_polling_mem_ready() != HAL_OK)//使能QE位
    {
        return HAL_ERROR;
    }
#endif
  if (memory_mapped) {
    qspi_flash_memory_mapped();
  }

  return HAL_OK;
}
/**
 * @brief   快速读取NOR Flash
 * @note    从指定地址开始读取指定长度的数据
 * @param   pbuf   : 读取到数据保存的地址
 * @param   address   : 指定开始读取的地址
 * @param   len   : 指定读取数据的字节数
 * @retval  0: 成功; 1: 失败
 */
int QUAD_Single_FAST_READ(uint8_t *data, uint32_t address, uint32_t len) {
  QSPI_CommandTypeDef command = {0};
	 if(qspi_flash_atuo_polling_mem_ready() != HAL_OK)//WREL位
    {
        return HAL_ERROR;
    }
  RxCplt = 0;
  if (memory_mapped) {
    qspi_flash_quit_memory_mapped();
  }

  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;//指令阶段的操作模式,单线传输
  command.AddressSize = QSPI_ADDRESS_24_BITS;
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; //QSPI_SIOO_INST_ONLY_FIRST_CMD  QSPI_SIOO_INST_EVERY_CMD

  command.Instruction = QUAD_OUT_FAST_READ_CMD;//0x6b
  command.DummyCycles = 8;//6
  command.AddressMode = QSPI_ADDRESS_1_LINE;
  command.DataMode = QSPI_DATA_4_LINES;
  command.NbData = len;
  command.Address = address;

  if (HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
    return HAL_ERROR;
  }

#ifdef QSPI_USE_DMA
if (HAL_QSPI_Receive_DMA(&hqspi, data) != HAL_OK) {
  return HAL_ERROR;
}
while (RxCplt == 0){};
  RxCplt = 0;
#else
  if (HAL_QSPI_Receive(&hqspi, data, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
	return HAL_ERROR;
  }
   if(qspi_flash_atuo_polling_mem_ready() != HAL_OK)//使能QE位
    {
        return HAL_ERROR;
    }
#endif
  if (memory_mapped) {
    qspi_flash_memory_mapped();
  }

  return HAL_OK;
}

/**
 * @brief   读NOR Flash
 * @note    从指定地址开始读取指定长度的数据
 * @param   pbuf   : 读取到数据保存的地址
 * @param   pbuf   : 指定开始读取的地址
 * @param   datalen: 指定读取数据的字节数
 * @retval  无
 */
int norflash_read(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
    QSPI_CommandTypeDef qspi_command_struct = {0};
   qspi_flash_atuo_polling_mem_ready();//查询wrel位


		RxCplt = 0;//清除接收标志位
    qspi_command_struct.InstructionMode = QSPI_INSTRUCTION_1_LINE;
    qspi_command_struct.Instruction = QUAD_INOUT_FAST_READ_4_BYTE_ADDR_CMD;//0xeb快速读取指令
    qspi_command_struct.AddressMode = QSPI_ADDRESS_4_LINES;
    qspi_command_struct.AddressSize = QSPI_ADDRESS_24_BITS;
    qspi_command_struct.Address = addr;
    qspi_command_struct.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;

    qspi_command_struct.DummyCycles = 6; //14此处必须要有空闲等待时间,否则数据和地址会出现错位
    qspi_command_struct.DataMode = QSPI_DATA_4_LINES;//数据阶段的操作模式:4线
    qspi_command_struct.NbData = datalen;
    qspi_command_struct.DdrMode = QSPI_DDR_MODE_DISABLE;
    qspi_command_struct.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
    //QSPI_SIOO_INST_ONLY_FIRST_CMD
    qspi_command_struct.SIOOMode = QSPI_SIOO_INST_ONLY_FIRST_CMD;//QSPI_SIOO_INST_EVERY_CMD
	//	QSPI_FLASH_CS_LOW();
if (HAL_QSPI_Command(&hqspi, &qspi_command_struct, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)
{
  return HAL_ERROR;
}


#ifdef QSPI_USE_DMA
if (HAL_QSPI_Receive_DMA(&hqspi, pbuf) != HAL_OK) {
  return HAL_ERROR;
}
#else
   if (HAL_QSPI_Receive(&hqspi, pbuf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) !=
      HAL_OK) {
return HAL_ERROR;
  }
 if(qspi_flash_atuo_polling_mem_ready() != HAL_OK)//WREL位
    {
        return HAL_ERROR;
    }
#endif
  //QSPI_FLASH_CS_HIGH(); //拉高片选信号
  return HAL_OK;
}
int Quad_Page_Program(uint8_t *pbuf, uint32_t addr, uint16_t datalen)
{
	QSPI_CommandTypeDef command = {0};
    qspi_flash_write_enable();

  command.InstructionMode = QSPI_INSTRUCTION_1_LINE;
  command.AddressSize = QSPI_ADDRESS_24_BITS;
  command.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
  command.DdrMode = QSPI_DDR_MODE_DISABLE;
  command.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
  command.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;

  command.Instruction = QUAD_IN_FAST_PROG_4_BYTE_ADDR_CMD;//0x32
  command.DummyCycles = 0;
  command.AddressMode = QSPI_ADDRESS_1_LINE;
  command.DataMode = QSPI_DATA_4_LINES;
  command.NbData = datalen;
  command.Address = addr;

	if(HAL_QSPI_Command(&hqspi, &command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE)!= HAL_OK){return HAL_ERROR;}
  #ifdef QSPI_USE_DMA
  if (HAL_QSPI_Transmit_DMA(&hqspi, pbuf) != HAL_OK) {
    return HAL_ERROR;
  }

  #else
   if(HAL_QSPI_Transmit(&hqspi, pbuf, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
    return HAL_ERROR;
  }

  qspi_flash_atuo_polling_mem_ready();

#endif
		return HAL_OK;
}

📓复位nor flash操作

  • GD25Q64复位使能和复位指令:Enable Reset (66H) and Reset (99H)
  • 复位内容:Volatile settings:易失性设置,包括易失性状态寄存器位、写使能锁存状态(WEL)、编程/擦除暂停状态、读取参数设置(P7-P0)、连续读取模式位设置(M7-M0)和环绕位设置(W6-W4).
  • 复位流程:执行复位命令序列包括:先发送使能复位命令(66H),然后将CS#拉高,再发送复位命令(99H),最后将CS#拉高。设备在接受复位命令后会花费大约tRST / tRST_E的时间来完成复位,在此期间不会接受任何其他命令。

在这里插入图片描述

  • 🌿High-Z时间:

在这里插入图片描述

  • STM32H750 ,时钟配置为400MHz,执行一个__NOP( )控制指令时间:1/400MHz=2.5ns,
  • 🌿复位命令发出之后,到nor flash 执行完内部复位所需的时间:12ms
    在这里插入图片描述

  • 来自:https://doc.embedfire.com/mcu/stm32/h743prov/hal/zh/latest/book/QSPI.html#qspi-7

**
* @brief  复位QSPI存储器。
* @param  QSPIHandle: QSPI句柄
* @retval 无
*/
static uint8_t QSPI_ResetMemory()
{
    QSPI_CommandTypeDef s_command;
    /* 初始化复位使能命令 */
    s_command.InstructionMode   = QSPI_INSTRUCTION_1_LINE;
    s_command.Instruction       = RESET_ENABLE_CMD;//0x66
    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(&QSPIHandle, &s_command,
        HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        return QSPI_ERROR;
    }

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

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

📙STM32CubeMX QSPI工程配置

  • ⏰时钟配置
    在这里插入图片描述

在这里插入图片描述

  • 🌿参数配置

在这里插入图片描述

  • FlashSize:2^FSIZE+1 ;是对 Flash 寻址所需的地址位数.
  • CS信号参考Nor flash数据手册数据:tSHSL:20ns
    QSPI所挂载的时钟HCLK:200/(1+1) =100MHz=0.01us=10ns;至少2个时钟周期,是为了能挂载的Nor flash得到响应,CS高电平时间配置最好大于2.我这里配置的6,因为像Winbond品牌的W25QXX,这里的CS时间需要50ns.

在这里插入图片描述
在这里插入图片描述

  • 🌿QSPI外设引脚配置
  • 🔖QSPI外设引脚配置不是固定的,可以根据实际使用情况,可以映射到任意支持的引脚上。

在这里插入图片描述

  • 🔨QSPI引脚配置:需要将外设引脚的速度设置为非常高(Very HIGH)。
    在这里插入图片描述
  • 🔖需要注意CS引脚配置,不能使能Fast Mode.
void MX_QUADSPI_Init(void)
{

  /* USER CODE BEGIN QUADSPI_Init 0 */

  /* USER CODE END QUADSPI_Init 0 */

  /* USER CODE BEGIN QUADSPI_Init 1 */

  /* USER CODE END QUADSPI_Init 1 */
  hqspi.Instance = QUADSPI;
  hqspi.Init.ClockPrescaler = 1; //200MHz/(1+1)=100MHz
  hqspi.Init.FifoThreshold = 32; //最大支持32
  hqspi.Init.SampleShifting = QSPI_SAMPLE_SHIFTING_HALFCYCLE;
  hqspi.Init.FlashSize = 22;//8MB= 2^[FSIZE+1] =1<<23
  
  hqspi.Init.ChipSelectHighTime = QSPI_CS_HIGH_TIME_6_CYCLE;//信号(CS)的高电平持续时间
  hqspi.Init.ClockMode = QSPI_CLOCK_MODE_0;/*时钟模式选择模式0,nCS为高电平(片选释放),传输数据CLK保持低电平*/
  hqspi.Init.FlashID = QSPI_FLASH_ID_1;
  hqspi.Init.DualFlash = QSPI_DUALFLASH_DISABLE;
  if (HAL_QSPI_Init(&hqspi) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN QUADSPI_Init 2 */

  /* USER CODE END QUADSPI_Init 2 */

}
  • 有关信号时间参数信息:
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  • 📝QSPI初始化配置:(参考野火内容介绍:https://doc.embedfire.com/mcu/stm32/h743prov/hal/zh/latest/book/QSPI.html#qspi-7

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

    /* QSPI存储器复位 */
    if (QSPI_ResetMemory() != QSPI_OK) {
        return QSPI_NOT_SUPPORTED;
    }
    /* 使能写操作 */
    if (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(&QSPIHandle, &s_command, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        return QSPI_ERROR;
    }
    /* 传输数据 */
    if (HAL_QSPI_Transmit(&QSPIHandle, &value, HAL_QPSI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) {
        return QSPI_ERROR;
    }
    /* 自动轮询模式等待存储器就绪 */
    if (QSPI_AutoPollingMemReady(W25Q256JV_SUBSECTOR_ERASE_MAX_TIME) != QSPI_OK) {
        return QSPI_ERROR;
    }

    /* 配置地址模式为 4 字节 */
    if (QSPI_Addr_Mode_Init() != QSPI_OK) {
        return QSPI_ERROR;
    }

    return QSPI_OK;
}

🍁注意事项

  • 🌿QSPI引脚输出速度都需要配置为GPIO_SPEED_FREQ_VERY_HIGH
    在这里插入图片描述

🔖默认开启QSPI外设功能时,所有外设引脚的默认输出速度都是低low,这一点需要注意,直接影响所有使用quad 4线指令的数据传输,导致数据出问题。

  • 🌿不容容量的Nor flash,其指令会有所增减和差异。
  • 🍕相关参考和资料推荐
  • 📌安富莱_STM32-V7:https://www.armbbs.cn/forum.php?mod=viewthread&tid=91590&highlight=STM32-V7
  • 📍QSPI驱动例程可以参考:正点原子STM32H750中的相关例程:http://47.111.11.73/docs/boards/stm32/zdyz_stm32h750_polaris.html
  • 🎈或者参考野火版的开发板资料:https://gitee.com/Embedfire-stm32h7-pro/ebf_stm32h743_pro_code_v
  • 🥕间接模式切换内存映射模式项目参考:https://github.com/ziancube/GD25Qxx-Stm32h747

📘GD/W25Qxx芯片状态寄存器量产方式修改

  • 🌿对于GD/W25Qxx状态寄存器,量产方式修改,可以借助CH341A编程器,配合上位机软件进行修改。
  • 🔖这里使用NeoProgrammer_2.2.0.10上位机软件修改GD/W25Q32相关状态寄存器位选项:
    在这里插入图片描述

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

相关文章:

  • 分布式系统架构怎么搭建?
  • Github 2025-01-25Rust开源项目日报Top10
  • 使用 Context API 管理临时状态,避免 Redux/Zustand 的持久化陷阱
  • SpringBoot源码解析(八):Bean工厂接口体系
  • 快速分析LabVIEW主要特征进行判断
  • MySQL中的读锁与写锁:概念与作用深度剖析
  • Django发送邮件代理服务器配置
  • Python实现文本数据可视化:构建动态词云
  • 《使用Gin框架构建分布式应用》阅读笔记:p1-p19
  • 移动硬盘文件误删怎么办?数据恢复全攻略
  • 用这条Prompt构建CoT+PoT验证器评估LLM输出,显著提高LLM推理准确性和一致性
  • uniapp引入ThorUI的方法
  • 国外电商系统开发-运维系统操作脚本
  • 哪款宠物空气净化器性价比高?希喂、米家和范罗士哪款更好?
  • do…while循环语句的使用
  • C# WinForm 用名字name字符串查找子控件
  • 数智时代的新航向:The Open Group 2024生态系统架构·可持续发展年度大会邀您共筑AI数字新时代
  • 技术总结(五)
  • DC系列靶机-DC2
  • 移情别恋c++ ദ്ദി˶ー̀֊ー́ ) ——7.list(无习题)
  • Java 线性搜索
  • 数据结构算法题:栈与队列的使用(一)
  • 追逐AGI!微软AI副总裁、Phi小模型领导者Bubeck将加入OpenAI
  • JVM的GC算法以及常见垃圾回收器
  • 多级缓存-
  • HTML5实现古典音乐网站源码模板1