手把手教你制作Keil的FLASH下载算法
Contents
起因
用 Keil 开发 STM32F429 + 外部 SPI Flash(W25QXX)项目时,把图片、字库等资源直接烧进外部 Flash 是刚需(比如 TouchGFX 把图片放在 QSPI 映射区 0x90000000)。但 Keil 默认只认识片内 Flash——点下载时它不知道你那颗 W25Q 该怎么擦、怎么写。解决办法就是给 Keil 做一个“Flash 下载算法”(.FLM 文件)。
需求
- 制作 STM32F429 外挂 W25QXX(SPI 接口)的下载算法,让 Keil 一键把数据烧进外部 Flash
- 烧写地址映射到 0x90000000(与 QSPI 内存映射区一致),工程里把资源直接分配到这段地址
技术实现原理
下载算法是什么:一段“借鸡生蛋”的小固件
调试器(ULINK/J-Link/ST-Link)自己不会擦写 W25Q——真正会操作 SPI Flash 的是 STM32 本身。Keil 的机制是:把“怎么擦写”写成一个裸机小工程(即 _Template 模板),编译成 FLM;下载时调试器先通过调试接口把这段代码搬进目标芯片的 RAM,让目标 MCU 自己跑,调试器只按约定的入口函数名调用并传参。这也解释了模板工程的几个特点:
- 它是纯裸机程序:不能依赖你板子上 App 已经做过的初始化,Init() 里要自己配时钟、配 SPI;
- 入口函数(Init/UnInit/EraseChip/EraseSector/ProgramPage/BlankCheck/Verify)的函数名和签名是 FlashOS.h 定死的协议,Keil 按名字找,少了就不行;
- 返回 0 表示成功、非 0 表示失败,调试器据此决定是否中止烧写。
地址换算:0x90000000 与 base_adr
Keil 传给算法的 adr 是映射区地址(0x90000000 起),而 W25QXX 驱动要的是芯片内部偏移(从 0 开始),所以 EraseSector/ProgramPage 里都有 (adr - base_adr) 这一步换算。FlashDev.c 里的 Device Size、Sector Size、Page Size、超时时间则是告诉 Keil 这块“Flash”长什么样:按 4096 字节扇区对齐擦除,超时给 2000/3000ms 防止大数据量烧写被误判失败。
BlankCheck 与 Verify 的取舍
模板里 BlankCheck 返回 1(每次强制擦除)、Verify 直接返回 0(不校验)——最省事的配置,代价是烧写偏慢、且烧完不读回验证。要求高时可以实现真正的查空(读回全 0xFF 就跳过擦除)和逐字校验,速度与可靠性都能提上来。
1、找到Keil的官方Flash下载算法路径进行修改
下载算法的模版在keil安装目录\ARM\Flash下的_Template文件夹,把此文件夹复制出来,进行修改。也可以自行修改此工程名。
|
|

2、选择自己的板子的芯片

3、添加SPI和W25QXX驱动
添加W25QXX的驱动代码,
去正点原子的论坛下载STM32F429的寄存器工程,拷贝出HARDWARE文件夹下的SPI和W25QXX驱动,和SYSTEM文件夹。借成熟驱动的目的是少踩 SPI 时序和 W25Q 命令码的坑,算法工程里只管调用。

4、添加相关驱动文件

5、修改FlashDev.c代码
修改FlashDev.c 中外部flash的大小
|
|
6、修改FlashPrg.c代码
按照模版添加驱动代码
|
|
7、编译后将xxx.FLM拷贝到C:\keil5\ARM\Flash目录
工程编译完成后在输出目录下把STM32F429_W25QXX.FLM拷贝到Keil安装目录C:\keil5\ARM\Flash中,
之后在工程的 Debug 设置 → Flash Download 里 Add 这条算法,并把编程地址范围设为 0x90000000 起的那段即可调用。
代码下载
Github地址:https://gitee.com/Aladdin-Wang/hellotouchGFX.git
代码说明
- FlashDev.c:这块外部 Flash 的“地图”——起始地址 0x90000000、16M 容量、4096 字节扇区、擦后初值 0xFF、编程/擦除超时。Keil 的下载对话框按这份表来安排擦写;
- Init():时钟、SPI、W25Q 的初始化全在这里做。算法是被搬进 RAM 运行的独立小固件,不能假设外设已被你的 App 配置过;
- EraseSector()/ProgramPage():核心各一行——
(adr-base_adr)把映射地址换算成 W25Q 内部偏移(擦除再除以 4096 得扇区号),然后交给驱动; - BlankCheck 返回 1、Verify 返回 0:强制擦除 + 跳过校验的最简配置,见上文原理节的分析。
踩坑记录与注意事项
- base_adr 只声明没赋值:全局变量初值为 0,
(adr-base_adr)实际等于没减。使用时要让 Keil 下载设置里的算法起始地址与 FlashDev.c 的 0x90000000 一致,并在 Init 里给 base_adr 赋 0x90000000(或直接用宏写死),否则擦写偏移整体错位,现象是烧完数据全在错误地址上; - 算法工程必须链接在 RAM 里:模板已配好分散加载,别动;它运行时目标芯片的 App 还没跑,一切外设初始化都要 Init 里自己做;
- 扇区号换算:EraseSector 里
(adr-base_adr)/4096依赖 FlashDev.c 声明的 Sector Size 与 W25Q 实际扇区一致(W25Q 系列为 4K),换成 256 字节页的 Flash 要相应调整; - 超时别给太小:W25Q 整片擦除几十秒,如果要用 EraseChip,注意 Keil 侧的 Erase Timeout 与驱动里的等待循环要配套;
- Verify 返回 0 等于放弃校验:烧写不读回验证,出现“图片偶发花屏/数据错”时先想到这一点,必要时实现真正的读回比较;
- FLM 更新后要重新添加:同名覆盖后建议重启 µVision,老版本对 FLM 缓存偶尔不刷新。
Author 软件开发大郭
LastMod 2022-06-03