最近在调试 STM32H743 的双 QSPI Flash 下载算法。前期因为其中一颗 W25Q256 本身存在硬件问题,导致下载失败、Verify 失败、屏幕花屏、双 Flash 读回异常等问题交织在一起,现象非常混乱。

最后还是在ai的帮助下完成了FLM的编写,于是整理出了主要过程以及分享AI调试的心得。

最后附上FLM文件。

一、资源配置

1.原理图页

本项目基于野火的STM32H743XIH6核心板进行的调试,带有两颗外部  W25Q256 QSPI Flash。

野火H743核心板 QSPI Flash 原理图

由于本项目更倾向于读速度稳定、延迟低的方向,且需要储存一部分图片资源故采用双QSPI Flash的方案。

2.cubeMX当中的配置

选择模式

我们在Connectivity中找到QUADSPI,按照上图进行配置

因为板子上有两颗 QSPI Flash:U7 = Bank1 Flash;U8 = Bank2 Flash;每颗 W25Q256 本身都是标准 Quad SPI Flash,也就是每颗 Flash 有 4 根数据线。

所以 CubeMX 不能选普通的单 Bank Quad SPI,而要选:

Dual Bank with Quad Lines

Bank1 用 4 根 QSPI 数据线,Bank2 也用 4 根 QSPI 数据线;两颗 Flash 并行工作,MCU 把它们当成一个 8bit 总线,64MB 的更大的外部 Flash。

又因为两颗 Flash 不是各自独立片选,而是共用同一个片选信号;所以两颗 Flash 必须同时被选中、同时接收命令、同时读写数据。

因此 CubeMX 里必须选:

Enable Chip Select 1 for both banks

下方参数配置

其中重点强调一下 Flash Size:这块板的双 QSPI Flash 是由 两个 32M 字节 Flash 组成 64M 字节的 8bit QSPI Flash;

计算过程是:

单颗 W25Q256 = 256 Mbit = 32 MByte

两颗 W25Q256:32MB + 32MB = 64MB

然后把 64MB 换成 2 的幂:

64MB    = 64 × 1024 × 1024 Byte
              = 67,108,864 Byte
              = 2^26 Byte

CubeMX / HAL 的 QSPI Flash Size 规则是:

外部 Flash 容量 = 2^(FlashSize + 1)        FlashSize = 25

引脚配置

对照自己的原理图调成合适的引脚即可,注意配置成高速

3.总结并检查

我们可以知道,CubeMX 主要负责这几件事:

1. 使能 QUADSPI 外设时钟;

2. 配置 QUADSPI 对应 GPIO 复用功能;

3. 配置 QSPI 时钟分频、FIFO、片选保持时间、FlashSize、Dual Flash 等寄存器初值;

4.生成 MX_QUADSPI_Init()HAL_QSPI_MspInit()

当我们所有的配置完成后,我们可以将生成的cubeMX配置(quadspi.c/h)文件说明需求后丢给AI去检查,这样总是能发现一些微小的错误,从而减少我们的调试负担。

二、代码部分编写

1.诊断信息结构体

驱动不只是返回 HAL_OKHAL_ERROR,还会保存 QSPI 初始化过程中的关键信息

/* W25Q256 常用命令 */
#define W25Q_CMD_READ_JEDEC_ID                ((uint8_t)0x9FU)  /* 读取 Flash ID */
#define W25Q_CMD_READ_STATUS_REG1             ((uint8_t)0x05U)  /* 读取状态寄存器1,包含 BUSY/WEL */
#define W25Q_CMD_READ_STATUS_REG2             ((uint8_t)0x35U)  /* 读取状态寄存器2,包含 QE 位 */
#define W25Q_CMD_WRITE_STATUS_REG2            ((uint8_t)0x31U)  /* 写状态寄存器2,用于设置 QE */
#define W25Q_CMD_VOLATILE_SR_WRITE_ENABLE     ((uint8_t)0x50U)  /* 允许写易失状态寄存器 */
#define W25Q_CMD_ENTER_4BYTE_ADDRESS_MODE     ((uint8_t)0xB7U)  /* 进入 4 字节地址模式 */
#define W25Q_CMD_FAST_READ_QUAD_OUT_4B        ((uint8_t)0x6CU)  /* 4 字节地址 Quad Output 快速读 */

