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

STM32H750xx【QSPI】轮询方式读写GD25Q64E

目录

GD25Q64E介绍

1、基本介绍

2、主要特性

3、引脚定义(SOP8)

4、功能说明

5、QuadSPI通讯简述

CubeMX配置

1、新建项目

2、基础配置

3、QuadSPI接口配置

4、配置时钟

5、生成工程

KeilMDK工程设置

1、创建BSP

2、基础设置

编写BSP驱动

0、QuadSPI接口结构体说明

1)QSPI_HandleTypeDef

2)QSPI_CommandTypeDef

3)QSPI_AutoPollingTypeDef

1、写使能

2、使能四线模式

3、读取ID

4、解除写保护

5、擦除扇区(Sector)

6、擦除整个芯片

7、页编程(写入数据)

8、读取数据

测试程序编写

1、Debug串口函数编写

2、QuadSPI读写测试(重要)

工程文件


GD25Q64E介绍

1、基本介绍

GD25Q64E是由兆易创新(GigaDevice)推出的一款64M-bit(8MB)容量的串行NOR Flash存储器。该芯片采用SPI接口,广泛应用于嵌入式系统、消费电子、通信设备等需要非易失性存储的场景。

GD官方资料获取:https://www.gigadevice.com.cn/product/flash/spi-nor-flash/gd25q64e

2、主要特性

  • 容量:64M-bit(8MB)

  • 接口类型:支持标准SPI(Serial Peripheral Interface),兼容双/四线(Dual/Quad SPI)模式

  • 工作电压:2.7V ~ 3.6V

  • 时钟频率:最高支持133MHz

  • 页面大小:256字节

  • 扇区大小:4KB

  • 块大小:32KB / 64KB

  • 擦写寿命:每个扇区/块10万次擦写

  • 数据保持时间:20年以上

  • 封装形式:SOP8、USON8、WSON8等多种封装

  • 工作温度范围:-40°C ~ +85°C

典型的应用:

  • 程序代码存储

  • FPGA 配置文件保存

  • 数据记录与参数存储

  • 固件升级

  • 物联网设备

3、引脚定义(SOP8)

从数据手册中我们可以看到相关的引脚设置

引脚编号名称功能说明
1CS#片选
2SO(IO1)数据输出(数据输入输出引脚1)
3WP#(IO2)写保护(数据输入输出引脚2)
4VSS
5SI(IO0)数据输入(数据输入输出引脚0)
6CLK时钟输入
7HOLD#(IO3)暂停信号(数据输入输出引脚3)
8VCC电源

4、功能说明

  • 快速读写:支持高速 SPI/QSPI 读写操作

  • 页编程:每次最多编程 256 字节

  • 灵活擦除:支持扇区(4KB)、块(32KB/64KB)、整片擦除

  • 掉电数据保护:关电后数据不丢失

  • 硬件/软件写保护:支持部分区域写保护,防止误操作

  • 深度掉电/待机模式:降低功耗

5、QuadSPI通讯简述

  • 读操作:主控拉低 CS#,发送四线快速读指令(如0x6B/0xEB等)和地址,GD25Q64E 在四线(IO0~IO3)模式下返回数据,数据吞吐量更高。

  • 写操作:先发送写使能(WREN)指令,随后发送四线页编程指令(如0x32),地址和数据均通过四线(IO0~IO3)传输,提高写入速度。

  • 擦除操作:同样先发送写使能指令,再发送四线擦除指令(如4KB扇区擦除0x20),地址用四线传输,可实现更快的数据擦除。

  • 模式切换:设备上电默认SPI模式,需通过设置状态寄存器切换到QSPI(四线)模式。

QSPI(Quad SPI)模式下,数据线数量从1线扩展到4线(IO0、IO1、IO2、IO3),极大提升数据传输速度,适用于高速固件加载和大数据量存储场景。

CubeMX配置

1、新建项目

我们直接利用CubeMX软件初始化一个工程

搜索 STM32H750VBT6 这个芯片,双击之后进入具体配置界面:

2、基础配置

使用外部高速/低速晶振:

我们先关闭MPU,这样的话我们就可以专注于QSPI-Flash的BSP代码编写。

打开串口,我们可以用这个串口查看调试信息。

因为我们原理图上的USART1的接口是PA9和PA10,所以我们修改相关的引脚接口

SWD调试接口配置:

3、QuadSPI接口配置

首先查看原理图的引脚: ​

我们可以很清晰的看到相关的引脚分配情况,我们直接开始配置引脚和我们的硬件原理图保持一致:

特别注意:这里我们的引脚输出速度一定要设定为最高!!!

接下来进行QuadSPI的常规

  • Clock Prescaler: 1

    • 240MHz/(1+1)=120MHz (内部会自动+1)QSPI时钟最快,看GD25Q64E的数据手册,最大的时钟速度为133MHz,我们不超过即可。

  • Fifo Threshold: 1

    • FIFO的阈值范围,直接默认为1即可。

  • Sample Shifting: Sample Shifting Half Cycle

    • 设定为半个时钟周期之后才开始数据采集,相当于加了一些延迟,读取数据的时候更有容错。

  • Flash Size: 23(GD25Q64是8MByte,23代表2\^23\=8MByte)

    • 特别说明:内部计算的时候计算这个Flash Size是会自动+1的,常规填写22即可,但涉及到内存映射的时候还是按照23填写(使其空间扩大一倍),否则使用内存映射的时候最后的空间映射会出问题!

  • Chip Select High Time: 5 Cycles

    • CS的片选使用的时候,高电平时间至少保持 5 Cycles

  • Clock Mode: LOW

    • 片选的信号空闲时,时钟信号设定为低电平。

  • Flash ID: Flash ID 1

  • Dual Flash: Disabled

4、配置时钟

  1. 选择 HSE

  2. 选择 Enable CSS

  3. 填写 480

  4. 回车,直接让 CubeMX 软件自己计算配置即可!

我们可以看到QuadSPI的时钟是240MHz:

之前配置 QuadSPI 的时候 Clock Prescaler 设定为了 1 ,因为内部分频会自动+1 ,所以 QuadSPI 的时钟速度就是:

  • 240MHz / (Clock Prescaler + 1)

    • = 240MHz / (1 + 1)

    • = 120MHz

5、生成工程

  • 工程名字设定为:QuadSPI-Flash-Poll_Project

  • Project Location设定工程的位置。

  • Toolchain/IDE 设定生成的工程是 Keil-MDK 的。

点击 GENERATE CODE 生成代码:

我们就能看到在相关的位置生成了工程目录:

KeilMDK工程设置

1、创建BSP

在工程目录中创建 BSP 文件夹,在 BSP 文件夹中再次创建一个 QuadSPI-Flash 的文件夹。

QuadSPI-Flash 的文件夹中创建 bsp_qspi_gd25q64e.cbsp_qspi_gd25q64e.h

打开项目,将我们的创建的 .h.c 文件添加到工程中

2、基础设置

使用KeilMDK的微库,使用AC6编译器:

优化等级降至 0

编写BSP驱动

0、QuadSPI接口结构体说明

我们在开始编写BSP之前,首先来了解下在STM32H7中要用的一些结构体。

1)QSPI_HandleTypeDef

QSPI_HandleTypeDef 是 STM32 HAL 库用于管理 QuadSPI 外设和数据传输状态的核心结构体。它包含了 QSPI 外设实例、初始化参数、收发缓冲区指针、DMA句柄、锁定状态、错误码以及超时等信息。常用于 BSP 驱动中的 QSPI 操作。

