1. BootLoader 是什么
BootLoader 可以理解为:
一段先于 App 运行的小程序,用来负责引导、升级、校验和跳转。
在 STM32 里,设备上电后,MCU 会从 Flash 的起始地址开始执行代码。
如果工程采用 BootLoader + App 架构,那么上电后的执行顺序通常是:
text
上电复位
↓
先运行 BootLoader
↓
BootLoader 判断是否升级 / 是否可跳转
↓
满足条件后跳转到 App
↓
App 开始运行业务功能
所以:
- BootLoader:负责"管理启动和升级"
- App:负责"真正业务功能"
2. 为什么需要 BootLoader
如果没有 BootLoader,程序通常就是:
text
上电 → 直接进入 App
这种方式简单,但有一个明显问题:
后期升级不方便,也不安全。
比如想实现:
- 串口升级
- CAN 升级
- 网口升级
- USB 升级
- 远程升级
这时候就需要有一段独立的小程序,专门负责"接收新固件并写入 Flash",这段程序就是 BootLoader。
BootLoader 的主要作用
- 决定设备启动流程
- 接收升级文件
- 校验升级文件是否正确
- 擦除旧 App
- 写入新 App
- 确认新 App 可运行
- 跳转到 App
一句话总结:
BootLoader 的价值在于让设备具备可升级能力,并且尽量保证升级过程安全可靠。
3. BootLoader 和 App 的关系
可以把它们理解成"前台调度员"和"正式业务程序"的关系。
3.1 BootLoader 的特点
- 代码量通常较小
- 放在 Flash 起始位置
- 上电最先运行
- 负责升级和跳转
- 一般不承载复杂业务逻辑
3.2 App 的特点
- 代码量较大
- 放在 BootLoader 后面的 Flash 区域
- 负责正常业务功能
- 由 BootLoader 跳转进入
3.3 常见 Flash 分区示意
假设 STM32 的 Flash 这样分:
text
0x08000000 ~ 0x08003FFF BootLoader
0x08004000 ~ 0x0801FFFF App
那么:
- BootLoader 起始地址:
0x08000000 - App 起始地址:
0x08004000
这就是典型的 BootLoader + App 分区结构。
4. BootLoader 的核心工作流程
BootLoader 不只是"跳转一下"这么简单,它一般包括这几个关键环节:
- 升级
- 校验
- 擦写
- 跳转
整体流程如下:
text
上电复位
↓
进入 BootLoader
↓
基础初始化
↓
检查是否需要升级
├─ 否:检查 App 是否有效 → 跳转 App
└─ 是:进入升级流程
↓
接收升级包
↓
校验升级包
↓
擦除 App 区域
↓
写入新的 App
↓
校验写入结果
↓
更新状态信息
↓
跳转 App
5. 升级、校验、擦写、跳转分别是什么
5.1 升级
升级的本质就是:
把新的 App 程序替换掉旧的 App 程序。
升级数据可能来自:
- 串口
- CAN
- USB
- 以太网
- 无线通信模块
- SD 卡
BootLoader 在升级阶段通常做的事有:
- 等待升级命令
- 接收固件数据
- 保存数据长度、版本号等信息
- 准备进入 Flash 擦写阶段
初学者要抓住的重点
BootLoader 自己通常不升级自己,主要升级的是 App 。
因为如果让程序一边运行一边把自己擦掉,风险很高。
5.2 校验
校验就是:
确认数据是对的、完整的、可运行的。
为什么要校验?
因为升级过程中可能发生:
- 丢包
- 数据损坏
- 接收不完整
- 写 Flash 异常
- 固件不匹配当前设备
常见校验内容
1)长度是否合法
比如固件大小不能超过 App 分区大小。
2)CRC 是否正确
发送端和接收端对同一份数据计算 CRC,判断是否一致。
3)固件头信息是否合法
比如:
- 版本号
- 目标芯片型号
- 固件长度
- 校验码
- 下载标志
4)App 向量表是否合法
比如检查 App 首地址里的前两个字:
- 第一个字:初始栈顶地址 MSP
- 第二个字:复位中断入口 Reset_Handler
如果这两个值明显不合理,说明 App 可能无效,不能跳。
初学者要抓住的重点
校验不是可有可无,而是 BootLoader 安全性的关键。
5.3 擦写
"擦写"是 Flash 操作里的核心动作。
Flash 和 RAM 不一样。
RAM 可以直接改,Flash 一般要遵循:
- 先擦除
- 再写入
为什么不能直接覆盖写?
因为 Flash 的硬件特性决定了:
- 某些位只能从 1 写成 0
- 要恢复成 1,需要先擦除整个页/扇区
所以升级新 App 时通常步骤是:
text
先擦除 App 所在的 Flash 区域
再按顺序写入新的固件数据
擦写的典型过程
第一步:解锁 Flash
允许程序进行 Flash 操作。
第二步:擦除 App 区域
比如从 0x08004000 开始,把原来的 App 区域擦掉。
第三步:按单位写入
可能按半字、字、双字等方式写入,具体和芯片有关。
第四步:写完后锁定 Flash
防止误操作。
初学者要抓住的重点
擦写是升级的执行手段,但也是高风险操作。
一旦擦掉旧 App,新 App 又没写完整,设备就可能无法正常运行。
所以擦写前后通常要配合校验和状态管理。
5.4 跳转
跳转是 BootLoader 的最后一步,也是你已经接触最多的一步。
它的目标是:
把 CPU 的执行权,从 BootLoader 交给 App。
但这里不是普通函数调用,而是"让 App 像独立程序一样开始运行"。
跳转前为什么要做准备
因为 BootLoader 已经运行过一段时间了,它可能已经配置了:
- 时钟
- SysTick
- 中断
- 串口
- 定时器
- DMA
- NVIC
而 App 希望拿到的是一个"尽量干净"的运行环境。
所以跳转前一般要做:
- 关闭全局中断
- 清除中断使能和挂起状态
- 关闭或恢复不需要的外设
- 关闭 SysTick
- 设置向量表偏移(如果需要)
- 设置 MSP
- 读取 App 的复位入口地址
- 跳转到 App 的入口函数
简化理解
App 起始地址处通常存着两个关键值:
text
App起始地址 + 0x00 : 初始 MSP
App起始地址 + 0x04 : Reset_Handler 地址
BootLoader 跳转本质上就是:
- 先把 MSP 设置为 App 的栈顶
- 再跳到 App 的 Reset_Handler
6. STM32 中 BootLoader 跳转的关键点
因为它和实际调试最相关。
6.1 MSP 是什么
MSP = Main Stack Pointer,主栈指针。
可以把它理解成:
CPU 当前使用的"主堆栈顶部位置"。
程序运行时,函数调用、局部变量、中断现场保护等,都离不开栈。
如果跳转到 App 前没有把 MSP 设置成 App 自己的栈地址,App 很可能一运行就异常。
所以 BootLoader 跳转前必须做:
c
__set_MSP(*(uint32_t *)APP_ADDR);
意思是:
把 App 首地址存放的那个栈顶值,装载到 MSP 里。
6.2 向量表是什么
向量表本质上是一张"中断入口地址表"。
在 Cortex-M 里,程序起始位置一般会放:
- 初始栈顶地址
- Reset_Handler
- NMI_Handler
- HardFault_Handler
- 各种中断入口地址
当程序切换到 App 后,如果仍然使用 BootLoader 的中断向量表,就可能导致:
- 中断进错函数
- 程序跑飞
- 进入异常
所以通常需要把向量表基地址改到 App 起始地址:
c
SCB->VTOR = APP_ADDR;
这一步的作用是:
告诉 CPU:以后中断入口去 App 的向量表里找。
6.3 为什么要清 NVIC
NVIC 是中断控制器。
BootLoader 运行期间,可能已经打开了一些中断。
如果跳转前不清掉,可能会发生:
- App 刚启动,中断突然进来
- 进来的还是 Boot 阶段遗留的中断
- App 还没初始化完成就被打断
- 最终死机或跑飞
所以跳转前常见做法是:
- 关闭所有中断使能
- 清掉所有挂起中断
这样是为了给 App 一个相对干净的中断环境。
6.4 为什么 SysTick 会影响跳转
SysTick 是系统滴答定时器,很多 HAL 函数依赖它,比如 HAL_Delay()。
如果 BootLoader 运行时启用了 SysTick,但跳转前没有关闭或正确恢复,可能会导致:
- App 刚启动就进入 SysTick 中断
- 中断入口不对
- 时基混乱
- 程序卡死或异常
这是初学者常会遇到问题的关键点之一:
App 虽然有自己的 SysTick 配置,但跳转瞬间如果系统环境没收拾干净,仍然会出问题。
7. 一个最小 BootLoader 逻辑怎么理解
下面是一个"逻辑版"的最小 BootLoader:
text
BootLoader main()
{
1. 初始化最基本硬件
2. 判断是否收到升级请求
3. 如果需要升级:
- 接收新固件
- 校验固件
- 擦除 App 区域
- 写入新固件
- 校验写入结果
4. 检查 App 是否有效
5. 如果有效,跳转到 App
6. 如果无效,则停留在 BootLoader 等待处理
}
这个结构已经能概括大多数 BootLoader 的骨架了。
8. 常见异常场景
做 BootLoader,不能只看正常流程,还要考虑异常。
8.1 升级包损坏
现象:
- 接收到的数据不完整
- CRC 不对
- 固件头异常
处理:
- 不写入 Flash
- 提示升级失败
- 保持原 App 不动
8.2 Flash 擦除到一半断电
现象:
- 原来的 App 已经部分被擦掉
- 新 App 还没写完整
后果:
- 设备重启后 App 无法运行
处理思路:
- BootLoader 上电后先检查 App 是否有效
- 如果无效,不跳转 App
- 留在升级模式等待重新下载
8.3 写入成功但跳转失败
可能原因:
- MSP 没设
- VTOR 没改
- 中断没清
- SysTick 没关
- App 工程链接地址不对
- App 自身初始化有问题
处理思路:
- 先确认 App 单独烧录能否运行
- 再确认 Boot 跳转准备动作是否完整
- 最后检查 App 的链接地址、向量表和启动文件
8.4 App 无效但 Boot 误跳转
现象:
- 跳转后直接死机
原因:
- Boot 没检查 App 首地址内容是否合理
- 把空白 Flash 当成有效程序
处理思路:
至少检查:
- 栈顶地址是否落在 RAM 区间
- Reset_Handler 是否落在 Flash App 区间
9. 初学者容易混淆的点
9.1 BootLoader 不是"普通 main 函数工程"那么简单
虽然它也有 main(),但它承担的是"系统引导"和"固件管理"职责,不能只按普通业务程序思路来看。
9.2 跳转不是普通函数调用
不是:
c
App_Main();
而是:
- 改 MSP
- 改向量表
- 跳 Reset_Handler
这是"程序控制权切换"。
9.3 App 不是从 main() 直接被调用的
实际上 App 也是从它自己的启动文件开始执行,最后才进 main()。
BootLoader 跳转到的是 App 的复位入口,不是直接跳 main()。
9.4 Boot 和 App 都要各自有清晰的地址规划
如果 Boot 和 App 地址重叠,或者 App 工程链接地址没改对,就算代码没问题,也会运行异常。
10. 初学阶段应该掌握什么
第一层:概念
- BootLoader 是什么
- App 是什么
- 为什么需要 BootLoader
第二层:流程
- 升级
- 校验
- 擦写
- 跳转
- 异常处理的大致思路
第三层:STM32 落地
- App 起始地址
- MSP
- VTOR
- NVIC
- SysTick
- Flash 分区
第四层:调试经验
- App 单独烧录是否能跑
- Boot 跳转后为什么会死
- 是地址问题、向量表问题,还是中断残留问题
13. 后续建议
-
先把 Boot → App 跳转吃透
重点是:
- MSP
- VTOR
- NVIC 清理
- SysTick 处理
-
再学 Flash 擦写
- 页擦除
- 写入单位
- 读回校验
-
再学升级标志位设计
- 什么时候进入升级
- 什么时候跳 App
- 升级失败后怎么处理
-
再学通信协议升级
- 串口升级
- CAN 升级
--- 待观友继续补充