typedef enum
{
    BSP_QSPI_ERROR_NONE = 0,          /* 无错误 */
    BSP_QSPI_ERROR_HAL_NOT_ENABLED,   /* HAL QSPI 模块未启用 */
    BSP_QSPI_ERROR_COMMAND,           /* QSPI 命令发送或接收失败 */
    BSP_QSPI_ERROR_ID,                /* JEDEC ID 读取或校验失败 */
    BSP_QSPI_ERROR_BUSY_TIMEOUT,      /* 等待 Flash 空闲超时 */
    BSP_QSPI_ERROR_QE,                /* Quad Enable 位设置失败 */
    BSP_QSPI_ERROR_4BYTE,             /* 进入 4 字节地址模式失败 */
    BSP_QSPI_ERROR_MEMORY_MAPPED,     /* Memory Mapped 模式开启失败 */
    BSP_QSPI_ERROR_READ_TEST          /* 0x90000000 映射读测试失败 */
} BSP_QSPI_ErrorTypeDef;

typedef struct
{
    uint8_t jedec_id[QSPI_JEDEC_ID_READ_BYTES];     /* 双 Flash JEDEC ID */
    uint8_t status_reg1[QSPI_STATUS_READ_BYTES];    /* 两颗 Flash 的 SR1 */
    uint8_t status_reg2[QSPI_STATUS_READ_BYTES];    /* 两颗 Flash 的 SR2 */
    uint8_t status_reg3[QSPI_STATUS_READ_BYTES];    /* 两颗 Flash 的 SR3 */
    uint32_t memory_mapped_enabled;                 /* 是否已进入内存映射模式 */
    uint32_t read_test_word;                        /* 从 0x90000000 读到的测试数据 */
    BSP_QSPI_ErrorTypeDef last_error;               /* 最近一次错误原因 */
} BSP_QSPI_InfoTypeDef;

2. 双 Flash 忙等待

两颗 Flash 会同时参与传输,因此等待忙状态时不能只看一颗芯片

static HAL_StatusTypeDef QSPI_WaitWhileBusy(uint32_t timeout_ms)
{
    uint32_t start_tick = HAL_GetTick();
    uint8_t sr1[QSPI_STATUS_READ_BYTES] = {0U, 0U};

    do
    {
        if (QSPI_ReadStatusPair(W25Q_CMD_READ_STATUS_REG1, sr1) != HAL_OK)
        {
            QSPI_SetError(BSP_QSPI_ERROR_COMMAND);
            return HAL_ERROR;
        }

        if (((sr1[0] & W25Q_SR1_BUSY) == 0U) &&
            ((sr1[1] & W25Q_SR1_BUSY) == 0U))
        {
            qspi_info.status_reg1[0] = sr1[0];
            qspi_info.status_reg1[1] = sr1[1];
            return HAL_OK;
        }
    } while ((HAL_GetTick() - start_tick) < timeout_ms);

    QSPI_SetError(BSP_QSPI_ERROR_BUSY_TIMEOUT);
    return HAL_TIMEOUT;
}

3. 开启 Quad 模式

W25Q256 要使用四线数据读取,必须打开 QE 位;两颗Flash的 QE 位都要打开

HAL_StatusTypeDef BSP_QSPI_EnableQuadMode(void)
{
    uint8_t sr2[QSPI_STATUS_READ_BYTES] = {0U, 0U};
    uint8_t write_sr2[QSPI_STATUS_READ_BYTES];

    if (QSPI_ReadStatusPair(W25Q_CMD_READ_STATUS_REG2, sr2) != HAL_OK)
    {
        QSPI_SetError(BSP_QSPI_ERROR_QE);
        return HAL_ERROR;
    }

    if (((sr2[0] & W25Q_SR2_QE) != 0U) &&
        ((sr2[1] & W25Q_SR2_QE) != 0U))
    {
        qspi_info.status_reg2[0] = sr2[0];
        qspi_info.status_reg2[1] = sr2[1];
        return HAL_OK;
    }

    write_sr2[0] = sr2[0] | W25Q_SR2_QE;
    write_sr2[1] = sr2[1] | W25Q_SR2_QE;

    if (QSPI_CommandNoData(W25Q_CMD_VOLATILE_SR_WRITE_ENABLE) != HAL_OK)
    {
        QSPI_SetError(BSP_QSPI_ERROR_QE);
        return HAL_ERROR;
    }

    if (QSPI_CommandTransmit(W25Q_CMD_WRITE_STATUS_REG2,
                             write_sr2,
                             QSPI_STATUS_READ_BYTES) != HAL_OK)
    {
        QSPI_SetError(BSP_QSPI_ERROR_QE);
        return HAL_ERROR;
    }

    return HAL_OK;
}

4. 进入 4 字节地址模式

W25Q256 单颗容量 32MB,双 Flash 总容量 64MB,已经超过 24 位地址能覆盖的范围。因此必须进入 4-byte address mode。

