ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

STM32H750外部Flash烧录算法实战:W25Q128从Keil到JFlash完整方案

2026/9/3 18:34:40 拓冰建站 浏览量
STM32H750外部Flash烧录算法实战:W25Q128从Keil到JFlash完整方案 简介面向STM32H750与W25Q128外部Flash的烧录算法工程适合嵌入式开发者在Keil MDK环境中为外部SPI/QSPI Flash添加编程算法解决固件无法直接烧写到外部Flash的问题。压缩包共80个文件以FlashDev.c、FlashPrg.c等C源文件和头文件为核心同时提供FLM烧录算法文件、Keil工程文件uvprojx/uvoptx、启动文件、调试配置文件以及编译中间产物axf/map/o等整体仅2.54MB目录紧凑。这一工程已有近3800人浏览学习覆盖了SPI接口初始化、扇区擦除、页编程、读取校验等算法关键流程并保留HAL/LL库适配痕迹与JLink调试配置。读者可将其作为模板根据实际板卡修改引脚时序、存储布局或Flash型号快速生成可用于量产烧录的算法工程。 做STM32H750开发的人应该都有这种感觉芯片性能拉满480MHz主频加Cortex-M7核心跑图形界面、语音识别都绰绰有余但128KB的内部Flash总在关键时候提醒你“我很小”。尤其是用模组星球这类STM32H750开发板做实际项目时代码加上字库、图片资源、协议栈体积轻松突破512KB外挂一片W25Q128几乎是标配。可是W25Q128接上去不代表工具链认它Keil、JFlash默认都不知道怎么去操作你手里那片SPI Flash这时候写一个专属的烧录算法工程就成了整个项目绕不过去的一环。这篇内容是我实现STM32H750 W25Q128烧录算法工程的完整记录从为什么需要烧录算法、算法内部到底做了什么到Keil工程配置、JFlash适配以及中间撞到的各种坑一次讲清楚。适合正在用H750做量产项目、想把代码或资源放到外部Flash里的同学参考。1. 为什么STM32H750需要外部Flash烧录算法1.1 128KB内部Flash的现实困境STM32H750这颗芯片从定位上就很有意思。它有一颗很强的Cortex-M7核心主频能到480MHz还带硬件双精度浮点甚至内部SRAM就有512KB。但ST官方只给了128KB的内置Flash摆明了让你外接存储。很多没有经验的工程师拿到芯片后发现GUI界面、日志系统、通信协议栈这些代码一加上去128KB很快就满了接下来只能打起外部Flash的主意。W25Q128是华邦出品的一款8MB SPI NOR Flash容量足够放代码和资源文件价格也便宜所以在H750的板子上特别常见模组星球的STM32H750开发板手册里也默认搭配了这颗Flash。问题在于芯片和Flash都接好了下载工具却不认识这个组合。Keil默认只懂STM32内部Flash的烧录协议JFlash的默认设备库也不包含某个特定板子上的W25Q128必须有人把这些下载逻辑写成驱动模块这就是烧录算法工程的来源。1.2 烧录算法的本质是什么烧录算法本质上是一段可以被下载工具加载进目标芯片RAM运行的底层驱动代码。它并不神秘更像是一份“接口说明书”告诉下载工具怎么初始化SPI/QSPI、怎么擦除扇区、怎么把数据按页写进去、怎么读回校验。下载工具在界面上显示“Program done”背后其实是它先把这段算法加载到SRAM里然后调用算法里的函数一层层把数据搬运到Flash指定地址。用生活化的比喻来说内部Flash下载就像在自家厨房做饭锅碗瓢盆都是现成的外部Flash烧录算法则是去野外露营你得先教会别人怎么搭灶、怎么生火。烧录算法就是那张“搭灶生火说明书”下载工具严格照做才能把程序代码安全写进W25Q128。1.3 什么时候必须自己写这个算法有人会问用STM32CubeProgrammer能不能直接往外部Flash写答案是不能。CubeProgrammer虽然支持外部Flash编程但前提是你得在工程里加上自定义的烧录算法文件。JFlash也一样它内置的算法只覆盖常见的评估板换一块自己设计的PCB板引脚不同、QSPI配置不同就必须手动加算法。但凡项目里需要把代码编址到0x90000000的内存映射区启动或者想把字库、资源文件预烧到Flash里都需要这个烧录算法工程。2. 烧录算法工程的整体拆解2.1 算法文件的核心函数与结构一个标准的烧录算法无论最终生成的是Keil的FLM文件还是JLink的ELF文件内部都要实现一组固定的接口函数。这组函数由下载工具在特定时机调用函数名作用对应W25Q128操作Init初始化硬件建立通信初始化QSPI外设读JEDEC ID确认Flash在线UnInit关闭硬件回到安全状态退出内存映射模式、禁用QSPIEraseChip全片擦除发0xC7命令整片擦除EraseSector扇区/块擦除发0x20命令擦除4KB扇区或0xD8擦除64KB块Program写入数据发0x02页编程命令按256字节页写入Verify校验数据读回Flash内容比对或返回CRC校验结果写算法的时候不需要全部实现但Init、EraseSector、Program这三个是基础缺一个下载步骤就跑不通。Keil下载时通常是先调用EraseSector擦除目标区域然后调用Program逐块写入最后通过读回比对或CRC来校验。如果算法里没有实现EraseChip很多下载工具不会报错只是全片擦除功能不可用导出生产文件时会受限。2.2 为什么选择QSPI而不是普通SPIW25Q128本身支持标准SPI、Dual SPI、Quad SPI以及QPI模式而STM32H750内部集成了QUADSPI外设支持内存映射模式可以把外部Flash直接映射到0x90000000地址空间访问。这就意味着单片机可以像访问内部Flash一样通过指针直接读取外部Flash里的代码和数据性能比普通SPI高一个量级。烧录算法里如果只用标准SPI不是不行但速度差距明显。我实际测过用标准SPI下载1MB数据大约需要3到4分钟换成四线Quad模式同样数据只要20秒左右。产线上如果单片机数量多这个差距就是效率瓶颈。所以算法里我强烈建议走QUADSPI并把时钟频率尽量拉高——H750的QUADSPI时钟可以做到系统时钟的四分之一以480MHz主频算就是120MHzW25Q128完全能承受这个频率。2.3 Keil和JFlash两套算法并非一回事写算法之前先想清楚要服务哪个工具。Keil使用的算法文件后缀是FLM内部是标准的ARM ELF格式但链接脚本、加载地址有特定要求。JFlash的算法文件本质上是ELF还要在JLinkDevices.xml里注册描述信息。两个工具虽然可以共用同一份源码但编译和注册方式不同不能直接把Keil生成的FLM扔给JFlash用也不能让Keil直接加载JLink的ELF。后面我会细说两边怎么处理。3. Keil下从零创建W25Q128烧录算法3.1 工程创建与分散加载配置在Keil里写烧录算法我推荐自己新建一个工程不要用默认模板。新建工程后从包管理器里选择STM32H750VB设备型号代码用HAL库或LL库都可以但建议直接复制官方QSPI例程里的驱动毕竟是验证过的。编译配置是重点。在Options for Target的Output选项卡里把输出文件名改成“W25Q128”这样生成的烧录算法文件会成为W25Q128.FLM。然后在Linker选项卡里取消“Use Memory Layout”手动指定分散加载文件。我用的典型配置如下LR_IROM1 0x08000000 0x00010000 { ER_IROM1 0x08000000 0x00010000 { *.o (RESET, First) *(InRoot$$Sections) .ANY (RO) } RW_IRAM1 0x20000000 0x00020000 { .ANY (RW ZI) } }地址0x08000000是内部Flash起始地址这里并不是真的把算法烧进内部Flash而是给烧录器一个加载地址最终这段代码会被工具搬运到SRAM中运行。RW段放在0x20000000的SRAM起始区域这样算法运行时变量不会和应用程序冲突。3.2 核心实现QSPI初始化和四个算法函数初始化函数Init是最关键的一步。QUADSPI不是简单拉起引脚就行要先配置协议格式、时钟分频、FIFO阈值然后进入待机状态再发指令读芯片ID确认W25Q128响应正常。核心代码大致如下int Init(void *args) { QSPI_HandleTypeDef hqspi {0}; hqspi.Instance QUADSPI; hqspi.Init.ClockPrescaler 1; // QSPI时钟 内核时钟 / 4 hqspi.Init.FifoThreshold 4; hqspi.Init.SampleShifting QSPI_SAMPLE_SHIFTING_HALFCLK; hqspi.Init.FlashSize 23; // 2^23 8MB ... return HAL_QSPI_Init(hqspi) HAL_OK ? 1 : 0; }页编程函数要注意一个坑W25Q128的页大小是256字节如果写入数据跨越页边界Flash的地址指针不会自动跳到下一页。所以Program函数必须按页边界拆分数据一次最多写256字节int Program(unsigned long addr, unsigned long len, unsigned char *buf) { while (len 0) { unsigned long page_offset addr 0xFF; unsigned long chunk (page_offset len 256) ? 256 - page_offset : len; HAL_QSPI_Transmit(hqspi, W25Q128_PAGE_PROGRAM, addr, buf, chunk); addr chunk; buf chunk; len - chunk; // 等待W25Q128内部编程完成 while (QSPI_IsBusy()) ; } return 1; }擦除函数同样有讲究。W25Q128支持4KB扇区擦除和64KB块擦除Keil的下载流程一般调用扇区擦除也就是0x20命令。擦除前要检查地址是否4KB对齐不对齐直接返回0否则会擦掉别的数据。全片擦除函数则可以发0xC7命令适合产线第一次烧录前清空Flash。3.3 编译生成FLM并验证代码写完后勾选“Create HEX File”以外的选项直接编译生成的就是W25Q128.FLM。拿到这个文件后把它复制到Keil安装目录下的“ARM\Flash”文件夹里然后在Options for Target的Utilities选项卡中点击“Settings”在Flash Download页面添加算法。添加成功后界面上应该能看到“W25Q128 External Flash”的条目勾选它就可以把应用程序直接下载到外部Flash。我第一次生成FLM时踩过一次坑编译选项里如果勾了MicroLIB算法体积可能变大影响加载速度如果不小心带上了启动文件和SystemInit代码会导致算法重定位后冲突。建议在工程里只保留QSPI驱动、算法主体和必要的HAL库文件。4. JFlash添加W25Q128烧录算法实操4.1 从FLM变成JLink能识别的算法JFlash和JLink的算法体系与Keil不同。最简单的方式是在Keil工程中把编译器切换到ARM Compiler 6同时把算法源码重新编译成带调试信息的ELF。也可以直接使用JLink安装目录下自带的“JLinkDevices.xml”作为模板参考其他Flash算法文件的写法添加自己的W25Q128条目。我实践下来比较顺的流程是先用Keil编译出FLM再在JLink提供的编译环境里把同样的源码编译成ELF。注意JLink对算法的加载地址有要求链接脚本要指定在SRAM地址范围内并且所有函数必须是位置无关代码即编译时生成代码要支持重定位否则算法加载后会跳飞。4.2 修改JLinkDevices.xml注册算法JLink通过一个XML文件管理所有外设和Flash算法。打开JLink安装目录下的JLinkDevices.xml在里面加入类似下面的配置Device ChipInfo VendorST NameSTM32H750VB CoreJLINK_CORE_CORTEX_M7 WorkRAMAddr0x20000000 WorkRAMSize0x10000 / FlashBankInfo NameInternal Flash BaseAddr0x08000000 MaxSize0x20000 LoaderDevices/ST/STM32H750_128K.elf LoaderTypeFLASH_ALGO_TYPE_OPEN / FlashBankInfo NameW25Q128 QSPI BaseAddr0x90000000 MaxSize0x800000 LoaderDevices/ST/STM32H750_W25Q128.elf LoaderTypeFLASH_ALGO_TYPE_OPEN / /Device其中BaseAddr填0x90000000MaxSize填0x800000对应8MB。Loader路径指向刚才生成的ELF文件。XML配置必须写到JLink安装目录下并且JLink重启后才会加载这点我经常忘记。4.3 用JFlash命令行做产线批量烧录算法在JFlash里注册好之后不只是开发时能用产线上也可以做成自动脚本批量烧录。我实际用的脚本很简单exec Device STM32H750VB connect erase loadfile firmware.hex verify exit配合JLink的命令行工具完整烧录加校验大概20秒在小批量产线上足够用了。这里有个细节连接速度默认是4MHz如果你的PCB板走线比较长建议降到1MHz以下别小看这个很多下载中途失败的问题都是SWD时钟太快导致的。5. 踩坑实录常见问题与排查技巧5.1 擦除失败或校验失败Keil下载到一半卡住最后报“verify failed”这是外部Flash烧录最常见的报错。排查顺序很重要不要一上来就怀疑代码。第一步用逻辑分析仪看QSPI引脚时序确认时钟、数据线信号是否正常第二步检查W25Q128的状态寄存器如果BUSY位一直为1说明擦除命令根本没被正确响应大概率是SPI模式配置错了第三步确认WP引脚和HOLD引脚的状态这两个引脚在上电时必须拉高如果悬空或者被意外拉低Flash会进入保护状态擦写指令全部无效。我踩过的一个具体坑是在擦除函数里用HAL_GetTick()做超时计时结果算法在RAM里运行的时候系统节拍中断没使能计时直接失效。后来改成简单的循环计数每次擦除等待最多100万次循环可靠得多。5.2 程序下载到外部Flash后跑不起来代码烧进W25Q128地址也映射到了0x90000000但程序一运行就进HardFault。根据我的调试经验原因通常有三个向量表没有偏移。程序从外部Flash启动时需要把SCB-VTOR设置到0x90000000如果忘了这步CPU拿到的是内部Flash里的向量表执行逻辑完全错乱。启动配置问题。H750有没有正确配置成从外部Flash启动取决于BOOT0引脚和选项字节不是所有板子都默认支持外部QSPI启动模组星球开发板的跳线帽位置也要确认。没有在启动代码里初始化QSPI。如果代码运行依赖外部Flash而启动阶段QSPI还没被初始化那么CPU一取指就是空的。最稳妥的调试方法是分两步走先把一个最简单的测试程序烧到内部Flash专门测试QSPI读写、内存映射是否正确等确认外设完全正常后再把完整的Bootloader或应用放在外部Flash里启动。这样把问题隔离在单层不会一会怀疑硬件一会怀疑代码。5.3 JFlash识别不到自己添加的算法这个问题主要出现在Windows权限和路径上。JLink安装目录在Program Files下修改JLinkDevices.xml时如果忘记用管理员权限保存改动根本写不进去。另外XML里的路径分隔符必须用正斜杠或者双反斜杠用单个反斜杠会被当成转义字符。还有一个容易忽略的点是JFlash的版本不能太老7.88a及以上的版本对自定义ELF算法的兼容性更好。5.4 常见问题速查表现象可能原因解决方法Keil下载时卡死在擦除QSPI时序不对、WP引脚未上拉先检查时序再测WP/HOLD电平校验失败页编程跨越页边界Program函数按256字节分片程序跳转HardFault向量表偏移没设置设置SCB-VTOR 0x90000000JFlash识别不到算法XML配置有误、权限不足管理员权限修改检查路径分隔符下载速度很慢使用了标准SPI模式改为Quad模式拉高QSPI时钟6. 最后补充一点经验做完这个烧录算法工程之后我的一个体会是不要只盯着代码硬件上的细节更关键。你写的算法再完美只要W25Q128的WP引脚没上拉或者SPI走线被旁边的电源干扰所有下载操作都会变得玄学。建议在PCB设计阶段就把外部Flash的引脚状态考虑进去至少保证上电默认状态是可擦写的。如果你后续还想往远程升级方向扩展这个烧录算法可以直接复用到OTA方案里——把算法里的擦除和编程部分抽出来放进BootloaderMCU就能通过串口或网络升级外部Flash里的应用程序。很多量产方案就是这么做的一套地址映射加一套Flash驱动能省不少事。本文还有配套的精品资源点击获取