ESP32的分区表(partitions)及如何配置

前言

stm32与esp32的flash区别?

特性ESP32STM32
Flash物理位置集成SPI Flash(非CPU直连)内部Flash(CPU直接访问)
管理方式通过分区表动态划分固定地址链接脚本分配
灵活性可动态调整分区大小需重新编译修改链接脚本
  • ESP32可额外连接外部SPI Flash(如扩展存储),但分区表默认不管理外部Flash。需手动通过SPI驱动或文件系统(如LittleFS)访问。

一、分区表的作用

  • Flash 物理特性: ESP32 的 SPI Flash 存储芯片本质上是连续的线性地址空间。分区表将这个物理空间逻辑划分成多个独立的区域,每个区域有特定的用途。

  • 功能隔离: 固件包含多个功能组件(如 Bootloader、应用程序、文件系统、配置数据、OTA 更新数据等)。分区表将它们隔离在不同区域,防止互相覆盖或干扰。

  • 灵活管理: 允许开发者根据项目需求(如应用程序大小、文件系统大小、OTA 支持等)灵活配置各分区的大小和位置。

  • 启动流程: Bootloader 依赖分区表来确定从哪个分区加载应用程序并启动。

  • OTA: 它定义了主应用程序分区、OTA 数据分区以及至少一个 OTA 应用程序分区(用于存储新固件)。

  • 数据持久化: 为 NVS (Non-Volatile Storage)、FATFS、SPIFFS 等文件系统或数据存储提供专用的、受保护的空间。

  • 安全: 支持定义安全相关的分区(如 NVS 加密密钥分区)。

二、分区表的结构

1. 默认bootloader区域 (0x0000 - 0x9000)

地址描述大小说明
0x0000Bootloader 头部信息4KB包含 Bootloader 的元数据(如 Flash 加密密钥、芯片兼容性标志等)
0x1000Bootloader 代码28KB实际的 Bootloader 可执行代码(负责初始化硬件、加载分区表、启动应用程序)
0x8000分区表 (Partition Table)4KB定义 Flash 的分区结构(如 app、data 等分区的偏移和大小)
0x9000第一个用户分区(如 nvs)-用户自定义分区的起始地址(如 nvs 分区)
  • 默认情况下,不要手动修改 0x0 - 0x9000 的内容
    错误的写入可能导致设备无法启动(需重新烧写 Bootloader 和分区表)。

  • 扩展 Bootloader 需谨慎
    如果自定义 Bootloader 超过 28KB,需调整分区表偏移(但需确保与 ESP-IDF 兼容)。

  • 加密影响
    启用 Flash 加密后,0x0 - 0x9000 的内容会被加密,但 Bootloader 会先解密再执行。

1. Bootloader 区域 (0x1000 - 0x8000)

  • 大小:
    固定为 0x7000(28KB),由编译时 bootloader.bin 决定。
  • 作用:
    初始化硬件(CPU 时钟、SPI Flash、串口等)。从 0x8000 读取分区表。根据分区表加载应用程序(从 app 分区)或进入 OTA 流程。
  • 不可修改:
    除非重新编译并烧写 Bootloader,否则此区域内容不会变化。

2. 分区表区域 (0x8000 - 0x9000)

就是这个文件partitions.csv的内容

  • 大小:
    固定 4KB(即使分区表实际内容不足 4KB,也会占用完整 Sector)。
  • 内容:
    二进制格式的分区表条目(每个条目 32 字节)。包含所有分区的名称、类型、子类型、偏移、大小和标志。
  • 查看方法:
    使用 esptool.py 读取并解析:
# 连接ESP32,然后读取0x8000位置,大小0x1000,生成文件partitions.bin,参考下午
esptool.py read_flash 0x8000 0x1000 test.bin

在这里插入图片描述

# 然后解析文件
gen_esp32part.py test.bin

在这里插入图片描述

3. 0x9000 之前未使用的空间

  • 0x8000 - 0x9000 之间的剩余空间:
    分区表仅占用 0x8000 开始的 4KB,但 0x9000 是下一个分区的起始地址,因此中间可能有未使用的空间(具体取决于分区表定义)。
  • 示例:
    如果分区表实际只占用 1KB,剩余的 0x8000 + 0x1000 到 0x9000(即 3KB)是空闲的,但无法直接利用(因为 Flash 擦除最小单位为 4KB Sector)。

