正点原子Apollo H743通过KEIL生成FLM算法实现外部QSPI-FLASH XIP功能(代码跑在外部FLASH)
Apollo V2 QSPI Flash XIP 设计说明
工程参考
ReturnDY/ApolloH743QSPI_FLASH_XIP
目标
在阿波罗 V2 STM32H743 开发板上,将应用程序存放在板载 W25Q256 QSPI Flash 中,并直接从外部 Flash 执行(XIP,Execute In Place)。
MCU 上电后,内部 Flash 中的 Bootloader 初始化 QSPI,开启 memory-mapped 模式,然后跳转至外部 Flash 中的应用。
存储布局
| 区域 | 地址范围 | 用途 |
|---|---|---|
| STM32 内部 Flash | 0x08000000 起 |
Bootloader、上电复位入口 |
| QSPI 映射窗口 | 0x90000000 - 0x91FFFFFF |
W25Q256,32 MiB |
| QSPI 镜像元数据 | 0x90000000 - 0x9000FFFF |
magic、版本、长度、CRC、升级状态 |
| QSPI XIP 应用 | 0x90010000 起 |
应用向量表、代码、只读常量、.data 初值 |
| AXI SRAM | 0x24000000 起 |
应用 .data、.bss、堆、栈 |
应用起始地址必须在以下四处完全一致:
-
XIP 应用 Scatter 文件中的
LR_XIP 0x90010000。 -
Keil Flash Download 中应用镜像的下载地址。
-
Bootloader 中的
APP_XIP_BASE。 -
跳转前写入
SCB->VTOR的向量表地址。
三类构建产物
| 产物 | 链接地址 | 下载位置 | 作用 |
|---|---|---|---|
| Bootloader | 0x08000000 |
STM32 内部 Flash | 初始化硬件并启动应用 |
| XIP 应用 | 0x90010000 |
W25Q256 QSPI Flash | 直接从 QSPI 执行 |
W25Q256.FLM |
Keil 临时装入 SRAM | 不保留在产品中 | 擦除、写入 QSPI Flash |
Bootloader 与 XIP 应用应使用两个 Keil Target,或使用两个独立工程;两者各自使用独立 Scatter 文件。
Scatter 文件只在链接期由编译器使用,MCU 运行时不会读取 Scatter 文件。
Keil 下载算法
Keil 下载程序时,会将 Flash Algorithm 临时装入 MCU SRAM 并调用其接口擦写 Flash。下载结束后,.FLM 不会留在 MCU 或 QSPI Flash 中。
Bootloader 一般使用 Keil 自带的 STM32H743 内部 Flash 下载算法。XIP 应用需要自定义 W25Q256.FLM,并在 Keil 的 Flash Download 设置中选中。
外部 Flash 下载算法至少实现:
int Init(unsigned long adr, unsigned long clk, unsigned long fnc); int UnInit(unsigned long fnc); int EraseSector(unsigned long adr); int ProgramPage(unsigned long adr, unsigned long sz, unsigned char *buf); int EraseChip(void);
针对 W25Q256,算法需要自行完成 QSPI/GPIO 初始化、写使能、忙等待、4 KiB 扇区擦除、256 字节分页写入和读回校验。Flash 基地址定义为 0x90000000,容量为 0x02000000,擦除扇区粒度为 4 KiB。
生成 W25Q256 下载算法 FLM
本项目的下载算法工程位于:
*\keli5\ARM\flash\<QSPI_FLASH>\NewDevice.uvprojx
它不是 XIP 应用工程。Keil 下载时会把它临时复制到 MCU SRAM 中执行,所以 FLM 的 Scatter 文件不能使用 LR_XIP。工程必须包含以下源文件:
FlashPrg.c FlashOS 调用入口:Init、EraseSector、ProgramPage 等 FlashDev.c FlashDevice 描述:名称、地址、容量、扇区大小 qspi_w25q256.c 与 HAL/CubeMX 无关的 QSPI 硬件与 W25Q256 驱动 FlashStk.s 下载算法 SRAM 栈大小(当前 0x400)
FlashDev.c 中的设备范围必须与目标芯片一致:
EXTSPI, 0x90000000, /* Flash 映射起始地址 */ 0x02000000, /* W25Q256:32 MiB */ 0x001000, 0x000000, /* 4 KiB 扇区 */
在 Options for Target -> Linker 中选择 Target.lin,

