Bootloader、App 和升级分区之间是什么关系?

Bootloader、App 和升级分区之间是什么关系?

在嵌入式设备中,固件升级通常不是简单地"把新程序写进去"。

一个完整的升级系统,通常包含三个重要部分:

  • Bootloader:启动程序
  • App:业务应用程序
  • 升级分区:临时存放新固件的区域

它们共同完成设备的启动、运行、升级和回滚。

一句话概括:

Bootloader 负责选择和启动,App 负责实现功能,升级分区负责暂存新固件并保障升级安全。


一、设备上电后,谁先运行?

设备上电或复位后,CPU 通常会从固定地址开始执行代码,这个地址一般指向 Bootloader。

典型启动流程如下:

text 复制代码
设备上电
   ↓
Bootloader 启动
   ↓
初始化时钟、RAM、看门狗等硬件
   ↓
检查升级标志
   ↓
校验 App 固件
   ↓
决定启动旧 App、新 App 或升级模式
   ↓
跳转到 App
   ↓
App 开始运行

所以,Bootloader 的运行时机早于 App。

App 并不是设备上电后第一个运行的程序,它需要等待 Bootloader 完成检查后,才能获得 CPU 的控制权。


二、Bootloader 是什么?

Bootloader 可以理解为设备中的"启动管家"或"系统引导程序"。

它通常位于 Flash 的固定区域,并且不会频繁变化。设备每次上电时,Bootloader 都会先执行。

Bootloader 的主要职责

1. 初始化基础硬件

Bootloader 一般会完成一些最基本的硬件初始化,例如:

  • 设置系统时钟
  • 初始化 SRAM
  • 配置看门狗
  • 初始化 Flash
  • 初始化串口、USB、CAN、以太网等升级接口
  • 检查按键或升级触发条件

Bootloader 不一定会初始化所有外设,它通常只初始化启动和升级所必需的硬件。


2. 检查是否需要升级

Bootloader 可以通过多种方式判断设备是否需要进入升级流程:

  • 检查 Flash 中的升级标志
  • 检查是否存在有效的新固件
  • 检查是否按下升级按键
  • 检查上位机是否发送升级命令
  • 检查 App 是否请求进入升级模式
  • 检查上一次升级是否异常中断

例如,可以在固定地址保存一个升级标志:

c 复制代码
#define UPDATE_FLAG_ADDR  0x0800F000
#define UPDATE_REQUEST    0x5AA55AA5

App 收到升级命令后,把标志写入 Flash,然后重启设备。

Bootloader 启动后读取这个标志:

c 复制代码
if (read_update_flag() == UPDATE_REQUEST)
{
    enter_upgrade_mode();
}
else
{
    jump_to_app();
}

3. 校验 App 固件

Bootloader 不能直接相信 Flash 中的程序,需要先判断固件是否完整、合法。

常见校验方式包括:

  • CRC 校验
  • MD5 校验
  • SHA-256 校验
  • 数字签名校验
  • 固件版本校验
  • 固件长度校验
  • 固件头信息校验

一个简单的固件头可能包含:

text 复制代码
固件标识
固件版本号
固件长度
固件 CRC
编译时间
硬件型号
数字签名

Bootloader 校验通过后,才会跳转到 App。

如果校验失败,就不能直接运行这份固件,否则可能出现:

  • 程序跑飞
  • 设备无法启动
  • 反复重启
  • 设备变砖

4. 跳转到 App

当 Bootloader 确认 App 有效后,就会跳转到 App 的入口地址。

跳转前一般需要完成以下操作:

  1. 关闭中断
  2. 停止 Bootloader 使用的外设
  3. 清除中断状态
  4. 设置 App 的栈顶指针
  5. 获取 App 的复位入口地址
  6. 跳转到 App 的复位处理函数

以 Cortex-M 单片机为例,App 起始地址可能是:

c 复制代码
#define APP_START_ADDR  0x08010000

跳转逻辑通常类似:

c 复制代码
typedef void (*app_entry_t)(void);

uint32_t app_stack = *(volatile uint32_t *)APP_START_ADDR;
uint32_t app_reset = *(volatile uint32_t *)(APP_START_ADDR + 4);

__set_MSP(app_stack);

app_entry_t app_entry = (app_entry_t)app_reset;
app_entry();

需要注意,实际项目中还要根据芯片型号处理中断向量表、Cache、MPU 和外设复位等问题。


三、App 是什么?

App 是设备真正执行产品功能的程序,也可以称为应用固件或业务固件。

例如:

  • 智能锁的密码识别
  • 温湿度计的数据采集
  • 电机控制器的控制算法
  • 传感器数据上传
  • 蓝牙通信
  • 屏幕显示
  • 按键处理
  • 网络通信
  • 参数存储

Bootloader 负责"启动谁",App 负责"设备具体做什么"。


四、App 固件内部通常包含什么?

一个典型的 App 固件通常由以下部分组成:

