Bootloader 内存分配入门:双区划分实战(STM32)

发布时间:2026/7/17 19:49:05
Bootloader 内存分配入门:双区划分实战(STM32) Bootloader 内存分配 · 入门篇系列Bootloader 内存分配 · 入门篇平台STM32F103C8T664 KB Flash / 20 KB RAM下一篇进阶篇 — 三区 A/B 分区与 OTA本文介绍 STM32 双区 Bootloader 方案将 Flash 划分为 Bootloader 区与 Application 区通过链接脚本、向量表偏移和跳转逻辑实现上电后从 Bootloader 进入 App 运行。目录为什么需要 BootloaderFlash 双区内存模型链接脚本配置向量表偏移 VTORBootloader 跳转 App完整工程示例编译与烧录常见问题1. 为什么需要 BootloaderBootloader 是芯片上电后首先运行的小程序常见职责包括通过 UART / USB / CAN 接收新固件并写入 FlashISP / OTA校验 App 完整性后跳转到 App 运行提供出厂测试、序列号写入等能力App 与 Bootloader 不能共用同一段 Flash否则互相覆盖。解决方案是将 Flash 物理分区各自链接到不同的起始地址。2. Flash 双区内存模型以 STM32F103C864 KB Flash为例典型划分如下0x0800_0000 ┌─────────────────────┐│ Bootloader 16 KB │ 上电入口0x0800_4000 ├─────────────────────┤│ Application 48 KB │ 业务固件0x0801_0000 └─────────────────────┘2.1 统一地址定义 memory_map.hBootloader 与 Application 工程共用同一份地址宏定义避免硬编码不一致#ifndef MEMORY_MAP_H#define MEMORY_MAP_H#define FLASH_BASE_ADDR 0x08000000U#define BL_FLASH_SIZE (16U * 1024U)#define APP_FLASH_SIZE (48U * 1024U)#define BL_START_ADDR FLASH_BASE_ADDR#define APP_START_ADDR (BL_START_ADDR BL_FLASH_SIZE)#define APP_VTOR_OFFSET (APP_START_ADDR - FLASH_BASE_ADDR)#endif宏定义值说明FLASH_BASE_ADDR0x08000000STM32 Flash 起始地址BL_FLASH_SIZE16 KBBootloader 占用空间APP_FLASH_SIZE48 KBApplication 可用空间APP_START_ADDR0x08004000App 链接与烧录起始地址APP_VTOR_OFFSET0x4000App 向量表相对 Flash 基址偏移3. 链接脚本配置链接脚本决定编译器将代码和数据放置到 Flash/RAM 的哪个地址。Bootloader 与 App 必须使用不同的链接脚本。3.1 Bootloader — STM32F103C8Tx_BL.ldMEMORY{FLASH (rx) : ORIGIN 0x08000000, LENGTH 16KRAM (xrw) : ORIGIN 0x20000000, LENGTH 20K}3.2 Application — STM32F103C8Tx_APP.ldMEMORY{FLASH (rx) : ORIGIN 0x08004000, LENGTH 48KRAM (xrw) : ORIGIN 0x20000000, LENGTH 20K}【关键】App 链接脚本中 FLASH 的 ORIGIN 必须等于 memory_map.h 中的 APP_START_ADDR否则中断向量表与代码地址全部错位运行后将 HardFault。工程FLASH ORIGINFLASH LENGTHRAMBootloader0x0800000016 KB20 KB与 App 共用Application0x0800400048 KB20 KB与 BL 共用RAM 由两个工程共用同一段物理内存0x20000000 起 20 KB。跳转时 Bootloader 会重新设置 MSPApp 启动后使用自己的栈互不冲突。4. 向量表偏移 VTORCortex-M 内核上电后默认从 0x08000000 读取向量表栈顶指针 SP 与复位向量 PC。App 链接在 0x08004000必须在启动早期将 VTOR 指向 App 起始地址否则所有中断仍会跳转到 Bootloader 区域的向量表导致运行异常。4.1 在 SystemInit 中设置/* system_stm32f1xx.c — App 工程 */#include memory_map.hvoid SystemInit(void){SCB-VTOR APP_START_ADDR;}4.2 或在 main() 最开始设置int main(void){SCB-VTOR APP_START_ADDR;__enable_irq();/* ... */}【说明】向量表第一个 word 为初始栈指针MSP第二个 word 为 Reset_Handler 地址。VTOR 设置后中断发生时 CPU 从 App 区域的向量表取中断服务函数地址。5. Bootloader 跳转 App从 Bootloader 跳转到 App 不能简单调用函数需要完成以下步骤关闭中断、清除 SysTick、清除 NVIC 挂起、设置 VTOR、校验栈指针合法性、设置 MSP、跳转到 App 复位向量。5.1 跳转函数 bl_jump.c/* bl_jump.h / bl_jump.c */#include memory_map.h#include stm32f1xx.hvoid jump_to_app(uint32_t app_addr){uint32_t sp *(__IO uint32_t *)app_addr;uint32_t pc *(__IO uint32_t *)(app_addr 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_addr;if ((sp 0x2FFE0000U) ! 0x20000000U) {return;}__set_MSP(sp);((void (*)(void))pc)();}5.2 跳转步骤说明步骤操作目的1读取 app_addr 处 SP 和 PC获取 App 向量表前两个 word2__disable_irq()防止跳转过程中中断干扰3关闭 SysTick清除 Bootloader 遗留的定时器状态4清除 NVIC ICER/ICPR取消所有挂起和使能的中断5SCB-VTOR app_addr将向量表指向 App 区域6校验 SP 是否在 RAM 范围防止跳转到无效 App7__set_MSP(sp)设置主栈指针8调用 pc 函数指针跳转到 App Reset_Handler5.3 Bootloader main.c#include bl_jump.h#include memory_map.hint main(void){HAL_Init();/* 可选简单校验 App 栈顶是否合法 */uint32_t sp *(__IO uint32_t *)APP_START_ADDR;if ((sp 0x2FFE0000U) 0x20000000U) {jump_to_app(APP_START_ADDR);}/* 校验失败或未烧 App进入 UART 升级模式 */uart_isp_mode();return 0;}上电流程HAL 初始化 → 检查 App 区栈顶是否合法 → 合法则跳转 App否则进入 ISP 升级模式等待接收固件。6. 完整工程示例6.1 目录结构stm32-dual-bootloader/├── Common/│ ├── Inc/memory_map.h│ └── Src/bl_jump.c├── Bootloader/│ ├── Core/Src/main.c│ ├── STM32F103C8Tx_BL.ld│ └── Drivers/ /* HALCubeMX 生成 */├── Application/│ ├── Core/Src/main.c│ ├── STM32F103C8Tx_APP.ld│ └── Drivers/└── README.md目录/文件说明Common/memory_map.hBootloader 与 App 共用的地址宏定义Common/bl_jump.c跳转 App 的公共实现Bootloader/16 KB 区工程上电入口含 ISP 逻辑Application/48 KB 区工程业务固件*_BL.ld / *_APP.ld各自独立的链接脚本6.2 Application main.c#include main.h#include memory_map.hint main(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_USART1_UART_Init();char msg[] Hello from APP!\r\n;HAL_UART_Transmit(huart1, (uint8_t *)msg, sizeof(msg) - 1, 100);while (1) {HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);HAL_Delay(500);}}6.3 Bootloader ISP 框架UART 接收示意void uart_isp_mode(void){uart_init(115200);uart_print(Bootloader ISP Mode\r\n);while (1) {if (receive_firmware_to_flash(APP_START_ADDR, APP_FLASH_SIZE) 0) {uart_print(Update OK, rebooting...\r\n);HAL_Delay(100);jump_to_app(APP_START_ADDR);}}}receive_firmware_to_flash() 需实现协议解析、Flash 擦写与校验逻辑属于 ISP/OTA 的具体实现本文仅给出框架示意。7. 编译与烧录7.1 编译顺序分别编译两个工程各自使用对应的链接脚本# Bootloaderarm-none-eabi-gcc ... -T STM32F103C8Tx_BL.ld -o bootloader.elf# Applicationarm-none-eabi-gcc ... -T STM32F103C8Tx_APP.ld -o application.elf7.2 烧录顺序必须先烧录 Bootloader再烧录 App。两者地址不同可分开烧录# ST-Link / OpenOCD — 烧录 Bootloaderopenocd -f interface/stlink.cfg -f target/stm32f1x.cfg \-c program bootloader.elf verify reset exit# 烧录 Application 到 0x08004000openocd ... \-c program application.bin 0x08004000 verify reset exit【注意】烧录 App 时必须指定起始地址 0x08004000若误烧到 0x08000000 将覆盖 Bootloader。7.3 验证方法复位后 LED 闪烁App 运行→ 跳转成功用调试器查看 0x08004000前两 word 应为合法 SP 与 Reset_Handler若出现 HardFault检查 SCB-VTOR 与链接脚本 ORIGIN 是否一致串口输出 Hello from APP! → App 正常初始化8. 常见问题问题原因解决方案App HardFaultVTOR 未设置App 内设置 SCB-VTOR APP_START_ADDR跳转后死机SP 不在 RAM 范围检查 App 链接脚本与栈配置中断不触发VTOR 仍指向 Bootloader 区确认 App 的 SystemInit 已设置 VTOR烧 App 后 Bootloader 没了烧录地址错误App 必须烧到 0x08004000OTA 断电变砖直接覆盖 App 区见进阶篇 A/B 分区方案跳转后外设异常Bootloader 未清除外设状态跳转前关闭 SysTick、清除 NVICApp 能跑但中断错乱VTOR 与链接地址不一致核对 memory_map.h 与 .ld 文件总结双区 Bootloader 方案核心三步分区Bootloader 与 App 使用不同 Flash 起始地址16 KB 48 KB链接各自 .ld 文件 ORIGIN 与 memory_map.h 保持一致跳转Bootloader 清中断、设 VTOR、改 MSP/PC 进入 App该方案适合 Flash 较小、升级需求简单的项目。若需要安全 OTA 与断电回滚可采用进阶篇的三区 A/B 分区方案。