ESP32 SPI Flash优化文件系统与分区策略一、先说结论4MB Flash 建议分 5 个区nvs(24KB) otadata(8KB) app(1.5MB × 2) spiffs(512KB)8MB Flash 可加 FAT 分区存历史数据。ESP32 的 Flash不只是存固件合理规划分区可同时放下Bootloader、应用程序、OTA分区、NVS参数、SPIFFS/LittleFS文件系统、FAT存传感器数据Flash 空间有限——4MB 怎么分才够用分区策略直接影响产品功能分区一旦定下OTA 升级不能改分区表所以前期就要规划好二、默认分区表ESP-IDF 默认分区表partitions_singleapp.csv# Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x4000 otadata, data, ota, 0xd000, 0x2000 app, app, factory, 0x10000, 1M只用了约1.04MB剩余3MB 浪费了。实际项目要自定义分区表。三、自定义分区表3.1 4MB Flash 推荐分区# partitions_4mb.csv # Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x6000 # 24KB NVS otadata, data, ota, 0xf000, 0x2000 # 8KB OTA数据 phy_init, data, phy, 0x11000, 0x1000 # 4KB PHY校准 factory, app, factory, 0x20000, 0x100000 # 1MB 工厂固件 ota_0, app, ota_0, 0x120000, 0x100000 # 1MB OTA分区0 ota_1, app, ota_1, 0x220000, 0x100000 # 1MB OTA分区1 spiffs, data, spiffs, 0x320000, 0x80000 # 512KB 文件系统编译时指定分区表idf.py-DSDKCONFIG_DEFAULTSsdkconfig.defaults\-DPARTITION_TABLE_CUSTOMpartitions_4mb.csv\-DPARTITION_TABLE_CUSTOM_FILENAMEpartitions_4mb.csv\build或在sdkconfig中设置CONFIG_PARTITION_TABLE_CUSTOMy CONFIG_PARTITION_TABLE_CUSTOM_FILEpartitions_4mb.csv3.2 8MB Flash 分区ESP32-S3-WROOM-1 有 8MB Flash可以更宽裕# partitions_8mb.csv # Name, Type, SubType, Offset, Size nvs, data, nvs, 0x9000, 0x10000 # 64KB NVS otadata, data, ota, 0x19000, 0x2000 # 8KB OTA数据 phy_init, data, phy, 0x1b000, 0x1000 # 4KB PHY factory, app, factory, 0x20000, 0x200000 # 2MB 工厂固件 ota_0, app, ota_0, 0x220000, 0x200000 # 2MB OTA分区0 ota_1, app, ota_1, 0x420000, 0x200000 # 2MB OTA分区1 spiffs, data, spiffs, 0x620000, 0x100000 # 1MB SPIFFS fatfs, data, fat, 0x720000, 0xE0000 # 896KB FAT四、LittleFS 文件系统LittleFS 比 SPIFFS 性能更好——磨损均衡更优秀、断电不丢数据、支持目录#includeesp_littlefs.hvoidlittlefs_init(void){esp_vfs_littlefs_conf_tconf{.partition_labelspiffs,// 复用spiffs分区.mount_point/data,.format_if_mount_failedtrue,.dont_mountfalse,};esp_err_terresp_vfs_littlefs_register(conf);if(err!ESP_OK){printf(LittleFS mount failed: %s\n,esp_err_to_name(err));return;}size_ttotal0,used0;esp_littlefs_info(spiffs,total,used);printf(LittleFS: total%zu used%zu free%zu\n,total,used,total-used);}// 用标准C函数操作文件voidsave_config(void){FILE*ffopen(/data/config.json,w);if(f){fprintf(f,{\ssid\:\HomeWiFi\,\interval\:30});fclose(f);}}voidload_config(void){FILE*ffopen(/data/config.json,r);if(f){charbuf[256];fgets(buf,sizeof(buf),f);fclose(f);printf(Config: %s\n,buf);}}五、FAT 文件系统FAT 适合存传感器历史数据——Windows 直接读#includeesp_vfs_fat.hstaticwl_handle_twl_handle;voidfatfs_init(void){esp_vfs_fat_sdmmc_mount_config_tmount_config{.format_if_mount_failedtrue,.max_files5,.allocation_unit_size512,};// 挂载到内部Flash的FAT分区esp_err_terresp_vfs_fat_spiflash_mount(/fat,fatfs,mount_config,wl_handle);if(err!ESP_OK){printf(FAT mount failed: %s\n,esp_err_to_name(err));return;}printf(FAT mounted at /fat\n);}// 写传感器数据CSVvoidlog_sensor_data(floattemp,floathumi){time_tnow;time(now);structtm*tlocaltime(now);FILE*ffopen(/fat/sensor_log.csv,a);if(f){fprintf(f,%04d-%02d-%02d %02d:%02d:%02d,%.2f,%.2f\n,t-tm_year1900,t-tm_mon1,t-tm_mday,t-tm_hour,t-tm_min,t-tm_sec,temp,humi);fclose(f);}}六、NVS 存储参数NVSNon-Volatile Storage专门存键值对配置#includenvs_flash.h#includenvs.hvoidnvs_save_wifi(constchar*ssid,constchar*password){nvs_handle_thandle;esp_err_terrnvs_open(wifi,NVS_READWRITE,handle);if(err!ESP_OK)return;nvs_set_str(handle,ssid,ssid);nvs_set_str(handle,password,password);nvs_commit(handle);nvs_close(handle);}voidnvs_load_wifi(char*ssid,size_tssid_len,char*password,size_tpass_len){nvs_handle_thandle;esp_err_terrnvs_open(wifi,NVS_READONLY,handle);if(err!ESP_OK){strcpy(ssid,);strcpy(password,);return;}nvs_get_str(handle,ssid,ssid,ssid_len);nvs_get_str(handle,password,password,pass_len);nvs_close(handle);}// 存二进制数据voidnvs_save_calib(float*offsets,intcount){nvs_handle_thandle;nvs_open(calib,NVS_READWRITE,handle);nvs_set_blob(handle,offsets,offsets,sizeof(float)*count);nvs_commit(handle);nvs_close(handle);}七、动态分区管理有时候需要运行时查看分区信息#includeesp_partition.hvoidprint_partition_info(void){// 遍历所有分区esp_partition_iterator_titesp_partition_find(ESP_PARTITION_TYPE_ANY,ESP_PARTITION_SUBTYPE_ANY,NULL);while(it){constesp_partition_t*partesp_partition_get(it);printf(%-10s type%d subtype%d addr0x%06lx size%luKB\n,part-label,part-type,part-subtype,(unsignedlong)part-address,(unsignedlong)part-size/1024);itesp_partition_next(it);}esp_partition_iterator_release(it);}// 查找特定分区constesp_partition_t*find_ota_partition(intslot){esp_partition_subtype_tsubtype(slot0)?ESP_PARTITION_SUBTYPE_APP_OTA_0:ESP_PARTITION_SUBTYPE_APP_OTA_1;returnesp_partition_find_first(ESP_PARTITION_TYPE_APP,subtype,NULL);}八、Flash 加密对安全要求高的产品启用 Flash 加密# menuconfig Security features → Flash Encryption → Enable Security features → Secure Boot → Enable加密后所有 Flash 内容加密存储用AES-256-XTS。读取时硬件自动解密。不烧 key 的设备读 Flash 看到的是密文。九、实测数据ESP32-WROOM-32 4MB Flash 40MHz操作速度读取4KB1.2ms写入4KB35ms擦除4KB Sector40ms擦除32KB Block120ms擦除64KB Block200msSPIFFS读取4KB1.8msLittleFS读取4KB1.5msFAT读取4KB1.4msNVS写入100字节25ms十、存储方案选型需求推荐方案原因WiFi凭证/配置参数NVS键值对原子写入网页/证书/音频LittleFS文件系统断电安全传感器历史CSVFATWindows可读小量二进制数据NVS blob简单大文件存储外挂SD卡容量大十一、踩坑清单#坑点建议1分区大小对齐分区起始地址必须 4KB 对齐app 分区必须 16KB 对齐写错会启动失败严格按对齐要求设计2app分区大小至少要留 20% 余量固件超过分区会编译失败预留空间3OTA分区大小两个 OTA 分区必须一样大否则 OTA 切换会失败保持等大4NVS磨损频繁写入会磨损 Flash同一块擦写 10 万次后会坏配置类数据偶尔写没问题5SPIFFS断电断电可能损坏文件系统改用 LittleFS断电不丢数据6FAT磨损没有磨损均衡频繁写同一文件会磨损用 WLWear Leveling层缓解7Flash加密启用后分区表也会加密key 丢失则 Flash 数据全部作废妥善保管 key8分区表修改改分区表后要idf.py erase-flash重新烧不擦除直接烧会分区错乱改后全擦重烧9PSRAM占用FlashESP32 的 PSRAM 和 Flash 共享 SPI 总线PSRAM 占 4 根线Flash 带宽减半评估带宽影响10多线程访问多任务同时读写 Flash 要加锁写操作会暂停 CPU 访问 Flash含取指令导致其他任务卡顿加锁并避免高频写