“ 本文围绕 ESP32-S3 展开,讲解其 SPI Flash 分区表设置、与 Flash 的连接,还介绍分区表配置、各分区初始化挂载。”
01 简介
1.ESP32-S3 的 分区表(Partition Table)
是一种用于划分 SPI Flash 存储空间的配置机制。它的作用类似于电脑硬盘的分区,把 Flash 划分为多个区域,每个区域用于存储不同类型的数据或程序,比如:
- 应用程序(如 factory、ota_0、ota_1);
- 系统数据(如 NVS、PHY 初始化数据);
- 文件系统(如 SPIFFS、FATFS)。
2. ESP32-S3 与 SPI Flash 的连接方式
ESP32-S3 通过 SPI/QSPI/OPI 接口 与外部 SPI Flash 通信,典型连接方案如下:
SPI Flash 信号 | ESP32-S3 管脚名称 | 管脚编号(QFN56) | 备注 |
SCK / CLK | SPICLK | 33 | 时钟线 |
MOSI / SI | SPID | 35 | 数据输入 |
MISO / SO | SPIQ | 34 | 数据输出 |
CS# | SPICS0 | 32 | 片选 |
WP# | SPIWP | 31 | 写保护 |
HOLD# / IO3 | SPIHD | 30 | 暂停 /IO3 |
SPI Flash 为外部独立芯片,需通过 PCB 与 ESP32-S3 连接。ESP32-S3 芯片本身不集成 Flash,因此所有程序和数据均存储于外接的 SPI Flash 中。部分模组(如ESP32-S3-WROOM-1
)虽将 Flash 与 SoC 集成在同一块 PCB 上,但本质上仍为外部组件。

