STM32H750VBT这颗芯片有个很有意思的设定:内核性能很强,Cortex-M7跑到480MHz,RAM整整给了1MB,结果内部Flash只留了128KB。这在很多项目里根本不够装应用,所以大家都会外挂一颗SPI NOR Flash来跑代码。而External Loader这个东西,就是让STM32CubeProgrammer能直接操作这颗外部Flash的关键桥梁。
我这次要分享的,是给手头一块基于STM32H750VBT的板子写W25Q64JVSSIQ这颗8MB QSPI Flash的External Loader的完整过程。标题看着像是个工具链配置任务,其实走完整条链路后你会发现,里面涉及了STM32CubeProgrammer的算法注册机制、QSPI外设的时序配置、Flash底层指令集的调用,甚至还包括怎么让芯片从外部Flash启动的bootloader设计。这篇文章会把每一步的来龙去脉讲透,适合正在做H750外部Flash方案、或者想搞懂External Loader原理的开发者参考。
1. 项目全景:为什么要自己做External Loader
1.1 128KB Flash和1MB RAM的“畸形”组合
STM32H750VBT的定位很明确:性能优先,存储靠外挂。它内部那个128KB Flash,说实话连一个稍微完整一点的LVGL界面工程都塞不下,更别说跑协议栈加应用逻辑了。但它的1MB RAM是个巨大的优势,所以常规思路是把代码放到外部QSPI Flash里,上电后由一段很小的bootloader把代码搬到RAM里跑,或者直接在外部Flash原地执行(XIP模式)。
问题来了:STM32CubeProgrammer这个官方烧录工具,默认只知道芯片内部的Flash,它不认识W25Q64JVSSIQ这颗外挂芯片。如果每次烧录都要先通过一段临时程序把固件写到Flash里,开发效率会低到让人崩溃。External Loader就是为解决这个问题而生的——它本质上是一个动态链接库(.stldr文件),STM32CubeProgrammer加载它之后,就能像操作内部Flash一样,对外部Flash进行擦除、编程、校验和读回。
把External Loader理解成一个“翻译官”就对了。CubeProgrammer说“我要往地址0x90000000写数据”,External Loader负责把这句话翻译成W25Q64JVSSIQ能听懂的SPI指令序列。
1.2 External Loader的注册流程
STM32CubeProgrammer对外部存储器的操作逻辑其实很清晰,它的工作流程分四步:
- 加载.stldr文件,读取里面描述Flash型号、大小、扇区布局的数据结构。
- 调用Init函数初始化QSPI外设,完成和W25Q64JVSSIQ的握手。
- 根据用户操作调用Erase、Write、Verify等函数,这些函数内部通过QSPI指令和Flash芯片完成数据交互。
- 操作完成后调用DeInit函数释放外设。
这个过程看似简单,但难点在于Loader本身必须是一个完整的、可独立运行的裸机程序。它不依赖任何RTOS,也不能调用HAL库之外的复杂组件,因为STM32CubeProgrammer加载它的时候,会把它当作芯片内部Flash算法一样去执行,资源和环境都需要自给自足。
1.3 为什么选W25Q64JVSSIQ这颗Flash
W25Q64JVSSIQ是华邦W25Q64JV系列中的一个型号,后缀SSIQ代表SOP-8封装、工业级温度范围。和市面上常见的W25Q64FV相比,JV系列支持更高的时钟频率(标准SPI模式下最高133MHz),而且支持QPI模式——全双工Quad SPI通信,地址和数据都可以通过4条IO线传输。在H750这种QSPI外设支持四线模式的芯片上,这颗Flash可以非常轻松地把读取带宽跑起来,在XIP场景下的代码执行效率接近内部Flash的70%到80%。
选择它的另一个原因是生态成熟。它的指令集是标准的SPI NOR Flash指令集,网上能找到大量现成的驱动参考,遇到问题也容易排查。相比那些国产或者小众型号,W25Q64JV系列在CubeProgrammer社区里已经被大量验证过,踩坑成本低。
2. 系统方案与QSPI外设配置
2.1 STM32H750VBT上的QSPI硬件接口
H750内部有两个QSPI外设,分别叫QUADSPI1和QUADSPI2,都支持传统的SPI模式、双线模式和四线模式。它们可以工作在单Flash模式,也可以配置成Dual Flash模式,通过QSPI1的BK2引脚同时挂两颗Flash,组成并行读写。
这次项目使用的是QUADSPI1的Bank 1,引脚分配为:
| 信号 | 引脚 | 说明 |
|---|---|---|
| QUADSPI1_CLK | PB2 | 时钟线 |
| QUADSPI1_BK1_NCS | PB6 | 片选,低有效 |
| QUADSPI1_BK1_IO0 | PD11 | 数据线0 |
| QUADSPI1_BK1_IO1 | PD12 | 数据线1 |
| QUADSPI1_BK1_IO2 | PE2 | 数据线2 |
| QUADSPI1_BK1_IO3 | PD13 | 数据线3 |
这个引脚组合是H750非常经典的一个映射,在很多官方评估板上都能看到类似的布局。需要注意的是,PD13和PD11在部分板卡上会被其他外设占用,比如以太网或者SDMMC,如果和你的板子冲突,需要换成QUADSPI2或者调整引脚映射。
2.2 QSPI外设参数配置
在STM32CubeMX里配置QUADSPI1时,有几个参数直接决定后续Loader能不能正常通信,这里逐个说清楚:
Clock Prescaler:QSPI时钟分频系数。H750的QSPI时钟源是AHB总线时钟,通常配置为200MHz。W25Q64JVSSIQ在四线Fast Read模式下最高支持133MHz,考虑布线质量和信号完整性,我实际用的是4分频,QSPI时钟50MHz。这个频率非常保守,但能保证在各种板卡上都能稳定运行。
Fifo Threshold:FIFO阈值,代表触发中断或DMA请求所需的字节数。在Loader里,我们更关心的是快速轮询读取,所以这个值保持默认的1即可。
Sample Shifting:采样移位。这个参数用于补偿Flash数据返回的时序延迟。W25Q64JVSSIQ在FAST READ命令下,从发出命令到数据输出之间有一个固定的Dummy周期,QSPI外设的Sample Shifting可以帮助对齐采样点。实际使用中,如果有数据错位的问题,这个参数是首选排查点。
Flash Size:代表Flash地址宽度,单位是bit。W25Q64JVSSIQ是8MB容量,需要24位地址,所以这个值设置为23。这个参数直接决定QSPI外设能够寻址的范围,填小了读不到高地址,填大了会导致一些奇奇怪怪的时序问题。
这里有个小知识点:Flash Size在STM32CubeMX里填的值是“地址位数减1”,因为寄存器里的FLASH_SIZE字段就是从0开始计数的。8MB = 2^24字节,所以填写23。
2.3 W25Q64JVSSIQ的关键指令集
写External Loader之前,必须先搞懂这颗Flash的指令集。这里列几个最核心的:
| 指令 | 命令码 | 说明 | 用途 |
|---|---|---|---|
| Write Enable | 0x06 | 写使能 | 在写/擦除前必须执行 |
| Read Status Register | 0x05 | 读取状态寄存器 | 轮询WIP位判断操作是否完成 |
| Read Data | 0x03 | 单线读 | 基础读取,速度慢但通用 |
| Fast Read | 0x0B | 快速读 | 带Dummy周期的高效读取 |
| Quad Output Fast Read | 0x6B | 四线快速读 | 4IO模式读取 |
| Page Program | 0x02 | 页编程 | 最大单次写256字节 |
| Quad Input Fast Program | 0x32 | 四线快速编程 | 4IO写入 |
| Sector Erase | 0x20 | 扇区擦除 | 4KB扇区擦除 |
| Block Erase | 0xD8 | 块擦除 | 64KB块擦除 |
| Chip Erase | 0xC7 | 全片擦除 | 整颗Flash擦除 |
External Loader的核心任务,就是把这些指令通过QSPI外设正确发出去,并处理状态轮询。注意Page Program一次最多只能写256字节,而QSPI外设的发送缓冲区有限,所以实际的Write函数需要把大块数据拆分成多个256字节的小块多次发送,这部分的逻辑在Loader里是最容易出bug的。
2.4 QUADSPI的命令配置结构体
STM32H750的HAL库中,QSPI命令是通过一个结构体QSPI_CommandTypeDef来定义的,它包含指令、地址、数据长度、Dummy周期数、数据模式等字段。关键是数据模式的选择:
- QSPI_DATA_NONE:不带数据的命令,比如写使能、擦除命令。
- QSPI_DATA_1_LINE:数据通过单线传输,比如传统的0x03读命令。
- QSPI_DATA_4_LINES:数据通过4条IO线传输,比如0x6B快速读命令。
需要注意的是,命令本身和地址部分也可以选择单线或四线传输。比如Quad Output Fast Read(0x6B)虽然数据是用四线传输的,但命令码0x6B和地址部分仍然是单线发送的;而QPI模式下的0xEB命令,则要求命令、地址、数据全部走四线。在External Loader里,地址用单线传输是最通用的方案,兼容性最好。
3. External Loader工程实现
3.1 从零开始创建工程结构
External Loader本质上是一个标准的STM32裸机工程,编译产物是.stldr文件而不是.hex或.bin。我选择从STM32CubeMX生成的空工程开始,然后手动添加Loader接口函数。
工程结构如下:
ExternalLoader/ ├── Core/ │ ├── Inc/ │ ├── Src/ │ │ ├── main.c │ │ ├── flash_if.c │ │ └── flash_if.h │ └── Startup/ ├── Drivers/ │ ├── CMSIS/ │ └── STM32H7xx_HAL_Driver/ ├── EWARM/ // IAR工程文件 └── STM32CubeIDE/ // 同时保留了CubeIDE工程在main.c中,我们需要空掉标准的主函数逻辑,转而暴露一组Loader接口函数给上位机调用。这些函数包括:
| 函数名 | 作用 |
|---|---|
| Init | 初始化QSPI外设,读取Flash ID确认通信正常 |
| DeInit | 反初始化QSPI外设,释放引脚 |
| Read | 从指定地址读取指定长度的数据 |
| Write | 向指定地址写入指定长度的数据 |
| EraseSector | 擦除指定地址的4KB扇区 |
| EraseChip | 擦除整颗Flash |
| GetStatus | 获取上一次操作的状态 |
| GetInfo | 返回Flash容量、扇区大小、起始地址等描述信息 |
每个函数内部都通过HAL_QSPI_Command和HAL_QSPI_Transmit/HAL_QSPI_Receive来完成Flash操作。
3.2 关键数据结构:Flash Region表
External Loader的核心数据结构是一个名为STM32_Flash_RegionTypeDef的数组,它描述了Flash的布局。W25Q64JVSSIQ的8MB容量配置如下:
static const STM32_Flash_RegionTypeDef STM32_FLASH_REGION_TABLE[] = { // 扇区起始地址, 扇区大小, 扇区数量 { 0x90000000, 0x1000, 2048 }, }; const uint32_t STM32_FLASH_REGION_TABLE_SIZE = sizeof(STM32_FLASH_REGION_TABLE) / sizeof(STM32_Flash_RegionTypeDef);这里的0x90000000是H750 QSPI外设BANK1的映射地址,0x1000是4KB扇区大小,2048等于8MB除以4KB。不同厂家的Loader模板在这个结构体上的命名略有差异,但核心都是告诉上位机“这个Flash支持哪些扇区、大小多少”。
3.3 Write函数的实现:拆解页编程限制
W25Q64JVSSIQ的Page Program限制是单次最多写入256字节,且不能跨页。所谓“不能跨页”,是指如果当前页只剩10个字节,你偏偏要写20个字节,后10个字节会回绕到页开头,覆盖掉已经写入的数据。所以Write函数必须处理页边界对齐。
我的实现思路是把写入区域按页边界切分,每一段都保证不跨页。代码逻辑如下:
int Write(uint32_t Address, uint32_t Size, uint8_t* Buffer) { QSPI_CommandTypeDef cmd; uint32_t end_address = Address + Size; uint32_t current_address = Address; uint32_t current_size = Size; uint8_t* current_buffer = Buffer; while (current_address < end_address) { uint32_t page_size = 256 - (current_address % 256); if (page_size > current_size) { page_size = current_size; } if (Write_Enable() != 0) { return 1; } cmd.Instruction = 0x02; // Page Program cmd.AddressMode = QSPI_ADDRESS_24_BITS; cmd.Address = current_address; cmd.DataMode = QSPI_DATA_1_LINE; cmd.NbData = page_size; cmd.DummyCycles = 0; cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; HAL_QSPI_Command(&hqspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Transmit(&hqspi, current_buffer, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); current_address += page_size; current_buffer += page_size; current_size -= page_size; while (Check_Busy()) { // 轮询WIP位直到写操作完成 } } return 0; }这段代码里最关键的是256 - (current_address % 256)这一行。它算出当前地址距离下一页边界还有多少字节,从而决定本次写入的长度。if判断去掉的话,一旦写入跨页,数据就会错乱,这个问题在写大文件时几乎必然触发,是Write函数最容易翻车的地方。
3.4 Erase函数的实现:状态轮询不能省
Sector Erase虽然是简单粗暴的一条0x20命令,但发完命令之后必须等待WIP位清零才能进行下一步操作。Check_Busy函数的实现方式很多,最简单的就是不断读取状态寄存器,检查第0位:
static int Check_Busy(void) { QSPI_CommandTypeDef cmd; uint8_t status = 0x01; cmd.Instruction = 0x05; // Read Status Register cmd.AddressMode = QSPI_ADDRESS_NONE; cmd.DataMode = QSPI_DATA_1_LINE; cmd.NbData = 1; cmd.DummyCycles = 0; cmd.AlternateByteMode = QSPI_ALTERNATE_BYTES_NONE; cmd.SIOOMode = QSPI_SIOO_INST_EVERY_CMD; while (status & 0x01) { HAL_QSPI_Command(&hqspi, &cmd, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); HAL_QSPI_Receive(&hqspi, &status, HAL_QPSI_TIMEOUT_DEFAULT_VALUE); } return 0; }擦除完一个扇区后,上位机可能紧接着就执行写入操作。如果不清WIP位,写入指令会被Flash直接忽略。所以每次擦除或者写入后,都必须等待内部操作完成。这里加一个超时保护会更稳妥,防止某些异常情况下无限轮询卡死。
实测下来,擦除4KB扇区典型耗时在45ms到90ms之间,如果用的Flash是劣质片子或者电压偏低,时间可能更长。轮询本身不费事,但千万别在轮询前漏掉Write Enable指令——每次擦除和写入前都必须先发0x06,这是SPI NOR Flash最基本也最容易遗漏的规矩。
3.5 编译输出.stldr文件
External Loader的编译产物不是标准的二进制镜像,而是带符号表的可执行文件。IAR环境的设置方法是:在工程选项的Output页面,把输出文件名改为自定义扩展名,然后编译后把生成的.out文件重命名为.stldr。Keil则需要在Options for Target的Output页面勾选“Create Executable”,然后将扩展名改为.stldr。
我这里用的是STM32CubeIDE,在工程属性里把Build Configuration的Artifact Name设置为目标Loader名,并把Artifact Extension设置为stldr。编译完成后,在Debug目录下就会生成.stldr文件。
文件生成后,建议用十六进制编辑器打开看一眼。正常的.stldr文件开头应该能看到头部描述信息,比如Flash大小、扇区大小、Loader版本等。如果头部信息错乱,大概率是链接脚本里分配给Loader的RAM/Flash地址不对。
3.6 Loader的链接脚本配置
External Loader和普通固件的最大区别在于它的运行环境。STM32CubeProgrammer会把Loader加载到芯片的SRAM中执行,所以链接脚本不需要配置内部Flash区域,而是把代码和数据的起始地址设置在RAM里。
对于H750,我使用DTCM RAM(0x20000000起始,128KB)作为Loader的运行区域。为什么不用AXI SRAM?因为DTCM直接连接到内核,没有总线延迟,执行效率更高。但要注意DTCM在H750上默认是被CPU独占的,如果你打算在Loader里用DMA或者MDMA来加速Flash传输,就要改用AXI SRAM。
链接脚本中的关键配置:
MEMORY { RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 128K } REGION_ALIAS("TEXT", RAM); REGION_ALIAS("DATA", RAM);把代码放到RAM里执行,这是External Loader和普通MCU工程最大的区别之一。
4. 实战:CubeProgrammer烧录与验证
4.1 安装Loader到正确位置
编译生成的.stldr文件需要复制到STM32CubeProgrammer安装目录下的ExternalLoader文件夹,在Windows上默认路径是:
C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\ExternalLoader把文件放到这个目录后,重启STM32CubeProgrammer,就能在烧录界面的External Loader下拉框中看到它。需要注意的是,如果CubeProgrammer正在运行,改动了ExternalLoader目录后可能需要重启软件才能识别到新Loader。
4.2 通过命令行验证Loader通信
在图形界面上操作之前,我习惯先用命令行工具验证Loader的基本通信是否正常。STM32CubeProgrammer提供CLI模式,可以直接执行编程操作,输出信息更详细,排查问题也方便:
STM32_Programmer_CLI.exe -c port=SWD mode=under-reset -el "W25Q64JVSSIQ" -i 0x90000000 0x800000这条命令的含义是:连接SWD,复位状态下执行,加载W25Q64JVSSIQ这个External Loader,然后读取地址0x90000000处的0x800000字节并显示信息。如果输出中能看到Flash ID匹配、数据区域全部是0xFF,说明Loader的Init和Read功能正常工作。
4.3 烧录测试固件到外部Flash
验证完通信后,我准备了一个简单的LED闪烁固件进行烧录测试。先把固件编译成.bin文件,然后通过CLI烧录:
STM32_Programmer_CLI.exe -c port=SWD mode=under-reset -el "W25Q64JVSSIQ" -w test.bin 0x90000000 -v参数说明:
-el指定External Loader文件名-w指定写入文件和目标地址-v执行烧录后的校验
正常情况下,CubeProgrammer会先执行扇区擦除,然后分页写入,最后回读校验。如果Loader的Write函数没有正确处理页边界,烧录到某个地址范围时就会出现校验失败。
4.4 让芯片从外部Flash启动
烧录成功只是第一步,真正跑起来还需要bootloader引导。我在内部Flash烧录了一段约2KB的引导程序,它的逻辑是初始化QSPI外设,读取外部Flash头部信息,把应用代码搬运到AXI SRAM中,然后跳转执行。
这样整个系统的工作流程就变成了:
- 上电后芯片从内部Flash启动,执行Bootloader。
- Bootloader初始化QSPI,把W25Q64JVSSIQ映射到0x90000000地址空间。
- Bootloader读取外部Flash的前几个字节(栈指针和应用入口),进行地址重映射。
- 跳转到外部应用代码执行。
开发期间频繁改代码时,只需要通过External Loader重新烧写到外部Flash即可,不用反复擦写内部Flash,开发速度提升非常明显。
5. 踩坑记录与排查思路
5.1 初始化失败:CUbeProgrammer报“Init Failed”
这个报错是Loader问题中最常见的。原因基本集中在几个方向:
- QSPI引脚别配置错。检查CubeMX里的引脚分配是否和实际板卡走线一致,尤其是IO2和IO3,这两个引脚在四线模式下缺一不可。
- 时钟没配对。H750的QSPI外设时钟频率过高时,Flash可能响应异常,先把分频系数调大,比如8分频,跑通了再逐步调高。
- Loader文件放错目录。确认.stldr文件确实被CubeProgrammer识别到,而不是在旧版本的加载目录里。
排查方法很简单,在Loader的Init函数里加一行Flash ID读取,打印出来对比手册值。W25Q64JVSSIQ的JEDEC ID是0xEF4017,如果你读出来是这个值,通信链路基本就是通的。
5.2 擦除效率低到无法接受
有人用Loader第一次擦除整片Flash,等了半天还在擦。原因其实很简单——他把擦除数设置成了扇区数量级,然后一个扇区一个扇区地擦。8MB的数据量,如果按4KB扇区一个个擦,总共要发2048次擦除指令,每次还要等WIP位清零。
更好的方式是利用Block Erase(64KB)或者直接在Loader里支持Chip Erase。当上位机发起全片擦除时,一次性发送0xC7命令,让Flash内部自己完成全部擦除,耗时大概在几十秒量级。如果用户只是擦除文件占用的那么几个扇区,那就按扇区擦,不搞全片。
5.3 校验失败:数据错位和Dummy Cycle
烧录过程中校验失败,最常见的原因就是采样时序没对齐。QSPI四线模式下,Flash返回数据时有一定的时序延迟,如果QSPI外设采样点太后或者太前,读回来的数据就会错位。
解决方法是调节QSPI外设的Sample Shifting参数,或者在Fast Read命令的DummyCycles上做调整。W25Q64JVSSIQ在Quad Output Fast Read(0x6B)模式下需要8个Dummy周期,如果这里配置成4或者16,读出来的数据就会不对。
排查思路:先用单线模式(0x03指令)验证Loader的读写链路,跑通之后再切换到四线模式,这样能快速缩小问题范围。
5.4 程序跳转后死机:中断向量表没调整
Bootloader把外部Flash的应用搬运到RAM后,跳转执行之前必须修改VTOR寄存器,把中断向量表指向应用程序所在地址。如果这部分没做,应用一触发中断就会跳到错误的向量表位置,直接硬错误。
H750上VTOR的写法:
SCB->VTOR = 0x24000000; // 指向AXI SRAM中应用代码的起始位置 __set_MSP(*(uint32_t*)0x24000000);同时需要注意,有些外设的中断优先级分组也会在启动阶段被Bootloader设置过,跳转进入应用后需要重新初始化一遍系统时钟和中断优先级,否则会出现非常隐蔽的随机死机问题。
5.5 QSPI Flash映射地址的优先级
QUADSPI外设在H750上映射到两个地址区间:0x90000000(Bank1)和0x70000000(Bank1的XIP区域?需要核实)。其实这里我说一下我用到的:H750的QSPI Bank1映射地址是0x90000000,Bank2是0xA0000000,XIP模式下读取地址就按这个来。如果你的工程里既用了内存映射模式、又用普通命令模式,切换时要注意关闭内存映射再发命令,否则会冲突。
实际操作中,我将QSPI配置为内存映射模式后,CubeProgrammer的读操作可以直接通过地址访问,但写入操作必须退出内存映射模式。所以Loader内部要有一套逻辑:写入数据前把Flash切换到命令模式,写完再切回内存映射模式。这个切换在部分HAL版本中需要额外注意Cache的失效和清理,不然会出现读到旧缓存数据的问题。
5.6 电源问题导致写入不稳定
W25Q64JVSSIQ的工作电压是1.8V到3.6V(型号里SSIQ后缀的最后一位代表电压范围,需要看具体编号)。如果你的板子给Flash供电的电压不稳,在大电流写入时可能出现偶发的写入失败。这种情况在外部Loader里很难直接定位到,只能通过提高CubeProgrammer的重试次数来缓解,或者检查板卡的电源布局。
我在这块板子上遇到过类似问题,排查了很久,最后发现是Flash的VCC引脚上的去耦电容离芯片太远,导致电源纹波偏大。在靠近VCC引脚的地方补了一颗100nF电容后,问题消失。这个坑比较隐蔽,如果你一切配置都正确,但写入还是会偶发失败,建议检查一下电源完整性。
6. 一些心得和后续扩展建议
6.1 从“能用”到“好用”的进化
用External Loader解决了基本烧录问题之后,你会发现它还能做很多额外的事。比如在CI/CD流水线里集成CubeProgrammer CLI命令,实现自动构建、自动烧录、自动校验。再比如通过Loader直接读取外部Flash内容,快速对比测试板和生产板的固件版本。这些工作如果走串口ISP或者JTAG,效率要低得多。
6.2 关于Loader调试的一个实用技巧
因为External Loader是裸机环境,没有标准输入输出,调试不方便。我的调试手段是在Loader里挂一个串口初始化,然后把关键状态通过UART打印出来。STM32CubeProgrammer在调用Loader时会先把整个Loader加载到SRAM,然后跳转到Init函数,所以我只需要在Init函数里初始化UART,后续所有函数的调试信息都能通过串口看到。
这个方法在排查奇怪问题时非常有效,比如能直观地看到擦除后Flash返回的状态寄存器值是否正常,写入时每次分页的地址是否正确。
6.3 最后的建议
如果你问我做这个External Loader最重要的是什么,我会说:把数据手册和参考手册打印出来放桌上,随时翻。W25Q64JVSSIQ的指令时序图、状态寄存器每一位的含义、H750 QSPI外设的时序配置,这些细节一个都不能凭记忆。很多看似“玄学”的烧录失败,最后都能在手册里找到答案。
整个项目做完之后,这套方案完全可以用在未来的产品上。只要把Flash型号相关的参数抽成配置项,换一颗Flash只需要改改扇区大小、指令集和容量描述,重新编译一个Loader就行。对于做H750平台开发的人来说,掌握External Loader的编写方法,等于把外部存储方案的最后一块拼图补齐了。