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 运行的全过程,分区表在其中扮演的角色一目了然。
从这张图你能看到,分区表是整个启动流程的“交通指挥员”。它不参与业务逻辑,但一旦它出问题,整个系统就瘫痪了。所以,分区表的完整性保护(CRC、备份、冗余存储)是咱们做固件开发的第一优先级。
1.6 小结
这一章咱们把分区表的基础讲透了。你知道了分区表是什么、它有什么用、常见的布局有哪些。下一章开始,咱们会深入分区表的保护机制——包括 CRC、备份、以及如何防止意外擦写。嗯,这些才是真正让你在项目中“不翻车”的关键。
一句话记住:分区表是嵌入式系统的“宪法”。宪法不乱,系统不乱。