最近在调试 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 FlashU7 Bank1 FlashU8 Bank2 Flash每颗 W25Q256 本身都是标准 Quad SPI Flash也就是每颗 Flash 有 4 根数据线。所以 CubeMX 不能选普通的单 Bank Quad SPI而要选Dual Bank with Quad LinesBank1 用 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两颗 W25Q25632MB 32MB 64MB然后把 64MB 换成 2 的幂64MB 64 × 1024 × 1024 Byte 67,108,864 Byte 2^26 ByteCubeMX / 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_OK或HAL_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 模式Instruction1 线发送 Address1 线发送32 位地址 Data4 线读取 DummyCycles8HAL_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. 复位 Flashb. 读取 JEDEC IDc. 检查 Flash 是否正常d. 开启 Quad 模式e. 进入 4 字节地址模式f. 开启 Memory Mappedg. 从 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 运行过程中的关键状态0x2407F000FLM 运行时把关键状态写到这块 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 和读写命令。