2、基础知识:单片机存储结构(Flash、RAM、ROM)、地址映射、启动流程与复位向量
Bootloader 的本质是一段在用户应用程序之前运行的固化代码,它的职责是接收外部数据(串口、CAN、USB、网络等),将其写入单片机内部的非易失性存储区,并完成跳转。要真正理解 Bootloader 的工作机制,首先必须透彻理解单片机的存储结构、地址映射规则以及上电后的启动流程。这三者构成了 Bootloader 设计的物理基础和逻辑起点。如果对存储映射理解不透彻,轻则导致跳转失败,重则直接损坏芯片的 Flash 内容,造成不可逆的后果。本章将围绕这三个核心主题展开,为后续的 Bootloader 分区规划、中断向量重映射和固件搬运打下坚实的理论基础。
上图从宏观上展示了单片机存储结构、地址映射与启动流程之间的关系。在物理层面,Flash 和 RAM 是 Bootloader 最关注的两个存储介质;在逻辑层面,地址映射决定了 CPU 如何访问这些介质;在时间维度上,启动流程决定了 CPU 从哪条指令开始执行。下面我们逐一深入剖析。
2.1 存储结构:Flash、RAM 与 ROM 的分工与特性
核心原理:单片机内部通常集成三种主要的存储介质,它们在掉电保持、读写速度和访问方式上有着本质区别。理解这些区别是进行 Bootloader 分区设计的前提。
- Flash(闪存):非易失性存储器,用于存放固件代码和常量数据。其特点是按扇区擦除、按字节/字编程,擦除操作耗时较长(通常为 10ms~1s 级别),且擦写寿命有限(通常 1万~10万次)。Bootloader 的核心工作就是操作 Flash。
- RAM(随机存取存储器):易失性存储器,用于存放变量、堆栈和动态数据。读写速度极快,但掉电数据丢失。在 Bootloader 中,RAM 通常作为接收数据的缓冲区,以及固件搬运时的临时存储区。
- ROM/OTP(只读存储器/一次性可编程):部分芯片(如 STM32 的系统存储区)包含出厂固化的 ROM,用户无法修改。这类区域通常存放芯片厂商提供的 ISP 引导程序,用于串口下载。
| 存储类型 | 掉电保持 | 写入方式 | 擦除单位 | 典型用途 | Bootloader 关注点 |
|---|---|---|---|---|---|
| Flash | 是 | 按字/字节编程 | 按扇区/页 | 代码、常量、固件 | 写入目标、擦除风险 |
| RAM | 否 | 直接读写 | 无 | 变量、栈、缓冲区 | 接收缓冲、临时存储 |
| ROM/OTP | 是 | 出厂固化 | 不可擦除 | ISP 引导、安全代码 | 可作为 Bootloader 来源 |
2.2 地址映射:CPU 视角下的存储访问
核心原理:CPU 并不直接关心数据存储在 Flash 还是 RAM 中,它只通过统一的地址总线访问存储空间。地址映射就是芯片设计者将物理存储介质映射到特定地址范围的过程。以 ARM Cortex-M 内核为例,其地址空间被划分为多个区域:0x00000000 起始的代码区、0x20000000 起始的 SRAM 区、0x40000000 起始的外设区等。
判断方法:在编写 Bootloader 时,需要明确以下关键地址:
- Flash 基地址:例如 STM32F103 的 Flash 起始地址为 0x08000000,而 Cortex-M 内核默认从 0x00000000 取向量表。芯片内部通过“别名映射”机制,将 0x08000000 映射到 0x00000000(取决于 BOOT 引脚配置)。
- RAM 基地址:例如 0x20000000,用于存放中断向量表的备份(可选)和启动时的栈顶指针。
- 外设寄存器地址:例如 RCC、GPIO、USART 等控制寄存器,Bootloader 需要通过这些寄存器初始化外设。
// 以 STM32F103 为例,查看链接脚本中的内存布局
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
// 注意:应用程序的链接脚本中,FLASH 的 ORIGIN 必须修改为 Bootloader 之后的位置,
// 例如 0x08004000,同时需要设置 VTOR 寄存器指向新的向量表地址。
FLASH ORIGIN 和 VTOR 设置。
2.3 启动流程与复位向量:CPU 的第一口“饭”
核心原理:Cortex-M 内核的启动流程是固定的:CPU 上电复位后,硬件自动从地址 0x00000000 读取初始栈顶指针(MSP),从地址 0x00000004 读取复位向量(Reset_Handler 的地址),然后跳转到该地址执行。这个“双字读取”机制是理解 Bootloader 跳转的关键。
操作流程:Bootloader 跳转到 App 时,需要模拟一次“软复位”过程:
- 关闭全局中断,确保跳转过程中不被中断打断。
- 从 App 向量表地址(例如 0x08004000)读取新的 MSP 值。
- 从 App 向量表地址 + 4 读取新的 Reset_Handler 地址。
- 设置 VTOR 寄存器指向新的向量表基地址(0x08004000)。
- 将新的 MSP 值写入主堆栈指针寄存器。
- 将新的 Reset_Handler 地址写入 PC 寄存器,完成跳转。
/* Bootloader 跳转代码示例 */
typedef void (*pFunction)(void);
void jump_to_app(uint32_t app_addr)
{
uint32_t msp_value = *(volatile uint32_t *)app_addr;
uint32_t reset_handler = *(volatile uint32_t *)(app_addr + 4);
__disable_irq(); // 关闭全局中断
SCB->VTOR = app_addr; // 设置向量表偏移
__set_MSP(msp_value); // 设置栈顶指针
pFunction jump = (pFunction)reset_handler;
jump(); // 跳转执行
}
FLASH ORIGIN 为 0x08010000,并在 App 的 SystemInit 函数中设置 SCB->VTOR = 0x08010000。此案例说明,地址映射和向量表偏移必须同时修改,缺一不可。
2.4 常见误区与总结复盘
误区一:混淆 ROM 与 Flash。在 Bootloader 语境下,ROM 通常指不可修改的系统存储区,而 Flash 才是用户可操作的存储介质。不要把“烧录到 ROM”理解为写入 Flash。
误区二:忽略 Flash 擦写寿命。Bootloader 频繁擦写 Flash 会加速芯片老化。设计时应加入擦写次数统计和磨损均衡策略,尤其是在存储参数/日志的场景下。
误区三:跳转前未正确设置 VTOR。对于 Cortex-M3/M4/M7 内核,如果 App 的向量表不在 0x00000000,必须显式设置 VTOR 寄存器。部分开发者只修改了链接脚本,却忘记在 App 启动代码中设置 VTOR,导致中断无法响应。
误区四:忽视 RAM 的初始化。Bootloader 跳转前,App 的启动代码会重新初始化 RAM(清零 ZI 段、加载 RW 段)。如果 Bootloader 使用了某些外设中断且未关闭,跳转后可能触发意外中断,导致 App 初始化失败。
| 检查项 | Bootloader 侧 | App 侧 |
|---|---|---|
| Flash 起始地址 | 固定为芯片起始地址 | 必须偏移(如 +0x10000) |
| 向量表 | 位于 Flash 起始处 | 位于 App 起始处,需设置 VTOR |
| 中断使能 | 跳转前全部关闭 | 重新初始化所需中断 |
| RAM 使用 | 避免使用 App 将用到的 RAM 区域(如栈) | 重新初始化栈和堆 |