4. ESP32分区表详解

在ESP32的OTA升级体系中,分区表(Partition Table)是决定“程序放在哪里、升级写到哪里、回滚依据是什么”的底层地图。如果分区表设计不合理,即使OTA代码逻辑完全正确,也可能出现升级失败、无法回滚、NVS数据丢失等问题。本节将围绕分区类型、factory分区、ota_0/ota_1分区、otadata分区和nvs分区,讲清它们的作用与配置方法。

一、分区表的基本结构与分区类型

ESP32的Flash默认从偏移地址0x8000开始存放分区表,分区表由若干条记录组成,每条记录描述一个分区的名称、类型、子类型、偏移地址和大小。分区类型(Type)用于区分大类别,子类型(SubType)用于进一步细分用途。常见类型如下:

  • app类型:存放可执行应用程序,子类型包括factory、ota_0、ota_1等。
  • data类型:存放数据,子类型包括nvs、otadata、phy、nvs_keys、spiffs、fat等。

分区表通常以CSV文件形式维护,编译时由工具转换为二进制并烧录到0x8000。一个典型的支持OTA的分区表如下:

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x4000,
otadata,  data, ota,     0xd000,  0x2000,
phy_init, data, phy,     0xf000,  0x1000,
factory,  app,  factory, 0x10000, 0x140000,
ota_0,    app,  ota_0,   0x150000,0x140000,
ota_1,    app,  ota_1,   0x290000,0x140000,

其中Offset可以留空,由工具自动计算;Size必须为0x1000的整数倍,且各分区不能重叠。

二、factory、ota_0与ota_1分区的作用

factory分区是出厂固件所在分区,通常存放最初烧录的稳定版本。它既可以作为“永不升级”的保底固件,也可以作为首次OTA的起点。若分区表中没有factory分区,则系统必须至少有一个ota_0分区,否则无法启动。

ota_0与ota_1分区是OTA升级的两个应用槽(slot)。ESP32的OTA机制采用A/B双槽设计:当前运行在ota_0时,新固件写入ota_1;下次启动时若otadata指向ota_1,则从ota_1启动。这样即使新固件启动失败,也能通过回滚机制切回旧槽。两个分区大小必须一致,且要能容纳最大固件体积。

关键点:factory、ota_0、ota_1都属于app类型,但子类型不同。OTA升级时,目标分区必须是ota_x子类型,不能直接覆盖factory分区(除非使用特殊方式)。

三、otadata分区:OTA状态与回滚依据

otadata分区是OTA的“状态记录本”,大小通常为0x2000(两个扇区),用于保存两个OTA槽的状态信息,包括:当前应启动哪个槽、该槽是否已被验证、启动失败计数等。ESP-IDF通过esp_ota_get_state_partition等接口读写该分区。

otadata的核心字段包括:

  • seq:序列号,数值更大的槽表示更新的固件。
  • state:状态,如NEW、PENDING_VERIFY、VALID、INVALID、ABORTED等。
  • crc:校验值,保证记录完整。

当新固件写入ota_1并设置otadata后,系统重启会尝试从ota_1启动。若固件在首次启动时调用esp_ota_mark_app_valid_cancel_rollback(),则状态变为VALID,回滚取消;若未标记或启动失败,则回滚到旧槽。这就是版本回滚的底层逻辑。

四、nvs分区的作用与配置

nvs(Non-Volatile Storage)分区用于存放键值对形式的非易失数据,例如Wi-Fi配置、设备参数、OTA版本号、回滚标志等。它与OTA密切相关:很多项目会在nvs中记录当前固件版本、升级时间、回滚次数,以便业务层判断。

nvs分区大小需根据实际键值数量评估,常见为0x4000到0x10000。若nvs过小,写入时会报ESP_ERR_NVS_NOT_ENOUGH_SPACE。配置时注意:nvs分区不能与otadata、phy_init等分区重叠,且建议放在分区表前部,便于工具自动计算偏移。

五、分区表配置方法与实操要点

在ESP-IDF中,分区表通过idf.py menuconfig中的Partition Table选项选择,可选用内置的partitions_two_ota.csv,也可自定义CSV文件。自定义时在项目根目录创建partitions.csv,并在menuconfig中设置Custom partition table CSV。

配置流程建议如下:

  1. 估算固件最大体积,确定ota_0/ota_1大小,通常留20%余量。
  2. 确定是否需要factory分区:若需要出厂保底固件则保留,否则可省略。
  3. 分配otadata为0x2000,nvs根据业务需求分配。
  4. 检查各分区偏移是否对齐、是否重叠。
  5. 编译后使用idf.py partition-table查看生成结果。

下表汇总了各分区的作用与典型配置:

分区名 类型/子类型 作用 典型大小
nvs data/nvs 键值数据、版本号、回滚标志 0x4000~0x10000
otadata data/ota OTA槽状态、启动选择、回滚依据 0x2000
factory app/factory 出厂固件、保底版本 与ota槽一致
ota_0 app/ota_0 OTA槽A,当前或备用固件 0x140000左右
ota_1 app/ota_1 OTA槽B,升级目标或回滚源 与ota_0一致

六、常见误区与复盘

误区1:只配置ota_0不配置ota_1,导致无法双槽升级和回滚。
误区2:otadata分区过小或缺失,OTA状态无法保存,升级后反复重启。
误区3:nvs分区与otadata偏移重叠,烧录时报分区表错误。
误区4:ota_0与ota_1大小不一致,升级时写入越界。

复盘建议:每次修改分区表后,先用idf.py partition-table确认布局,再执行一次完整的“升级—验证—回滚”测试。只有分区表、otadata状态和nvs记录三者协同正确,ESP32的OTA与版本回滚才能稳定运行。

ESP32 Flash 分区布局与 OTA 回滚关系 Flash 地址空间 0x8000 分区表 0x9000 nvs 0xd000 otadata 0xf000 phy_init 0x10000 factory 0x150000 ota_0 0x290000 ota_1 当前运行 ota_0 旧固件 v1.0 新固件写入 ota_1 v2.0 待验证 otadata 更新 seq 增大,指向 ota_1 启动失败 / 未标记 回滚到 ota_0 标记 VALID 取消回滚,保留 v2.0 nvs 记录 版本号 / 回滚次数