HAL_StatusTypeDef BSP_QSPI_Enter4ByteAddressMode(void)
{
    if (QSPI_CommandNoData(W25Q_CMD_ENTER_4BYTE_ADDRESS_MODE) != HAL_OK)
    {
        QSPI_SetError(BSP_QSPI_ERROR_4BYTE);
        return HAL_ERROR;
    }

    HAL_Delay(1U);
    return HAL_OK;
}

5. 开启 Memory Mapped 模式

Instruction:1 线发送
Address:1 线发送,32 位地址
Data:4 线读取
DummyCycles:8
HAL_StatusTypeDef BSP_QSPI_EnableMemoryMappedMode(void)
{
    QSPI_CommandTypeDef cmd;
    QSPI_MemoryMappedTypeDef memory_mapped_cfg;

    QSPI_FillBaseCommand(&cmd);
    cmd.Instruction = W25Q_CMD_FAST_READ_QUAD_OUT_4B;
    cmd.AddressMode = QSPI_ADDRESS_1_LINE;
    cmd.AddressSize = QSPI_ADDRESS_32_BITS;
    cmd.Address     = 0U;
    cmd.DataMode    = QSPI_DATA_4_LINES;
    cmd.DummyCycles = QSPI_MEMORY_MAPPED_DUMMY_CYCLES;

    memory_mapped_cfg.TimeOutActivation = QSPI_TIMEOUT_COUNTER_DISABLE;
    memory_mapped_cfg.TimeOutPeriod     = 0U;

    if (HAL_QSPI_MemoryMapped(&hqspi, &cmd, &memory_mapped_cfg) != HAL_OK)
    {
        QSPI_SetError(BSP_QSPI_ERROR_MEMORY_MAPPED);
        return HAL_ERROR;
    }

    qspi_info.memory_mapped_enabled = 1U;
    return HAL_OK;
}

6. 总初始化流程

a. 复位 Flash
b. 读取 JEDEC ID
c. 检查 Flash 是否正常
d. 开启 Quad 模式
e. 进入 4 字节地址模式
f. 开启 Memory Mapped
g. 从 0x90000000 做读测试

HAL_StatusTypeDef BSP_QSPI_BringUpMemoryMapped(void)
{
    uint8_t id[QSPI_JEDEC_ID_READ_BYTES] = {0U};

    QSPI_ClearInfo();

    if (BSP_QSPI_ResetMemory() != HAL_OK)
        return HAL_ERROR;

    if (BSP_QSPI_ReadJEDECID(id, sizeof(id)) != HAL_OK)
        return HAL_ERROR;

    if (QSPI_CheckJEDECID() != HAL_OK)
        return HAL_ERROR;

    if (BSP_QSPI_EnableQuadMode() != HAL_OK)
        return HAL_ERROR;

    if (BSP_QSPI_Enter4ByteAddressMode() != HAL_OK)
        return HAL_ERROR;

    if (BSP_QSPI_EnableMemoryMappedMode() != HAL_OK)
        return HAL_ERROR;

    if (BSP_QSPI_MemoryMappedReadTest() != HAL_OK)
        return HAL_ERROR;

    QSPI_SetError(BSP_QSPI_ERROR_NONE);
    return HAL_OK;
}

三、外部 QSPI Flash 下载算法编写

1. 调试背景

这个项目使用的是 STM32H743,并外挂两颗 W25Q256 QSPI Flash。外部 Flash 主要用于存放 TouchGFX 图片资源等数据,因此不仅要求 Keil 能把数据下载进去,还要求程序运行时能够通过 Memory Mapped 地址正确读取。由于下载算法这些文件目前使用AI编写非常方便,编译及工程部分不做过多讲述。

不过值得提一下的是,在提供的FLM编译工程中,需要重新修改路径,否则会出现报错

最开始调试时,外部 Flash 下载算法一直不稳定,主要现象包括:

下载失败
Verify 失败
屏幕花屏
双 Flash 读回数据异常

后来经过硬件排查,确认其中一颗 Flash 存在问题。更换并修复 Flash 后,之前很多随机异常消失,问题也从“硬件不稳定”转变为“下载算法细节不正确”。

所以本文只记录 更换好 Flash 之后,到最终成功 的这段调试过程。

最终成功版本为:

WF_H743X_S13_NOHALF_MV.FLM

最终现象为:

擦除成功
下载成功
Verify 成功
TouchGFX 屏幕显示正常

2. Verify 成功很关键

在这个项目里,FLM 下载算法不是只要“能写进去”就算结束。

Keil5 DownLoad界面​

因为 TouchGFX 的图片资源会放在外部 QSPI Flash 中,程序运行时会通过类似下面的地址访问外部资源:

0x90000000

也就是说,Keil 下载、Keil Verify、程序运行时读取,这三条链路必须看到同一份正确数据。