text 复制代码
+---------------------------+
| 中断向量表                |
+---------------------------+
| 复位入口                  |
+---------------------------+
| 程序代码 .text            |
+---------------------------+
| 只读常量 .rodata          |
+---------------------------+
| 已初始化数据 .data        |
+---------------------------+
| 未初始化数据 .bss         |
+---------------------------+
| 用户参数 / 配置区域       |
+---------------------------+
| 固件校验信息              |
+---------------------------+

1. 中断向量表

中断向量表中保存了:

  • 初始栈顶地址
  • 复位处理函数地址
  • 各种中断服务函数地址

Bootloader 跳转到 App 时,实际上会使用 App 自己的向量表。


2. 程序代码

程序代码包括:

  • 主函数
  • 驱动程序
  • 通信协议
  • 控制算法
  • 数据处理逻辑
  • 状态机
  • 故障处理逻辑

这些代码最终会被编译、链接,并生成 .bin.hex.elf 文件。


3. 版本信息和校验信息

为了支持可靠升级,App 固件通常会携带一些元数据:

text 复制代码
产品型号:DEVICE_A
硬件版本:V2.0
软件版本:V1.3.5
固件长度:128 KB
CRC 校验值:0x12345678

Bootloader 可以根据这些信息判断:

  • 固件是否属于当前设备
  • 固件版本是否可以升级
  • 固件是否完整
  • 固件是否被篡改

五、升级分区是什么?

升级分区是 Flash 中专门用于存放新固件的区域。

它也可以叫:

  • Download 分区
  • OTA 分区
  • Secondary 分区
  • Backup 分区
  • App B 分区
  • 暂存分区

升级分区的核心作用是:

让新固件先写入一个独立区域,验证成功后再切换运行。

这样可以避免直接覆盖当前正在运行的 App。


六、为什么不能直接覆盖当前 App?

假设设备只有一个 App 区域,升级时直接擦除并写入新固件。

如果升级过程中发生以下情况:

  • 设备突然断电
  • 通信中断
  • Flash 写入失败
  • 固件数据不完整
  • CRC 校验失败
  • 看门狗复位

那么原来的 App 可能已经被破坏,新 App 又没有写完整,设备就无法再次启动。

这就是常说的"设备变砖"。

因此,更可靠的方式是:

text 复制代码
当前 App 继续运行
        ↓
新固件下载到升级分区
        ↓
完整校验新固件
        ↓
重启设备
        ↓
Bootloader 切换到新固件

七、典型的 Flash 分区方式

一种常见的分区结构如下:

text 复制代码
Flash 起始地址
+---------------------------+
| Bootloader                |
+---------------------------+
| App A:当前运行版本       |
+---------------------------+
| App B:升级分区           |
+---------------------------+
| 参数区 / 标志位 / 日志区   |
+---------------------------+
Flash 结束地址

例如:

text 复制代码
0x08000000 - 0x0800FFFF:Bootloader
0x08010000 - 0x0807FFFF:App A
0x08080000 - 0x080EFFFF:App B
0x080F0000 - 0x080FFFFF:参数和升级标志

具体地址需要根据以下因素进行设计:

  • 芯片 Flash 容量
  • Bootloader 大小
  • App 固件大小
  • 是否采用双分区
  • 是否需要保存参数
  • 是否需要加密和签名
  • 是否需要保存升级日志

八、一次完整升级流程

下面以 App A 正在运行、App B 作为升级分区为例。

第一步:App 请求升级

用户通过手机、上位机或云端发起升级。

App 收到升级命令后,进入升级准备状态:

text 复制代码
停止非必要任务
保存设备参数
设置升级标志
重启设备

第二步:Bootloader 进入升级模式

设备重启后,Bootloader 读取升级标志,判断需要进入升级流程。

此时 Bootloader 可能通过以下接口接收固件:

  • UART
  • USB
  • CAN
  • RS485
  • Ethernet
  • Wi-Fi
  • BLE
  • 4G

第三步:新固件写入升级分区

Bootloader 将接收到的新固件写入 App B 区域。

写入过程中需要注意:

  • 按 Flash 擦除粒度擦除
  • 按芯片要求对齐写入
  • 进行分包传输
  • 对每个数据包校验
  • 记录当前写入偏移
  • 处理断点续传
  • 防止越界写入

第四步:校验新固件

写入完成后,Bootloader 对 App B 进行完整校验:

text 复制代码
固件长度是否正确?
固件 CRC 是否正确?
产品型号是否匹配?
硬件版本是否兼容?
数字签名是否有效?
版本号是否允许升级?

只有全部通过,才允许切换。


第五步:设置启动标志

校验通过后,Bootloader 或升级程序会设置启动标志:

text 复制代码
BOOT_TARGET = APP_B
IMAGE_STATE = PENDING

然后重启设备。


第六步:试运行新 App

Bootloader 再次启动后,发现 App B 处于待确认状态,于是尝试启动 App B。

新 App 启动后,需要完成自检,例如:

  • 外设初始化成功
  • 参数读取成功
  • 通信正常
  • 关键任务正常运行
  • 看门狗没有异常复位

如果自检成功,App B 将状态修改为:

text 复制代码
IMAGE_STATE = CONFIRMED

此时 App B 成为正式运行版本。


第七步:升级失败时自动回滚