这个结构体一般定义在工程中:\Libraries\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_qspi.h

主要字段说明如下:

  • Instance:QSPI 寄存器基地址,通常为 QUADSPI 外设的指针。

  • Init:QSPI 初始化参数结构体(QSPI_InitTypeDef),包括数据宽度、时钟分频等设置。

  • pTxBuffPtr/pRxBuffPtr:发送/接收缓冲区指针,指向待发送/接收的数据。

  • TxXferSize/RxXferSize:发送/接收的数据大小(字节数)。

  • TxXferCount/RxXferCount:剩余待发送/接收的数据计数。

  • hmdma:MDMA(内存到外设 DMA)句柄指针,用于加速数据传输。

  • Lock:用于实现多线程/中断安全的数据访问。

  • State:QSPI 当前通信状态(空闲、忙、错误等),用于流程控制。

  • ErrorCode:错误码,标识 QSPI 通信过程中出现的错误类型。

  • Timeout:QSPI 内存访问的超时时间设置。

  • 回调函数(如果使能了注册回调) :包括错误/中止/完成/超时等事件的回调处理函数,便于异步通信和事件响应。

使用方式:

  • 在 BSP 驱动中,通常会定义一个全局 QSPI_HandleTypeDef 变量(如 QSPI_HandleTypeDef hqspi),在初始化、读写、擦除等操作时传递给 HAL QSPI API(如 HAL_QSPI_Command, HAL_QSPI_Transmit, HAL_QSPI_Receive 等),以实现控制和数据收发。

2)QSPI_CommandTypeDef

QSPI_CommandTypeDef 是 STM32 HAL 库中用于配置 QuadSPI 命令的结构体。它定义了通过 QSPI 总线与外部 Flash通信时所需的各种参数,包括指令、地址、模式、数据长度等。

这个结构体一般定义在工程中:\Libraries\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_qspi.h

主要字段说明如下:

  • Instruction:要发送到 Flash 的操作指令(如读/写/擦除等),通常为 8 位。

  • Address:目标地址,通常为 24 位或 32 位,需要根据 GD25Q64E 的寻址空间设置。

  • AlternateBytes:备用字节,可用于某些特殊命令。

  • AddressSize:地址大小,可以为 8/16/24/32 位,通常小于 128MBit 的Flash选用 24 位,而大于则选用32位。

  • AlternateBytesSize:备用字节大小,通常不用时设为无。

  • DummyCycles:虚拟周期数,QSPI 快速读时常需设定,需要根据实际调整。

  • InstructionMode:指令阶段使用的线数(单线、双线、四线),QSPI 操作通常选用四线。

  • AddressMode:地址阶段线数,支持单/双/四线。

  • AlternateByteMode:备用字节阶段线数。

  • DataMode:数据阶段线数,QSPI 快速读/写一般用四线。

  • NbData:数据传输的字节数,读写操作时设置为实际长度。

  • DdrMode:是否启用 DDR(双倍速)。

  • DdrHoldHalfCycle:DDR 半周期保持。

  • SIOOMode:单次指令发送模式。

一般应用场景:

  • 使用 HAL_QSPI_CommandHAL_QSPI_ReceiveHAL_QSPI_TransmitHAL_QSPI_AutoPolling 时进行必要的命令模式配置。

3)QSPI_AutoPollingTypeDef

QSPI_AutoPollingTypeDef 是 STM32 HAL 库中用于配置 QSPI 自动轮询模式的结构体。自动轮询模式常用于等待外部 Flash完成擦除或编程操作,通过自动读取状态寄存器判断操作是否结束,提高效率和响应速度。

这个结构体一般定义在工程中:\Libraries\STM32H7xx_HAL_Driver\Inc\stm32h7xx_hal_qspi.h

主要字段说明如下:

  • Match:目标匹配值。用于与读取到的状态寄存器(经过掩码 Mask)进行比较,判断是否达到指定状态(如空闲)。

  • Mask:掩码值。对 Flash 返回的状态字节进行掩码处理,只比较关心的位(如 GD25Q64E 的“忙”位)。

  • Interval:自动轮询间隔时钟周期数。决定每次状态寄存器轮询之间的等待时间,单位为 QSPI 时钟周期。

  • StatusBytesSize:状态字节数。设置每次读回的状态寄存器长度。

  • MatchMode:匹配方式。指定比较的判定方法,如全部匹配或部分匹配。

  • AutomaticStop:自动停止。轮询到匹配状态后自动停止。

应用场景:

  • 在 BSP 驱动中,自动轮询模式通常用于等待 Flash 完成写入或擦除操作。比如,先发起擦除命令,再配置 QSPI_AutoPollingTypeDef,调用 HAL_QSPI_AutoPolling() 轮询状态寄存器的“忙”位(BUSY),直到 Flash 空闲后再进行后续操作。

1、写使能

在 GD25Q64E 进行写入或擦除操作前,必须先执行“写使能”操作。写使能的流程如下:

  1. 发送写使能指令 0x06 向 Flash 发送 0x06 指令(WREN),使能后续的写/擦除操作。

  2. 轮询状态寄存器 可选步骤:轮询状态寄存器1的 WEL(写使能锁存)位(位1)是否被置位,确保写使能成功。

这是最终要的一个操作,奠定了之后的一切写入和擦除操作。

我们根据这个Flash数据手册中 Read Status Register (RDSR) 章节,可知 0x05 0x35 0x15 每个命令可以读取一个字节(8Bits)的数据,所以24bits的状态信息需要3个命令才能完全读取:

命令对应的Bit位数
0x05第0位到第7位 (S0 ~ S7)
0x35第8位到第15位 (S8 ~ S15)
0x15第16位到第24位(S16 ~ S23)

我们需要的是 WEL(写使能锁存) 这个状态,所以我们查询 STATUS REGISER 表格可以看到 WEL 状态位于 S1 也就是位 1

所以我们只需要读状态寄存器的第一个字节即可,用 0x05 这个命令。

首先在 BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.h 中定义相关的寄存器:

#define QSPI_CMD_WRITE_ENABLE       0x06    /* 写使能 */#define GD25Q64E_CMD_READ_STATUS_1	0x05    /* 读取状态寄存器第0位到第7位(S0 ~ S7) */
#define GD25Q64E_CMD_READ_STATUS_2	0x35    /* 读取状态寄存器第8位到第15位(S8 ~ S15) */
#define GD25Q64E_CMD_READ_STATUS_3	0x15    /* 读取状态寄存器第16位到第23位(S16 ~ S23) */

BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.h 中开始编写函数:

