Bootloader 内存分配入门:双区划分实战(STM32)
Bootloader 内存分配 · 入门篇
系列:Bootloader 内存分配 · 入门篇
平台:STM32F103C8T6(64 KB Flash / 20 KB RAM)
下一篇:进阶篇 — 三区 A/B 分区与 OTA
本文介绍 STM32 双区 Bootloader 方案:将 Flash 划分为 Bootloader 区与 Application 区,通过链接脚本、向量表偏移和跳转逻辑实现上电后从 Bootloader 进入 App 运行。
目录
- 为什么需要 Bootloader
- Flash 双区内存模型
- 链接脚本配置
- 向量表偏移 VTOR
- Bootloader 跳转 App
- 完整工程示例
- 编译与烧录
- 常见问题
1. 为什么需要 Bootloader
Bootloader 是芯片上电后首先运行的小程序,常见职责包括:
- 通过 UART / USB / CAN 接收新固件并写入 Flash(ISP / OTA)
- 校验 App 完整性后跳转到 App 运行
- 提供出厂测试、序列号写入等能力
App 与 Bootloader 不能共用同一段 Flash,否则互相覆盖。解决方案是将 Flash 物理分区,各自链接到不同的起始地址。
2. Flash 双区内存模型
以 STM32F103C8(64 KB Flash)为例,典型划分如下:
0x0800_0000 ┌─────────────────────┐
│ Bootloader 16 KB │ 上电入口
0x0800_4000 ├─────────────────────┤
│ Application 48 KB │ 业务固件
0x0801_0000 └─────────────────────┘
2.1 统一地址定义 memory_map.h
Bootloader 与 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_ADDR |
0x08000000 |
STM32 Flash 起始地址 |
|
BL_FLASH_SIZE |
16 KB |
Bootloader 占用空间 |
|
APP_FLASH_SIZE |
48 KB |
Application 可用空间 |
|
APP_START_ADDR |
0x08004000 |
App 链接与烧录起始地址 |
|
APP_VTOR_OFFSET |
0x4000 |
App 向量表相对 Flash 基址偏移 |
3. 链接脚本配置
链接脚本决定编译器将代码和数据放置到 Flash/RAM 的哪个地址。Bootloader 与 App 必须使用不同的链接脚本。
3.1 Bootloader — STM32F103C8Tx_BL.ld
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 16K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
3.2 Application — STM32F103C8Tx_APP.ld
MEMORY
{
FLASH (rx) : ORIGIN = 0x08004000, LENGTH = 48K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
【关键】App 链接脚本中 FLASH 的 ORIGIN 必须等于 memory_map.h 中的 APP_START_ADDR,否则中断向量表与代码地址全部错位,运行后将 HardFault。
|
工程 |
FLASH ORIGIN |
FLASH LENGTH |
RAM |
|
Bootloader |
0x08000000 |
16 KB |
20 KB(与 App 共用) |
|
Application |
0x08004000 |
48 KB |
20 KB(与 BL 共用) |
RAM 由两个工程共用同一段物理内存(0x20000000 起 20 KB)。跳转时 Bootloader 会重新设置 MSP,App 启动后使用自己的栈,互不冲突。
4. 向量表偏移 VTOR
Cortex-M 内核上电后默认从 0x08000000 读取向量表(栈顶指针 SP 与复位向量 PC)。App 链接在 0x08004000,必须在启动早期将 VTOR 指向 App 起始地址,否则所有中断仍会跳转到 Bootloader 区域的向量表,导致运行异常。
4.1 在 SystemInit 中设置
/* system_stm32f1xx.c — App 工程 */
#include "memory_map.h"
void 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.h"
void 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 向量表前两个 word |
|
2 |
__disable_irq() |
防止跳转过程中中断干扰 |
|
3 |
关闭 SysTick |
清除 Bootloader 遗留的定时器状态 |
|
4 |
清除 NVIC ICER/ICPR |
取消所有挂起和使能的中断 |
|
5 |
SCB->VTOR = app_addr |
将向量表指向 App 区域 |
|
6 |
校验 SP 是否在 RAM 范围 |
防止跳转到无效 App |
|
7 |
__set_MSP(sp) |
设置主栈指针 |
|
8 |
调用 pc 函数指针 |
跳转到 App Reset_Handler |
5.3 Bootloader main.c
#include "bl_jump.h"
#include "memory_map.h"
int 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/ /* HAL,CubeMX 生成 */
├── Application/
│ ├── Core/Src/main.c
│ ├── STM32F103C8Tx_APP.ld
│ └── Drivers/
└── README.md
|
目录/文件 |
说明 |
|
Common/memory_map.h |
Bootloader 与 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.h"
int 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 编译顺序
分别编译两个工程,各自使用对应的链接脚本:
# Bootloader
arm-none-eabi-gcc ... -T STM32F103C8Tx_BL.ld -o bootloader.elf
# Application
arm-none-eabi-gcc ... -T STM32F103C8Tx_APP.ld -o application.elf
7.2 烧录顺序
必须先烧录 Bootloader,再烧录 App。两者地址不同,可分开烧录:
# ST-Link / OpenOCD — 烧录 Bootloader
openocd -f interface/stlink.cfg -f target/stm32f1x.cfg \
-c "program bootloader.elf verify reset exit"
# 烧录 Application 到 0x08004000
openocd ... \
-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 HardFault |
VTOR 未设置 |
App 内设置 SCB->VTOR = APP_START_ADDR |
|
跳转后死机 |
SP 不在 RAM 范围 |
检查 App 链接脚本与栈配置 |
|
中断不触发 |
VTOR 仍指向 Bootloader 区 |
确认 App 的 SystemInit 已设置 VTOR |
|
烧 App 后 Bootloader 没了 |
烧录地址错误 |
App 必须烧到 0x08004000 |
|
OTA 断电变砖 |
直接覆盖 App 区 |
见进阶篇 A/B 分区方案 |
|
跳转后外设异常 |
Bootloader 未清除外设状态 |
跳转前关闭 SysTick、清除 NVIC |
|
App 能跑但中断错乱 |
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 分区方案。
更多推荐

所有评论(0)