Flash 分区表
ESP32 的 Flash(通常 4MB)通过分区表划分不同用途区域。分区表位于 Flash 的固定地址(0x8000),记录每个分区的类型、子类型、起始地址和大小。
ESP32 典型 Flash 分区布局(4MB):
偏移地址 大小 类型 名称
─────────────────────────────────────────────────────────
0x000000 32KB bootloader 引导程序(只读)
0x008000 3KB partition 分区表本身
0x008C00 16KB nvs NVS 存储(Wi-Fi 校准、用户配置)
0x00c000 8KB phy_init PHY 初始化数据(RF 校准)
0x010000 1.9MB factory 出厂应用固件
0x1f0000 1.9MB ota_0 OTA 固件 A(双 OTA 时使用)
0x3d0000 192KB spiffs SPIFFS/LittleFS 文件系统
─────────────────────────────────────────────────────────
自定义分区表(partitions.csv):
# Name, Type, SubType, Offset, Size, Flags
nvs, data, nvs, 0x9000, 0x6000,
ota_data, data, ota, 0xf000, 0x2000,
app0, app, ota_0, 0x10000, 0x1E0000,
app1, app, ota_1, 0x1F0000, 0x1E0000,
littlefs, data, spiffs, 0x3D0000, 0x30000,
NVS — 非易失性存储
NVS(Non-Volatile Storage)是 ESP-IDF 提供的键值对存储系统,数据存储在 Flash 的专用分区,断电后保留。它是存储 Wi-Fi 密码、设备配置、校准参数等小量数据的最佳方式。
NVS 核心概念
Namespace(命名空间)
类似文件夹,隔离不同模块的键值对。键名在同一命名空间内唯一,不同命名空间可有同名键。命名空间名最长 15 字节。
键(Key)
字符串标识符,最长 15 字节。支持存储类型:uint8/16/32/64、int8/16/32/64、float、double、字符串、二进制数据(Blob)。
磨损均衡
NVS 内部实现了 Flash 磨损均衡算法,避免反复写同一位置导致 Flash 寿命缩短。理论上每个扇区可擦写约 10 万次,NVS 将写操作分散到整个 NVS 分区。
NVS 分区大小建议
至少 0x6000(24KB)。系统需要约 8KB 存储 Wi-Fi 配置和 PHY 校准数据,剩余才是用户可用空间。
NVS 读写示例
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"
#define NVS_NS "app_config" // 命名空间
static const char *TAG = "NVS";
void nvs_write_config(const char *ssid, const char *password, int32_t interval)
{
nvs_handle_t handle;
ESP_ERROR_CHECK(nvs_open(NVS_NS, NVS_READWRITE, &handle));
nvs_set_str(handle, "wifi_ssid", ssid);
nvs_set_str(handle, "wifi_pass", password);
nvs_set_i32(handle, "report_interval", interval);
ESP_ERROR_CHECK(nvs_commit(handle)); // 必须 commit 才真正写入 Flash
nvs_close(handle);
ESP_LOGI(TAG, "配置已保存");
}
void nvs_read_config(void)
{
nvs_handle_t handle;
esp_err_t err = nvs_open(NVS_NS, NVS_READONLY, &handle);
if (err == ESP_ERR_NVS_NOT_FOUND) {
ESP_LOGW(TAG, "命名空间不存在,使用默认配置");
return;
}
char ssid[33] = {0};
size_t ssid_len = sizeof(ssid);
nvs_get_str(handle, "wifi_ssid", ssid, &ssid_len);
int32_t interval = 60; // 默认值
nvs_get_i32(handle, "report_interval", &interval);
ESP_LOGI(TAG, "SSID: %s, 上报间隔: %" PRId32 "秒", ssid, interval);
nvs_close(handle);
}
void app_main(void)
{
/* 初始化 NVS(必须在 Wi-Fi 初始化前调用)*/
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()); // 清空后重试
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
nvs_write_config("HomeWiFi", "password123", 30);
nvs_read_config();
}
LittleFS — 推荐文件系统
LittleFS 是 ARM Mbed 团队开发的嵌入式文件系统,专为 Flash 存储设计,具有断电安全、磨损均衡、压缩等特性。ESP-IDF v5 起推荐使用 LittleFS 替代老旧的 SPIFFS。
LittleFS 优点
- 支持真正的目录结构
- 原子操作(断电安全)
- 更好的磨损均衡
- 更快的文件系统修复
SPIFFS 局限性
- 不支持目录(所有文件在根)
- 断电可能损坏文件
- 大量小文件时性能下降
- 官方建议迁移到 LittleFS
#include "esp_littlefs.h"
#include "esp_log.h"
#include <stdio.h>
#include <sys/stat.h>
static const char *TAG = "LFS";
void littlefs_init(void)
{
esp_vfs_littlefs_conf_t conf = {
.base_path = "/littlefs", // 挂载点
.partition_label = "littlefs", // 分区表中的名称
.format_if_mount_failed = true, // 挂载失败时自动格式化
};
ESP_ERROR_CHECK(esp_vfs_littlefs_register(&conf));
size_t total = 0, used = 0;
esp_littlefs_info("littlefs", &total, &used);
ESP_LOGI(TAG, "文件系统: 总计 %d KB, 已用 %d KB", total/1024, used/1024);
}
void write_sensor_log(float temp, float hum, uint32_t timestamp)
{
FILE *f = fopen("/littlefs/sensor.csv", "a");
if (!f) {
ESP_LOGE(TAG, "无法打开文件");
return;
}
fprintf(f, "%" PRIu32 ",%0.1f,%0.1f\n", timestamp, temp, hum);
fclose(f);
}
void read_sensor_log(void)
{
FILE *f = fopen("/littlefs/sensor.csv", "r");
if (!f) return;
char line[64];
while (fgets(line, sizeof(line), f)) {
ESP_LOGI(TAG, "%s", line);
}
fclose(f);
}
上传文件到 Flash 的方法
将本地文件(HTML/配置文件/证书)打包到 LittleFS 镜像中:
1. 将文件放入 data/ 目录
2. CMakeLists.txt 中添加 littlefs_create_partition_image(littlefs ../data FLASH_IN_PROJECT)
3. idf.py flash 时会自动打包上传
SD 卡(SPI 模式)
#include "driver/spi_common.h"
#include "driver/sdspi_host.h"
#include "sdmmc_cmd.h"
#include "esp_vfs_fat.h"
#define SD_CS GPIO_NUM_5
#define SD_CLK GPIO_NUM_18
#define SD_MOSI GPIO_NUM_23
#define SD_MISO GPIO_NUM_19
void sd_card_init(void)
{
sdmmc_host_t host = SDSPI_HOST_DEFAULT();
spi_bus_config_t bus_cfg = {
.mosi_io_num = SD_MOSI,
.miso_io_num = SD_MISO,
.sclk_io_num = SD_CLK,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_bus_initialize(host.slot, &bus_cfg, SDSPI_DEFAULT_DMA);
sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
slot_config.gpio_cs = SD_CS;
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_vfs_fat_sdspi_mount("/sdcard", &host, &slot_config, &mount_config, &card);
sdmmc_card_print_info(stdout, card);
/* 之后可以用标准 POSIX 文件 API */
FILE *f = fopen("/sdcard/log.txt", "a");
fprintf(f, "ESP32 启动\n");
fclose(f);
}
SD 卡 SPI 模式速度限制
SPI 模式下 SD 卡最高约 20~25 MHz,实际读写速度约 2~4 MB/s。若需更高速度(如连续录制音频/视频),应选用 SDMMC 4-bit 接口,但需注意引脚冲突(GPIO 12/13/14/15 与 JTAG 和 Flash 共用)。