3. 存储器与分区基础

双分区 Bootloader 的可靠性,首先取决于对 MCU 存储器物理特性的准确理解。Flash 与 RAM 在读写粒度、擦除寿命、访问速度上差异巨大,而分区布局、链接脚本、向量表重定位又必须与这些物理约束严格对齐。本节从存储器特性出发,逐步推导到双分区布局的设计原则。

一、Flash 与 RAM 的核心特性对比

Flash 属于非易失存储器,断电后数据保留,适合存放 Bootloader、应用程序和标定参数。它的关键约束是:写入只能把位从 1 变为 0,擦除才能把位恢复为 1,因此写前必须擦除,且擦除以扇区为单位。RAM 属于易失存储器,可任意字节读写,速度快,但断电丢失,适合存放运行时变量、栈、堆和临时缓冲区。

特性FlashRAM
易失性非易失易失
读粒度字节/字字节/字
写粒度字/页(如 8/16/32 字节)字节
擦除粒度扇区(如 2KB/16KB/128KB)无需擦除
擦写寿命约 1 万~10 万次无限次
典型用途Bootloader、App、参数栈、堆、变量、缓存

工程上必须牢记:Flash 擦除次数有限,升级过程中若频繁擦写同一扇区,会加速磨损。因此双分区设计要尽量让 A/B 分区轮流承担升级写入,避免单区被反复擦写。

二、扇区结构与地址映射

不同 MCU 的 Flash 扇区大小并不均匀。以常见 ARM Cortex-M 为例,低地址区往往是小扇区(如 16KB),高地址区是大扇区(如 64KB/128KB)。Bootloader 通常放在起始小扇区,App 放在后续大扇区。地址映射关系必须在链接脚本和分区表中同时体现,否则会出现“代码链接地址与实际烧录地址不一致”的致命错误。

/* 典型 Flash 扇区分布(示意) */
Sector 0 : 0x0800_0000 - 0x0800_3FFF   16 KB   Bootloader
Sector 1 : 0x0800_4000 - 0x0800_7FFF   16 KB   Bootloader 备份/参数
Sector 2 : 0x0800_8000 - 0x0800_BFFF   16 KB   Slot A 头部
Sector 3 : 0x0800_C000 - 0x0801_FFFF   80 KB   Slot A 主体
Sector 4 : 0x0802_0000 - 0x0803_FFFF  128 KB   Slot B 主体
Sector 5 : 0x0804_0000 - 0x0804_0FFF    4 KB   升级标志/元数据

地址映射的本质是:链接地址 = 运行地址 = 烧录地址。对于需要重定位的 App,链接脚本中的 ORIGIN 必须等于该分区在 Flash 中的实际起始地址。若 App 需要复制到 RAM 运行,则还要区分 LMA(加载地址)与 VMA(运行地址)。

三、链接脚本与向量表重定位

链接脚本决定各段(.text、.data、.bss)的放置位置。双分区场景下,Slot A 与 Slot B 通常使用两份链接脚本,或通过宏切换基地址。向量表重定位则是 App 跳转后的第一要务:Cortex-M 的向量表默认位于 0x0000_0000 或 0x0800_0000,App 运行前必须把 SCB->VTOR 设置为自身向量表基地址。

/* App 启动时重定位向量表 */
#define APP_BASE   0x08008000UL
#define APP_VTOR   (APP_BASE + 0x0000UL)

void app_relocate_vector(void)
{
    SCB->VTOR = APP_VTOR;   /* 指向 App 向量表 */
    __DSB();
    __ISB();
}
误区:只修改链接脚本却不重定位 VTOR。结果是中断仍然跳转到 Bootloader 的向量表,App 的中断服务函数永远不会被执行,表现为“程序能跑但中断失效”。

四、双分区布局设计原则

双分区布局不是简单地把 Flash 一分为二,而要综合考虑升级原子性、回滚能力、元数据独立性和扇区对齐。推荐遵循以下原则:

  • Bootloader 独立且受保护:放在起始扇区,升级过程中绝不擦除,必要时保留备份区。
  • A/B 分区等大或按需分配:若 App 体积接近,等大最简;若差异大,可按最大版本预留,避免升级失败。
  • 元数据单独扇区:升级标志、版本号、CRC、有效标志放在独立扇区,避免与 App 擦除冲突。
  • 扇区边界对齐:每个分区起始地址必须是扇区起始地址,否则擦除会误伤相邻分区。
  • 预留 RAM 共享区:Bootloader 与 App 通过固定 RAM 地址传递升级请求、跳转参数。
设计口诀:Bootloader 不动、A/B 轮换、元数据独立、地址对齐、向量重定位。五条同时满足,双分区才具备可恢复性。

五、典型场景与复盘

某项目将 App 链接到 0x0800_8000,但分区表中 Slot A 实际从 0x0800_C000 开始,导致烧录后程序跑飞。复盘发现:链接脚本与分区表由不同工程师维护,缺少统一地址定义头文件。改进方案是建立唯一的 partition.h,链接脚本通过 INCLUDE 或预处理宏引用同一组地址,从源头消除不一致。

另一个常见问题是升级时擦除了元数据扇区,导致升级标志丢失、Bootloader 无法判断该跳转哪个分区。正确做法是把元数据放在独立扇区,并在擦除 App 前先读取并缓存元数据,写入完成后再更新。

双分区 Flash 布局与向量表重定位示意 Flash 地址空间 Bootloader 0x0800_0000 ~ 0x0800_3FFF 元数据扇区 升级标志 / 版本 / CRC Slot A(运行区) 0x0800_8000 起 向量表 + .text + .data VTOR = 0x0800_8000 Slot B(升级区) 0x0802_0000 起 接收新固件 校验后切换 ① 上电:Bootloader 读取元数据 判断有效分区与升级请求 ② 跳转前:设置 VTOR 与 MSP SCB->VTOR = 分区基地址 ③ 升级:擦除 Slot B 并写入新固件 写前擦除,按扇区对齐 ④ 校验通过:更新元数据并切换 下次启动跳转新分区 关键约束:分区起始地址必须扇区对齐;VTOR 必须指向当前运行分区;元数据独立于 App 扇区

综上,存储器与分区基础是双分区 Bootloader 的地基。只有把 Flash/RAM 特性、扇区结构、地址映射、链接脚本、向量表重定位和布局原则统一到同一套地址定义中,后续的升级流程与恢复机制才有可靠的物理支撑。