2. partitions.csv 文件结构 (0x9000 - )

这个文件partitions.csv的内容存放在(0x8000 - 0x9000) 内

1. menuconfig里分区相关配置

在这里插入图片描述

  • Single factory app, no OTA (默认)
    适用于 无 OTA 功能 的固件,仅包含一个 factory 应用分区
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,		data,	nvs,	0x9000,		24K,
phy_init,	data,	phy,	0xf000,		4K,
factory,	app,	factory,0x10000,	2M,
storage,	data,	fat,	0x210000,	1M,
  • Single factory app (large), no OTA
    适用于 无 OTA 功能 的固件,仅包含一个 factory 应用分区
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,		data,	nvs,	0x9000,		24K,
phy_init,	data,	phy,	0xf000,		4K,
factory,	app,	factory,0x10000,	1500K,
  • Factory app, two OTA definitions
    支持 OTA 升级,预留 ota_0 和 ota_1 分区
# ESP-IDF Partition Table
# Name, Type, SubType, Offset, Size, Flags
nvs,		data,	nvs,	0x9000,		16K,
otadata,	data,	ota,	0xd000,		8K,
phy_init,	data,	phy,	0xf000,		4K,
factory,	app,	factory,0x10000,	1M,
ota_0,		app,	ota_0,	0x110000,	1M,
ota_1,		app,	ota_1,	0x210000,	1M,
  • Custom partition table CSV
    需要配置自定义分区CSV文件的名称。此路径默认在项目根目录。但是,如果提供了CSV文件的绝对路径,则配置绝对路径。

下面是一个参考例子
- Bootloader: 0x1000 - 0x8000 (隐含)。
- 分区表: 0x8000 (固定)。
- nvs (加密): 0x9000 (紧接分区表后), 24KB。存储加密的设备配置。
- otadata: 0xf000 (自动计算 0x9000+0x6000=0xF000), 8KB。管理 OTA 状态。
- phy_init: 0x11000 (0xF000+0x2000=0x11000), 4KB。RF 参数。
- factory: 0x12000 (0x11000+0x1000=0x12000), 1MB。出厂应用/回退。
- ota_0 / ota_1: 各 1MB。OTA 应用。
- spiffs: 0x312000 (0x212000+1M=0x312000), 3MB。文件系统存储网页、日志等。

# Name,   Type, SubType, Offset,  Size,     Flags
nvs,      data, nvs,     0x9000,  0x6000,   encrypted
otadata,  data, ota,     0xf000,  0x2000,
phy_init, data, phy,     0x11000, 0x1000,
factory,  app,  factory, 0x12000, 1M,
ota_0,    app,  ota_0,   0x112000,1M,
ota_1,    app,  ota_1,   0x212000,1M,
spiffs,   data, spiffs,  0x312000,3M,

总大小: 0x312000 (3, 145, 728) + 3MB (3, 145, 728) = 6, 291, 456 Bytes ≈ 6MB。剩余约 2MB 未使用(在 0x612000 - 0x800000 之间),可作为未来扩展预留。

三、关键字段说明

1. Name: 分区的逻辑名称。

  • 在代码中可以通过这个名称来操作分区(如挂载文件系统、获取分区信息)。在同一张分区表中必须唯一。常见名称示例: nvs, otadata, factory, ota_0, ota_1, storage, coredump。

2. Type: 定义分区的主要类型。

ESP-IDF 预定义了几种核心类型:

app: 应用程序分区。

  • 用于存储可执行的 ESP-IDF 应用程序固件。Bootloader 会从这里加载并运行代码。至少需要一个 app 分区(通常是 factory 或 ota_X)。

data: 数据分区。

  • 用于存储非执行代码的数据,如配置文件、文件系统、OTA 元数据、NVS 数据、PHY 初始化数据等。

(0x40-0xFE): 自定义类型。

  • 用户可以根据需要定义自己的类型(使用十六进制值)。

3. SubType: 子类型

[app]factory: 出厂应用程序分区。

  • 通常是 Bootloader 的默认启动选择(如果没有 OTA 数据或 OTA 数据无效)。

[app]ota_0 到 ota_15: OTA 应用程序分区。

  • 用于存储通过 OTA 更新的应用程序固件。ota_data 分区记录当前活动的 OTA 槽位 (ota_0 或 ota_1 等)。

[app]test: 用于工厂测试程序。