如果只是下载过程显示成功,但 Verify 失败,或者运行时 Memory Mapped 读出的数据和写入数据不一致,那么最后的表现很可能就是:

程序能跑
屏幕能亮
但图片资源异常
最终表现为花屏

这也是本次调试中非常重要的判断:
屏幕花屏不是简单的颜色格式问题,而是外部 Flash 中的数据内容本身不对,或者读取路径不一致。

3. 关于使用AI调试的重点:SRAM 探针是关键

FLM 下载算法运行在 Keil 的下载流程中,失败时通常只给出类似信息:

这些信息太粗,无法判断到底失败在哪一步。

因此在 STM32H743 的 SRAM 中划出一块区域作为探针区,用来记录 FLM 运行过程中的关键状态

0x2407F000

FLM 运行时,把关键状态写到这块 SRAM。下载失败后,通过 Keil 的 Memory Window 查看这个地址,就能知道算法内部执行到了哪一步。

探针主要记录:

当前阶段
错误码
操作地址
写入长度
Flash ID
状态寄存器
读回数据
Memory Mapped 读出的数据

简单示例:

typedef struct
{
    volatile uint32_t magic;
    volatile uint32_t stage;
    volatile uint32_t error;
    volatile uint32_t addr;
    volatile uint32_t size;
    volatile uint32_t flash_id_1;
    volatile uint32_t flash_id_2;
    volatile uint32_t status_1;
    volatile uint32_t status_2;
    volatile uint32_t readback0;
    volatile uint32_t readback1;
    volatile uint32_t readback2;
    volatile uint32_t readback3;
} FLM_Probe_t;

#define FLM_PROBE_ADDR   0x2407F000UL
#define FLM_PROBE        ((FLM_Probe_t *)FLM_PROBE_ADDR)

在keil5调试界面读取参数:

这部分人看着可能会很费力,但是可以让ai自行设置,自行阅读有着极高的调试效率。

在关键位置写入状态:

FLM_PROBE->magic = 0x48474351;
FLM_PROBE->stage = 0x01;     // 进入 Init
FLM_PROBE->error = 0;

这个探针非常重要。它让问题从“猜测”变成了“有证据定位”。

4. 探针带来的判断

加入探针后,可以看到:

JEDEC ID 能正确读到
QSPI 初始化能执行
擦除流程能进入
写入流程能完成
Verify 一进入就失败

这说明基础通信不是完全错误的。

也就是说:

不是 Flash 完全无法通信
不是 QSPI 初始化完全失败
不是单纯硬件损坏

真正的问题集中在:

写入后读回不一致
Memory Mapped 读回不一致
双 Flash 数据排列可能不一致
访问宽度或分块策略有问题

这个结论直接缩小了排查范围。

如果没有探针,我们可能还会继续怀疑硬件、焊接、QSPI 引脚或 Flash 芯片,即使使用AI不断修改,也会难以成功;
但探针证明,Flash ID、初始化、擦除、写入流程都能进入,关键问题在 Verify 和运行时读取一致性上。

5. 调试路线

更换好 Flash 后,我重新整理了一版标准 FLM。

最开始的结果是:

擦除后进入下载阶段失败

调整 Program 写入流程后,结果变成:

下载成功
Verify 失败

这说明擦除和写入大体已经能跑,问题转向读回一致性。

后续测试过几个方向

其中:

AUTO:自动路径不稳定,擦除阶段失败
IV:间接读取能跑,但 Verify 仍失败
SWAP:交换双 Flash 数据顺序后仍失败
SPLIT:复杂分裂写入反而引入新问题
P256:回到 256 字节页编程,但仍不够
MV:最接近程序运行时读取方式,最终保留

最终方向逐渐明确:

不要继续堆复杂处理
保留标准页编程
取消 HALF
取消 SPLIT
使用 Memory Mapped Verify
保证 Verify 和程序运行时读取路径一致

6. 最终版本

关键修改:

取消 HALF 写入策略
取消复杂 SPLIT 写入策略
不再使用 SWAP 方向
保留标准页编程
使用 Memory Mapped Verify
减少额外数据重排
保持写入和读回路径一致

测试结果:

擦除成功
下载成功
Verify 成功
TouchGFX 屏幕显示正常

这说明外部 Flash 中的数据已经能被 Keil Verify 正确读回,也能被程序运行时通过 Memory Mapped 地址正确读取。

四、使用条件

本 FLM 适用于:STM32H743XIH6 + 双 W25Q256 + Dual Bank Quad Lines + 共用 CS + 4-byte address mode + 0x90000000 Memory Mapped 访问方式。不同板卡、不同 Flash、不同片选连接方式不能直接套用,需要重新核对引脚、FlashSize、DummyCycles 和读写命令。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