1. 分区表基础:嵌入式系统的“地契”

各位同学,咱们今天聊点实在的。分区表这东西,说白了就是一张“地契”。它告诉芯片:你的代码该放哪儿,数据该存哪儿,启动时先跑哪段程序。我刚开始做嵌入式那会儿,觉得这玩意儿就是个配置文件,随便写写就行。后来有一次,一个字节写错,整批设备变砖……嗯,从那以后我再也不敢小看分区表了。

1.1 什么是分区表

分区表,就是一张描述存储介质(比如 Flash、EEPROM)如何划分区域的表格。它记录了每个分区的起始地址、大小、属性和用途。

你想想看,一个 16MB 的 Flash,如果不分区,你只能从头到尾顺序读写。但有了分区表,你可以把 0x000000–0x100000 划给 Bootloader,0x100000–0x800000 划给 App,剩下的给文件系统。每个分区各司其职,互不干扰。

核心概念:分区表 = 存储布局的元数据。它本身也存储在 Flash 的固定位置(通常是 0x000000 或末尾扇区),系统启动时最先被读取。

1.2 分区表在嵌入式系统中的作用

分区表的作用,我归纳为三点:

  • 地址隔离:防止 Bootloader 写坏 App 区,或者 App 误擦除配置数据。我在一个智能家居项目中遇到过,App 升级时把校准参数区覆盖了,结果所有传感器读数都飘了 20%。
  • 启动引导:芯片上电后,硬件自动跳转到复位向量。Bootloader 根据分区表找到有效的 App 分区,跳过去执行。没有分区表,你连从哪儿启动都不知道。
  • 升级与回滚:这是咱们这门课的核心。分区表可以定义 A/B 两个 App 分区,升级时写 B 区,失败就回滚到 A 区。没有分区表,你连“哪个分区是好的”都判断不了。

我的习惯:每个项目我都会在分区表末尾留一个 4KB 的“保留区”,专门存分区表本身的 CRC 校验值。这样万一 Flash 某位翻转,我能第一时间发现,而不是傻乎乎地按错误地址跳转。

1.3 常见分区布局

嵌入式系统里,最常见的分区布局就三种:Bootloader、App、Data。咱们一个一个说。

1.3.1 Bootloader 分区

Bootloader 是系统上电后第一个执行的程序。它通常很小(16KB–256KB),负责:

  • 初始化硬件(时钟、内存、外设)
  • 校验 App 分区的完整性
  • 决定跳转到哪个 App 分区
  • 提供固件升级入口(比如通过 UART、USB、OTA)

我记得有个项目,Bootloader 只用了 32KB,但里面塞了完整的 CRC 校验和 RSA 签名验证。每次启动都要算几秒钟,客户嫌慢。后来我把校验逻辑挪到 App 启动后异步执行,Bootloader 只做“快速检查”——嗯,这就是经验。

1.3.2 App 分区

App 分区存放主应用程序。根据升级策略,它可以是一个分区(单分区),也可以是两个分区(A/B 分区)。

策略 优点 缺点 适用场景
单分区 Flash 占用少,实现简单 升级失败就变砖,无法回滚 低成本、低风险设备
A/B 分区 升级失败自动回滚,零停机 Flash 占用翻倍,逻辑复杂 车机、医疗、工业控制

避坑指南:我曾经在一个 A/B 分区项目里,把两个 App 分区的起始地址写反了。结果每次升级都写到 Bootloader 区,设备直接变砖。后来我强制在分区表里加了一个“魔数”字段——每个分区开头写一个固定值(比如 0xDEADBEEF),Bootloader 启动时先检查魔数,不对就拒绝跳转。

1.3.3 Data 分区

Data 分区存的是运行时数据:Wi-Fi 配置、校准参数、日志文件、用户设置等。它通常使用文件系统(LittleFS、SPIFFS)或裸数据存储。

这里有个坑:Data 分区不能和 App 分区共用擦除粒度。App 分区通常按 4KB 扇区管理,而文件系统可能按 256 字节页操作。我建议 Data 分区单独划一块区域,并且预留 10%–20% 的冗余空间,防止文件系统碎片导致写失败。

1.4 分区表结构示例

下面是一个典型的 16MB Flash 分区表布局。我用的是裸分区表(没有文件系统),每个分区用结构体描述。

// 分区表结构体定义
typedef struct {
    uint32_t magic;          // 魔数,固定为 0x50415254 ("PART")
    uint32_t version;        // 分区表版本号
    uint32_t crc32;          // 分区表本身的 CRC32 校验
    uint32_t entry_count;    // 分区条目数量

    partition_entry_t entries[8]; // 最多 8 个分区
} partition_table_t;

// 分区条目
typedef struct {
    char     name[16];       // 分区名称,如 "bootloader"
    uint32_t type;           // 类型:0=bootloader, 1=app, 2=data
    uint32_t start_addr;     // 起始地址(相对 Flash 基址)
    uint32_t size;           // 分区大小(字节)
    uint32_t flags;          // 属性:可读、可写、可执行、加密等
    uint32_t reserved;       // 保留
} partition_entry_t;

实际布局(16MB Flash,扇区大小 4KB):

分区名称 起始地址 大小 类型 说明
分区表 0x000000 4KB 元数据 存储分区表本身,CRC 保护
Bootloader 0x001000 64KB Boot 启动加载器,含升级入口
App_A 0x011000 2MB App 主应用程序 A 区
App_B 0x211000 2MB App 主应用程序 B 区(A/B 升级)
Data 0x411000 1MB Data 配置、日志、用户数据
保留 0x511000 剩余 — 预留,用于未来扩展

我的建议:分区表本身一定要放在 Flash 的第一个扇区。因为很多 MCU 的硬件 BootROM 只支持从 0x000000 读取固定长度的数据。如果你把分区表放别处,Bootloader 可能根本找不到它。

1.5 分区表与系统启动流程

咱们用一张图来串一下整个流程。这张 SVG 展示了从芯片上电到 App 运行的全过程,分区表在其中扮演的角色一目了然。

分区表与系统启动流程 分区表与系统启动流程 芯片上电 / 复位 读取分区表(0x000000) 校验分区表 CRC 进入恢复模式 CRC 失败 查找有效 App 分区 CRC 成功 跳转到 App 运行 说明: 分区表是启动流程的“第一道关卡”。CRC 失败时,系统进入恢复模式, 等待外部升级工具重新烧写分区表或固件。

从这张图你能看到,分区表是整个启动流程的“交通指挥员”。它不参与业务逻辑,但一旦它出问题,整个系统就瘫痪了。所以,分区表的完整性保护(CRC、备份、冗余存储)是咱们做固件开发的第一优先级。

1.6 小结

这一章咱们把分区表的基础讲透了。你知道了分区表是什么、它有什么用、常见的布局有哪些。下一章开始,咱们会深入分区表的保护机制——包括 CRC、备份、以及如何防止意外擦写。嗯,这些才是真正让你在项目中“不翻车”的关键。

一句话记住:分区表是嵌入式系统的“宪法”。宪法不乱,系统不乱。