/******************************************************************* 函 数 名 称:QSPI_PollingWEL* 函 数 说 明:等待写使能位(WEL)被置位* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:无
******************************************************************/
static int QSPI_PollingWEL(void)
{QSPI_CommandTypeDef sCommand = {0};QSPI_AutoPollingTypeDef sConfig = {0};sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;     /* 一线指令模式 */sCommand.Instruction     = GD25Q64E_CMD_READ_STATUS_1;  /* 读取状态寄存器1命令 */sCommand.AddressMode     = QSPI_ADDRESS_NONE;           /* 无地址模式 */sCommand.DataMode        = QSPI_DATA_1_LINE;            /* 一线数据模式 */sConfig.Mask            = 0x02;                         /* 只关心WEL位 */sConfig.Match           = 0x02;                         /* WEL位为1 */sConfig.MatchMode       = QSPI_MATCH_MODE_AND;          /* AND匹配模式 */sConfig.StatusBytesSize = 1;                            /* 状态寄存器1有1个字节 */sConfig.Interval        = 0x10;                         /* 轮询间隔为16个QSPI时钟周期 */sConfig.AutomaticStop   = QSPI_AUTOMATIC_STOP_ENABLE;   /* 自动停止轮询 */if (HAL_QSPI_AutoPolling(&hqspi, &sCommand, &sConfig, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;return 0; 
}
  • 功能: 用于自动轮询 GD25Q64E 的状态寄存器1(SR1,指令 0x05),等待 WEL(Write Enable Latch,位1)被置位,确保写使能命令已经生效。

  • 实现流程

    1. 构造 QSPI 命令结构体,设置为一线指令模式,指令为读取状态寄存器1(0x05),无地址,一线数据模式。

    2. 构造自动轮询配置结构体,只关心 WEL 位(Mask=0x02),匹配条件为 WEL=1(Match=0x02),其它参数如轮询间隔、匹配模式等根据实际设置。

    3. 调用 HAL_QSPI_AutoPolling 自动轮询芯片状态,直到 WEL 位被置位或超时。

    4. 成功返回 0,失败返回 1。

  • 典型用法: 作为写使能命令后的状态确认步骤,确保芯片状态正确,避免后续写/擦除失败。

/******************************************************************* 函 数 名 称:QSPI_WriteEnable* 函 数 说 明:发送写使能命令,并等待WEL位被置位* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:无
******************************************************************/
static int QSPI_WriteEnable(void)
{QSPI_CommandTypeDef sCommand = {0};sCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;     /* 一线指令模式 */sCommand.Instruction     = GD25Q64E_CMD_WRITE_ENABLE;   /* 写使能命令 */sCommand.AddressMode     = QSPI_ADDRESS_NONE;           /* 无地址模式 */sCommand.DataMode        = QSPI_DATA_NONE;              /* 无数据模式 *//* 发送写使能命令 */if (HAL_QSPI_Command(hqspi, &sCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 等待WEL位被置位 */return QSPI_Polling_Wait_WEL();
}
  • 函数说明:用于向 GD25Q64E Flash 芯片发送写使能命令(WREN,0x06),并等待写使能锁存位(WEL)被置位,以允许后续的写入或擦除操作。

  • 实现流程

    1. 构造 QSPI 命令结构体,设置为一线指令模式,指令为写使能命令(0x06),无地址、无数据模式。

    2. 调用 HAL_QSPI_Command 发送写使能命令。

    3. 命令发送成功后,调用 QSPI_PollingWEL 轮询状态寄存器,等待 WEL 位被置位(即写使能生效)。

    4. 若整个过程成功,返回 0,否则返回 1。

  • 典型用法: 写入或擦除 Flash 前,需先调用此函数以保证芯片处于可写状态。

2、使能四线模式

Flash芯片上电默认是普通SPI模式,也没有使能四线读写,所以我们需要设置下Flash的模式。

从Flash的数据手册中可知:若需使用QSPI四线读写,须先使能QE(Quad Enable)位。

从状态寄存器中可以得知:QE的使能位在 24 个Bit中的 第 9 位。

而且,在Note中看到的是 Non-volatile writable 掉电不丢失,所以我们可以将流程这样设计:

  1. 读取 SR2,检查 QE 是否已置位。

  2. 如果未置位,先写使能(WREN)。

  3. 写 SR2,把 QE 位置 1。

  4. 轮询 BUSY,等待操作完成。

我们查看命令列表看到,写状态寄存器使用 0x01/0x31/0x11 这三个命令,分别对应的状态寄存器的位数是:

命令对应的Bit位数
0x01第0位到第7位 (S0 ~ S7)
0x31第8位到第15位 (S8 ~ S15)
0x11第16位到第24位(S16 ~ S23)

同时我们从这段文字中提取到了两个很重要的信息:

  • 写入的过程中会使能 WIP 的状态位,直到写入完成。

  • WIP位处于状态寄存器的第0位。

我们继续查看状态寄存器表,发现了Flash擦除和页编程的时候都会使能这个寄存器

首先在 BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.h 中进行相关的定义:

#define GD25Q64E_CMD_WRITE_STATUS_1	    0x01    /* 写状态寄存器第0位到第7位(S0 ~ S7) */
#define GD25Q64E_CMD_WRITE_STATUS_2	    0x31    /* 写状态寄存器第8位到第15位(S8 ~ S15) */
#define GD25Q64E_CMD_WRITE_STATUS_3	    0x11    /* 写状态寄存器第16位到第23位(S16 ~ S23) */

BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.h 中开始编写函数:

/******************************************************************* 函 数 名 称:QSPI_PollingWIP* 函 数 说 明:等待状态位(WIP)被置0,擦除和页编程的时候WIP位会被置位1* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:无
******************************************************************/
static int QSPI_PollingWIP(void)
{QSPI_CommandTypeDef FlashCommand = {0};QSPI_AutoPollingTypeDef PollConfig = {0};FlashCommand.InstructionMode    = QSPI_INSTRUCTION_1_LINE;      /* 一线指令模式 */FlashCommand.AddressSize        = QSPI_ADDRESS_24_BITS;         /* 24位地址 */FlashCommand.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;    /* 无交替字节 */FlashCommand.Instruction        = GD25Q64E_CMD_WRITE_STATUS_1;  /* 读取状态寄存器1命令 */FlashCommand.AddressMode        = QSPI_ADDRESS_NONE;            /* 无地址模式 */FlashCommand.DataMode           = QSPI_DATA_1_LINE;             /* 一线数据模式 */FlashCommand.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */FlashCommand.DummyCycles        = 0;                            /* 无需指令空闲周期 */PollConfig.Mask                = 0x01;                         /* 只关心WIP位 */PollConfig.Match               = 0x00;                         /* WIP位为0 */PollConfig.MatchMode           = QSPI_MATCH_MODE_AND;          /* AND匹配模式 */PollConfig.StatusBytesSize     = 1;                            /* 状态寄存器1有1个字节 */PollConfig.Interval            = 0x10;                         /* 轮询间隔为16个QSPI时钟周期 */PollConfig.AutomaticStop       = QSPI_AUTOMATIC_STOP_ENABLE;   /* 自动停止轮询 *//* 轮询WIP位,会不断的查询目标寄存器的数值,直到WIP位被置位 */if (HAL_QSPI_AutoPolling(&hqspi, &FlashCommand, &PollConfig, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;return 0; 
}

此函数的作用就是轮询等待状态寄存器中的WIP位被置0,也就是等待写入/擦除操作结束。

/******************************************************************* 函 数 名 称:QSPI_EnableQuadMode* 函 数 说 明:使能四线模式* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:无
******************************************************************/
static int QSPI_EnableQuadMode(void)
{uint8_t Sr2 = 0;QSPI_CommandTypeDef FlashCommand = {0};/* 读 SR2 */FlashCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;         /* 一线指令模式 */FlashCommand.AddressMode     = QSPI_ADDRESS_NONE;               /* 无地址模式 */FlashCommand.DataMode        = QSPI_DATA_1_LINE;                /* 一线数据模式 */FlashCommand.Instruction     = GD25Q64E_CMD_READ_STATUS_2;      /* 读取状态寄存器2命令 */FlashCommand.NbData          = 1;                               /* 读取1个字节 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;if (HAL_QSPI_Receive(&hqspi, &Sr2, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;/* 如果已经是四线模式,则直接返回 */if (Sr2 & 0x02) return 0; /* 写使能 */if (QSPI_WriteEnable() != 0) return 1;/* 写SR2 */uint8_t WriteSR2 = (Sr2 | 0x02);FlashCommand.Instruction = GD25Q64E_CMD_WRITE_STATUS_2;         /* 写状态寄存器2命令 */FlashCommand.DataMode    = QSPI_DATA_1_LINE;                    /* 一线数据模式 */FlashCommand.NbData      = 1;                                   /* 发送1个字节 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;if (HAL_QSPI_Transmit(&hqspi, &WriteSR2, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;/* 等待WIP位被清0 */return QSPI_PollingWIP();
}

此函数首先读取了 QE 的状态位判断是不是已经被置 1 ,如果已经置1则直接返回,否则使用相关的写入命令对QE位进行修改,然后等待写入结束(WIP置0)。

3、读取ID

阅读 Flash 的数据手册,看到了ID定义表:

我们可以看到有好个类ID的读取命令:

其中 MID 是厂家ID,ID 是设备ID,我们读取使用 0x9F 命令即可,直接读取三个字节:厂家编号+设备编号+容量信息

首先在 BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.h 中进行相关的定义:

#define GD25Q64E_CMD_READ_ID            0x9F    /* 读取器件ID */

BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.c 中开始编写函数:

/******************************************************************* 函 数 名 称:QSPI_ReadFlashID* 函 数 说 明:读取Flash ID* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:无
******************************************************************/
int QSPI_ReadFlashID(uint8_t *mid, uint8_t *did, uint8_t *uid)
{QSPI_CommandTypeDef FlashCommand = {0};uint8_t id[3] = {0};FlashCommand.InstructionMode    = QSPI_INSTRUCTION_1_LINE;      /* 一线指令模式 */FlashCommand.AddressSize        = QSPI_ADDRESS_24_BITS;         /* 24位地址 */FlashCommand.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;    /* 无交替字节 */FlashCommand.AddressMode        = QSPI_ADDRESS_NONE;            /* 无地址模式 */FlashCommand.DdrMode            = QSPI_DDR_MODE_DISABLE;        /* 关闭DDR模式 */FlashCommand.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;    /* DDR模式下的时钟延迟 */FlashCommand.Instruction        = GD25Q64E_CMD_READ_ID;         /* 读取器件ID命令 */FlashCommand.DataMode           = QSPI_DATA_1_LINE;             /* 一线数据模式 */FlashCommand.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */FlashCommand.NbData             = 3;                            /* 读取3个字节 */FlashCommand.DummyCycles        = 0;                            /* 无需指令空闲周期 *//* 发送读取ID命令 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 读取ID数据 */if (HAL_QSPI_Receive(&hqspi, id, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 解析ID数据 */if (mid != NULL) *mid = id[0];if (did != NULL) *did = id[1];if (uid != NULL) *uid = id[2];return 0;
}

用于读取 GD25Q64E Flash 芯片的器件 ID。通过发送 读取ID命令(0x9F) ,获取芯片厂商、型号和容量信息,用于芯片识别和初始化自检。

  • mid:指向存放厂家ID的指针(Manufacturer ID),兆易创新为 0xC8

  • did:指向存放器件型号ID的指针(Device ID),GD25Q64E为 0x40

  • uid:指向存放容量ID的指针(Unique ID/Memory Capacity),GD25Q64E为 0x17

  • 三者均可为 NULL,若不需要某项ID可传 NULL

4、解除写保护

这个其实不是非必要的,以防外一擦除或者写入的时候失败,我们设计的这个函数,每次在 Flash 的 Init 时运行一下即可。

从 Flash 数据手册的这个表里面我们可以看到,无论CMP等于几,只要我们将BP0BP1BP21,那么所有的保护都会关闭。

BP0BP1BP2在状态寄存器的 S2 ~ S4 位,而且是在第一个字节中。

Note 提示了它是掉电不丢失的。

BSP\QuadSPI-Flash\bsp_qspi_gd25q64e.c 中开始编写函数,用于检测并解除保护:

/******************************************************************* 函 数 名 称:QSPI_Unprotect* 函 数 说 明:解除写保护* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:无
******************************************************************/
static int QSPI_Unprotect(void)
{uint8_t ReadSR1  = 0;uint8_t WriteSR1 = 0;QSPI_CommandTypeDef FlashCommand = {0};/* 读 SR1 */FlashCommand.InstructionMode = QSPI_INSTRUCTION_1_LINE;         /* 一线指令模式 */FlashCommand.AddressMode     = QSPI_ADDRESS_NONE;               /* 无地址模式 */FlashCommand.DataMode        = QSPI_DATA_1_LINE;                /* 一线数据模式 */FlashCommand.Instruction     = GD25Q64E_CMD_READ_STATUS_1;      /* 读取状态寄存器1命令 */FlashCommand.NbData          = 1;                               /* 读取1个字节 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;if (HAL_QSPI_Receive(&hqspi, &ReadSR1, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;/* 将BP0、BP1和BP2置1(分别是SR1的S2/S3/S4位)*/WriteSR1 = (ReadSR1 | 0x1C); // 0x1C: 00011100/* 写使能 */if (QSPI_WriteEnable() != 0) return 1;/* 写SR1 */FlashCommand.Instruction = GD25Q64E_CMD_WRITE_STATUS_1;         /* 写状态寄存器1命令 */FlashCommand.DataMode    = QSPI_DATA_1_LINE;                    /* 一线数据模式 */FlashCommand.NbData      = 1;                                   /* 发送1个字节 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;if (HAL_QSPI_Transmit(&hqspi, &WriteSR1, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK) return 1;/* 等待WIP位被清0 */return QSPI_PollingWIP();
}

用于解除 GD25Q64E Flash 的写保护,具体做法是将状态寄存器1 (SR1) 的 Block Protect 位 BP0、BP1、BP2(分别对应 SR1 的 S2、S3、S4 位)全部置为 1,从而实现解锁或取消保护指定的存储区域。这个函数可以在BSP初始化的时候运行一下!

5、擦除扇区(Sector)

我们从Flash手册的开头 FEATURES 中可以了解到扇区的大小是 4KB,块是 32KB 或者 64KB 的,也就是我们操作扇区的时候,擦除起始地址必须是被目标扇区整除的。

查看相关的命令(0x20): ​

其中提到了三个点

  • 必须先写使能,然后等待WEL标志被置位。

  • 在擦除期间,可以查看WIP标志位,当为1时还在擦除,为0时则完成擦除,可以用这个来判断擦除完成。

  • 如果被 BP0 ~ BP4 所设置为保护区域,那么擦除操作将无效。

所以我们针对这三个点进行代码的编写,首先是写使能,这个之前已经实现过了,而且里面还包含了WEL标志置位的等待函数。

接下来就可以编写具体的擦除函数了,首先在.h定义命令:

#define GD25Q64E_CMD_ERASE_SECTOR_4KB   0x20    /* 扇区擦除,擦除大小为4KB */
#define GD25Q64E_CMD_ERASE_SECTOR_32KB  0x52    /* 扇区擦除,擦除大小为32KB */
#define GD25Q64E_CMD_ERASE_SECTOR_64KB  0xD8    /* 扇区擦除,擦除大小为64KB */

然后再 .c 中编写函数:

/******************************************************************* 函 数 名 称:QSPI_EraseFlashSector* 函 数 说 明:擦除扇区* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:擦除大小默认为64KB,SectorAddr必须能被64KB整除。
******************************************************************/
int QSPI_EraseFlashSector(uint32_t SectorAddr)
{QSPI_CommandTypeDef FlashCommand = {0};/* 写使能 */if (QSPI_WriteEnable() != 0) return 1;FlashCommand.InstructionMode    = QSPI_INSTRUCTION_1_LINE;      /* 一线指令模式 */FlashCommand.AddressSize        = QSPI_ADDRESS_24_BITS;         /* 24位地址 */FlashCommand.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;    /* 无交替字节 */FlashCommand.DdrMode            = QSPI_DDR_MODE_DISABLE;        /* 关闭DDR模式 */FlashCommand.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;    /* DDR模式下的时钟延迟,因为关闭了DDR模式所以不管 *//** 注意:GD25Q64E的扇区擦除命令是0x20,擦除大小为4KB*       如果需要擦除32KB或64KB,可以使用0x52或0xD8命令*       这里为了速度快,统一使用64KB扇区擦除命令*       SectorAddr必须能被64KB整除* 可选命令:* GD25Q64E_CMD_ERASE_SECTOR_4KB   (扇区擦除,擦除大小为4KB)* GD25Q64E_CMD_ERASE_SECTOR_32KB  (扇区擦除,擦除大小为32KB)* GD25Q64E_CMD_ERASE_SECTOR_64KB  (扇区擦除,擦除大小为64KB)*/FlashCommand.Instruction        = GD25Q64E_CMD_ERASE_SECTOR_64KB;    /* 64KB扇区擦除命令 */FlashCommand.Address            = SectorAddr;                   /* 扇区地址,要确保能被目标扇区大小整除 */FlashCommand.AddressMode        = QSPI_ADDRESS_1_LINE;          /* 一线地址模式 */FlashCommand.DataMode           = QSPI_DATA_NONE;               /* 不发送数据 */FlashCommand.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */FlashCommand.DummyCycles        = 0;                            /* 无需指令空闲周期 *//* 发送扇区擦除命令 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 等待WIP位被清0 */return QSPI_PollingWIP();
}

用于擦除 GD25Q64E Flash 芯片的一个扇区(Sector),默认擦除大小为 64KB。用户只需传入待擦除的扇区首地址,函数会完成整个擦除流程。

  • GD25Q64E 支持 4KB、32KB 和 64KB 三种扇区擦除命令(0x20、0x52、0xD8),本函数选择速度较快的 64KB 擦除命令。

  • 必须保证传入的 SectorAddr 为 64KB 对齐,否则可能擦除到错误的区域。

  • 擦除操作不可逆,数据会被彻底清除。

具体的擦除时间可以参照 Flash 的数据手册末尾表格

6、擦除整个芯片

整个操作很慢,所以一般情况下我们不使用,作为储备函数使用。

我们直接在 .h 中定义:

#define GD25Q64E_CMD_ERASE_CHIP         0xC7    /* 芯片全擦除 */

.c 中编写函数:

/******************************************************************* 函 数 名 称:QSPI_EraseFlashChip* 函 数 说 明:擦除整个芯片* 函 数 形 参:无* 函 数 返 回:0:成功 1:失败* 备       注:慎用,会擦除整个芯片,速度非常慢,大约需要几十秒!
******************************************************************/
int QSPI_EraseFlashChip(void)
{QSPI_CommandTypeDef FlashCommand = {0};/* 写使能 */if (QSPI_WriteEnable() != 0) return 1;FlashCommand.InstructionMode    = QSPI_INSTRUCTION_1_LINE;      /* 一线指令模式 */FlashCommand.AddressSize        = QSPI_ADDRESS_24_BITS;         /* 24位地址 */FlashCommand.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;    /* 无交替字节 */FlashCommand.DdrMode            = QSPI_DDR_MODE_DISABLE;        /* 关闭DDR模式 */FlashCommand.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;    /* DDR模式下的时钟延迟,因为关闭了DDR模式所以不管 */FlashCommand.Instruction        = GD25Q64E_CMD_ERASE_CHIP;      /* 全芯片擦除命令 */FlashCommand.Address            = 0;                            /* 无需传入地址 */FlashCommand.AddressMode        = QSPI_ADDRESS_1_LINE;          /* 一线地址模式 */FlashCommand.DataMode           = QSPI_DATA_NONE;               /* 不发送数据 */FlashCommand.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;     /* 每次传输都发指令 */FlashCommand.DummyCycles        = 0;                            /* 无需指令空闲周期 *//* 发送扇区擦除命令 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 等待WIP位被清0 */return QSPI_PollingWIP();
}

这里我们不需要发送地址,也无需发送数据等待即可。

具体的擦除时间可以参照 Flash 的数据手册:

7、页编程(写入数据)

首先我们解释下页编程这个概念:

  • 页编程(Page Program)是 NOR Flash(GD25Q64E 属于 NOR)写入数据的基本单位。

  • Flash 芯片的存储空间被划分为若干“页”(page),每一页通常是 256 字节。

  • 一次编程操作最多只能向一个页写入(即每次最多写 256 字节,从页的起始地址到页的结束地址)。

  • 如果写入数据跨越两个页,会自动在下一次编程命令中继续写入下一页。

  • 页编程的好处

    • 可以高效地管理写入,减少对存储单元的损耗。

    • 避免写入超出页界限导致数据错乱。

我们可以直接理解成写入操作即可。

我们查看 Flash 的数据手册,可以看到有以下这些和页相关的命令:

0x02 (PP) 和 0x32 (Quad Page Program) 都是页编程命令,用于向 Flash 芯片写入(编程)数据。

  • 0x02 (Page Program) :用普通 SPI 总线写数据。

  • 0x32 (Quad Page Program) :用 QSPI 四线模式写数据(速度快)。

0x75(PES)0x7A(PER)0x42 是一些其他的命令:

  • 0x75 (PES) :暂停程序/擦除过程

  • 0x7A (PER) :恢复程序/擦除过程

  • 0x42:向安全寄存器写入数据

我们直接用快速四线写入命令(0x32)作为基本的写入操作,首先在 .h 定义:

#define GD25Q64E_CMD_PAGE_PROGRAM       0x02    /* 页编程 */
#define GD25Q64E_CMD_QUAD_PAGE_PROGRAM  0x32    /* 四线快速页编程(快速写入) */

然后在 .c 编写函数:

/******************************************************************* 函 数 名 称:QSPI_WriteFlash* 函 数 说 明:快速利用页编程写入Flash数据,标准页大小为256Byte。* 函 数 形 参:PageAddr:页地址,要确保能被256Byte整除*             pData:数据缓冲区指针*             totalSize:写入数据大小,最大不能超过256Byte* 函 数 返 回:0:成功 1:失败* 备       注:
******************************************************************/
int QSPI_WriteFlash(uint32_t PageAddr, uint8_t *pData, uint32_t totalSize)
{QSPI_CommandTypeDef FlashCommand = {0};/* 写使能 */if (QSPI_WriteEnable() != 0) return 1;FlashCommand.InstructionMode    = QSPI_INSTRUCTION_1_LINE;          /* 一线指令模式 */FlashCommand.AddressSize        = QSPI_ADDRESS_24_BITS;             /* 24位地址 */FlashCommand.AlternateByteMode  = QSPI_ALTERNATE_BYTES_NONE;        /* 无交替字节 */FlashCommand.DdrMode            = QSPI_DDR_MODE_DISABLE;            /* 关闭DDR模式 */FlashCommand.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;        /* DDR模式下的时钟延迟,因为关闭了DDR模式所以不管 */FlashCommand.AddressMode        = QSPI_ADDRESS_1_LINE;              /* 一线地址模式 */FlashCommand.DataMode           = QSPI_DATA_4_LINES;                /* 四线数据模式 */FlashCommand.SIOOMode           = QSPI_SIOO_INST_ONLY_FIRST_CMD;    /* 仅第一次传输时发送指令 */FlashCommand.DummyCycles        = 0;                                /* 无需指令空闲周期 */FlashCommand.Instruction        = GD25Q64E_CMD_QUAD_PAGE_PROGRAM;   /* 四线快速页编程命令 */FlashCommand.Address            = PageAddr;                         /* 页地址,要确保能被256Byte整除 */FlashCommand.NbData             = totalSize;                        /* 写入数据大小 *//* 发送页编程命令 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 发送数据 */if (HAL_QSPI_Transmit(&hqspi, pData, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 等待WIP位被清0 */return QSPI_PollingWIP();
}

8、读取数据

这算是整个 Flash 最重要的一个步骤,无论是后来的内存映射还是其他的操作,所有的操作都取决于整个读取的速度,我们直接开始看Flash的数据手册:

手册中有这样的几个命令,我们只关注快速读取(fast read),这样确保读取的速度最快!

官方命名命令解释
READ DATA BYTES (READ)0x03最基本的读命令,直接用 SPI 一线模式读取数据。<br />速度较慢,不带 Dummy 时钟。<br />适合初始化、慢速场合。<br />
FAST READ0x0B比 0x03 快,因为发完地址后加一个 Dummy 字节(8个时钟),紧接着高速输出数据。<br />还是单线 SPI,只是速度提升了。<br />用于有 Dummy 支持的主控,提高读取速度。<br />
DUAL OUTPUT FAST READ0x3B地址和指令用一线 SPI,数据输出用双线(2线)模式。<br />比普通 SPI 读快一倍,适合 MCU 支持 Dual SPI 的场合。<br />
QUAD OUTPUT FAST READ0x6B地址和指令用一线 SPI,数据输出用四线(4线)模式(QSPI)。<br />速度比普通 SPI 读快四倍,适合速度要求高的应用。<br />
DUAL I/O FAST READ0xBB地址、数据都用双线(2线)模式传输。<br />比 Dual Output 更快,因为地址也用双线模式。<br />
QUAD I/O FAST READ0xEB地址、数据都用四线(QSPI)模式传输。<br />是最快的读操作,适合高性能主控(支持 QSPI)。<br />

我们直接使用 【QUAD I/O FAST READ】 接口命令。

关于我们之后将要使用的 四线快速输入输出读取命令(0xEB) 我们读取数据手册:

这都是 0xEB 这个命令下的语句,从中我们可以读出几个非常重要的信息:

  • 当前用的是 1‑4‑4 Quad I/O:指令单线→地址四线→(可选模式字节/Dummy)→数据四线。

  • 4‑4‑4(QPI) 模式:指令本身也走 4 线并行(一次输出 4bit×2 个时钟\=1字节)。Flash 需要先执行 “Enter QPI Mode” 指令(绝大多数 GigaDevice 为 0x38,退出是 0xFF,务必查你的 Datasheet QPI 章节确认)。

  • QE\=1 只是允许 Quad I/O,不等于直接使用 4-4-4 模式,也就是 4线指令-4线命令-4线数据

  • QE 置位 1 ≠ 进入 QPI 模式。QE 只是允许使用 1-1-4 / 1-4-4 / 1-1-2 / 1-2-2 等扩展 I/O 操作。若要使用 4-4-4(即指令也 4 线)必须显式发送“进入 QPI 模式”指令

但是但是但是,在手册中没有明显的找到相关的进入4-4-4指令的命令,所以我们使用的是1-4-4模式。

经过实际的代码测试其中有一个关键的参数:Dummy

这是指令空闲时间,之前我们使用单线模式发送一些简单的命令,写入的速度没有达到读取的速度这么高,所以我们不怎么关注于这个参数,现在我们直接把速度拉满,任何一点小小的时序错误都不行,关于这个参数的问题,还是来读Flash的数据手册:

我们发现了这个表格,上面详细的列出了,在DC位置0和置1时速度分别有什么样的变化,我们使用的是 0xEB 这个命令,按照手册来说一般情况下需要选择的是 6 或者 10,但是经过作者的测试,发现这个其实和板子的一些硬件参数也有关系,例如走线,和芯片引脚的配置等等。所以我们要根据实际的情况来进行设置。

下面我先设置为 6 进行测试,后面我们再进行一些调整和测试。

.h 中定义:

注意:QSPI_DUMMY_CYCLES_READ_QUAD 需要我们最后测试的时候进行调整。目前先设定默认 6

#define QSPI_DUMMY_CYCLES_READ_QUAD     6       /* 四线读取时的指令空闲周期数 */#define GD25Q64E_CMD_READ_DATA          0x03    /* 读取数据 */
#define GD25Q64E_CMD_FAST_READ          0x0B    /* 快速读取数据 */
#define GD25Q64E_CMD_FAST_READ_DUAL     0x3B    /* 双线快速读取数据 */
#define GD25Q64E_CMD_FAST_READ_QUAD     0x6B    /* 四线快速读取数据 */
#define GD25Q64E_CMD_FAST_READ_IO_DUAL  0xBB    /* 双线输入输出快速读取数据 */
#define GD25Q64E_CMD_FAST_READ_IO_QUAD  0xEB    /* 四线输入输出快速读取数据 */

.c 中编写函数:

/******************************************************************* 函 数 名 称:QSPI_ReadFlash* 函 数 说 明:快速读取Flash数据,可以超过标准页大小256Byte,不能超过Flash芯片的容量大小* 函 数 形 参:startAddr:数据起始地址*             pData:数据缓冲区指针*             totalSize:读取数据大小,可以大于标准页256Byte,不能超过Flash芯片容量* 函 数 返 回:0:成功 1:失败* 备       注:
******************************************************************/
int QSPI_ReadFlash(uint32_t startAddr, uint8_t *pData, uint32_t totalSize)
{QSPI_CommandTypeDef FlashCommand = {0};FlashCommand.DdrMode            = QSPI_DDR_MODE_DISABLE;            /* 关闭DDR模式 */FlashCommand.DdrHoldHalfCycle   = QSPI_DDR_HHC_ANALOG_DELAY;        /* DDR模式下的时钟延迟,因为关闭了DDR模式所以不管 */FlashCommand.InstructionMode    = QSPI_INSTRUCTION_1_LINE;          /* 一线指令模式 */FlashCommand.AddressMode        = QSPI_ADDRESS_4_LINES;             /* 四线地址模式 */FlashCommand.DataMode           = QSPI_DATA_4_LINES;                /* 四线数据模式 */FlashCommand.AlternateByteMode  = QSPI_ALTERNATE_BYTES_4_LINES;     /* 四线交替字节模式 */FlashCommand.AlternateBytesSize = QSPI_ALTERNATE_BYTES_8_BITS;      /* 8位交替字节 */FlashCommand.AlternateBytes     = 0x00;                             /* 模式字节 */FlashCommand.AddressSize        = QSPI_ADDRESS_24_BITS;             /* 24位地址 */FlashCommand.SIOOMode           = QSPI_SIOO_INST_EVERY_CMD;         /* 每次传输时发送指令 */FlashCommand.Instruction        = GD25Q64E_CMD_FAST_READ_IO_QUAD;   /* 四线快速输入输出读取命令 */FlashCommand.Address            = startAddr;                        /* 读取数据的起始地址 */FlashCommand.NbData             = totalSize;                        /* 读取数据的大小 *//* 这个需要根据实际测试结果进行调整 */FlashCommand.DummyCycles        = QSPI_DUMMY_CYCLES_READ_QUAD;      /* 四线读取时的指令空闲周期数 *//* 发送命令 */if (HAL_QSPI_Command(&hqspi, &FlashCommand, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;/* 读取数据 */if (HAL_QSPI_Receive(&hqspi, pData, HAL_QSPI_TIMEOUT_DEFAULT_VALUE) != HAL_OK)return 1;return 0;
}

用于快速读取 GD25Q64E Flash 数据,支持一次读取任意长度(可超过256字节页长),只要不超过芯片总容量。利用 QSPI 四线模式和“快速读”命令,大幅提升读取速度。

  • 使用QSPI四线模式,速度远高于普通SPI读取(单线模式)。

  • 支持跨页读取,不受256字节页长限制,适合大量数据一次性读取。

  • DummyCycles需要根据主控和Flash速度调整,否则容易读错数据。

  • 适合固件升级、资源加载等对速度和批量数据有要求的场景。

测试程序编写

1、Debug串口函数编写

/* 串口简单输出(阻塞方式) */
static void uart_puts(const char *s)
{HAL_UART_Transmit(&huart1, (uint8_t*)s, (uint16_t)strlen(s), 1000);
}static void uart_print_hex(const uint8_t *buf, uint32_t len)
{char out[4];for (uint32_t i = 0; i < len; i++) {static const char HEX[] = "0123456789ABCDEF";out[0] = HEX[(buf[i] >> 4) & 0x0F];out[1] = HEX[ buf[i]       & 0x0F];out[2] = ( (i+1) % 16 == 0 ) ? '\n' : ' ';out[3] = 0;HAL_UART_Transmit(&huart1, (uint8_t*)out, (out[2]=='\n')?3:2, 1000);}if (len % 16 != 0) uart_puts("\r\n");
}

2、QuadSPI读写测试(重要)

这个很重要,我们要根据这个结果进行测试 QSPI_DUMMY_CYCLES_READ_QUAD 的数值,再根据实际的情况进行调整,因为这个数值不稳定容易数据移位而导致读取的数据是错误的。

例如:

  • 写入:A0A1A2A3A4A5A6A7A8A9AAABACADAEAF

  • 读出:0A1A2A3A4A5A6A7A8A9AAABACADAEAF

我们可以看到开头的一些数据丢失了,所以这就是 DummyCycles (指令空闲时间)的数值偏大,读取的数据不及时导致开头丢掉了,这时候我们就可以修改DummyCycles的数字减小再次进行测试。

/* Demo:测试 QSPI-Flash的读写步骤:1. 读取 JEDEC ID2. 擦除64K块 -> 写一页 (32字节)3. 直接读取数据查看相关的结果
*/
static void QSPI_TestReadWrite(void)
{uint8_t tx[QSPI_TEST_LEN];uint8_t rx[QSPI_TEST_LEN];uint32_t i;uint8_t mid=0, did=0, uid=0;/* 1. 读取 JEDEC ID */if (QSPI_ReadFlashID(&mid,&did,&uid) != 0) {uart_puts("Read ID Fail\r\n");return;}char msg[80];snprintf(msg, sizeof(msg), "JEDEC ID: %02X %02X %02X\r\n", mid,did,uid);uart_puts(msg);/* 准备写入数据模式递增 */for (i = 0; i < QSPI_TEST_LEN; i++) {tx[i] = (uint8_t)(0xA0 + i);}uart_puts("Erase 64K Block...\r\n");if (QSPI_EraseFlashSector(QSPI_TEST_ADDR) != 0) { uart_puts("Erase Fail\r\n"); return; }uart_puts("Page Program...\r\n");if (QSPI_WriteFlash(QSPI_TEST_ADDR,tx,QSPI_TEST_LEN) != 0) { uart_puts("Program Fail\r\n"); return; }/* 直接读取数据查看相关的结果 */if (QSPI_ReadFlash(QSPI_TEST_ADDR, rx, QSPI_TEST_LEN) != 0) {uart_puts("Read Flash Fail\r\n");return;}uart_puts("TX:\r\n"); uart_print_hex(tx, QSPI_TEST_LEN);uart_puts("RX:\r\n"); uart_print_hex(rx, QSPI_TEST_LEN);int diff = 0; for (i = 0; i < QSPI_TEST_LEN; i++) { if (tx[i] != rx[i]) { diff = 1; break; } }uart_puts(diff?"COMPARE: FAIL\r\n":"COMPARE: OK\r\n");
}

main.c的代码如下:

/* USER CODE BEGIN Header */
/********************************************************************************* @file           : main.c* @brief          : Main program body******************************************************************************* @attention** Copyright (c) 2025 STMicroelectronics.* All rights reserved.** This software is licensed under terms that can be found in the LICENSE file* in the root directory of this software component.* If no LICENSE file comes with this software, it is provided AS-IS.********************************************************************************/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "quadspi.h"
#include "usart.h"
#include "gpio.h"/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "string.h"
#include "stdio.h"
#include "bsp_qspi_gd25q64e.h"/* USER CODE END Includes *//* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD *//* USER CODE END PTD *//* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD *//* USER CODE END PD *//* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM *//* USER CODE END PM *//* Private variables ---------------------------------------------------------*//* USER CODE BEGIN PV */
#define QSPI_TEST_ADDR   0x00000000u      /* 64KB边界;确保 <= 8MB */
#define QSPI_TEST_LEN    32               /* 测试字节数(<=256, 一页) */
/* USER CODE END PV *//* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
static void QSPI_TestReadWrite(void);
static void uart_puts(const char *s);
static void uart_print_hex(const uint8_t *buf, uint32_t len);
/* USER CODE END PFP *//* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 *//* USER CODE END 0 *//*** @brief  The application entry point.* @retval int*/
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_QUADSPI_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 *//* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */if (QSPI_FlashInit() != 0) {uart_puts("QSPI Init Fail\r\n");Error_Handler();}uart_puts("STM32H750xx Init OK\r\n");/* 运行Flash读写测试 */QSPI_TestReadWrite();while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}/*** @brief System Clock Configuration* @retval None*/
void SystemClock_Config(void)
{RCC_OscInitTypeDef RCC_OscInitStruct = {0};RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};/** Supply configuration update enable*/HAL_PWREx_ConfigSupply(PWR_LDO_SUPPLY);/** Configure the main internal regulator output voltage*/__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE0);while(!__HAL_PWR_GET_FLAG(PWR_FLAG_VOSRDY)) {}/** Initializes the RCC Oscillators according to the specified parameters* in the RCC_OscInitTypeDef structure.*/RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;RCC_OscInitStruct.HSEState = RCC_HSE_ON;RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;RCC_OscInitStruct.PLL.PLLM = 5;RCC_OscInitStruct.PLL.PLLN = 192;RCC_OscInitStruct.PLL.PLLP = 2;RCC_OscInitStruct.PLL.PLLQ = 2;RCC_OscInitStruct.PLL.PLLR = 2;RCC_OscInitStruct.PLL.PLLRGE = RCC_PLL1VCIRANGE_2;RCC_OscInitStruct.PLL.PLLVCOSEL = RCC_PLL1VCOWIDE;RCC_OscInitStruct.PLL.PLLFRACN = 0;if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK){Error_Handler();}/** Initializes the CPU, AHB and APB buses clocks*/RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2|RCC_CLOCKTYPE_D3PCLK1|RCC_CLOCKTYPE_D1PCLK1;RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;RCC_ClkInitStruct.SYSCLKDivider = RCC_SYSCLK_DIV1;RCC_ClkInitStruct.AHBCLKDivider = RCC_HCLK_DIV2;RCC_ClkInitStruct.APB3CLKDivider = RCC_APB3_DIV2;RCC_ClkInitStruct.APB1CLKDivider = RCC_APB1_DIV2;RCC_ClkInitStruct.APB2CLKDivider = RCC_APB2_DIV2;RCC_ClkInitStruct.APB4CLKDivider = RCC_APB4_DIV2;if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_4) != HAL_OK){Error_Handler();}
}/* USER CODE BEGIN 4 */
/* 串口简单输出(阻塞方式) */
static void uart_puts(const char *s)
{HAL_UART_Transmit(&huart1, (uint8_t*)s, (uint16_t)strlen(s), 1000);
}static void uart_print_hex(const uint8_t *buf, uint32_t len)
{char out[4];for (uint32_t i = 0; i < len; i++) {static const char HEX[] = "0123456789ABCDEF";out[0] = HEX[(buf[i] >> 4) & 0x0F];out[1] = HEX[ buf[i]       & 0x0F];out[2] = ( (i+1) % 16 == 0 ) ? '\n' : ' ';out[3] = 0;HAL_UART_Transmit(&huart1, (uint8_t*)out, (out[2]=='\n')?3:2, 1000);}if (len % 16 != 0) uart_puts("\r\n");
}/* Demo:测试 QSPI-Flash的读写步骤:1. 读取 JEDEC ID2. 擦除64K块 -> 写一页 (32字节)3. 直接读取数据查看相关的结果
*/
static void QSPI_TestReadWrite(void)
{uint8_t tx[QSPI_TEST_LEN];uint8_t rx[QSPI_TEST_LEN];uint32_t i;uint8_t mid=0, did=0, uid=0;/* 1. 读取 JEDEC ID */if (QSPI_ReadFlashID(&mid,&did,&uid) != 0) {uart_puts("Read ID Fail\r\n");return;}char msg[80];snprintf(msg, sizeof(msg), "JEDEC ID: %02X %02X %02X\r\n", mid,did,uid);uart_puts(msg);/* 准备写入数据模式递增 */for (i = 0; i < QSPI_TEST_LEN; i++) {tx[i] = (uint8_t)(0xA0 + i);}uart_puts("Erase 64K Block...\r\n");if (QSPI_EraseFlashSector(QSPI_TEST_ADDR) != 0) { uart_puts("Erase Fail\r\n"); return; }uart_puts("Page Program...\r\n");if (QSPI_WriteFlash(QSPI_TEST_ADDR,tx,QSPI_TEST_LEN) != 0) { uart_puts("Program Fail\r\n"); return; }/* 直接读取数据查看相关的结果 */if (QSPI_ReadFlash(QSPI_TEST_ADDR, rx, QSPI_TEST_LEN) != 0) {uart_puts("Read Flash Fail\r\n");return;}uart_puts("TX:\r\n"); uart_print_hex(tx, QSPI_TEST_LEN);uart_puts("RX:\r\n"); uart_print_hex(rx, QSPI_TEST_LEN);int diff = 0; for (i = 0; i < QSPI_TEST_LEN; i++) { if (tx[i] != rx[i]) { diff = 1; break; } }uart_puts(diff?"COMPARE: FAIL\r\n":"COMPARE: OK\r\n");
}
/* USER CODE END 4 *//*** @brief  This function is executed in case of error occurrence.* @retval None*/
void Error_Handler(void)
{/* USER CODE BEGIN Error_Handler_Debug *//* User can add his own implementation to report the HAL error return state */__disable_irq();while (1){}/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/*** @brief  Reports the name of the source file and the source line number*         where the assert_param error has occurred.* @param  file: pointer to the source file name* @param  line: assert_param error line source number* @retval None*/
void assert_failed(uint8_t *file, uint32_t line)
{/* USER CODE BEGIN 6 *//* User can add his own implementation to report the file name and line number,ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) *//* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

下载程序之后,查看串口Debug输出的数据我们可以发现:

  • ID读取成功,和预期的一致。

  • 读取的信息出现了偏移,很明显的是 RX(读取出来的数据)出现了最前面 A0 这个数据的丢失,这就和我们的DummyCycles数值过大有关,发送指令之后等待的时间过长,导致了数据读取不及时,丢掉了最前面的数据,所以我们需要修改DummyCycles的数值,在前面的《编写BSP驱动/读取数据》章节中我们定义的QSPI_DUMMY_CYCLES_READ_QUAD6(使用的默认值),我们接下来开始将这个数字逐步-1,直到最后的对别结果是完全正确的才可以。

经过两次测试,发现当QSPI_DUMMY_CYCLES_READ_QUAD4的时候数据是正确的,所以我们可以将这个数字固定下来了,就直接使用4

至此读写测试就全部完成了。

工程文件

http://www.dtcms.com/a/399710.html

相关文章:

  • 校园网站素材国内搜索引擎排名第一
  • 中国制造网的网络营销方式桔子seo工具
  • wordpress扒站教程网站总体规划说明
  • IP地址管理:部署phpIPAMv1.7.3常见问题
  • 自己做服务器网站表白网站建设
  • 【计算广告】广告出价相关约束问题:PID控制、MPC预测算法
  • 教你如何建设网站网站建设和维护费用
  • kotlin图算法
  • 汕尾网站建设 生意好吗有专门为个人网站做推广的吗
  • 网站建设企业响应式网站模板宿迁城乡住房建设厅网站
  • 建网站需要学什么网站专业术语中SEO的意思是
  • 2025无人机在低空物流中的应用实践
  • Python实现海鸥优化算法(Seagull Optimization Algorithm, SOA)(附完整代码)
  • PostgreSQL 知识体系
  • 网站建设与维护专业实训室昆山做网站的
  • 插针弹簧镀金耐腐蚀、高导电的电子连接“保护盾”|深圳同远
  • 【MySQL✨】MySQL 入门之旅 · 第十一篇:MySQL 表连接(JOIN)基础
  • 德州有做网站的制作表白网站
  • 公司网站做推广成化区建设局网站
  • Openssl TRNG provider demo guide
  • 建立论坛网站做自己的网站不是免费的
  • 赵艳红网站建设规划卢松松博客源码 wordpress博客模板
  • IoT水利监控系统:从需求到实现的完整技术方案
  • 小杰机器学习(seven)——贝叶斯分类
  • 名作之壁吧网站建设参与网站网站建设可判几年
  • mobaxterm里面勾选了Follow terminal folder,但是不生效
  • 培训餐饮网站建设中企动力如何
  • vector的使用和模拟
  • 织梦网站栏目无法生成网站关键字多少个
  • n8n中的postgres节点中插入数据怎么自动插入,不设置id?