其内容如下。PRG 0 PI 表示下载算法以位置无关形式组织,Keil 会把它装入 SRAM;DSCR 区域保存 FlashDevice 描述,供 Keil 识别算法支持的地址范围。
PRG 0 PI
{
PrgCode +0 { * (+RO) }
PrgData +0 { * (+RW,+ZI) }
}
DSCR +0
{
DevDscr +0 { FlashDev.o }
}
在 Options for Target -> Linker -> Misc Controls 中加入:
--keep FlashDevice

否则链接器可能删除未被普通 C 调用引用的 FlashDevice 描述,生成的文件无法作为 Flash Algorithm 使用。
在 Options for Target -> Output 中:
-
勾选
Create Executable; -
Output Name 设置为
STM32H743_W25Q256; -
不需要生成 HEX 或 BIN。

在 Options for Target -> User -> After Build/Rebuild 中勾选 Run User Program 1,命令为:
cmd.exe /C copy "Objects\%L" ".\@L.FLM"
Keil 首先生成 Objects\STM32H743_W25Q256.axf。上述命令把这个符合 FlashOS Scatter 布局的 AXF 复制为:
D:\keli5\ARM\QSPI_FLASH\STM32H743_W25Q256.FLM

构建成功后,将 FLM 放入 Keil 算法目录:
*\keli5\ARM\Flash\STM32H743_W25Q256.FLM
在 XIP 应用工程中选用 FLM
打开 XIP 应用工程的 Options for Target -> Utilities -> Settings -> Flash Download。点击 Add,在弹窗中选中 W25Q256 QSPI Flash (32MB) 后,必须再点击弹窗右下角的 Add。返回上一窗口后,Programming Algorithm 列表应实际出现以下范围:
W25Q256 QSPI Flash (32MB) 0x90000000 - 0x91FFFFFF

