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 0x01
const 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;
// 初始化 NVS
ret = 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的SSID
memset(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的SSID
memset(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 |
思维导图如下,仅供参考

3494

被折叠的 条评论
为什么被折叠?



