3、分区表详解:ESP32分区表格式、类型与自定义方法

ESP32 的 Flash 存储空间并非一个单一的整体,而是被划分为多个功能不同的区域,这些区域的布局由一张被称为“分区表”(Partition Table)的配置表决定。分区表定义了每个区域的起始地址、大小、类型和名称。无论是使用 SPIFFS 还是 LittleFS 管理文件,还是通过 OTA 升级固件,都依赖于一个正确配置的分区表。理解分区表的结构与自定义方法,是进行嵌入式文件系统开发和固件管理的基础。

ESP32 Flash (通常 4MB / 8MB / 16MB) Bootloader 0x1000 Partition Table 0x8000 app0 (factory) 0x10000 OTA 应用槽位 app1 (ota_1) OTA 应用槽位 spiffs / littlefs 数据存储区 文件系统挂载点 分区表核心字段 # name, type, subtype, offset, size, flags nvs, data, nvs, 0x9000, 0x5000, phy_init, data, phy, 0xe000, 0x1000, factory, app, factory, 0x10000, 0x200000,

上图展示了 ESP32 Flash 中典型的分区布局。Bootloader 和分区表本身占据固定位置,其后是应用程序分区(app)和数据分区(data)。文件系统(SPIFFS/LittleFS)属于数据分区的一种,其大小和位置完全由分区表决定。下面我们将深入解析分区表的格式、类型以及如何根据项目需求进行自定义。

3.1 分区表格式与核心字段解析

ESP32 的分区表是一个二进制文件,通常由 CSV(逗号分隔值)格式的文本文件通过构建工具 gen_esp32part.py 生成。在工程中,我们直接编辑 CSV 文件,然后由 ESP-IDF 构建系统自动完成转换。CSV 文件的每一行代表一个分区,格式如下:

# name,   type, subtype, offset,   size,    flags
nvs,      data, nvs,      0x9000,   0x5000,
phy_init, data, phy,      0xe000,   0x1000,
factory,  app,  factory,  0x10000,  0x200000,
spiffs,   data, spiffs,   0x210000, 0x1F0000,

各字段含义如下:

  • name:分区名称,用于在代码中通过 esp_partition_find 或 esp_vfs_spiffs_register 等 API 查找分区。名称不能包含空格。
  • type:分区类型,主要分为 app(应用程序)和 data(数据)两大类。自定义类型可指定为 0x40-0xFE 之间的数值。
  • subtype:子类型,进一步细化分区用途。对于 app 类型,常见的有 factory、ota_0、ota_1 等;对于 data 类型,有 nvs、phy、spiffs、littlefs 等。
  • offset:分区在 Flash 中的起始地址(十六进制)。如果留空,系统会自动根据前一个分区的结束地址计算。
  • size:分区大小(十六进制)。必须保证分区不重叠,且不超过 Flash 总容量。
  • flags:可选标志位,目前通常留空。加密标志 encrypted 可用于 Flash 加密场景。
关键理解: 分区表的 offset 字段可以省略,但强烈建议显式指定。显式指定可以避免因前面分区大小调整导致后续分区地址漂移,尤其在多分区、OTA 升级场景下,固定地址有助于调试和维护。

3.2 常用分区类型与子类型详解

ESP-IDF 预定义了多种分区类型,理解它们的用途是合理规划 Flash 空间的前提。下表列出了最常见的分区类型及其适用场景:

类型 子类型 典型用途 注意事项
app factory 出厂固件,默认启动应用 如果存在 OTA 分区,factory 可作为回滚点
ota_0 / ota_1 OTA 升级的交替槽位 至少需要两个 OTA 分区才能实现安全升级
test 工厂测试固件 不常用,一般开发阶段使用
data nvs 非易失性存储,保存密钥、校准数据、系统参数 NVS 有磨损均衡,但单次写入不宜过大
phy_init WiFi/BT 物理层初始化数据 出厂时烧录,一般不需要修改
spiffs SPIFFS 文件系统 适合小文件、日志存储,但掉电恢复能力较弱
littlefs LittleFS 文件系统 具备掉电保护、磨损均衡,推荐用于数据存储

在 ESP-IDF 中,spiffs 和 littlefs 子类型在分区表中是明确区分的。虽然两者都是文件系统,但底层实现不同,挂载时使用的驱动也不同。例如,LittleFS 在 ESP-IDF 中通过 esp_littlefs 组件支持,而 SPIFFS 则使用 esp_spiffs 组件。

3.3 自定义分区表的操作流程

自定义分区表通常有两种方式:修改默认的 partitions.csv 文件,或在工程配置中选择预设模板。以下是标准操作流程:

  1. 确定 Flash 容量:通过 idf.py menuconfig 的 “Serial flasher config” 菜单,确认目标 Flash 大小(如 4MB、8MB)。
  2. 创建或编辑 CSV 文件:在工程根目录创建 partitions.csv,或复制默认模板进行修改。默认模板位于 $IDF_PATH/components/partition_table/partitions_singleapp.csv。
  3. 计算分区大小与偏移:确保所有分区大小之和不超过 Flash 容量,且地址对齐(通常 4KB 对齐)。Bootloader 占用 0x1000 起始的 0x1000 字节,分区表本身占用 0x8000 起始的 0x1000 字节。
  4. 配置工程使用自定义分区表:在 menuconfig 中,进入 “Partition Table” 选项,选择 “Custom partition table CSV”,并指定 CSV 文件路径。
  5. 编译烧录:执行 idf.py build flash monitor,系统会解析 CSV 并生成二进制分区表烧录到 Flash。
