1. 为什么需要自定义分区表?

第一次用ESP32开发项目时,我也遇到过编译后程序空间不足的问题。明明代码量不大,却总是提示"No space left on device"。后来发现,这其实是ESP32默认分区表分配的空间太小导致的。ESP32的Flash存储空间就像一块大蛋糕,分区表就是切蛋糕的刀,决定了每块蛋糕的大小和用途。

官方默认的分区表为了兼容各种使用场景,给应用程序(APP)分配的空间往往比较保守。比如4MB Flash的ESP32-WROOM-32模块,默认只给APP分区分配1.25MB空间。如果你的项目用到了大量库文件或者需要存储资源,这点空间根本不够用。这时候就需要自定义分区表,重新分配Flash空间。

2. 理解ESP32分区表结构

2.1 分区表的基本组成

ESP32的分区表是一个CSV格式的文件,定义了Flash中各个区域的用途。每个分区包含6个关键信息:

  • Name:分区名称,如nvs、otadata等
  • Type:分区类型,主要有app(应用程序)、data(数据)两种
  • SubType:子类型,进一步说明分区用途
  • Offset:分区起始地址(16进制)
  • Size:分区大小(16进制)
  • Flags:特殊标志,如加密等

举个例子,这是8MB Flash的默认分区表片段:

# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x5000,
otadata, data, ota, 0xe000, 0x2000,
app0, app, ota_0, 0x10000, 0x330000,

2.2 常见分区的作用

  • nvs:存储设备配置参数,如Wi-Fi密码
  • otadata:OTA升级时记录当前使用的固件分区
  • app0/app1:两个OTA固件分区,可以交替升级
  • spiffs:文件系统分区,存放网页、配置文件等
  • coredump:崩溃时保存错误信息

3. 在PlatformIO中创建自定义分区表

3.1 新建分区表文件

首先在项目根目录下创建一个CSV文件,比如custom_partitions.csv。可以直接复制默认分区表内容作为起点。PlatformIO默认的分区表文件位于:

~/.platformio/packages/framework-arduinoespressif32/tools/partitions

建议先备份原始分区表,再开始修改。我习惯把分区表文件放在项目的/data目录下,方便管理。

3.2 调整分区大小

重点来了!我们需要重新分配APP分区的大小。假设我们用的是8MB Flash的ESP32:

原始配置:

app0, app, ota_0, 0x10000, 0x330000,  # 3.1875MB

修改后:

app0, app, ota_0, 0x10000, 0x500000,  # 5MB

这里把APP分区从3.1875MB扩大到了5MB。注意要确保:

  1. 各分区的Offset不能重叠
  2. 总大小不超过Flash容量
  3. 保留必要的系统分区(nvs、otadata)

3.3 计算分区地址的技巧

计算分区地址时,我推荐使用这个公式:

下一个分区的Offset = 当前分区的Offset + 当前分区的Size

比如:

app0, app, ota_0, 0x10000, 0x500000,
spiffs, data, spiffs, 0x510000, 0x2E0000,

这里spiffs的Offset就是0x10000 + 0x500000 = 0x510000。

4. 在PlatformIO中配置自定义分区表

4.1 修改platformio.ini

在项目的platformio.ini文件中添加:

[env:esp32dev]
platform = espressif32
board = esp32dev
board_build.partitions = data/custom_partitions.csv

4.2 验证分区表

编译前,建议先用这个命令检查分区表是否有效:

pio run -t partition-table

如果看到类似这样的输出,说明分区表配置正确:

Checking size partitions.csv
Partition table verified.

4.3 常见错误排查

  1. 分区重叠:检查各分区的Offset和Size是否冲突
  2. 超出Flash容量:确保所有分区Size总和不超过芯片规格
  3. 缺少必要分区:nvs和otadata分区必须保留
  4. 格式错误:CSV文件不能有多余的空格或符号

5. 实战案例:优化OTA项目分区

最近做一个智能家居项目时,我需要同时支持OTA升级和文件系统。硬件是16MB Flash的ESP32-S3,这是最终的分区表配置:

# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
otadata, data, ota, 0xf000, 0x2000,
app0, app, ota_0, 0x10000, 0x300000,
app1, app, ota_1, 0x310000, 0x300000,
spiffs, data, spiffs, 0x610000, 0x9E0000,

这个配置:

  • 给每个OTA分区分配了3MB空间
  • 文件系统分区高达9.875MB
  • 保留了必要的系统分区

编译后,可用程序空间从默认的1.2MB提升到了3MB,完美解决了之前频繁出现的空间不足问题。

6. 高级技巧与注意事项

6.1 优化nvs分区大小

nvs分区默认是20KB(0x5000),但实际项目中:

  • 简单设备:16KB足够
  • 复杂配置:可能需要32KB

建议根据实际存储的参数数量调整。

6.2 处理特殊需求

  1. 不需要OTA:可以只保留一个app分区,节省空间

    factory, app, factory, 0x10000, 0x300000,
    
  2. 需要更多文件空间:可以适当减小app分区,增大spiffs

  3. 安全需求:可以添加加密标志

    storage, data, nvs, 0x9000, 0x6000, encrypted
    

6.3 性能优化建议

  1. 将频繁访问的数据放在Flash前部(地址小的区域)
  2. 对齐分区Offset到4KB边界(0x1000的倍数)
  3. 大文件尽量放在连续的空间

7. 调试与验证

修改分区表后,可以通过以下方式验证:

  1. 查看编译日志中的分区信息

  2. 在代码中打印分区信息:

    #include "esp_partition.h"
    
    void print_partitions() {
        esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
        while (it != NULL) {
            const esp_partition_t *p = esp_partition_get(it);
            printf("Type=%02x, SubType=%02x, Address=0x%06x, Size=0x%06x, Label=%s\n",
                   p->type, p->subtype, p->address, p->size, p->label);
            it = esp_partition_next(it);
        }
    }
    
  3. 使用esptool查看实际烧录情况:

    esptool.py read_flash 0x8000 0xc00 partition_table.bin
    

记得在实际硬件上测试所有功能,特别是OTA升级和文件系统操作。我在一个项目中就遇到过spiffs分区设置过大导致写入速度变慢的问题,后来调整为4MB后性能明显改善。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