[data]ota: OTA 数据分区。

  • 存储 OTA 升级过程的元数据,最重要的是记录当前活动的 OTA 应用程序 (ota_0, ota_1 等)。大小通常为 0x2000 (8KB)。

[data]phy: PHY 初始化数据分区。

  • 存储 RF 物理层 (PHY) 的初始化参数。如果未提供,使用编译到 Bootloader 中的默认 PHY 参数。大小通常为 0x1000 (4KB)。

[data]nvs: NVS (Non-Volatile Storage) 分区。

  • 用于存储键值对形式的配置数据(Wi-Fi SSID/密码、设备参数等)。ESP-IDF 的 nvs_flash 库使用它。推荐最小 0x3000 (12KB)。

[data]nvs_keys: NVS 密钥分区。

  • 如果启用了 NVS 加密,此分区存储用于加密 NVS 分区的密钥。大小通常为 0x1000 (4KB)。

[data]spiffs, fat, littlefs: 文件系统分区。

  • 用于存储 SPIFFS、FATFS 或 LittleFS 文件系统的数据。

[data]coredump: Core Dump 分区。

  • 用于存储发生严重错误(如崩溃)时的内存转储信息,便于后续调试。

[data]efuse_em: 模拟 eFuse 分区 (高级用法)。

[data]undefined / (0x00): 通用数据分区,子类型未指定。

  • 用户可以在代码中自定义其用途。

[ (0x40-0xFE)]: 自定义子类型

  • 用户可自定义子类型含义

4. Offset: 起始地址

  • 该分区在 Flash 中的起始地址(十六进制表示)。绝对关键参数! 必须指定。必须满足

    1. 对齐: 偏移地址必须对齐到 0x1000 (4KB) 边界(Flash 擦除操作的最小单位通常是 Sector=4KB)。gen_esp32part.py 会强制对齐,但最好在 CSV 中明确指定对齐的地址。
    2. 顺序: 分区在 Flash 中的物理顺序应与它们在表中定义的顺序一致(偏移地址递增)。
    3. 不重叠: 分区的偏移+大小不能超出 Flash 总大小,且分区之间不能有重叠区域。
  • 0xNNNN 或 ‘-’: 如果设置为 -,工具会自动计算偏移,使其紧接在前一个分区之后(考虑对齐)。

5. Size: 分区的大小

  • 必须指定大小
    示例: 0x1000 (4KB), 16K (16KB), 1M (1MB), 0x200000 (2MB)。
  • 大小也必须满足 4KB 对齐(工具会自动向上取整到最近的 4KB 边界)。

6. Flags (可选):

  • 为分区设置附加标志(可选字段)。当前主要支持的标志是 encrypted。
  • encrypted: 标记该分区在 Flash 加密启用时需要被加密。Bootloader 在加载 app 分区前会自动解密它。数据分区如果包含敏感信息(如 NVS),也应标记为 encrypted。
  • 多个标志用逗号分隔(目前只有 encrypted 有意义)。

四、 实际应用案例

1. esptool.py和gen_esp32part.py工具

  • esptool.py:烧录分区表到 Flash
# 烧录分区表
esptool.py -p /dev/ttyUSB0 -b 460800 write_flash 0x8000 partitions.bin

# 读取现有分区表
esptool.py -p /dev/ttyUSB0 read_flash 0x8000 0x1000 partition_table.bin

# 验证烧录内容
esptool.py -p /dev/ttyUSB0 verify_flash 0x8000 partitions.bin

# 擦除分区表区域
esptool.py -p /dev/ttyUSB0 erase_region 0x8000 0x1000
  • gen_esp32part.py:
    • 将 CSV 格式的分区表 转换为 二进制文件(供烧录到 Flash)。
    • 将 二进制分区表 反向解析为 CSV 文件(用于查看或修改)。
# 将 partitions.csv 转换为二进制文件
gen_esp32part.py partitions.csv partitions.bin

# 从 Flash 中读取的分区表二进制文件解析为 CSV
gen_esp32part.py partition_table.bin partition_table.csv

# 检查 CSV 文件是否有错误(如偏移重叠、未对齐等)
gen_esp32part.py --verify partitions.csv

2. idf.py flash工具

  • 用于将编译生成的固件(包括 Bootloader、分区表、应用程序、文件系统等)一键烧录到 ESP32 的 Flash 中

