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启动。这样即使新固件启动失败,也能通过回滚机制切回旧槽。两个分区大小必须一致,且要能容纳最大固件体积。
三、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。
配置流程建议如下:
- 估算固件最大体积,确定ota_0/ota_1大小,通常留20%余量。
- 确定是否需要factory分区:若需要出厂保底固件则保留,否则可省略。
- 分配otadata为0x2000,nvs根据业务需求分配。
- 检查各分区偏移是否对齐、是否重叠。
- 编译后使用
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一致 |
六、常见误区与复盘
误区2:otadata分区过小或缺失,OTA状态无法保存,升级后反复重启。
误区3:nvs分区与otadata偏移重叠,烧录时报分区表错误。
误区4:ota_0与ota_1大小不一致,升级时写入越界。
复盘建议:每次修改分区表后,先用idf.py partition-table确认布局,再执行一次完整的“升级—验证—回滚”测试。只有分区表、otadata状态和nvs记录三者协同正确,ESP32的OTA与版本回滚才能稳定运行。