在嵌入式开发中,随着 GUI 界面、大数据存储等复杂应用的普及,STM32 单片机的片内 Flash 往往难以满足存储需求,外扩 QSPI 接口 Flash 成为主流解决方案。但多数开发者会遇到一个关键痛点:Keil MDK 仅为 STM32 官方开发板提供默认下载算法,自定义硬件的 QSPI Flash 无法实现一键烧录调试,需手动制作专属下载算法。
ST 官方 LAT1198 应用笔记提供了高效解决方案,以 STM32H750 为例,详细拆解了基于 Keil MDK 通用模板制作 QSPI 外部 Flash 下载算法的全流程。本文基于该笔记,从算法原理、工程搭建、核心代码开发到实际应用,全程聚焦实操性,让开发者无需深入底层协议,即可快速制作稳定可用的下载算法,直接应用于项目开发。
资料获取:【应用笔记】LAT1198 通过 KEIL 制作 QSPI 接口的外部 Flash 下载算法
1. QSPI Flash 下载算法核心原理
1.1 下载算法的本质与作用
Keil 的 Flash 下载算法(FLM 文件)是一段运行在片内 SRAM的程序,核心功能是实现外部 Flash 的初始化、擦除、编程、校验等操作。其工作逻辑为:调试时 Keil 将 FLM 文件加载到片内 RAM,通过调用算法中的标准接口函数,与外部 QSPI Flash 交互,完成用户程序(AXF 文件)的烧录与校验,实现 “一键下载 + 调试” 的无缝衔接。
1.2 三大核心操作流程
下载算法的工作过程围绕 “擦除 - 烧录 - 校验” 三个核心流程展开,每一步都有明确的执行逻辑,确保烧录的准确性:
- 擦除流程:Keil 加载算法到 RAM→执行 Init 初始化→选择全片擦除(EraseChip)或扇区擦除(EraseSector)→执行 Uninit 收尾;
- 烧录流程:初始化算法→加载用户程序到 RAM 缓存→调用 ProgramPage 函数将数据写入 QSPI Flash→执行 Uninit;
- 校验流程:初始化算法→读取烧录后的 Flash 数据→与 RAM 中的用户程序数据比对(或计算 CRC 校验)→确认数据一致性。
1.3 QSPI 的关键特性
QSPI(Quad SPI)通过 4 条数据线实现并行传输,速率是标准 SPI 的 4 倍,且支持内存映射模式—— 初始化后可将 QSPI Flash 映射到 STM32 的地址空间,Keil 可直接通过总线读取数据,无需额外编写读取函数,大幅简化算法开发。
2. 前期准备与环境搭建
2.1 硬件与软件准备
- 硬件:STM32H750 开发板、QSPI Flash 芯片(如 MT25TL01G 128M)、ST-LINK 调试器;
- 软件:Keil MDK(V5.36 及以上)、STM32CubeMX、STM32H7 系列 HAL 库、STM32CubeProgrammer(可选,用于调试)。
2.2 关键文件与模板获取
Keil 自带通用 Flash 算法模板,路径为:C:\Keil_v5\ARM\Packs\ARM\CMSIS\5.6.0\Device\_Template_Flash,核心文件包括:
FlashDev.c:Flash 信息描述文件,需填写 QSPI Flash 的型号、容量、扇区大小、映射地址等参数;FlashPrg.c:算法核心实现文件,需编写 Init(初始化)、EraseChip(全片擦除)、EraseSector(扇区擦除)、ProgramPage(页编程)等接口函数;NewDevice.uvprojx:工程模板,可直接拷贝修改名称(如STM32H750_QSPI_Flash.uvprojx)。
3. Keil 工程核心配置(关键步骤)
工程配置的核心是确保算法文件与 STM32H750 兼容,且能生成正确的 FLM 格式文件,步骤如下:
步骤 1:修改工程基础设置
- 打开工程,点击【Project→Options for Target】,在【Device】标签页选择芯片型号 “STM32H750VBTx”;
- 【Output】标签页:设置输出文件名(如
STM32H750_QSPI),勾选 “Create HEX File”(可选); - 【Linker】标签页:取消勾选 “Use Memory Layout from Target Dialog”,启用分散加载文件(模板自带
Target.lin),在 “Misc Controls” 中输入--diag_suppress L6305,屏蔽无关警告; - 【User】标签页:在 “After Build/Rebuild” 的 Run#1 中输入命令:
cmd.exe /C copy "Objects\%L" ".\@L.FLM",实现编译后自动将 AXF 文件转换为 FLM 算法文件; - 【Target】标签页:设置 “Code Generation” 中的 “ROPI” 和 “RWPI” 为勾选状态,确保算法可加载到 RAM 任意位置,不依赖固定地址。
步骤 2:添加 QSPI 驱动与 HAL 库文件
- 用 STM32CubeMX 生成一个 STM32H750 的空白工程,拷贝工程中的
Drivers文件夹(含 CMSIS、HAL 库)到算法工程目录; - 在 Keil 中新建三个分组:CMSIS、HAL、QSPI,分别添加对应文件:
- CMSIS:
system_stm32h7xx.c(来自Drivers\CMSIS\Device\ST\STM32H7xx\Source\Templates); - HAL:
stm32h7xx_hal.c、stm32h7xx_hal_gpio.c、stm32h7xx_hal_qspi.c、stm32h7xx_hal_rcc.c、stm32h7xx_hal_cortex.c等核心库文件; - QSPI:自行编写或从成熟工程中拷贝调试好的 QSPI 驱动文件(如
STM32H750_QSPI.c);
- CMSIS:
- 添加头文件路径:在【C/C++】标签页的 “Include Paths” 中,添加所有库文件和驱动文件的头文件目录(如
Drivers\CMSIS\Include、Drivers\STM32H7xx_HAL_Driver\Inc); - 修改
stm32h7xx_hal_conf.h:启用 QSPI 相关宏定义(#define HAL_QSPI_MODULE_ENABLED),并根据硬件实际晶振修改HSE_VALUE(如 8MHz 晶振改为#define HSE_VALUE 8000000U)。
4. 核心文件修改(算法实现关键)
4.1 修改 FlashDev.c:配置 QSPI Flash 信息
该文件用于向 Keil 提供 QSPI Flash 的关键参数,需根据实际芯片手册修改,示例如下(以 MT25TL01G 128M Flash 为例):
#include "FlashDev.h"
// Flash设备信息结构体
const FLASH_DEVICE FlashDevice[] = {
{
FLASH_DRV_VERS, // 驱动版本
"STM32H750 QSPI MT25TL01G", // 算法名称(Keil中显示)
EXTSPI, // 外部SPI/QSPI类型
0x90000000, // QSPI Flash内存映射起始地址
0x08000000, // Flash总容量(128MB=0x8000000字节)
0x20000, // 扇区大小(128KB,需与Flash手册一致)
0, // 扇区数量(自动计算,无需填写)
256, // 页大小(256字节,QSPI Flash标准页大小)
0xFF, // 擦除后默认值
// 擦除、编程、校验超时时间
1000, // 扇区擦除超时(ms)
3000, // 全片擦除超时(ms)
10, // 页编程超时(ms)
},
{0, NULL, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} // 结束标记
};
4.2 修改 FlashPrg.c:实现核心算法函数
该文件是算法的核心,需实现 Keil 调用的标准接口函数,所有函数均基于 HAL 库和 QSPI 驱动编写:
(1)初始化函数 Init:配置 QSPI 并启用内存映射
#include "FlashPrg.h"
#include "stm32h7xx_hal.h"
#include "STM32H750_QSPI.h"
QSPI_HandleTypeDef hqspi;
// 算法初始化:返回0表示成功,非0失败
int Init(unsigned long adr, unsigned long clk, unsigned long fnc) {
// 初始化系统时钟(直接拷贝CubeMX生成的SystemClock_Config函数)
SystemClock_Config();
// 初始化QSPI Flash
if (BSP_QSPI_Init(&hqspi) != HAL_OK) {
return 1;
}
// 启用QSPI内存映射模式(关键:Keil可直接总线读取数据)
if (BSP_QSPI_EnableMemoryMappedMode(&hqspi) != HAL_OK) {
return 1;
}
return 0;
}
(2)全片擦除函数 EraseChip
// 全片擦除:返回0成功,1失败
int EraseChip(void) {
// 重新初始化QSPI
BSP_QSPI_DeInit(&hqspi);
if (BSP_QSPI_Init(&hqspi) != HAL_OK) {
return 1;
}
// 发送全片擦除命令
if (BSP_QSPI_Erase_Chip(&hqspi) != HAL_OK) {
return 1;
}
// 等待擦除完成(查询Flash状态)
while (BSP_QSPI_GetStatus(&hqspi) != QSPI_STATUS_READY);
return 0;
}
(3)扇区擦除函数 EraseSector
// 扇区擦除:adr为扇区起始地址,返回0成功
int EraseSector(unsigned long adr) {
uint32_t eraseAddr = adr & 0x0FFFFFFF; // 屏蔽高位,仅保留Flash地址
// 重新初始化QSPI
BSP_QSPI_DeInit(&hqspi);
if (BSP_QSPI_Init(&hqspi) != HAL_OK) {
return 1;
}
// 发送扇区擦除命令(0x20000为扇区大小,需与FlashDev.c一致)
if (BSP_QSPI_Erase_Sector(&hqspi, eraseAddr, 0x20000) != HAL_OK) {
return 1;
}
// 等待擦除完成
while (BSP_QSPI_GetStatus(&hqspi) != QSPI_STATUS_READY);
// 重新启用内存映射模式
BSP_QSPI_EnableMemoryMappedMode(&hqspi);
return 0;
}
(4)页编程函数 ProgramPage
// 页编程:block_start为起始地址,size为数据长度,buffer为数据缓冲区
int ProgramPage(unsigned long block_start, unsigned long size, unsigned char *buffer) {
uint32_t progAddr = block_start & 0x0FFFFFFF;
// 重新初始化QSPI(退出内存映射模式)
BSP_QSPI_DeInit(&hqspi);
if (BSP_QSPI_Init(&hqspi) != HAL_OK) {
return 1;
}
// 写入数据(按页编程,单次最大256字节)
if (BSP_QSPI_Write(&hqspi, buffer, progAddr, size) != HAL_OK) {
return 1;
}
// 等待编程完成
while (BSP_QSPI_GetStatus(&hqspi) != QSPI_STATUS_READY);
// 重新启用内存映射模式
BSP_QSPI_EnableMemoryMappedMode(&hqspi);
return 0;
}
(5)读取与校验函数(可选)
无需手动实现:启用内存映射模式后,Keil 会通过总线直接读取 QSPI Flash 数据;校验功能由 Keil 自动完成(计算 CRC 值比对),若需自定义校验逻辑,可实现Verify函数。
5. 算法编译与 Keil 配置使用
5.1 编译生成 FLM 文件
点击 Keil 的【Build】按钮编译工程,编译成功后,工程目录会生成STM32H750_QSPI.FLM文件(由 User 标签页的命令自动转换)。
5.2 Keil 中添加下载算法
- 将生成的 FLM 文件拷贝到 Keil 的算法目录:
C:\Keil_v5\ARM\Flash; - 打开用户应用工程,点击【Project→Options for Target→Debug→Flash Download】;
- 点击【Add】按钮,在弹出的列表中选择 “STM32H750 QSPI MT25TL01G”(FlashDev.c 中定义的算法名称);
- 配置 “RAM for Algorithm”:起始地址设为
0x20000000(STM32H750 片内 SRAM 起始地址),大小设为0x00008000(32KB,确保算法有足够运行空间); - 擦除选项选择 “Erase Sectors”(扇区擦除,比全片擦除更快),勾选 “Program” 和 “Verify”,点击【OK】保存。
5.3 测试验证
连接 ST-LINK 调试器,点击 Keil 的【Download】按钮,若输出窗口显示 “Programming... OK”“Verification... OK”,说明算法生效,用户程序已成功烧录到 QSPI Flash;启动调试后,可正常设置断点、运行程序,验证功能无误。
6. 开发避坑与关键技巧
- 时钟配置必须正确:SystemClock_Config 函数建议直接拷贝 CubeMX 生成的可用代码,确保 QSPI 外设时钟频率匹配(STM32H750 的 QSPI 时钟最大支持 133MHz);
- QSPI 驱动需提前调试:算法开发前,先在独立工程中验证 QSPI 的初始化、擦除、写入功能,避免驱动问题导致算法失败;
- 内存映射模式是关键:Init、EraseSector、ProgramPage 函数中,操作完成后需重新启用内存映射模式,否则 Keil 无法读取 Flash 数据;
- RAM 空间预留充足:“RAM for Algorithm” 的大小需根据算法复杂度调整,最小不低于 16KB,否则会出现算法加载失败;
- 参考官方 DEMO:若开发遇到问题,可参考 ST 官方固件包中的 QSPI 算法示例(如 STM32H7 系列的 Flash 算法工程),替换 QSPI 驱动和参数即可快速适配。
基于 Keil MDK 通用模板制作 QSPI 外部 Flash 下载算法,核心是 “配置兼容工程 + 实现标准接口 + 适配 QSPI 驱动”,无需深入底层协议,即可解决自定义硬件的一键烧录调试问题。该方法不仅适用于 STM32H750,稍作修改(替换芯片型号、QSPI 参数、驱动文件)即可适配 STM32F4/F7/H5 等系列,以及不同型号的 QSPI Flash 芯片。
对于嵌入式开发者而言,掌握该方法能大幅提升外扩 Flash 应用的开发效率,避免手动烧录的繁琐操作,尤其适用于需要频繁调试的项目开发场景。
315