第 7 章:NVS 参数持久化
7.1 知识要点
- NVS(Non-Volatile Storage)的分区结构与命名空间
- 基本类型(u8/i32/float)和 blob 的读写 API
- 参数初始化标志位的设计模式
- USB CDC 串口命令解析与参数热更新
7.2 课程内容
机器人控制系统中,PID 参数等调试数据需要在断电后保留。ESP32-S3 的 NVS 提供了一个键值存储系统,基于 Flash 的磨损均衡分区,支持字符串、整数、浮点数和任意二进制数据(blob)的持久化存储。本章演示将 PID 参数以 blob 形式存入 NVS,并通过 USB CDC 串口命令实时修改。
7.3 基础学习
NVS 结构
NVS 使用命名空间(namespace)隔离不同模块的数据,每个命名空间下可以存储多个键值对:
NVS Flash
└── namespace: "pid_params"
├── key: "init" → u8(是否已初始化)
└── key: "params" → blob(pid_params_t 结构体)初始化标志模式
首次上电时 NVS 为空,需要写入默认值。通过一个 init 标志位判断是否已初始化,避免每次重启都覆盖用户修改的参数:
c
uint8_t inited = 0;
nvs_get_u8(h, "init", &inited);
if (!inited) {
// 使用默认值,不从 NVS 读取
}Blob 读写
Blob 可以存储任意结构体,读取时需要传入缓冲区大小:
c
// 写入
nvs_set_blob(h, "params", &g_params, sizeof(pid_params_t));
nvs_commit(h); // 必须 commit 才会真正写入 Flash
// 读取
size_t sz = sizeof(pid_params_t);
nvs_get_blob(h, "params", &g_params, &sz);7.4 程序学习
加载参数(带默认值回退):
c
static void params_load(void)
{
nvs_handle_t h;
if (nvs_open("pid_params", NVS_READONLY, &h) != ESP_OK) goto defaults;
uint8_t inited = 0;
nvs_get_u8(h, "init", &inited);
if (!inited) { nvs_close(h); goto defaults; }
size_t sz = sizeof(pid_params_t);
if (nvs_get_blob(h, "params", &g_params, &sz) == ESP_OK) {
nvs_close(h);
return;
}
defaults:
g_params.kp = 447.0f;
g_params.ki = 4.7f;
g_params.kd = 47.0f;
}保存参数:
c
static void params_save(void)
{
nvs_handle_t h;
ESP_ERROR_CHECK(nvs_open("pid_params", NVS_READWRITE, &h));
nvs_set_blob(h, "params", &g_params, sizeof(pid_params_t));
nvs_set_u8(h, "init", 1);
nvs_commit(h);
nvs_close(h);
}串口命令解析:
c
char line[64];
if (fgets(line, sizeof(line), stdin)) {
float val;
if (sscanf(line, "kp %f", &val) == 1) {
g_params.kp = val;
params_save();
} else if (strcmp(line, "show") == 0) {
printf("kp=%.2f ki=%.2f kd=%.2f\n",
g_params.kp, g_params.ki, g_params.kd);
}
}7.5 课程总结
本章掌握了 NVS 的命名空间、blob 读写和 commit 机制,实现了 PID 参数的断电保持和串口热更新。这一模式适用于所有需要持久化的配置参数,是嵌入式系统调试的重要工具。