风险提示: 修改分区表会导致 Flash 数据布局变化。如果之前烧录过旧固件,重新烧录新分区表后,原有文件系统数据(如 SPIFFS 中的文件)可能无法访问,因为分区地址和大小已改变。升级分区表后,通常需要执行 idf.py erase-flash 擦除整个 Flash,再烧录新固件。

3.4 实战案例:为 LittleFS 自定义分区

假设我们使用一块 4MB Flash 的 ESP32 开发板,需要实现 OTA 升级功能,同时希望使用 LittleFS 存储用户配置和日志文件。默认的 partitions_singleapp.csv 只有一个 app 分区和一个 1.5MB 的 SPIFFS 分区,无法满足 OTA 需求。我们需要设计一个包含双 OTA 槽位和 LittleFS 分区的自定义分区表。

背景与约束:

  • Flash 总容量:4MB(0x400000)。
  • Bootloader 区域:0x1000 - 0x8000(固定)。
  • 分区表区域:0x8000 - 0x9000(固定)。
  • NVS 分区:0x9000 - 0xE000(20KB),用于存储系统参数。
  • PHY init 分区:0xE000 - 0xF000(4KB),用于 WiFi 校准数据。
  • OTA 应用槽位:每个槽位至少 1.3MB,以保证固件有足够空间。
  • LittleFS 分区:剩余空间,用于文件存储。

初始现象与目标: 默认分区表无法进行 OTA 升级,且 SPIFFS 在异常掉电时容易损坏文件。目标是通过自定义分区表,实现安全的 OTA 升级和可靠的文件存储。

关键输入数据与计算:

Flash 总大小: 0x400000 (4MB)
Bootloader:   0x1000  (4KB)
Partition表:  0x8000  (4KB)
NVS:          0x9000  (20KB) -> 0x9000 + 0x5000 = 0xE000
PHY init:     0xE000  (4KB)  -> 0xE000 + 0x1000 = 0xF000
OTA 槽位 0:   0x10000 (1.3MB) -> 0x10000 + 0x150000 = 0x160000
OTA 槽位 1:   0x160000 (1.3MB) -> 0x160000 + 0x150000 = 0x2B0000
LittleFS:     0x2B0000 (剩余 0x150000 = 1.3MB) -> 0x2B0000 + 0x150000 = 0x400000

实际操作步骤:

  1. 在工程目录下创建 partitions_ota_littlefs.csv 文件,内容如下:
# name,   type, subtype, offset,   size,    flags
nvs,      data, nvs,      0x9000,   0x5000,
phy_init, data, phy,      0xe000,   0x1000,
factory,  app,  factory,  0x10000,  0x150000,
ota_1,    app,  ota_1,    0x160000, 0x150000,
littlefs, data, littlefs, 0x2B0000, 0x150000,
  1. 在 menuconfig 中设置 “Partition Table” 为 “Custom partition table CSV”,并指定该文件路径。
  2. 在代码中挂载 LittleFS 时,指定分区标签为 littlefs:
esp_vfs_littlefs_conf_t conf = {
    .base_path = "/store",
    .partition_label = "littlefs",
    .format_if_mount_failed = true,
    .dont_mount = false,
};
esp_vfs_littlefs_register(&conf);

结果对比与复盘:

  • 升级前:只能运行单固件,无法 OTA;SPIFFS 在掉电测试中出现过文件损坏。
  • 升级后:支持双槽位 OTA,升级失败可自动回滚;LittleFS 在连续 50 次随机掉电测试中,文件系统完整性保持良好。
  • 代价:LittleFS 分区大小从默认的 1.5MB 减少到 1.3MB,但换来了 OTA 能力和更高的数据可靠性。
经验总结: 在规划分区时,不要将 Flash 空间用尽。预留 10%-15% 的余量,用于未来固件体积增长或新增功能分区。同时,OTA 槽位大小应大于当前固件体积的 1.5 倍,以应对固件膨胀。

3.5 常见误区与总结复盘

在实际项目中,分区表配置错误是导致启动失败、文件系统无法挂载的常见原因。以下是几个高频误区:

  • 误区一:分区大小超过 Flash 容量。编译时可能不报错,但烧录时会出现校验失败或运行时崩溃。务必使用 idf.py partition-table 命令检查生成的分区表信息。
  • 误区二:忽略 4KB 对齐。某些 Flash 操作要求地址对齐,未对齐可能导致读写异常。所有 offset 和 size 建议为 0x1000(4KB)的整数倍。
  • 误区三:混淆 SPIFFS 和 LittleFS 的子类型。在分区表中将 littlefs 写成 spiffs,会导致挂载时驱动不匹配,返回错误。必须严格对应。
  • 误区四:OTA 分区数量不足。如果只有 factory 分区而没有 ota_0 和 ota_1,OTA 功能将无法启用。至少需要两个 app 分区(一个作为当前运行,一个作为升级目标)。

总结复盘: 分区表是 ESP32 存储管理的“宪法”,它决定了系统如何分配和使用 Flash 资源。通过本章的学习,我们掌握了分区表的 CSV 格式、关键字段、常用类型以及自定义方法。在实际工程中,建议遵循以下原则:

  1. 先规划后编码:在项目启动阶段就确定 Flash 布局,避免后期频繁调整。
  2. 显式指定 offset:避免隐式计算带来的不确定性。
  3. 使用版本化管理:将分区表 CSV 文件纳入版本控制,记录每次修改的原因。
  4. 测试掉电场景:对于文件系统分区,务必进行掉电可靠性测试,验证分区配置是否满足需求。

掌握分区表的自定义能力,意味着你可以根据具体应用场景(如 OTA、数据存储、日志记录)灵活裁剪 Flash 空间,为后续的固件升级和文件管理打下坚实基础。