一、概述
无论是新手还是大佬,基于STM32单片机的开发,使用STM32CubeMX都是可以极大提升开发效率的,并且其界面化的开发,也大大降低了新手对STM32单片机的开发门槛。
本文主要讲述STM32芯片QSPI功能的配置及其相关知识。
二、软件说明
STM32CubeMX是ST官方出的一款针对ST的MCU/MPU跨平台的图形化工具,支持在Linux、MacOS、Window系统下开发,其对接的底层接口是HAL库,另外习惯于寄存器开发的同学们,也可以使用LL库。STM32CubeMX除了集成MCU/MPU的硬件抽象层,另外还集成了像RTOS,文件系统,USB,网络,显示,嵌入式AI等中间件,这样开发者就能够很轻松的完成MCU/MPU的底层驱动的配置,留出更多精力开发上层功能逻辑,能够更进一步提高了嵌入式开发效率。
演示版本 6.9.0
三、QSPI简介
QSPI(Quad SPI,四线串行外设接口)是一种常用的串行外部存储器接口,用于连接微控制器和外部闪存存储器。它是SPI(Serial Peripheral Interface,串行外设接口)的一种变种,通过增加数据线的数量,提高了数据传输速度。
QSPI使用四根线进行全双工的数据传输,包括一个时钟线、一个数据输入线、一个数据输出线和一个片选线。通过在每个时钟周期内同时传输四个数据位,QSPI能够提供更高的数据传输速度和带宽,相比传统的SPI接口,具有更高的性能。
QSPI广泛应用于存储器扩展、快速数据存取以及固件更新等领域。它可以连接各种外部闪存存储器,如串行闪存(Serial Flash),用于存储程序代码、配置数据等。
QSPI是SPI的一种变体,除了标准SPI和QSPI外,还有DSPI和OSPI两种SPI总线变体,下面表格列出了这四种接口的特点及差异。
这里来看下STM32H750中QSPI的外设描述。

四、QSPI配置

这是一段 嵌入式 / 硬件配置参数(General Parameters) ,常见于 MCU(微控制器单元)或 Flash 存储控制器的配置,各参数含义如下:
- Clock Prescaler(时钟预分频器)
值:255
作用:对时钟信号分频,降低时钟频率,用于控制通信速率(如 SPI 总线时钟),数值越大,实际工作时钟越低,通信越慢但可能更稳定。 - Fifo Threshold(FIFO 阈值)
值:1
作用:FIFO(先入先出队列)的触发阈值,当数据量达到 1 时,可能触发中断 / 数据传输,用于控制数据缓冲的触发条件。 - Sample Shifting(采样移位)
值:No Sample Shifting
作用:控制数据采样的时序偏移,"No Sample Shifting" 表示不偏移,直接按默认时序采样,确保数据对齐。 - Flash Size(闪存容量)
值:1
作用:定义 Flash 存储芯片的容量配置(需结合实际芯片,可能是按 "块 / 分区" 或逻辑容量设置 ),影响地址映射、读写范围。 - Chip Select High Time(片选高电平时间)
值:1 Cycle
作用:控制 "片选信号(CS)" 高电平持续周期,确保 Flash 芯片在指令 / 数据传输前后有稳定的电平时序,避免误操作。 - Clock Mode(时钟模式)
值:Low
作用:定义时钟信号的极性 / 相位(如 SPI 时钟模式),"Low" 通常指空闲时钟电平为低,配合采样边沿(如上升沿 / 下降沿)决定通信时序。 - Flash ID(闪存标识)
值:Flash ID 1
作用:区分不同 Flash 芯片(多芯片系统中),通过 ID 识别硬件,加载对应驱动 / 配置,确保适配芯片型号。 - Dual Flash(双闪存)
值:Disabled
作用:关闭 "双闪存模式",仅单 Flash 工作;使能后可支持双芯片并行 / 冗余,提升存储容量或可靠性。
这些参数共同控制 Flash 存储与 MCU 的通信时序、数据缓冲、硬件适配 ,需结合具体芯片手册(如 SPI 控制器、Flash 型号)调整,确保读写稳定。
五、应用配置
5.1 端口配置
这里用的是正点原子的ALIENTEK北极星STM32H750开发板,查看QSPI相关的原理图如下,可以看到这里需要用到6个引脚,分别是QSPI_BK1_NCS(PB6)、QSPI_BK1_CLK(PB2)、QSPI_BK1_IO1(PF9)、QSPI_BK1_IO2(PF8)、QSPI_BK1_IO3(PF7)、QSPI_BK1_IO4(PF6)。要注意原理图这里虽然写的是W25Q32,但实际读出来的FlashID表示这是一个W25Q64的芯片,大家要根据实际情况调整。



- CubeMX端口配置

5.2 QSPI配置