如果 App B 启动失败,或者在规定时间内没有完成确认,Bootloader 会认为升级失败。

然后执行回滚:

text 复制代码
启动 App A
清除 App B 的待确认标志
记录升级失败原因

这样设备仍然可以使用旧版本运行。


九、A/B 分区和单分区有什么区别?

单分区升级

text 复制代码
Bootloader + App

升级时直接覆盖原来的 App。

优点:

  • 占用空间小
  • 设计简单
  • 成本较低

缺点:

  • 断电容易变砖
  • 回滚困难
  • 对升级流程要求高

A/B 双分区升级

text 复制代码
Bootloader + App A + App B

一个分区运行,另一个分区接收升级包。

优点:

  • 升级过程更安全
  • 支持快速回滚
  • 可以保留旧版本
  • 适合 OTA 和无人值守设备

缺点:

  • 需要更大的 Flash
  • 分区管理更复杂
  • 需要设计版本和状态管理

对于工业设备、智能家居、车载设备和远程 OTA 产品,A/B 分区通常更加可靠。


十、三者之间的关系

可以用下面这句话理解:

text 复制代码
Bootloader:负责启动和选择
App:负责设备功能
升级分区:负责暂存和保护

它们的关系如下:

text 复制代码
设备上电
   ↓
Bootloader 先运行
   ↓
检查 App 和升级分区
   ↓
选择有效固件
   ↓
跳转到 App
   ↓
App 执行业务功能
   ↓
需要升级时,将新固件写入升级分区
   ↓
Bootloader 校验并切换版本

十一、开发中最容易踩的坑

1. Bootloader 和 App 的地址不一致

链接脚本、烧录地址和跳转地址必须保持一致。

例如 App 实际链接地址是:

text 复制代码
0x08010000

但 Bootloader 却跳转到了:

text 复制代码
0x08000000

就会导致程序无法正常启动。


2. 忘记修改 App 的中断向量表

App 不再位于 Flash 起始地址后,需要重新设置向量表偏移。

Cortex-M 中通常需要配置:

c 复制代码
SCB->VTOR = APP_START_ADDR;

否则中断可能跳转到错误地址。


3. 跳转前没有关闭中断和外设

Bootloader 使用过的中断、定时器或串口,如果没有清理,可能干扰 App 的正常运行。


4. 没有做固件完整性校验

只判断"有没有数据"是不够的,必须校验:

  • 长度
  • CRC
  • 版本
  • 型号
  • 签名

5. 升级标志没有设计状态机

建议至少区分以下状态:

text 复制代码
EMPTY       空分区
DOWNLOADING 下载中
DOWNLOADED  下载完成
VERIFIED    校验通过
PENDING     待启动确认
CONFIRMED   启动成功
ROLLBACK    需要回滚

如果只使用一个简单标志位,遇到断电或异常复位时,很难判断当前处于什么状态。


十二、总结

Bootloader、App 和升级分区并不是三个互相独立的程序区域,而是一套完整的启动和升级体系。

  • Bootloader:设备上电后首先运行,负责初始化、校验、升级和跳转。
  • App:实现设备的核心业务功能,是用户真正使用的程序。
  • 升级分区:存放待升级的新固件,避免直接破坏当前运行版本。
  • A/B 分区:通过保留新旧两份固件,实现安全升级和失败回滚。

最终可以记住这句话:

Bootloader 是门卫,App 是业务主体,升级分区是新版本的候场区,也是设备升级失败时的安全保障。

相关推荐
LCG元2 小时前
STM32F103 NTC 热敏电阻测温实战:分压网络设计、Steinhart-Hart 标定与 ADC 采样优化实测
stm32·单片机·嵌入式硬件
怀民民民3 小时前
嵌入式软件学习路线与项目实践记录:STM32、FreeRTOS、Linux 与 OpenMV
stm32·freertos·嵌入式软件·嵌入式linux·openmv
辰域电子4 小时前
STM32项目开源:实验室消防预警控制系统(代码 + 原理图 + 仿真)
stm32·单片机·嵌入式硬件·开源·proteus
ysu_031416 小时前
TinyML 模型部署:从 Edge Impulse 到 STM32F103
驱动开发·stm32·嵌入式硬件·mcu·硬件架构·硬件工程·dsp开发
糖糖单片机设计1 天前
基于STM32的电子密码锁设计与实现(矩阵键盘+防拆检测+GSM远程报警)
人工智能·stm32·单片机·嵌入式硬件
充哥单片机设计1 天前
【STM32开源项目】智能家电控制系统
stm32·单片机·嵌入式硬件·毕业设计·智能家电控制·家电控制
玄芯散人1 天前
【筑基·057】Git代码时光机:版本控制入门到分支管理
git·版本控制·嵌入式开发
捷瑞电子工坊1 天前
嵌入式蓝桥杯从零点亮第一个LED
stm32·蓝桥杯·cubemx·嵌入式·led·ll·锁存
新晨单片机设计1 天前
S004R-基于STM32单片机智能门禁系统(密码、指纹、人脸、刷卡、蓝牙)【Proteus仿真+Keil程序+原理图】
stm32·单片机·嵌入式硬件·proteus