默认烧写固件

  • -p:指定串口 -b:设置烧录波特率

idf.py flash -p /dev/ttyUSB0 -b 921600

仅烧录应用程序

  • 不更新 Bootloader 或分区表
  • 适用场景:代码修改后快速测试,节省烧录时间。

idf.py app-flash

仅烧录分区表

idf.py partition-table-flash

仅烧录文件系统

  • 前提:项目中配置了文件系统分区(如 SPIFFS、FATFS)。

idf.py storage-flash

3. 读取 NVS、SPIFFS 和 FATFS 数据

#include "nvs_flash.h"
#include "esp_spiffs.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"

// 读取 NVS 键值对数据
void read_nvs_data() {
    nvs_handle_t nvs_handle;
    esp_err_t ret;
    
    // 初始化 NVS
    ret = nvs_flash_init();
    if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        ESP_ERROR_CHECK(nvs_flash_erase());
        ret = nvs_flash_init();
    }
    ESP_ERROR_CHECK(ret);

    // 打开命名空间
    ret = nvs_open("storage", NVS_READONLY, &nvs_handle);
    if (ret != ESP_OK) {
        printf("NVS 打开失败: %s\n", esp_err_to_name(ret));
        return;
    }

    // 读取整数
    int32_t restart_count = 0;
    ret = nvs_get_i32(nvs_handle, "restart_count", &restart_count);
    printf("设备重启次数: %d\n", restart_count);

    // 读取字符串
    char ssid[32] = {0};
    size_t ssid_len = sizeof(ssid);
    ret = nvs_get_str(nvs_handle, "wifi_ssid", ssid, &ssid_len);
    printf("WiFi SSID: %s\n", ssid);

    nvs_close(nvs_handle);
}

// 读取 SPIFFS 文件数据
void read_spiffs_data() {
    // 配置并挂载 SPIFFS
    esp_vfs_spiffs_conf_t conf = {
        .base_path = "/spiffs",
        .partition_label = "spiffs_storage",
        .max_files = 5,
        .format_if_mount_failed = false
    };
    
    esp_err_t ret = esp_vfs_spiffs_register(&conf);
    if (ret != ESP_OK) {
        printf("SPIFFS 挂载失败: %s\n", esp_err_to_name(ret));
        return;
    }

    // 打开并读取文件
    FILE* f = fopen("/spiffs/config.json", "r");
    if (f == NULL) {
        printf("无法打开配置文件\n");
    } else {
        char buffer[256];
        while (fgets(buffer, sizeof(buffer), f) != NULL) {
            printf("%s", buffer);
        }
        fclose(f);
    }

    // 卸载 SPIFFS
    esp_vfs_spiffs_unregister(conf.partition_label);
}

// 读取 FATFS 数据 (SD卡示例)
void read_fatfs_data() {
    sdmmc_host_t host = SDMMC_HOST_DEFAULT();
    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
    
    // FATFS 挂载配置
    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 5,
        .allocation_unit_size = 16 * 1024
    };
    
    sdmmc_card_t* card;
    esp_err_t ret = esp_vfs_fat_sdmmc_mount("/sdcard", &host, &slot_config, &mount_config, &card);
    if (ret != ESP_OK) {
        printf("SD卡挂载失败: %s\n", esp_err_to_name(ret));
        return;
    }

    // 读取日志文件
    FILE* f = fopen("/sdcard/system.log", "r");
    if (f == NULL) {
        printf("无法打开日志文件\n");
    } else {
        char line[128];
        printf("系统日志内容:\n");
        while (fgets(line, sizeof(line), f) != NULL) {
            printf("%s", line);
        }
        fclose(f);
    }

    // 卸载文件系统
    esp_vfs_fat_sdmmc_unmount();
}

void app_main() {
    // 读取三种存储的数据
    read_nvs_data();
    read_spiffs_data();
    read_fatfs_data();
}

五、相关问题

1. OTA1 OTA2与factory的关系

分区子类型作用
factoryapp, factory出厂固件,作为最终回退版本(安全备份),通常不参与常规OTA更新。
ota_0app, ota_0第一个OTA(OTA1),用于存储通过OTA更新的固件。
ota_1app, ota_1第二个OTA(OTA2),用于交替存储OTA更新的固件。
otadatadata, ota记录当前活动的OTA分区(如选择 ota_0 或 ota_1),大小固定为 8KB。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值