Clock Prescaler: 查了下W25Q64的芯片,标准SPI模式最高频率可以到80M,这里配置成4分频,即50M。
Fifo Threshold: 4 字节刚好和指令包对齐,DMA 一次搬运刚好完成 1 次指令收发,搬运效率最高。
Sample Shifting: 当前SPI频率配置是50M,因为W25Q64数据输出延迟约 6~8ns,加上走线延迟,默认采样点会采到不稳定的跳变沿数据,所以这里配置Sample Shifting Half Cycle是为了延时半个时钟周期采样(10ns),让采样的位置处在稳定状态下,不至于刚好采到跳变时的值。建议频率高于40M的要开启这个功能。
Flash Size: 因为用的是W25Q64,其容量为8M,即最大地址位需要23位,这里配置为24位。
Chip Select High Time: 手册里还要求两次 SPI 指令之间,片选 CS 拉高后必须维持的最小时长为50ns,根据当前50M的频率计算,Chip Select High Time设置5个时钟周期即为100ns,大于手册要求的50ns。


因为GPIO端口是操作Flash芯片的,按照配置频率可以达到50M,所以GPIO速率得调成Very High。

六、接口及应用
w25qxx芯片驱动
c
/**
******************************************************************************
* @file w25qxx.c
* @brief W25Q64 QSPI Flash 驱动实现
* @note 基于 STM32H750 HAL QSPI,Quad SPI 模式
*
* 数据线模式说明:
* 1-1-1: 标准 SPI 模式 (指令1线 / 地址1线 / 数据1线)
* 1-1-4: Quad Output (指令1线 / 地址1线 / 数据4线)
* 1-4-4: Quad I/O (指令1线 / 地址4线 / 数据4线)
*
* W25Q64 时序 (QSPI_CLK = 200MHz / Prescaler8 = 25MHz):
* 扇区擦除: 典型 45ms, 最大 400ms
* 页编程: 典型 0.7ms, 最大 3ms
* Quad读: 6B cmd, 8 dummy cycles
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "w25qxx.h"
/* ======================== 外部变量 ======================================== */
extern QSPI_HandleTypeDef hqspi;
/* ======================== 调试变量 =========================================
* 在 debugger 中查看这些变量快速定位问题
*
* g_debug_step: 最后执行的操作步骤代码
* g_debug_hal_status: HAL 函数返回的 HAL_StatusTypeDef
* g_debug_error_code: hqspi.ErrorCode (HAL QSPI 错误码)
*
* 关键步骤码:
* 0 = 空闲/成功
* 1-9 = WritePage 流程步骤
* 10 = QSPI_WriteEnable 入口
* 20 = EraseSector: 地址越界
* 21 = EraseSector: 写使能失败
* 22 = EraseSector: 擦除命令失败
* 23 = EraseSector: 等待完成超时
* 50 = WritePage: 页边界越界
* 51 = WritePage: 写使能失败
* 52 = WritePage: 命令发送失败
* 53 = WritePage: 数据发送失败
* 54 = WritePage: 轮询超时
* 90 = QSPI_WriteEnable 内部: 命令失败
* 100 = Init: 复位失败
* 101 = Init: Quad模式配置失败
* 102 = Init: ID读取失败
* 103 = Init: ID不匹配
*/
volatile uint32_t g_debug_step = 0;
volatile uint32_t g_debug_hal_status = 0;
volatile uint32_t g_debug_error_code = 0;
/* 调试辅助宏 */
#define DBG_SET(s) do { g_debug_step = (s); } while(0)
#define DBG_HAL(s, r) do { g_debug_step = (s); g_debug_hal_status = (uint32_t)(r); } while(0)
#define DBG_ERR(s) do { g_debug_step = (s); g_debug_error_code = hqspi.ErrorCode; } while(0)
/* ======================== 芯片信息 ======================================== */
W25Qxx_InfoTypeDef w25qxx_info = {
.FlashSize = W25QXX_FLASH_SIZE,
.EraseSectorSize = W25QXX_SECTOR_SIZE,
.EraseSectorNumber = W25QXX_FLASH_SIZE / W25QXX_SECTOR_SIZE,
.EraseBlock32KSize = W25QXX_BLOCK_SIZE_32K,
.EraseBlock32KNumber = W25QXX_FLASH_SIZE / W25QXX_BLOCK_SIZE_32K,
.EraseBlock64KSize = W25QXX_BLOCK_SIZE_64K,
.EraseBlock64KNumber = W25QXX_FLASH_SIZE / W25QXX_BLOCK_SIZE_64K,
.PageSize = W25QXX_PAGE_SIZE,
.PageNumber = W25QXX_FLASH_SIZE / W25QXX_PAGE_SIZE,
};
/* ======================== 私有函数声明 ==================================== */
static uint8_t QSPI_WriteEnableCmd(void);
static uint8_t QSPI_AutoPollBusy(uint32_t TimeoutMs);
static uint8_t QSPI_ResetMemory(void);
static uint8_t QSPI_EnableQuadMode(void);
static void QSPI_EnsureReady(void);
/* ======================================================================== */
/* 公共 API 实现 */
/* ======================================================================== */
/**
* @brief 初始化 W25Q64 Flash
* @note 流程: 复位 -> Quad模式使能 -> JEDEC ID验证
* @retval 0: 成功 | 1: 复位失败 | 2: ID不匹配
* 3: ID读取失败 | 4: Quad配置失败
*/
uint8_t W25Qxx_Init(void)
{
uint32_t jedec_id;
/* 1. 中止任何挂起操作 */
QSPI_EnsureReady();
/* 2. 复位芯片 */
if (QSPI_ResetMemory() != 0)
{
DBG_SET(100);
return 1;
}
HAL_Delay(10);
/* 3. 使能 Quad 模式 (写入须先于 Quad 操作) */
if (QSPI_EnableQuadMode() != 0)
{
DBG_SET(101);
return 4;
}
/* 4. 读取 JEDEC ID 验证 */
jedec_id = W25Qxx_ReadID();
if (jedec_id == 0)
{
DBG_SET(102);
return 3;
}
if (jedec_id != W25Q64_JEDEC_ID)
{
DBG_SET(103);
return 2;
}
DBG_SET(0);
return 0;
}
/**
* @brief 读取 JEDEC ID
* @retval 32位 ID (0xEF4017 = W25Q64),失败返回 0
*/
uint32_t W25Qxx_ReadID(void)
{
QSPI_CommandTypeDef cmd;
uint8_t buf[3] = {0};
HAL_StatusTypeDef ret;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_READ_JEDEC_ID;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 3;
ret = HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT);
if (ret != HAL_OK)
{
return 0;
}
ret = HAL_QSPI_Receive(&hqspi, buf, W25QXX_CMD_TIMEOUT);
if (ret != HAL_OK)
{
return 0;
}
return ((uint32_t)buf[0] << 16) | ((uint32_t)buf[1] << 8) | (uint32_t)buf[2];
}
/**
* @brief 读取唯一 ID (8字节)
*/
void W25Qxx_ReadUID(uint8_t *pUID)
{
QSPI_CommandTypeDef cmd;
if (pUID == NULL) return;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_READ_UNIQUE_ID;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.DummyCycles = 32; /* W25Q64 UID 需要 4 字节 dummy */
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 8;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
return;
HAL_QSPI_Receive(&hqspi, pUID, W25QXX_CMD_TIMEOUT);
}
/**
* @brief 读取状态寄存器
* @param reg_num: 1 = SR1, 2 = SR2
* @retval 寄存器值
*/
uint8_t W25Qxx_ReadStatusReg(uint8_t reg_num)
{
QSPI_CommandTypeDef cmd;
uint8_t val = 0;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = (reg_num == 2) ? W25QXX_CMD_READ_STATUS_REG2
: W25QXX_CMD_READ_STATUS_REG;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 1;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
return 0;
HAL_QSPI_Receive(&hqspi, &val, W25QXX_CMD_TIMEOUT);
return val;
}
/* ======================================================================== */
/* 擦除操作 */
/* ======================================================================== */
/**
* @brief 擦除单个扇区 (4KB)
* @param SectorAddr: 扇区编号 [0, 2047]
*/
void W25Qxx_EraseSector(uint32_t SectorAddr)
{
QSPI_CommandTypeDef cmd;
uint32_t addr;
/* 边界检查 */
if (SectorAddr >= w25qxx_info.EraseSectorNumber)
{
DBG_SET(20);
return;
}
addr = SectorAddr * W25QXX_SECTOR_SIZE;
QSPI_EnsureReady();
/* 写使能 */
if (QSPI_WriteEnableCmd() != 0)
{
DBG_SET(21);
return;
}
/* 发送擦除命令 */
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_SECTOR_ERASE;
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = addr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
{
DBG_ERR(22);
return;
}
/* 等待擦除完成 */
if (QSPI_AutoPollBusy(W25QXX_SECTOR_ERASE_TIMEOUT) != 0)
{
DBG_SET(23);
return;
}
}
/**
* @brief 擦除 32KB 块
*/
void W25Qxx_EraseBlock32K(uint32_t BlockAddr)
{
QSPI_CommandTypeDef cmd;
uint32_t addr;
if (BlockAddr >= w25qxx_info.EraseBlock32KNumber) return;
addr = BlockAddr * W25QXX_BLOCK_SIZE_32K;
QSPI_EnsureReady();
if (QSPI_WriteEnableCmd() != 0) return;
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_BLOCK_ERASE_32KB;
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = addr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK) return;
QSPI_AutoPollBusy(W25QXX_BLOCK32K_ERASE_TIMEOUT);
}
/**
* @brief 擦除 64KB 块
*/
void W25Qxx_EraseBlock64K(uint32_t BlockAddr)
{
QSPI_CommandTypeDef cmd;
uint32_t addr;
if (BlockAddr >= w25qxx_info.EraseBlock64KNumber) return;
addr = BlockAddr * W25QXX_BLOCK_SIZE_64K;
QSPI_EnsureReady();
if (QSPI_WriteEnableCmd() != 0) return;
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_BLOCK_ERASE_64KB;
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = addr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK) return;
QSPI_AutoPollBusy(W25QXX_BLOCK64K_ERASE_TIMEOUT);
}
/**
* @brief 整片擦除 (耗时约 40 秒)
*/
void W25Qxx_EraseChip(void)
{
QSPI_CommandTypeDef cmd;
QSPI_EnsureReady();
if (QSPI_WriteEnableCmd() != 0) return;
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_CHIP_ERASE;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK) return;
QSPI_AutoPollBusy(W25QXX_CHIP_ERASE_TIMEOUT);
}
/* ======================================================================== */
/* 写入操作 */
/* ======================================================================== */
/**
* @brief 四线页编程 (单页内写入)
* @note 使用 Quad Input Page Program (0x32) 命令
* 写完后自动等待 BUSY 位清除
* @param pData: 数据缓冲区
* @param PageAddr: 页编号 [0, 32767]
* @param Offset: 页内偏移 [0, 255]
* @param Size: 写入字节数 (Offset+Size <= 256)
* @retval 0:成功 1:超出页边界 2:写使能失败
* 3:命令失败 4:数据发送失败 5:等待超时
*/
uint8_t W25Qxx_WritePage(uint8_t *pData, uint32_t PageAddr, uint32_t Offset, uint32_t Size)
{
QSPI_CommandTypeDef cmd;
/* 参数校验 */
if (pData == NULL || Size == 0) return 0;
if ((Offset + Size) > W25QXX_PAGE_SIZE)
{
DBG_SET(50);
return 1;
}
QSPI_EnsureReady();
/* 写使能 */
if (QSPI_WriteEnableCmd() != 0)
{
DBG_SET(51);
return 2;
}
/* 配置 Quad Page Program 命令 */
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_QUAD_PAGE_PROGRAM; /* 0x32 */
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = PageAddr * W25QXX_PAGE_SIZE + Offset;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_4_LINES; /* 四线数据 */
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = Size;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
{
DBG_ERR(52);
return 3;
}
/* 发送数据 (4线) */
if (HAL_QSPI_Transmit(&hqspi, pData, W25QXX_CMD_TIMEOUT) != HAL_OK)
{
DBG_ERR(53);
return 4;
}
/* 等待编程完成 */
if (QSPI_AutoPollBusy(W25QXX_PAGE_PROGRAM_TIMEOUT) != 0)
{
DBG_SET(54);
return 5;
}
DBG_SET(0);
return 0;
}
/**
* @brief 扇区内写入 (自动处理跨页)
* @note 先擦除扇区再分页写入
*/
uint8_t W25Qxx_WriteSector(uint8_t *pData, uint32_t SectorAddr, uint32_t Offset, uint32_t Size)
{
uint32_t addr;
uint32_t remain;
uint32_t page_offset;
uint32_t page_remain;
uint32_t write_size;
uint8_t *pCur;
uint8_t ret;
if (pData == NULL || Size == 0) return 0;
if ((Offset + Size) > W25QXX_SECTOR_SIZE) return 1;
addr = SectorAddr * W25QXX_SECTOR_SIZE + Offset;
remain = Size;
pCur = pData;
while (remain > 0)
{
page_offset = addr % W25QXX_PAGE_SIZE;
page_remain = W25QXX_PAGE_SIZE - page_offset;
write_size = (remain < page_remain) ? remain : page_remain;
ret = W25Qxx_WritePage(pCur, addr / W25QXX_PAGE_SIZE, page_offset, write_size);
if (ret != 0) return ret;
pCur += write_size;
addr += write_size;
remain -= write_size;
}
return 0;
}
/* ======================================================================== */
/* 读取操作 */
/* ======================================================================== */
/**
* @brief 标准读取 (1线 SPI, 命令 0x03)
*/
void W25Qxx_ReadData(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
QSPI_CommandTypeDef cmd;
if (pData == NULL || Size == 0) return;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_READ_DATA;
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = ReadAddr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = Size;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK) return;
HAL_QSPI_Receive(&hqspi, pData, W25QXX_CMD_TIMEOUT);
}
/**
* @brief 四线快速读取 (命令 0x6B, Quad Output Fast Read)
* @note 指令1线 / 地址1线 / 8个dummy周期 / 数据4线
*/
void W25Qxx_FastReadData(uint8_t *pData, uint32_t ReadAddr, uint32_t Size)
{
QSPI_CommandTypeDef cmd;
if (pData == NULL || Size == 0) return;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_FAST_READ_QUAD; /* 0x6B */
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.Address = ReadAddr;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_4_LINES; /* 四线接收数据 */
cmd.DummyCycles = 8; /* 8个 dummy 周期 */
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = Size;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK) return;
HAL_QSPI_Receive(&hqspi, pData, W25QXX_CMD_TIMEOUT);
}
/* ======================================================================== */
/* 控制操作 */
/* ======================================================================== */
/**
* @brief 写使能 (公共接口)
*/
uint8_t W25Qxx_WriteEnable(void)
{
return QSPI_WriteEnableCmd();
}
/**
* @brief 写禁止
*/
uint8_t W25Qxx_WriteDisable(void)
{
QSPI_CommandTypeDef cmd;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_WRITE_DISABLE;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK) return 1;
return QSPI_AutoPollBusy(W25QXX_CMD_TIMEOUT);
}
/**
* @brief 等待写操作完成 (轮询 BUSY 位)
*/
uint8_t W25Qxx_WaitForWriteEnd(void)
{
return QSPI_AutoPollBusy(W25QXX_CMD_TIMEOUT);
}
/**
* @brief 进入掉电模式
*/
void W25Qxx_EnterPowerDown(void)
{
QSPI_CommandTypeDef cmd;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_ENTER_POWER_DOWN;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT);
}
/**
* @brief 退出掉电模式
*/
void W25Qxx_ExitPowerDown(void)
{
QSPI_CommandTypeDef cmd;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_RELEASE_POWER_DOWN;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT);
HAL_Delay(1);
}
/**
* @brief 使能内存映射模式 (Quad SPI, 只读)
* @note 映射后 Flash 内容可直接通过指针访问
* 地址 = 0x90000000 + Flash物理地址
*/
void W25Qxx_EnableMemoryMappedMode(void)
{
QSPI_CommandTypeDef cmd;
QSPI_MemoryMappedTypeDef mem_cfg;
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_FAST_READ_QUAD;
cmd.AddressMode = QSPI_ADDRESS_1_LINE;
cmd.AddressSize = QSPI_ADDRESS_24_BITS;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_4_LINES;
cmd.DummyCycles = 8;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
mem_cfg.TimeOutPeriod = 0x10;
mem_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_ENABLE;
HAL_QSPI_MemoryMapped(&hqspi, &cmd, &mem_cfg);
}
/**
* @brief 中止 QSPI 操作 (紧急恢复)
*/
void W25Qxx_Abort(void)
{
HAL_QSPI_Abort(&hqspi);
}
/* ======================================================================== */
/* 私有辅助函数 */
/* ======================================================================== */
/**
* @brief 确保 QSPI 接口处于空闲状态
*/
static void QSPI_EnsureReady(void)
{
if (HAL_QSPI_GetState(&hqspi) != HAL_QSPI_STATE_READY)
{
HAL_QSPI_Abort(&hqspi);
}
}
/**
* @brief 发送写使能命令 (0x06)
* @retval 0:成功 1:失败
*/
static uint8_t QSPI_WriteEnableCmd(void)
{
QSPI_CommandTypeDef cmd;
HAL_StatusTypeDef ret;
DBG_SET(10);
QSPI_EnsureReady();
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_WRITE_ENABLE;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
ret = HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT);
if (ret != HAL_OK)
{
DBG_HAL(90, ret);
DBG_ERR(90);
return 1;
}
return 0;
}
/**
* @brief 硬件自动轮询 BUSY 位,等待 Flash 就绪
* @param TimeoutMs: 超时时间 (ms)
* @retval 0:就绪 1:超时
*/
static uint8_t QSPI_AutoPollBusy(uint32_t TimeoutMs)
{
QSPI_CommandTypeDef cmd;
QSPI_AutoPollingTypeDef cfg;
/* 配置状态寄存器读取 (1线模式,不受 QE 位影响) */
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_READ_STATUS_REG; /* 0x05 */
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE; /* 1线读取 */
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 1;
/* 轮询配置: 等待 BUSY 位 = 0 (与掩码匹配) */
memset(&cfg, 0, sizeof(cfg));
cfg.MatchMode = QSPI_MATCH_MODE_AND;
cfg.AutomaticStop = QSPI_AUTOMATIC_STOP_ENABLE;
cfg.Mask = W25QXX_SR1_BUSY; /* 只检查 BUSY 位 */
cfg.Match = 0x00; /* 期望 BUSY = 0 */
cfg.StatusBytesSize = 1;
cfg.Interval = 1; /* 轮询间隔: 1个SCK周期 */
if (HAL_QSPI_AutoPolling(&hqspi, &cmd, &cfg, TimeoutMs) != HAL_OK)
{
return 1;
}
return 0;
}
/**
* @brief 复位 W25Q64
* @note 先发使能复位(0x66),再发复位(0x99)
*/
static uint8_t QSPI_ResetMemory(void)
{
QSPI_CommandTypeDef cmd;
QSPI_EnsureReady();
/* 使能复位命令 */
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_ENABLE_RESET;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_NONE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 0;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
return 1;
/* 复位设备 */
cmd.Instruction = W25QXX_CMD_RESET_DEVICE;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
return 1;
return 0;
}
/**
* @brief 使能 Quad 模式 (设置状态寄存器2的 QE 位)
* @note W25Q64 需要 QE=1 才能使用 Quad SPI 操作
* Quad 操作包括: Quad Page Program(0x32), Quad Fast Read(0x6B)
*/
static uint8_t QSPI_EnableQuadMode(void)
{
uint8_t sr1, sr2;
uint8_t status_reg[2];
QSPI_CommandTypeDef cmd;
QSPI_EnsureReady();
/* 1. 读取当前状态寄存器值 */
sr1 = W25Qxx_ReadStatusReg(1);
sr2 = W25Qxx_ReadStatusReg(2);
/* 2. 检查 QE 位是否已设置 */
if (sr2 & W25QXX_SR2_QE)
{
return 0; /* 已经是 Quad 模式 */
}
/* 3. 写使能 (状态寄存器写入需要) */
if (QSPI_WriteEnableCmd() != 0) return 1;
/* 4. 写状态寄存器 (SR1 + SR2) */
status_reg[0] = sr1;
status_reg[1] = sr2 | W25QXX_SR2_QE; /* 设置 QE 位 */
memset(&cmd, 0, sizeof(cmd));
cmd.InstructionMode = QSPI_INSTRUCTION_1_LINE;
cmd.Instruction = W25QXX_CMD_WRITE_STATUS_REG;
cmd.AddressMode = QSPI_ADDRESS_NONE;
cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE;
cmd.DataMode = QSPI_DATA_1_LINE;
cmd.DummyCycles = 0;
cmd.DdrMode = QSPI_DDR_MODE_DISABLE;
cmd.DdrHoldHalfCycle = QSPI_DDR_HHC_ANALOG_DELAY;
cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD;
cmd.NbData = 2;
if (HAL_QSPI_Command(&hqspi, &cmd, W25QXX_CMD_TIMEOUT) != HAL_OK)
return 1;
if (HAL_QSPI_Transmit(&hqspi, status_reg, W25QXX_CMD_TIMEOUT) != HAL_OK)
return 1;
/* 5. 等待写入完成 */
if (QSPI_AutoPollBusy(W25QXX_WRITE_SR_TIMEOUT) != 0)
return 1;
/* 6. 验证 QE 位 */
sr2 = W25Qxx_ReadStatusReg(2);
if (!(sr2 & W25QXX_SR2_QE))
{
return 1;
}
return 0;
}
c
/**
******************************************************************************
* @file w25qxx.h
* @brief W25Q64 QSPI Flash 驱动头文件
* @note 适用于 STM32H750 + W25Q64 (8MB / 64Mbit)
******************************************************************************
*/
#ifndef __W25QXX_H
#define __W25QXX_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include <string.h>
/* ======================== W25Q64 Flash 参数 =============================== */
#define W25QXX_FLASH_SIZE 0x800000UL /* 8MB = 64Mbit */
#define W25QXX_SECTOR_SIZE 0x1000UL /* 4KB 扇区 */
#define W25QXX_BLOCK_SIZE_32K 0x8000UL /* 32KB 块 */
#define W25QXX_BLOCK_SIZE_64K 0x10000UL /* 64KB 块 */
#define W25QXX_PAGE_SIZE 0x100UL /* 256B 页 */
/* JEDEC Manufacturer & Device ID */
#define W25Q64_JEDEC_ID 0xEF4017UL
/* ======================== 命令定义 ======================================== */
#define W25QXX_CMD_WRITE_ENABLE 0x06 /* 写使能 */
#define W25QXX_CMD_WRITE_DISABLE 0x04 /* 写禁止 */
#define W25QXX_CMD_READ_STATUS_REG 0x05 /* 读状态寄存器1 */
#define W25QXX_CMD_READ_STATUS_REG2 0x35 /* 读状态寄存器2 */
#define W25QXX_CMD_WRITE_STATUS_REG 0x01 /* 写状态寄存器 */
#define W25QXX_CMD_READ_DATA 0x03 /* 标准读 (1线) */
#define W25QXX_CMD_FAST_READ 0x0B /* 快速读 (1线) */
#define W25QXX_CMD_FAST_READ_QUAD 0x6B /* 四线快速读 */
#define W25QXX_CMD_PAGE_PROGRAM 0x02 /* 页编程 (1线) */
#define W25QXX_CMD_QUAD_PAGE_PROGRAM 0x32 /* 四线页编程 */
#define W25QXX_CMD_SECTOR_ERASE 0x20 /* 4KB 扇区擦除 */
#define W25QXX_CMD_BLOCK_ERASE_32KB 0x52 /* 32KB 块擦除 */
#define W25QXX_CMD_BLOCK_ERASE_64KB 0xD8 /* 64KB 块擦除 */
#define W25QXX_CMD_CHIP_ERASE 0xC7 /* 整片擦除 (0xC7/0x60) */
#define W25QXX_CMD_READ_JEDEC_ID 0x9F /* 读JEDEC ID */
#define W25QXX_CMD_READ_UNIQUE_ID 0x4B /* 读唯一ID */
#define W25QXX_CMD_ENTER_POWER_DOWN 0xB9 /* 进入掉电模式 */
#define W25QXX_CMD_RELEASE_POWER_DOWN 0xAB /* 退出掉电模式 */
#define W25QXX_CMD_ENABLE_RESET 0x66 /* 使能复位 */
#define W25QXX_CMD_RESET_DEVICE 0x99 /* 复位设备 */
/* ======================== 状态寄存器位定义 ================================= */
#define W25QXX_SR1_BUSY (1U << 0) /* 忙标志 */
#define W25QXX_SR1_WEL (1U << 1) /* 写使能锁存 */
#define W25QXX_SR2_QE (1U << 1) /* Quad使能位 */
/* ======================== 超时定义 (ms) =================================== */
#define W25QXX_CMD_TIMEOUT 100U /* 命令超时 */
#define W25QXX_SECTOR_ERASE_TIMEOUT 500U /* 扇区擦除超时 */
#define W25QXX_BLOCK32K_ERASE_TIMEOUT 1200U /* 32KB块擦除超时 */
#define W25QXX_BLOCK64K_ERASE_TIMEOUT 2500U /* 64KB块擦除超时 */
#define W25QXX_CHIP_ERASE_TIMEOUT 90000U /* 整片擦除超时 */
#define W25QXX_PAGE_PROGRAM_TIMEOUT 10U /* 页编程超时 */
#define W25QXX_WRITE_SR_TIMEOUT 20U /* 写状态寄存器超时 */
/* ======================== 类型定义 ======================================== */
typedef struct {
uint32_t FlashSize; /* Flash总大小 (字节) */
uint32_t EraseSectorSize; /* 扇区大小 (字节) */
uint32_t EraseSectorNumber; /* 扇区数量 */
uint32_t EraseBlock32KSize; /* 32KB块大小 (字节) */
uint32_t EraseBlock32KNumber; /* 32KB块数量 */
uint32_t EraseBlock64KSize; /* 64KB块大小 (字节) */
uint32_t EraseBlock64KNumber; /* 64KB块数量 */
uint32_t PageSize; /* 页大小 (字节) */
uint32_t PageNumber; /* 页数量 */
} W25Qxx_InfoTypeDef;
/* ======================== 公共变量 ======================================== */
extern W25Qxx_InfoTypeDef w25qxx_info;
/* 调试变量 - 在 debugger 中查看定位问题 */
extern volatile uint32_t g_debug_step;
extern volatile uint32_t g_debug_hal_status;
extern volatile uint32_t g_debug_error_code;
/* ======================== API 函数声明 ==================================== */
/** @brief 初始化 QSPI Flash (复位 + Quad使能 + ID验证)
* @retval 0:成功 1:复位失败 2:ID不匹配 3:ID读取失败 4:Quad配置失败
*/
uint8_t W25Qxx_Init(void);
/** @brief 读取 JEDEC ID (厂商ID + 设备ID)
* @retval 32位ID值 (如 0xEF4017 表示 W25Q64),失败返回 0
*/
uint32_t W25Qxx_ReadID(void);
/** @brief 读取唯一ID (8字节)
* @param pUID: 缓冲区指针 (至少8字节)
*/
void W25Qxx_ReadUID(uint8_t *pUID);
/** @brief 读取指定状态寄存器
* @param reg_num: 寄存器编号 (1 或 2)
* @retval 寄存器值
*/
uint8_t W25Qxx_ReadStatusReg(uint8_t reg_num);
/* ---- 擦除操作 ---- */
/** @brief 擦除指定扇区 (4KB)
* @param SectorAddr: 扇区编号 [0, FlashSize/SectorSize)
*/
void W25Qxx_EraseSector(uint32_t SectorAddr);
/** @brief 擦除 32KB 块
* @param BlockAddr: 块编号 [0, FlashSize/BlockSize32K)
*/
void W25Qxx_EraseBlock32K(uint32_t BlockAddr);
/** @brief 擦除 64KB 块
* @param BlockAddr: 块编号 [0, FlashSize/BlockSize64K)
*/
void W25Qxx_EraseBlock64K(uint32_t BlockAddr);
/** @brief 整片擦除 (耗时约 40 秒)
*/
void W25Qxx_EraseChip(void);
/* ---- 写入操作 ---- */
/** @brief 四线页编程 (单页内写入,不超过页边界)
* @param pData: 数据缓冲区
* @param PageAddr: 页编号
* @param Offset: 页内偏移 (字节)
* @param Size: 写入字节数 (Offset+Size <= 256)
* @retval 0:成功 1:超出页边界 2:写使能失败 3:命令失败 4:数据发送失败 5:等待超时
*/
uint8_t W25Qxx_WritePage(uint8_t *pData, uint32_t PageAddr, uint32_t Offset, uint32_t Size);
/** @brief 扇区内写入 (自动跨页处理)
* @param pData: 数据缓冲区
* @param SectorAddr: 扇区编号
* @param Offset: 扇区内偏移 (字节)
* @param Size: 写入字节数 (Offset+Size <= 4096)
* @retval 0:成功 非0:失败
*/
uint8_t W25Qxx_WriteSector(uint8_t *pData, uint32_t SectorAddr, uint32_t Offset, uint32_t Size);
/* ---- 读取操作 ---- */
/** @brief 标准读取 (1线 SPI 模式)
* @param pData: 数据缓冲区
* @param ReadAddr: 读取起始地址
* @param Size: 读取字节数
*/
void W25Qxx_ReadData(uint8_t *pData, uint32_t ReadAddr, uint32_t Size);
/** @brief 四线快速读取 (Quad Fast Read)
* @param pData: 数据缓冲区
* @param ReadAddr: 读取起始地址
* @param Size: 读取字节数
*/
void W25Qxx_FastReadData(uint8_t *pData, uint32_t ReadAddr, uint32_t Size);
/* ---- 控制操作 ---- */
/** @brief 写使能
* @retval 0:成功 1:失败
*/
uint8_t W25Qxx_WriteEnable(void);
/** @brief 写禁止
* @retval 0:成功 1:失败
*/
uint8_t W25Qxx_WriteDisable(void);
/** @brief 等待写操作完成 (轮询 BUSY 位)
* @retval 0:成功 1:超时
*/
uint8_t W25Qxx_WaitForWriteEnd(void);
/** @brief 进入掉电模式
*/
void W25Qxx_EnterPowerDown(void);
/** @brief 退出掉电模式
*/
void W25Qxx_ExitPowerDown(void);
/** @brief 使能内存映射模式 (Quad SPI, 只读直接访问)
*/
void W25Qxx_EnableMemoryMappedMode(void);
/** @brief 中止 QSPI 传输 (紧急恢复)
*/
void W25Qxx_Abort(void);
#ifdef __cplusplus
}
#endif
#endif /* __W25QXX_H */
接口调用
c
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "w25qxx.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 ---------------------------------------------------------*/
QSPI_HandleTypeDef hqspi;
/* USER CODE BEGIN PV */
uint32_t flash_id;
uint8_t write_data[256];
uint8_t read_data[256];
uint8_t status;
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MPU_Config(void);
static void MX_GPIO_Init(void);
static void MX_QUADSPI_Init(void);
/* USER CODE BEGIN PFP */
/* 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();
/* MPU Configuration--------------------------------------------------------*/
MPU_Config();
/* 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();
/* USER CODE BEGIN 2 */
/* 调试变量声明 - 在调试器中查看这些变量定位问题 */
extern volatile uint32_t g_debug_step;
extern volatile uint32_t g_debug_hal_status;
extern volatile uint32_t g_debug_error_code;
/* 读取Flash ID验证QSPI硬件 */
flash_id = W25Qxx_ReadID();
/* 初始化W25Q64 Flash */
status = W25Qxx_Init();
if (status != 0)
{
/* Flash初始化失败 */
while(1);
}
/* 1. 先擦除扇区0 (必须先擦除才能正确写入) */
W25Qxx_EraseSector(0);
/* 等待擦除完成 (扇区擦除最多400ms) */
HAL_Delay(500);
/* 2. 准备要写入的数据 */
for (int i = 0; i < 256; i++)
{
write_data[i] = i;
}
/* 3. 写入一页数据 */
status = W25Qxx_WritePage(write_data, 0, 0, 256);
if (status != 0)
{
/* 写入失败 - 在调试器中查看以下变量定位问题:
g_debug_step: 最后执行的步骤
g_debug_hal_status: HAL函数返回状态
g_debug_error_code: hqspi.ErrorCode
步骤对应:
1=进入WritePage, 3=计算地址完成, 4=调用写使能
5=写使能失败(看step=16, hal_status=1, error_code)
6=写使能成功, 7=配置页编程命令, 8=HAL_QSPI_Command失败
9=发送数据, 10=数据发送失败, 11=开始轮询, 12=轮询超时
10~16=QSPI_WriteEnable内部步骤
*/
while(1);
}
/* 等待页面编程完成 */
HAL_Delay(5);
/* 4. 读取数据验证 - 地址0 */
W25Qxx_FastReadData(read_data, 0, 256);
/* 写入和读取都成功!请在调试时查看 read_data 内容 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
- 效果演示

七、注意事项
1、擦除、写之前,都必须先发写使能指令,这是Flash芯片标注的事项。

2、为防止因为软件操作Flash芯片操作到一半复位,导致Flash芯片卡在某个异常状态,在Flash芯片初始化时,最好复位下芯片。
3、Flash芯片的QSPI模式配置是可以保存的,所以在开启芯片的QSPI模式之前,可以先读取相应的寄存器,如果已经是QSPI模式,则不需要再重新配置。
八、相关链接
对于刚入门的小伙伴可以先看下STM32CubeMX的基础使用及Keil的基础使用。
【工具使用】STM32CubeMX-基础使用篇
【工具使用】Keil5软件使用-基础使用篇