3. 通信协议与模式
- 基础 SPI 模式:使用 4 线(CLK、CS、MOSI、MISO),支持标准 SPI 协议;
- QSPI 模式 :通过 4 线同时传输地址和数据,带宽提升至 4 倍,需 Flash 芯片支持(如
qio
或qout
模式); - OPI 模式(Octal SPI):8 线并行传输,适用于高性能需求,但需专用 Flash 型号。
4.ESP32-S3 兼容主流厂商的 SPI Flash,以下为典型型号:
1. 华邦电子(Winbond)
- W25Q64JV:8MB,支持 QSPI,电压 3.3V,封装 SOIC-8;
- W25Q128JV:16MB,最高时钟频率 133MHz,适用于大容量存储需求。
2.兆易创新(GigaDevice)
- GD25Q32C:4MB,低功耗设计(<1mW),支持 XIP(片上执行);
- GD25Q128C:16MB,工作电压 1.7V~3.6V,兼容宽电压系统。
3. 美光(Micron)
- MT25QL128:16MB,采用 Octal SPI 接口,适合高速数据吞吐场景。
02 如何设置分区表
分区表是一个存储在 Flash 固定位置(默认 0x8000)的二进制数据结构,记录了各分区的起始地址、大小、类型、子类型等信息。ESP32-S3 启动时,Bootloader 会先读取分区表,再根据表中信息加载对应分区的程序(如 app 分区)或访问数据(如 NVS 分区)。
分区名 | 谁来负责初始化 | 典型触发代码/ 位置 | 备注 |
nvs | nvs_flash_init() | 用户代码 `app_main() | 必须手动调用 |
phy\_init | Wi-Fi/BT 协议栈内部 | esp_wifi_init() 或 esp_bt_controller_init() | 协议栈自动读取校准数据 |
factory | ROM 引导程序 + CMake 链接 | 上电 ROM 直接映射到地址 0x10000 | 无需应用层初始化 |
vfs(FAT) | esp_vfs_fat_spiflash_mount()` 或 esp_vfs_fat_register() | 用户代码 | 示例:fatfs_spiflash/main.c |
storage (SPIFFS) | esp_spiffs_mount()` 或 esp_vfs_spiffs_register()` | 用户代码 | 示例:spiffsgen/main.c |
具体流程(以 ESP-IDF 框架为例)。
1.分区表的格式
# 名称, 类型, 子类型, 起始偏移量, 大小, 标志(可选)nvs, data, nvs, , 0x40000, # 4MB NVS 分区(存储配置)phy_init, data, phy, , 0x1000, # 射频校准数据分区
factory, app, factory, , 0x100000, # 1MB factory app 分区(默认程序)ota_0, app, ota_0, , 0x100000, # 1MB OTA 分区 0
ota_1, app, ota_1, , 0x100000, # 1MB OTA 分区 1
vfs, data, fat, , 0x200000, # 2MB 文件系统分区
类型(Type):app(应用程序)或 data(数据);
子类型(SubType):app 类型下有 factory(默认程序)、ota_0~ota_15(OTA 分区);data 类型下有 nvs、phy(射频数据)、fat(文件系统)等;
偏移量(Offset):可省略(自动按顺序分配),但需确保不重叠;
大小:支持 KB(如 64KB)、MB(如 2MB)或十六进制(如 0x10000)。
2.手动创建分区表
创建步骤:
1)创建分区表文件:在项目根目录下新建 partitions.csv,按上述格式填写分区信息。
2)指定分区表路径:
- 在项目的 CMakeLists.txt 中添加:
set(PARTITION_TABLE_CSV partitions.csv) # 指向自定义分区表文件
- 或通过 ESP-IDF 配置工具(menuconfig)设置:
3)进入 Partition Table → Partition Table (Custom partition table CSV) → 输入自定义 CSV 文件路径(如 partitions.csv)。
3.VSCode ESP-IDF 工程自动创建分区表
大多数情况下,使用 VSCode ESP-IDF 自动创建的分区表。VSCode ESP-IDF 工程会使用框架自带的默认分区表(default_partitions.csv),适用于大多数基础场景(包含 factory app、nvs、phy_init 等必要分区)。
若使用默认分区表:无需额外操作,正常编译(Build)和烧录(Flash)即可,VSCode 会自动处理分区表的生成和烧录。
4. 需要修改分区表的场景
1)需要支持 OTA 升级
默认分区表不含 OTA 分区,若需实现无线升级功能,必须添加至少两个 OTA 分区(如 ota_0 和 ota_1),示例:
csv
ota_0, app, ota_0, , 1M,
ota_1, app, ota_1, , 1M,
2)默认分区大小不足
若 NVS 分区(默认 5KB)存储不下设备配置(如多个 WiFi 密码、传感器校准数据),需扩大其容量(如 0x40000 即 256KB);
若使用 FAT 文件系统存储大量日志或文件,需新增或扩大 fat 类型分区(如 2M)。
3)自定义数据分区需求
需独立存储特定数据(如固件备份、加密密钥)时,可新增自定义数据分区,例如:
csv
firmware_backup, data, 0x80, , 512KB, # 子类型 0x80 为自定义
4)Flash 容量超过默认分区表支持范围
默认分区表适用于 4MB 及以下 Flash,若使用 8MB/16MB Flash 且需充分利用空间,需重新规划分区大小(如扩大 app 分区至 4MB)。
5)多应用程序切换
需在设备中运行多个独立应用(如主程序 + 调试程序)时,需为每个应用分配独立的 app 分区。
03 各分区的“初始化 / 挂载
1.nvs
只要打算使用 NVS(非易失性存储)保存 / 读取键值数据,就必须在初始化阶段调用 nvs_flash_init()。没有它,任何 nvs_open、nvs_set_*、nvs_get_* 都会直接返回 ESP_ERR_NVS_NOT_INITIALIZED。
esp_err_t 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());
ESP_ERROR_CHECK(nvs_flash_init());
}
2.phy_init(Wi-Fi/BT 协议栈内部,开发者只需启动协议栈)
/* Wi-Fi 例:协议栈会自动读取 0xF000 处的校准数据 */
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg)); // 内部会加载 phy_init
3. factory(ROM 引导加载器直接运行,应用层无需代码)
4. vfs (FAT) —— 把 0x200000 起的 10 MB 分区挂载为 /vfs
#include "esp_vfs_fat.h"
#include "wear_levelling.h"
#define FAT_PARTITION_LABEL "vfs"
wl_handle_t wl_handle;
void mount_fat(void){
esp_vfs_fat_mount_config_t mount_config = {
.max_files = 8,
.format_if_mount_failed = true,
.allocation_unit_size = 512
};
ESP_ERROR_CHECK(esp_vfs_fat_spiflash_mount_rw_wl( "/vfs", FAT_PARTITION_LABEL, &mount_config, &wl_handle));
}
5. storage (SPIFFS) —— 把 0xC00000 起的 4 MB 分区挂载为 /spiffs
#include "esp_spiffs.h"
#define SPIFFS_PARTITION_LABEL "storage"
void mount_spiffs(void){
esp_vfs_spiffs_conf_t conf = {
.base_path = "/spiffs",
.partition_label = SPIFFS_PARTITION_LABEL,
.max_files = 5,
.format_if_mount_failed = true
};
ESP_ERROR_CHECK(esp_vfs_spiffs_register(&conf));
}
6. app_main() 模板
void app_main(void){
/* 1. NVS 初始化 */
esp_err_t 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());
ESP_ERROR_CHECK(nvs_flash_init());
}
/* 2. Wi-Fi/BT → 自动使用 phy_init 分区 */
esp_netif_init();
esp_event_loop_create_default();
esp_netif_create_default_wifi_sta();
wifi_init_config_t wifi_cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifi_cfg));
/* 3. 挂载 FAT 文件系统 */
mount_fat();
/* 4. 挂载 SPIFFS 文件系统 */
mount_spiffs();
/* 5. 主循环或其它业务逻辑 */
for (;;) { vTaskDelay(pdMS_TO_TICKS(1000));
}}