Boot Loader
目录
程序的升级方式
Boot引脚配置组合
复位
系统复位:
软件复位
低功耗管理复位
电源复位
内存分配
2) 先建立 Flash 内存模型
方案一:双区划分(Bootloader + App)
2.1 分区示意
2.2 关键配置
2.3 方案一适用场景
2.4 方案一风险与对策
方案二:三区划分(Bootloader + App1 + App2)
3.1 分区示意
3.2 升级流程(A/B 分区思想)
3.3 启动标志位
3.4 方案二适用场景
3.5 与方案一对比
实践章节:从零配置双区 Bootloader
4.1 统一内存映射头文件
4.2 编译与烧录顺序
4.3 调试技巧
常见问题 FAQ
系列扩展(可选后续文章)
程序的升级方式
ICP连线编程:(SWD,ST-Link)可以直接覆盖写入到Flash。
ISP系统内编程:通过内置的BootLoader程序,从串口(UART)、USB、CAN、I2C、SPI等接口来更新程序,不依赖烧录器。
IAP程序内编程:是指在程序运行过程中,由MCU自己控制擦除和重写内部Flash,完成固件升级。
触发特定的条件会进入 IAP Boot Loader程序 自己完成程序的升级更新,通常 IAP Boot Loader程序在flash的最开始位置优先判断当前是否需要进行更新程序
控制升级
(1)传输升级之后的程序到开发板的存储空间
(2)更改 IAP Boot Loader的升级flag
(3)重新上电 让程序执行boot loader
FLASH是按页进行擦除的
(1)小容量和中容量产品页大小都是1K 大容量产品时2K
(2)flash 起始地址都是0x0800 0000
Boot引脚配置组合
| Boot1 | Boot0 | 启动模式 | 说明 |
|---|---|---|---|
| X | 0 | 主闪存存储器 | 从内置Flash启动,正常操作模式 |
| 0 | 1 | 系统存储器 | 从系统存储器启动,用于串口/USB等ISP编程 |
| 1 | 1 | 内置SRAM | 从SRAM启动,用于调试和特殊应用 |
注:X表示"无关"(0或1都可以)
复位
系统复位:
当发生以下任一事件时,产生一个系统复位:
1. NRST引脚上的低电平(外部复位)
2. 窗口看门狗计数终止(WWDG复位)
3. 独立看门狗计数终止(IWDG复位)
4. 软件复位(SW复位)
5. 低功耗管理复位
软件复位
低功耗管理复位
电源复位
(1)上电/掉电复位(POR/PDR复位)
(2)从待机模式中返回
电源复位将复位除了备份区域外的所有寄存器。
备份域复位
烧录的文件类型
BIN文件是一种二进制文件,它包含了纯粹的二进制数据,没有任何附加格式或地址信息。
(1)如果烧录bin文件需要指定flash的地址
(2)中断向量表4字节对齐 64字节(第一个四字节是RAM起始地址——RAM起始地址+栈大小,第二个四字节是复位中断——写入flash地址+4)
(3)函数代码=>指令码
(4)常量字符串
(5)全局变量初始值
keil中配置生成bin文件:$K\ARM\ARMCC\bin\fromelf.exe --bin --output=.\OUTPUT\@L.bin !L
HEX文件是用ASCII字符表示的文本文件,它包含了程序数据以及地址和校验等附加信息。每行以冒号(:)起始,后面每两个字母是一个8bit的16进制数;
系统内boot loader
(1)通过控制boot0和boot1引脚电平位01复位进入到boot loader模式中
(2)使用串口完成数据的烧录 串口参数默认为115200 默认使用偶校验 数据位8 停止位1
(3)存储位置特殊0x1FFF F000
自定义
(1)区别:程序本身存放的位置不同 flash中 地址是起始位置0x0800 0000(程序冲突的问题:boot loader程序和执行的Application程序需要存放在不同的位置;AB区存放 B区一定要存放在flash起始的位置;假设boot loader程序是4KB≈4096 那么A区程序最小的地址可用就是0x0800 1000;考虑安全问题可以安排boot loader比较大的空间来使用16K A程序的地址0x0800 4000)
(2)功能:串口挂起等待接收程序数据=>写道对应的flash当中
复位之后一直执行boot loader,只能跳转执行到Application。
相同开发板栈大小是一样的 栈顶地址可以通用
复位中断地址=>确定的flash地址
程序在编译的时候一定要制定好专门的起始Flash位置
内存分配
1) 我们使用Bootloader的时候有以下几种方法:
方案一:
我们将flash分为两个区,分别是bootloader、app。通过更改起始地址和app程序的中断向量偏移地址将bootloader和app放在两个区域。如下图所示;
方案二:
我们将flash分为三个区,分别是bootloader、app1、app2
2) 先建立 Flash 内存模型
以常见 STM32 为例(32KB Bootloader + 剩余给 App):
┌─────────────────────────────────────┐ 0x0800_0000
│ Bootloader 区 │ 16~32 KB
├─────────────────────────────────────┤ 0x0800_8000 (示例)
│ Application 区 │ 剩余 Flash
├─────────────────────────────────────┤
│ 可选:参数/标志位区 │
└─────────────────────────────────────┘ 0x080F_FFFF
要点:
- 起始地址:链接脚本里
FLASH ORIGIN/LENGTH - 向量表偏移:
SCB->VTOR = App 起始地址(Cortex-M) - 跳转:Bootloader 校验 App 后,设置 SP、PC 并跳转
方案一:双区划分(Bootloader + App)
2.1 分区示意
Flash 存储器校验通过后跳转Bootloader0x0800_0000Application0x0800_8000
| 区域 | 起始地址 | 大小 | 作用 |
|---|---|---|---|
Bootloader |
| 32 KB | 上电首先运行,负责升级/跳转 |
App |
| 224 KB | 业务固件 |
2.2 关键配置
① Bootloader 链接脚本(示例)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 32K
RAM (xrw): ORIGIN = 0x20000000, LENGTH = 64K
}
② App 链接脚本
MEMORY
{
FLASH (rx) : ORIGIN = 0x08008000, LENGTH = 224K
RAM (xrw): ORIGIN = 0x20000000, LENGTH = 64K
}
③ App 启动时设置向量表偏移
#define APP_START_ADDR 0x08008000U
void app_init(void)
{
SCB->VTOR = APP_START_ADDR;
__enable_irq();
}
或在system_stm32xxx.c中:
#define VECT_TAB_OFFSET 0x8000U
SCB->VTOR when the app starts
④ Bootloader 跳转到 App
typedef void (*pFunction)(void);
void jump_to_app(uint32_t app_addr)
{
uint32_t sp = *(volatile uint32_t *)app_addr;
uint32_t pc = *(volatile uint32_t *)(app_addr + 4);
pFunction jump = (pFunction)pc;
__disable_irq();
SCB->VTOR = app_addr;
__set_MSP(sp);
jump();
}
2.3 方案一适用场景
- 简单 OTA,只需一个 App 槽位
- Flash 较小、成本敏感
- 升级策略:先擦 App 区再写入,失败则 Bootloader 仍可运行
2.4 方案一风险与对策
| 风险 | 对策 |
|---|---|
升级中断导致 App 损坏 | 双备份(见方案二)或外部 Flash 暂存 |
向量表未重定位 | 启动 HardFault |
地址配置不一致 | 统一维护 |
方案二:三区划分(Bootloader + App1 + App2)
3.1 分区示意
Flash 三区模型当前运行OTA 写入校验成功,切换BootloaderApp1 运行区App2 备份/升级区
典型布局(256 KB Flash 示例):
| 区域 | 地址范围 | 大小 | 角色 |
|---|---|---|---|
Bootloader |
| 32 KB | 启动与切换 |
App1 |
| 112 KB | 当前运行固件 |
App2 |
| 112 KB | 新固件下载区 |
3.2 升级流程(A/B 分区思想)
1. 下载新固件到 App22. 校验 CRC/签名3. 拷贝 App2 → App1(或切换启动标志)4. 跳转到 App1 运行OTA 服务器BootloaderApp1 运行区App2 下载区
3.3 启动标志位
在 Flash 或 EEPROM 中保存:
typedef struct {
uint32_t magic; // 0xDEADBEEF
uint8_t active_slot; // 0=App1, 1=App2
uint32_t app1_crc;
uint32_t app2_crc;
} boot_info_t;
Bootloader 逻辑:
- 读
boot_info - 校验 active slot 的 CRC
- 失败则尝试另一槽位
- 跳转至有效 App
3.4 方案二适用场景
- 需要安全 OTA,升级失败可回滚
- 产品级 IoT、汽车、工业设备
- Flash 足够大(通常 ≥ 512 KB 更从容)
3.5 与方案一对比
| 对比项 | 方案一(双区) | 方案二(三区) |
|---|---|---|
Flash 占用 | 省空间 | 多占一个 App 槽 |
升级安全性 | 中等 | 高(A/B 备份) |
实现复杂度 | 低 | 中高 |
回滚能力 | 弱 | 强 |
典型产品 | 简单 MCU 产品 | 路由器、智能设备 |
实践章节:从零配置双区 Bootloader
4.1 统一内存映射头文件
// memory_map.h
#define FLASH_BASE 0x08000000U
#define BL_SIZE (32 * 1024)
#define APP_START (FLASH_BASE + BL_SIZE)
#define APP_SIZE (224 * 1024)
#define VECT_TAB_OFFSET BL_SIZE
Bootloader 与 App 工程都#include "memory_map.h",避免地址写错。
4.2 编译与烧录顺序
- 先编译 Bootloader → 烧录到
0x0800_0000 - 再编译 App(带偏移链接脚本)→ 烧录到
0x0800_8000 - 上电:Bootloader 运行 → 跳转 App
4.3 调试技巧
- 用 J-Link / ST-Link 查看 Flash 内容
- 断点打在
jump_to_app前,检查 SP、PC - 用
readelf -l app.elf确认 Load Address
常见问题 FAQ
Q1:App 能直接烧在 0x0800_0000 吗?
不能。会与 Bootloader 冲突,且向量表地址错误。
Q2:RAM 需要分区吗?
一般 Bootloader 与 App 共用 RAM;跳转前需关闭外设、清中断,避免 Bootloader 状态影响 App。
Q3:三区时 App1、App2 链接脚本一样吗?
起始地址不同,需两套ORIGIN;或同一套模板,编译时传入不同宏。
Q4:如何实现回滚?
升级前把 App1 备份到 App2;新固件失败时 Bootloader 从 App2 恢复或切换启动槽。
系列扩展
- 《Cortex-M 向量表与 VTOR 深度解析》
- 《OTA 固件签名与 CRC 校验实战》
- 《从双区升级到三区的迁移指南》
- 《STM32 / ESP32 Bootloader 完整工程模板》