只在弹窗中选中算法但未点击 Add 不会把算法加入工程,会导致下载日志出现:
No Algorithm found for: 90000000H
XIP 应用项目不应保留 STM32H7x_2048 片内 Flash 算法。Bootloader 工程仍使用该片内 Flash 算法;XIP 应用工程只使用 W25Q256 QSPI Flash (32MB)。
当前已实现的最小 XIP 配置将应用向量表放在
0x90000000。本文前述0x90010000是为未来 64 KiB 镜像元数据预留的布局。若采用该预留布局,必须同步修改 XIP Scatter、Boot 的应用基址与 VTOR;不能只修改其中一处。
两份 Scatter 文件
Bootloader
Bootloader 是普通内部 Flash 程序,向量表和代码位于 0x08000000,可写数据、堆和栈位于 SRAM。
LR_BOOT 0x08000000 0x00020000
{
ER_BOOT 0x08000000 0x00020000
{
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_RAM 0x24000000 0x00080000
{
.ANY (+RW +ZI)
}
}
XIP 应用
应用的代码和只读常量位于 QSPI,所有可写数据位于 SRAM:
LR_XIP 0x90010000 0x01000000
{
ER_XIP 0x90010000 0x01000000
{
*.o (RESET, +First)
*(InRoot$$Sections)
.ANY (+RO)
}
RW_AXI 0x24000000 0x00080000
{
.ANY (+RW +ZI)
}
}
应用启动代码会将 .data 初值从 QSPI 复制至 SRAM,并清零 .bss;流程与普通 Flash 应用相同,只是代码存储区由内部 Flash 变成 QSPI Flash。
运行流程
上电复位 -> STM32 Boot ROM 根据 BOOT 配置从内部 Flash 启动 -> Bootloader 的 Reset_Handler(0x08000000) -> 配置时钟、MPU/Cache、QSPI GPIO 和 W25Q256 -> QSPI 进入 memory-mapped 模式 -> 校验 QSPI 应用的镜像头、向量表和 CRC -> 设置 VTOR 为 0x90010000,跳转到应用 Reset_Handler -> 应用直接从 QSPI 执行
Bootloader 必须在读取 0x90010000 前完成 W25Q256 QE 位、四字节地址模式与 QSPI memory-mapped 读模式配置。
Bootloader 跳转应用
这里是普通跳转,不是硬件复位。因此 QSPI 的 GPIO、时钟和 memory-mapped 配置都会保留。
#define APP_XIP_BASE 0x90010000UL
typedef void (*app_entry_t)(void);
static void boot_jump_to_xip_app(void)
{
uint32_t msp = *(volatile uint32_t *)APP_XIP_BASE;
uint32_t reset = *(volatile uint32_t *)(APP_XIP_BASE + 4U);
__disable_irq();
SysTick->CTRL = 0;
SysTick->LOAD = 0;
SysTick->VAL = 0;
for (uint32_t i = 0; i < 8U; i++)
{
NVIC->ICER[i] = 0xFFFFFFFFU;
NVIC->ICPR[i] = 0xFFFFFFFFU;
}
SCB->VTOR = APP_XIP_BASE;
__DSB();
__ISB();
__set_MSP(msp);
((app_entry_t)reset)();
}
跳转前至少校验:MSP 位于有效 SRAM、Reset Handler 的 Thumb 位有效(reset & 1U)、Reset Handler 位于 QSPI 应用范围内、镜像 CRC 正确。
XIP 应用不得重新初始化、反初始化、复位 QSPI,也不得退出 QSPI memory-mapped 模式。应用启动代码应继续保持:
SCB->VTOR = 0x90010000UL;
NVIC_SystemReset()、看门狗复位、NRST 或掉电复位会丢失 QSPI 配置。此时 MCU 会重新进入内部 Flash Bootloader,Bootloader 再次初始化 QSPI 后跳转应用。
MPU 与 Cache
0x90000000 映射区必须配置为可执行的 Normal Memory。XIP 常规读操作建议启用 Cache 以获得性能。
擦写 QSPI 后、重新读取或执行新内容前,需要进行 Cache 维护:
SCB_InvalidateICache(); SCB_InvalidateDCache(); __DSB(); __ISB();
XIP 运行时更新限制
应用从 QSPI 执行期间,不能安全擦写同一颗 QSPI Flash。擦写前必须退出 memory-mapped 模式,而这会使 CPU 无法从 QSPI 取指。
第一版建议由内部 Flash Bootloader 执行所有 QSPI 镜像更新;XIP 应用只运行,不写自身所在的 QSPI Flash。
若以后确实需要应用在线更新,以下内容必须全部放入 SRAM:
-
擦除、写入函数及其所有调用函数;
-
这些函数使用的字符串、常量、查找表;
-
向量表和可能触发的 ISR,或在更新期间全局关闭中断;
-
栈和运行期间使用的 DMA 描述符。
完成擦写后重新进入 memory-mapped 模式,失效 Cache,之后才能恢复 XIP 执行。
当前阶段边界
-
不要把 Bootloader 与 QSPI 应用链接成一个普通镜像。
-
不要在 XIP 应用中调用 CubeMX 生成的
MX_QUADSPI_Init()。 -
不要把 Keil
.FLM当作产品 Bootloader。 -
不要让镜像元数据区与可执行代码区重叠。
-
A/B 分区、回滚、签名与在线升级应在基础 XIP 启动验证成功后再加入。
更多推荐

所有评论(0)