ESP32的系统存储
1. ESP32 中的分区表(Partition Table)
作用:
将 Flash 划分为多个区域,便于管理程序和数据。
支持多种类型的分区:app(程序)、data(数据)、自定义类型等。
分区名 | 类型 | 子类型 | 大小 | 说明 |
nvs | data | nvs | 0x6000 | 非易失存储 |
phy_init | data | phy | 0x1000 | PHY 初始化数据(通常不用) |
factory | app | factory | 1M | 默认启动程序 |
OTA 分区表(partitions_two_ota.csv):
新增 ota data:记录当前 OTA 程序信息
多个 app 分区:ota_0、ota_1 等,支持固件升级
自定义分区:
类型可自定义(0x40–0xFE)
使用 esp_partition_find_first()获取分区指针
使用 esp_partition_write/read() 进行读写
操作自定义分区的 API 示例
#include "esp_partition.h"#define USER_PARTITION_TYPE 0x40
#define USER_PARTITION_SUBTYPE 0x01const esp_partition_t *partition = esp_partition_find_first(USER_PARTITION_TYPE, USER_PARTITION_SUBTYPE, NULL);// 擦除
esp_partition_erase_range(partition, 0, 4096);// 写入
esp_partition_write(partition, 0, "hello", 5);// 读取
char buf[1024];
esp_partition_read(partition, 0, buf, 5);
2. ESP32 中的 NVS(Non-Volatile Storage)
概念:
键值对存储,掉电不丢失
支持命名空间(namespace)隔离不同模块的键名
常用 API:
nvs_open() // 打开命名空间
nvs_set_str() // 写入字符串
nvs_get_str() // 读取字符串
nvs_set_blob() // 写入二进制数据
nvs_get_blob() // 读取二进制数据
nvs_commit() // 提交写入
nvs_close() // 关闭句柄
使用流程:
1. 初始化 nvs_flash_init()
2. 打开命名空间 nvs_open()
3. 读写数据
4. 提交并关闭
完整使用流程示例
#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_err.h"
#include "nvs_flash.h"
#include <string.h>
#include "esp_log.h"#define NAME_SPACE_WIFI1 "wifi1"
#define NAME_SPACE_WIFI2 "wifi2"#define NVS_SSID_KEY "ssid"
#define NVS_PASSWORD_KEY "password"// 修正后的读取函数
esp_err_t nvs_blob_read(const char* namespace_name, const char* key, void* buffer, size_t maxlen)
{nvs_handle_t nvs_handle;esp_err_t err;// 打开命名空间err = nvs_open(namespace_name, NVS_READONLY, &nvs_handle);if (err != ESP_OK) {ESP_LOGE("NVS", "打开命名空间失败: %s", esp_err_to_name(err));return err;}// 第一步:获取数据长度size_t length = 0;err = nvs_get_blob(nvs_handle, key, NULL, &length);if (err != ESP_OK) {ESP_LOGE("NVS", "获取数据长度失败: %s", esp_err_to_name(err));nvs_close(nvs_handle);return err;}// 检查缓冲区是否足够if (length > 0 && length <= maxlen) {// 第二步:实际读取数据err = nvs_get_blob(nvs_handle, key, buffer, &length);if (err != ESP_OK) {ESP_LOGE("NVS", "读取数据失败: %s", esp_err_to_name(err));}} else {ESP_LOGE("NVS", "缓冲区太小或数据长度为0: 需要%d字节,但只有%d字节", length, maxlen);err = ESP_FAIL;}nvs_close(nvs_handle);return err;
}void app_main(void)
{nvs_handle_t nvs_handle1;esp_err_t ret;// 初始化 NVSret = nvs_flash_init();if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_LOGI("NVS", "NVS 需要擦除重建");nvs_flash_erase();ESP_ERROR_CHECK(nvs_flash_init());} else if (ret != ESP_OK) {ESP_LOGE("NVS", "NVS 初始化失败: %s", esp_err_to_name(ret));return;}ESP_LOGI("NVS", "=== 开始写入 NVS 数据 ===");// 命名空间1 - 存储 WiFi 配置ret = nvs_open(NAME_SPACE_WIFI1, NVS_READWRITE, &nvs_handle1);if (ret == ESP_OK) {// 注意:使用 nvs_set_blob 时,长度应该包含字符串终止符nvs_set_blob(nvs_handle1, NVS_SSID_KEY, "wifi_esp32", strlen("wifi_esp32") + 1);nvs_set_blob(nvs_handle1, NVS_PASSWORD_KEY, "12345678", strlen("12345678") + 1);nvs_commit(nvs_handle1);nvs_close(nvs_handle1);ESP_LOGI("NVS", "命名空间 %s 写入完成", NAME_SPACE_WIFI1);}// 命名空间2 - 存储另一个 WiFi 配置ret = nvs_open(NAME_SPACE_WIFI2, NVS_READWRITE, &nvs_handle1);if (ret == ESP_OK) {// 修正:使用正确的字符串长度nvs_set_blob(nvs_handle1, NVS_SSID_KEY, "helloworld", strlen("helloworld") + 1);nvs_set_blob(nvs_handle1, NVS_PASSWORD_KEY, "87654321", strlen("87654321") + 1);nvs_commit(nvs_handle1);nvs_close(nvs_handle1);ESP_LOGI("NVS", "命名空间 %s 写入完成", NAME_SPACE_WIFI2);}vTaskDelay(pdMS_TO_TICKS(1000));ESP_LOGI("NVS", "=== 开始读取 NVS 数据 ===");// 读取并打印所有配置char read_buffer[64];// 读取命名空间1的SSIDmemset(read_buffer, 0, sizeof(read_buffer));if (nvs_blob_read(NAME_SPACE_WIFI1, NVS_SSID_KEY, read_buffer, sizeof(read_buffer)) == ESP_OK) {ESP_LOGI("NVS", "namespace:%s, key:%s -> value:%s", NAME_SPACE_WIFI1, NVS_SSID_KEY, read_buffer);}// 读取命名空间1的密码memset(read_buffer, 0, sizeof(read_buffer));if (nvs_blob_read(NAME_SPACE_WIFI1, NVS_PASSWORD_KEY, read_buffer, sizeof(read_buffer)) == ESP_OK) {ESP_LOGI("NVS", "namespace:%s, key:%s -> value:%s", NAME_SPACE_WIFI1, NVS_PASSWORD_KEY, read_buffer);}// 读取命名空间2的SSIDmemset(read_buffer, 0, sizeof(read_buffer));if (nvs_blob_read(NAME_SPACE_WIFI2, NVS_SSID_KEY, read_buffer, sizeof(read_buffer)) == ESP_OK) {ESP_LOGI("NVS", "namespace:%s, key:%s -> value:%s", NAME_SPACE_WIFI2, NVS_SSID_KEY, read_buffer);}// 读取命名空间2的密码memset(read_buffer, 0, sizeof(read_buffer));if (nvs_blob_read(NAME_SPACE_WIFI2, NVS_PASSWORD_KEY, read_buffer, sizeof(read_buffer)) == ESP_OK) {ESP_LOGI("NVS", "namespace:%s, key:%s -> value:%s", NAME_SPACE_WIFI2, NVS_PASSWORD_KEY, read_buffer);}ESP_LOGI("NVS", "=== 演示完成 ===");
}
3. VFS 与 SPIFFS 文件系统
VFS(虚拟文件系统):
提供统一接口,屏蔽底层文件系统差异
通过 esp_vfs_register() 注册驱动
支持多个挂载点,路径最长匹配
SPIFFS(SPI Flash File系统):
轻量级文件系统,适用于 SPI NOR Flash
不支持目录,路径为扁平结构
使用前需在分区表中定义data, spiffs类型分区使用流程:
// 配置并挂载
esp_vfs_spiffs_conf_t conf = {.base_path = "/spiffs",.partition_label = NULL,.max_files = 5,.format_if_mount_failed = true
};
esp_vfs_spiffs_register(&conf);// 使用标准 C 文件操作
FILE* f = fopen("/spiffs/hello.txt", "w");
fprintf(f, "Hello World!");
fclose(f);// 卸载
esp_vfs_spiffs_unregister(conf.partition_label);
🧩 总结对比:
存储方式 | 适用场景 | 特点 |
分区表 | 程序分区、OTA、自定义数据 | 硬件划分,直接读写 |
NVS | 配置信息、键值对数据 | 轻量、命名空间隔离 |
SPIFFS | 文件存储(如网页、日志) | 支持文件操作,挂载到 VFS |
思维导图如下,仅供参考