外观
3.4 NVS 配置存储
这节要搞懂:怎么让设置断电之后还在。 需要基础:3.3
一、为什么需要它
普通变量存在 RAM 里,一断电就没了。
但闹钟时间、WiFi 密码这些,你肯定希望拔了电源重新插上还在。
二、什么是 NVS
NVS(Non-Volatile Storage,非易失性存储)是 ESP-IDF 提供的键值对存储组件。数据保存在 Flash 中,掉电不丢失。它内部处理了磨损均衡和掉电保护,比直接读写 Flash 安全得多。
打个比方:NVS 就是芯片里的一个小记事本,你按"名字 → 内容"的方式往上记,断电了本子还在。
三、它的数据模型
就是最简单的**"名字 → 值",再用命名空间**(相当于文件夹)分组:
命名空间 "wifi" ├─ "ssid" → "MyHome"
└─ "pass" → "12345678"
命名空间 "clock" ├─ "hour" → 7
└─ "min" → 30为什么要命名空间:避免不同模块起了相同的键名互相覆盖。
四、存一个数字
c
#include "nvs_flash.h"
#include "nvs.h"
esp_err_t storage_save_volume(uint8_t volume)
{
nvs_handle_t handle;
// 1. 打开命名空间(读写模式)
esp_err_t err = nvs_open("clock", NVS_READWRITE, &handle);
if (err != ESP_OK) {
return err;
}
// 2. 写入
err = nvs_set_u8(handle, "volume", volume);
// 3. ⭐ 提交!!不提交等于白写
if (err == ESP_OK) {
err = nvs_commit(handle);
}
// 4. 关闭
nvs_close(handle);
return err;
}最经典的坑:忘了 commit
nvs_set_u8 只是写在缓存里,没有真正存进 Flash。
必须调用 nvs_commit(),否则一断电就全没了。
症状:设置的时候看着好好的,一重启就恢复默认值 —— 十有八九是忘了 commit。
五、读一个数字
c
uint8_t storage_load_volume(void)
{
nvs_handle_t handle;
uint8_t volume = 70; // ⭐ 先给个合理的默认值
if (nvs_open("clock", NVS_READONLY, &handle) == ESP_OK) {
// 读不到的话,volume 保持默认值 70
nvs_get_u8(handle, "volume", &volume);
nvs_close(handle);
}
return volume;
}一定要给默认值
第一次开机时 NVS 里什么都没有,读取会失败。
先赋一个合理的默认值,读失败就用默认的,程序才不会拿到一个随机数。
六、存字符串(比如 WiFi 密码)
c
// 存
esp_err_t storage_save_wifi(const char *ssid, const char *password)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("wifi", NVS_READWRITE, &handle);
if (err != ESP_OK) return err;
nvs_set_str(handle, "ssid", ssid);
nvs_set_str(handle, "pass", password);
err = nvs_commit(handle);
nvs_close(handle);
return err;
}
// 读
esp_err_t storage_load_ssid(char *buffer, size_t buffer_size)
{
nvs_handle_t handle;
if (nvs_open("wifi", NVS_READONLY, &handle) != ESP_OK) {
return ESP_ERR_NVS_NOT_FOUND;
}
size_t length = buffer_size; // ⚠️ 要先填缓冲区大小
esp_err_t err = nvs_get_str(handle, "ssid", buffer, &length);
nvs_close(handle);
return err;
}七、一次存一整个结构体(推荐)
配置一多,一个个存太啰嗦。打包成结构体一次存:
c
typedef struct {
char city[32]; // 天气城市,如 "shenzhen"
char alarm[16]; // "07:30" 或 "close"
bool auto_light; // 自动亮度开关
uint8_t light; // 当前亮度 0~100
uint8_t auto_light_min; // 自动亮度下限
uint8_t auto_light_max; // 自动亮度上限
uint8_t volume; // 音量 0~100
uint8_t refresh_minutes; // 天气刷新间隔(分钟)
float maxtemp_set; // 温度报警上限
float minitemp_set; // 温度报警下限
uint8_t maxhum_set; // 湿度报警上限
uint8_t minihum_set; // 湿度报警下限
} app_config_t;
// 保存
esp_err_t config_save(const app_config_t *config)
{
nvs_handle_t handle;
esp_err_t err = nvs_open("clock", NVS_READWRITE, &handle);
if (err != ESP_OK) return err;
err = nvs_set_blob(handle, "cfg", config, sizeof(app_config_t));
if (err == ESP_OK) {
err = nvs_commit(handle);
}
nvs_close(handle);
return err;
}
// 读取
void config_load(app_config_t *config)
{
// 先填默认值
strcpy(config->city, "shenzhen");
strcpy(config->alarm, "close"); // 默认不开闹钟
config->auto_light = true;
config->light = 80;
config->auto_light_min = 15;
config->auto_light_max = 90;
config->volume = 70;
config->refresh_minutes = 10;
nvs_handle_t handle;
size_t length = sizeof(app_config_t);
if (nvs_open("clock", NVS_READONLY, &handle) == ESP_OK) {
// 读成功就覆盖默认值,失败就保持默认
nvs_get_blob(handle, "cfg", config, &length);
nvs_close(handle);
}
}blob 是什么
blob(Binary Large Object)就是"一坨二进制数据"。
NVS 不关心里面是什么,原样存、原样取。用它存结构体最方便。
八、别频繁写 ⭐
Flash 有擦写寿命
NVS 存在 Flash 里,大约能擦写 10 万次。
| 数据 | 能存吗 |
|---|---|
| 闹钟、城市、亮度、音量、报警阈值 | ✅ 用户改的时候才写,一天几次 |
| 当前温度(2 秒一次) | ❌ 绝对不行!几天就把 Flash 写坏了 |
原则:只在用户改动设置的时候写,绝不定时写。
九、恢复出厂设置
c
nvs_flash_erase(); // 擦掉整个 NVS 分区可以做成"同时长按两个键 5 秒恢复出厂"。
本节要点
- NVS = 存在 Flash 里的键值对存储,掉电不丢
- 用命名空间分组,避免键名冲突
- ⭐
nvs_set之后必须nvs_commit,否则白存 - 读之前先给默认值,第一次开机读不到很正常
- 配置多就用 blob 存整个结构体
- ⭐ 别定时写!Flash 会写坏
操作过程
目标:改一个配置,断电重启,验证它确实保住了。
第 1 步:编译烧录
第 2 步:看开机时读到了什么
在 app_config_init() 之后加一行打印:
c
app_config_t cfg;
app_config_get(&cfg);
ESP_LOGI(TAG, "config loaded: city=%s alarm=%s light=%d volume=%d",
cfg.city, cfg.alarm, cfg.light, cfg.volume);第一次开机(NVS 还是空的)应该看到默认值:
I (1234) main: config loaded: city=shenzhen alarm=close light=80 volume=70第 3 步:改一个配置
用微信小程序连上蓝牙(3.10),往配置特征值写:
json
{"volume":30}串口应该打印出配置被更新了。
第 4 步:断电重启
直接拔掉 USB 再插上 —— 不要按复位键,要真正断电才能验证 NVS。
第 5 步:应该看到什么
I (1234) main: config loaded: city=shenzhen alarm=close light=80 volume=30
↑ 保住了volume 从 70 变成了 30 并且断电后还在 —— 说明 NVS 工作正常。
第 6 步:故意做错一次(重要)
把 app_config.c 里保存配置的那个 nvs_commit() 注释掉,重新烧录,再改一次音量,然后断电重启。
你会看到配置又变回去了。
这就是最经典的 NVS 坑
nvs_set_* 只是写进缓存,不 commit 断电就全丢。
亲手制造一次这个现象,比看十遍文字管用。测完记得把那行加回去。
动手试试
- 存一个数字进 NVS,重启后读出来,确认还在。
- 故意把
nvs_commit()注释掉,重启看数据是不是丢了 —— 加深印象。 - 用蓝牙改一次音量(3.10),重启验证它确实存住了。