拓冰建站拓冰建站
首页 / 资讯中心 / 正文

手写W25Q128烧录算法FLM,解决STM32H7外部SPI Flash在线调试

简介面向STM32H723ZET6微控制器与W25Q128 SPI Flash组合的Keil烧录算法FLM源码工程专供需要在MDK环境下完成程序下载、擦除与调试的嵌入式开发人员使用。FLM即Flash Loader Middleware源码基于STM32H7硬件闪存接口实现包含初始化、读写、擦除等核心算法并配套启动文件、链接脚本、Keil工程配置和编译中间产物总共1566个文件压缩包约14.41MB。核心代码以C/H文件形式提供算法主体icf/s等文件定义内存布局与启动流程uvprojx/uvoptx可直接打开工程最终生成的FLM文件可加载到Keil烧录工具中同时包含的lib/a、o/crf等文件支持编译调试与依赖链接。已有492人学习适合正在研究STM32H7外部Flash烧录机制、或需要定制量产下载算法的工程师参考可帮助理解FLM算法结构并快速移植适配自身板卡。 板子上配了stm32h723zet6和一片W25Q128想在Keil里直接烧外部SPI NOR Flash结果发现MDK自带的FLM烧录算法根本不认这颗芯片。Flash Download列表里翻来翻去只有内部Flash的算法要么就是官方支持的个别外部存储器型号。这个场景在项目里太常见了内部Flash不够用外挂一片W25Q128做数据存储或OTA备份真到量产烧录或者在线调试的时候算法这一步就卡住了。这篇文章就把我自己写W25Q128烧录算法FLM的过程完整记录一遍从FLM的原理、Keil工程怎么建、几个关键函数怎么写到最后的编译配置和踩坑记录都会讲到。适合手里正好有STM32H7系列外挂SPI NOR Flash、又不满足于用串口工具单独烧录的开发者。看完你就能自己生成一个Keil可直接加载的.flm文件实现在线下载和调试不用再依赖第三方烧录软件。1. FLM烧录算法到底是什么为什么W25Q128必须自己写1.1 Keil为什么需要FLMFLM的全称是Flash Loader Module说白了就是一段可以被Keil调试器加载并执行的“驱动程序”。MDK在做下载、擦除、校验操作时并不是直接操作Flash而是把FLM这个动态库一样的插件加载到目标芯片的RAM里然后跳进去调用它提供的几个固定接口函数。内部Flash之所以开箱即用是因为芯片厂商把这些算法编进了CMSIS-Pack里Keil启动时会自动识别。外部Flash的处境就不一样了。W25Q128是SPI接口的NOR Flash虽然市面上用得极多但ST官方Pack里并没有把这种通用SPI Flash的算法都给你备好。你需要在Flash Download里Add一个Algorithm找不到对应的条目就只能自己做一个FLM让Keil去加载。FLM本身其实是个ELF格式的文件里面有三个关键段PrgCode、PrgData和DevDscr。Keil在加载时把PrgCode和PrgData放进RAM for Algorithm指定的区域然后读取DevDscr里的器件描述信息这样Flash Download对话框里就能显示出算法名称、起始地址、页大小这些参数了。1.2 什么时候需要自己搞一套很多人一上来就问能不能直接把W25Q128挂到FMC总线用“外部NOR Flash”的现成算法答案是不行。FMC接口的NOR Flash走的是并行总线W25Q128走的是SPI串行协议两者在时序上完全不是一回事算法自然不能通用。哪怕是同样封装、同样容量的SPI Flash不同厂家的指令集和状态寄存器定义也有细微差别稳妥做法就是按自家板子的具体型号写一份。这里有一个简单判断标准如果你的外部Flash能在Flash Download列表里找到完全对得上的算法并且硬件连接也跟算法假设一致那直接用现成的就好不用折腾。但绝大多数情况下W25Q128这类通用SPI Flash都不在官方列表里而且每个人用的SPI引脚、时钟极性、分频系数都不一样算法必须跟着硬件走。自己写FLM还有一个好处初始化函数里可以做JEDEC ID校验接线有问题第一时间就能查出来比盲烧稳得多。2. 动手前的准备工作2.1 硬件连接与手册关键参数我这次用的板子是stm32h723zet6外扩W25Q128接在SPI1上CS用PB6SCK用PB3MISO用PB4MOSI用PB5。当然引脚定义很灵活关键是自己在Init函数里把GPIO复用配置对。W25Q128支持SPI Mode 0和Mode 3我习惯用Mode 0也就是CPOL0、CPHA0这样时序理解起来最直观。动手之前请务必翻一遍W25Q128数据手册重点记几个参数容量是128Mbit也就是16MB页大小256字节页编程指令是0x02扇区擦除按4KB对齐指令是0x20擦除块还有64KB的0xD8指令但算法里最常用的是扇区擦除读状态寄存器指令0x05bit0是BUSY位为1表示Flash正在忙必须等待它清零再发下一条命令。页编程典型的等待时间是0.4ms到3ms扇区擦除典型50ms到400ms整片擦除可能要几十秒。这些等待时间会直接影响你在FLM里填的超时参数。2.2 Keil工程骨架与FlashOS.h新建一个空工程Device选stm32h723zet6。不要勾选“初始化所有外设到复位状态”那种默认启动流程FLM是给调试器用的不需要C库初始化那一大套东西。最关键的一步是Options for Target里的Output页一定要勾上Create Flash Loader Output生成的文件后缀是.flm。如果不勾编出来的只是普通axfKeil没法识别。工程里需要包含FlashOS.h这个头文件定义了FLM接口函数原型和FlashDevice结构体。它在MDK安装目录下可以通过搜文件名找到通常在ARM\Packs\ARM\CMSIS...\FlashOS.h这种位置。如果用的是STM32CubeH7固件包里的模板路径也大差不差。你不需要完整理解这个头文件的每个宏但几个核心类型名必须认识struct FlashDevice以及Init、UnInit、EraseChip、EraseSector、ProgramPage、Verify这几个函数的声明。后面写源码就是围绕这套接口展开的。3. FLM源码核心实现3.1 从FlashDevice描述开始FLM文件的器件描述信息最终体现在一个全局结构体变量上名字规定必须是FlashDevice。Keil加载FLM时就是靠它拿到器件名称、起始地址、容量、页大小这些字段的。我的写法大致是这样#include FlashOS.h #include stdint.h #define EXT_FLASH_BASE 0x90000000UL #define EXT_FLASH_SIZE (16 * 1024 * 1024) #define EXT_PAGE_SIZE 256UL struct FlashDevice const FlashDevice { FLASH_DRV_VERS, W25Q128_SPI 0x90000000, EXTSPI, EXT_FLASH_BASE, EXT_FLASH_SIZE, EXT_PAGE_SIZE, 0xFF, 0xFF, 0xFF, 0xFF, 2000, 2000, 0, 0, 0, 0, };这段代码里最需要注意的是字段顺序FlashOS.h在不同MDK版本里结构体定义略有差异我这边用的字段含义是版本号、名称、器件类型、起始地址、总容量、页大小、擦除后的默认值、若干超时参数等。DevType写成EXTSPI表示外部SPI Flash。超时参数我这里给了2000ms要大于W25Q128扇区擦除最大时间400ms留足余量。基地址这里我统一约定为0x90000000这是STM32H7系列外部存储器映射区域里的一个常用地址。SPI Flash本身不是内存映射设备这个地址只是给Keil做符号定位用的真正访问Flash时算法内部会把这个地址减掉换算成实际的片内偏移。如果你的板子约定或者其他工具用的基地址不一样只要算法里的宏和Keil配置保持一致就行。3.2 Init里把外设和时序准备到位Init函数是FLM的入口Keil在开始下载前会先调用它参数包括目标地址、时钟频率和功能码。这个函数里要做的事情比我一开始想的要多使能GPIO和SPI外设时钟、配置引脚复用、初始化SPI模式、分频系数、最后再读一次JEDEC ID确认W25Q128在线。很多教程写FLM只给个空壳Init实际用起来会莫名其妙失败就是因为外设压根没配。int Init(unsigned long adr, unsigned long clk, unsigned long fnc) { RCC-AHB4ENR | RCC_AHB4ENR_GPIOBEN; RCC-APB2ENR | RCC_APB2ENR_SPI1EN; GPIOB-MODER (GPIOB-MODER ~(3UL (3*2) | 3UL (4*2) | 3UL (5*2) | 3UL (6*2))) | (2UL (3*2)) | (2UL (4*2)) | (2UL (5*2)) | (2UL (6*2)); GPIOB-AFR[0] | (5UL (3*4)) | (5UL (4*4)) | (5UL (5*4)) | (5UL (6*4)); GPIOB-OSPEEDR | (3UL (3*2)) | (3UL (4*2)) | (3UL (5*2)) | (3UL (6*2)); SPI1-CR1 SPI_CR1_MSTR | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_BR_2 | SPI_CR1_BR_1 | SPI_CR1_SPE; SPI1-CR2 SPI_CR2_FRXTH | (7UL 8); // 8bit数据, 8位帧 if (w25q_read_jedec_id() ! 0xEF4018UL) return 1; return 0; }JEDEC读到0xEF4018就说明是W25Q128读不到就检查接线和时钟配置。返回1表示初始化失败Keil会中止操作并报错。这个设计能帮你把“算法问题”和“硬件问题”快速区分开算是我最推荐的一个小细节。3.3 扇区擦除和页编程怎么对齐W25Q128擦除和编程是所有FLM函数里的重头戏。擦除扇区的流程是发写使能命令0x06然后发扇区擦除指令0x20后面跟24位地址最后反复读状态寄存器等BUSY位清零。这里有个容易忽略的点每一条写操作之前都要重新发送0x06写使能状态不会一直保持所以擦除函数和页编程函数里都要有这一步。int EraseSector(unsigned long adr) { unsigned long addr adr - EXT_FLASH_BASE; w25q_write_enable(); w25q_send_cmd_3addr(0x20, addr); w25q_wait_busy(2000); return 0; }页编程函数返回前同样要等待BUSY。W25Q128的页编程一次最多写256字节而且不能跨页但Keil在调用ProgramPage时会根据你FlashDevice里填的PageSize值来切分所以函数内部其实不用考虑跨页问题。不过为了安全我会加一个判断如果sz超过256就直接返回1防止调用方传入异常数据。这里还要提一下整片擦除的EraseChip函数。如果你在Flash Download里选择Erase Full ChipKeil就会调用它。整片擦除指令是0xC7等待时间要长得多我会直接把超时设到40000ms。平时调试建议选Erase Sectors这样速度更快也不用整片擦。3.4 Cache一致性Cortex-M7特有的坑stm32h723zet6是Cortex-M7内核带I-Cache和D-Cache。FLM里的ProgramPage收到数据缓冲区指针时这个缓冲区可能位于普通SRAM区域而M7的D-Cache正好会对CPU访问缓存数据产生“干扰”。典型故障现象是代码逻辑完全正确但烧进去的数据总是隔几个字节出错或者Verify永远失败。解决办法是在把缓冲区的数据发给SPI外设之前主动Clean D-Cache在Verify回读比较之前Invalidate D-Cache。对应代码是SCB_CleanDCache_by_Addr((uint32_t *)((uint32_t)buf ~0x1FUL), sz); // 发送数据或者读回数据之后 SCB_InvalidateDCache_by_Addr((uint32_t *)((uint32_t)buf ~0x1FUL), sz);这里注意对齐到32字节Cortex-M7的Cache line大小是32字节直接传原始指针可能会漏掉边界数据。虽然FLM本身运行在RAM里代码段不经过Flash缓存但数据缓冲区在普通SRAM时Cache问题依然存在。我的建议是不管缓冲区放在哪里只要不明确知道它在DTCM且DCache关闭就统一做Clean和Invalidate。4. 编译配置与把FLM装进Keil4.1 分散加载和RAM地址怎么设FLM和普通固件最大的不同在于它要被放进RAM里执行而不是从内部Flash启动。所以链接地址不能使用默认的0x08000000必须把代码段放到RAM区域。stm32h723zet6的RAM资源比较多我习惯用0x20000000起始的DTCM区域给个0x8000大小给算法用绰绰有余。最简单的做法是在Options for Target的Target页里把IROM1的Start改成0x20000000Size改成0x8000IRAM1保持默认。然后在Linker页取消勾选Use Memory Layout from Target Dialog自己写一个简单的分散加载文件内容类似LR_FLM 0x20000000 0x8000 { ER_FLM 0x20000000 0x8000 { * (PrgCode, PrgData) } RW_FLM 0 { * (DevDscr) } }这段分散加载把PrgCode和PrgData固定在0x20000000DevDscr跟在后面。Keil加载FLM时会去读这些段的符号信息所以段的命名最好保持规范。编译之前再确认一下Output页的Create Flash Loader Output是勾选状态生成的.flm文件会在工程的Output目录里。4.2 安装算法并在Flash Download里调用得到W25Q128_Burner.flm文件之后把它复制到Keil安装目录下的ARM\Flash文件夹里。比如我这边就是C:\Keil_v5\ARM\Flash。有的版本会扫描CMSIS-Pack目录里的FLM但直接放ARM\Flash最通用重启Keil后Flash Download对话框里就能看到这个算法了。接着在工程Options for Target里打开Debug页选择调试器后点Settings进入Flash Download选项卡。先点Add手动选择刚添加的W25Q128算法然后在下方把Programming Algorithm的参数填对Start Address填0x90000000Size填0x1000000RAM for Algorithm的Start填0x20000000Size填0x8000。擦除方式建议选Erase Sectors这样每次下载只擦除需要的区域。全部设置好再回到主界面点Download如果一切正常日志窗口里就会出现Programming Done和Verify OK。5. 常见问题与排查技巧实录5.1 下载报错速查表我在做这个算法前前后后遇到不少问题把它们整理成了一张速查表大概能覆盖七八成的情况。现象可能原因解决办法Flash Download里找不到算法.flm没复制到ARM\Flash目录确认文件名和路径重启KeilLoad algorithm at 0x20000000失败RAM for Algorithm大小不够或者FLM代码段超过0x8000调大RAM for Algorithm的Size精简代码Verify failed at AddressCache未处理SPI分频太高写使能没生效加Clean/Invalidate降低SPI时钟检查0x06时序擦除超时超时参数填太小Flash供电不稳把TimeOut调到2000ms以上确认供电整片擦除极慢选成了Erase Full Chip调试时改成Erase Sectors读回全是0xFFCS极性错误时钟模式不对地址偏移算错检查CPOL/CPHA核对adr-EXT_FLASH_BASE下载偶尔成功偶尔失败写使能后没等WEL置位发送0x06后读状态寄存器确认WEL1再操作最经典的全0xFF问题多数情况是SPI Mode选错了W25Q128吃Mode 0和Mode 3如果你初始化成了Mode 1Flash数据线就不会正常响应。5.2 我踩过的几个坑第一次写完FLM下载时报错一直指向0x90000000地址不可写。我追了半天才发现是FlashDevice结构体的DevType没有设成EXTSPI而是延续了模板里的ONCHIPKeil认为这是个片内器件访问路径和处理逻辑就全变了。所以这个字段别随手复制一定要按外部SPI Flash来写。还有一个问题让我印象很深Verify失败总是从中间某一段开始。一开始怀疑是时序问题后来才想起来板子上的Cortex-M7开了D-CacheProgramPage里的数据缓冲区被Cache挡了一道。加了Cache维护之后问题彻底消失。从那以后我给所有H7系列的FLM都默认加上Clean和Invalidate不管当前配置是否真的需要。另外排查期间建议先用一个最简单的外设读写测试工程直接在RAM里跑把W25Q128的JEDEC ID读出来、写一个扇区再读回来对比。这个测试工程要和FLM使用完全一样的GPIO和SPI配置。把这一步验证过了再回来纠结FLM本身的问题排查范围会小很多。5.3 一个提升成功率的调试技巧如果你在Keil的Command窗口里能进入调试状态可以先把普通固件下载到内部Flash然后在调试会话里用表达式窗口手动调用FLM里的Init、EraseSector和ProgramPage函数。当然这需要你记得函数地址或者用符号窗口查。这样做的好处是每次调试不用反复触发整个下载流程只看单个函数的行为定位问题非常快。我个人最常用的是这种组合先用普通工程验证底层SPI读写再用调试器单步跑FLM函数确认每个函数返回0最后才回到Keil的Download按钮做全流程测试。三层下来基本能把“算法本身的问题”和“工程配置的问题”完全分开。6. 实操之外的几句心里话这套FLM源码我前前后后调了两天最深的体会是难点不在代码量而在“运行环境完全不一样”这个思维转换上。普通固件有启动文件、有C库初始化、有完整的中断环境而FLM就是一段裸奔的小程序要在调试器投递的RAM环境里独立完成所有事情所以要特别留意Cache、内存布局和超时这些平时容易被忽略的点。如果只是给手里这块板子做下载工具写到这里其实就够了。但如果你以后还会换用其他型号的SPI NOR Flash这套结构的复用性很高换一下JEDEC ID的校验值改改页编程和擦除指令码超时时间按新芯片手册调一调基本上就能继续用。这也是我建议认真写一份自己Flash底层驱动的原因它帮你把芯片时序吃透了后面做Bootloader、OTA、文件系统都会顺手很多。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门