如何用 Zephyr NVS 持久化保存应用配置并在重启后读取
2026/9/15 18:42:36 网站建设 项目流程

如何用 Zephyr NVS 持久化保存应用配置并在重启后读取

【免费下载链接】zephyrPrimary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.项目地址: https://gitcode.com/GitHub_Trending/ze/zephyr

如果你的嵌入式应用需要在掉电或重启后仍然保留某些配置——例如一个 IP 地址字符串、一把密钥二进制数据、一个重启计数器——Zephyr 的 NVS(Non-Volatile Storage,非易失性存储)服务提供了一种基于 id-data 键值对的 flash 存储机制:用 16 位无符号整数作为 id,把二进制数据块、字符串、整型或它们的任意组合写进 flash,重启后按 id 读回。本文以仓库中现成的示例 samples/subsys/kvss/nvs 为主线,说明如何挂载 NVS、写入与读取配置、并通过示例应用自动重启验证数据确实被持久化。适用前提是目标开发板具备 flash 支持(这是示例文档列出的唯一硬件要求)。

先了解 NVS 的存储模型,参数才不会配错

在写代码之前,需要知道几个影响nvs_fs结构体配置的事实(来源:NVS 服务文档):

  • 每个条目以 id(16 位)+ data 的形式存储。每个条目在 flash 中占 8 字节 metadata 加数据本身,metadata 自带一个 CRC,只保证写操作完成;数据本身可选地用 CRC-32 保护(CONFIG_NVS_DATA_CRC)。
  • flash 区域被划分为若干 sector,条目以 FIFO 方式追加到当前 sector 的末尾写位置;sector 写满后启用一个新 sector。擦除 sector 前,NVS 会检查该 id-data 对是否还有其他 sector 中的副本,没有就先拷贝过去,因此任何已用 id 始终至少保留一份有效数据。
  • 如果某 id-data 对与已存内容相同,NVS 不会真正写 flash。
  • 当 free space 不足、即将陷入反复擦除时,NVS 会返回"没有空间"而不是无限擦除。

准备条件:启用 flash 与 NVS 的 Kconfig

示例自带的 prj.conf 给出了完成本场景所需的最小配置:

CONFIG_FLASH=y CONFIG_NVS=y CONFIG_LOG=y CONFIG_LOG_MODE_IMMEDIATE=y CONFIG_NVS_LOG_LEVEL_DBG=y CONFIG_REBOOT=y CONFIG_MPU_ALLOW_FLASH_WRITE=y CONFIG_MAIN_STACK_SIZE=2048

其中与存储直接相关的是CONFIG_FLASH(flash 驱动)和CONFIG_NVSCONFIG_REBOOT是示例中用sys_reboot()演示"重启后读回"所需的能力;CONFIG_MPU_ALLOW_FLASH_WRITE在启用 MPU 时访问片内 SoC flash 所必需(后文排查部分会解释为什么)。CONFIG_LOGCONFIG_LOG_MODE_IMMEDIATECONFIG_NVS_LOG_LEVEL_DBG只是为了让调试输出更完整,CONFIG_MAIN_STACK_SIZE=2048调大了主线程栈。

挂载 NVS:配置 struct nvs_fs

NVS 的文件系统用struct nvs_fs描述(定义见 include/zephyr/kvss/nvs.h)。文档要求四个关键参数:

  • flash_device:指向一个已就绪的 flash 设备;
  • sector_size:必须同时满足是 flash 擦除页大小的整数倍、且为 2 的幂;
  • sector_count:至少 2,因为总有一个 sector 被保留为空,用于在擦除前拷贝现有数据;
  • offset:NVS 存储区在 flash 中的起始偏移。

示例代码(摘自 samples/subsys/kvss/nvs/src/main.c)用设备树中的storage_partition分区直接取到设备和偏移,并用 flash 驱动查询出页大小作为 sector 大小:

#include <zephyr/drivers/flash.h> #include <zephyr/storage/flash_map.h> #include <zephyr/kvss/nvs.h> static struct nvs_fs fs; #define NVS_PARTITION storage_partition #define NVS_PARTITION_DEVICE PARTITION_DEVICE(NVS_PARTITION) #define NVS_PARTITION_OFFSET PARTITION_OFFSET(NVS_PARTITION) int main(void) { struct flash_pages_info info; fs.flash_device = NVS_PARTITION_DEVICE; if (!device_is_ready(fs.flash_device)) { printk("Flash device %s is not ready\n", fs.flash_device->name); return 0; } fs.offset = NVS_PARTITION_OFFSET; if (flash_get_page_info_by_offs(fs.flash_device, fs.offset, &info)) { return 0; } fs.sector_size = info.size; /* sector 大小取 flash 页大小 */ fs.sector_count = 3U; /* 示例用 3 个 sector */ if (nvs_mount(&fs)) { printk("Flash Init failed\n"); return 0; } ... }

挂载成功后nvs_mount()返回 0;失败时返回负 errno。此时 flash 上已有的 NVS 内容会在初始化阶段被校验,metadata 缺失或错误的条目会被忽略,不会报错中断。

写入与读取配置:nvs_write / nvs_read 的返回语义

NVS 的高层 API 在 include/zephyr/kvss/nvs.h 中声明,核心函数及返回值约定:

  • nvs_write(fs, id, data, len):写入条目。成功时返回实际写入字节数;若内容与已存数据相同则不写 flash、返回 0;出错返回负 errno。len为 0 时等效于删除该条目。
  • nvs_read(fs, id, data, len):读取最新条目。成功时返回值等于请求的字节数;若条目实际数据比请求长度更长,返回值会大于请求长度,表示还有数据未读完;出错返回负 errno(例如条目不存在)。
  • nvs_read_hist(fs, id, data, len, cnt):按历史序号读取,cnt=0是最新值,cnt=1是次新值,以此类推;读到历史尽头返回负值。
  • nvs_delete(fs, id):删除条目。
  • nvs_calc_free_space(fs)/nvs_sector_max_data_size(fs):分别计算整个文件系统可用空间、当前活跃 sector 的连续可用空间。注意前者在 SPI flash 上是耗时操作。

示例中的"先读、读不到再写"模式是持久化配置的典型用法,以保存一个重启计数器为例:

uint32_t reboot_counter = 0U; if (nvs_read(&fs, RBT_CNT_ID, &reboot_counter, sizeof(reboot_counter)) > 0) { /* 条目存在,打印读回的当前值 */ printk("Id: %d, Reboot_counter: %d\n", RBT_CNT_ID, reboot_counter); } else { /* 条目不存在(首次上电),写入初值 */ printk("No Reboot counter found, adding it at id %d\n", RBT_CNT_ID); nvs_write(&fs, RBT_CNT_ID, &reboot_counter, sizeof(reboot_counter)); }

示例对不同类型数据各存了一个条目:id=1 存字符串 "192.168.1.1"(IP 地址),id=2 存 8 字节密钥二进制,id=3 存 32 位重启计数器,id=4、5 分别用于演示删除和大数组。字符串按strlen(buf)+1写入,即连同结尾\0一起持久化。

运行示例并用控制台输出验证重启后读回

示例应用会周期性调用sys_reboot(0)自动重启,每次重启把计数器加 1 并写回 flash,然后从 flash 读出——如果重启后读到的值是递增后的数字,就证明数据确实跨重启保留了下来。三个行为参数在 Kconfig 中可调:

  • CONFIG_NVS_SAMPLE_MAX_REBOOT:最大重启次数,默认 5,范围 1~1000;
  • CONFIG_NVS_SAMPLE_REBOOT_COUNTDOWN:重启倒计时迭代次数,默认 5,范围 1~60;
  • CONFIG_NVS_SAMPLE_SLEEP_TIME:每次迭代的睡眠毫秒数,默认 100,范围 10~10000。

按示例 README 以 nrf51dk/nrf51822 为例构建并烧录(对应文档中 build + flash 两个目标):

west build -b nrf51dk/nrf51822 samples/subsys/kvss/nvs west flash

烧录后观察控制台。文档给出的示例输出(文档示例,实际版本号和日志会随 Zephyr 版本变化):

***** Booting Zephyr OS v1.12.0-rc1-176-gf091be783 ***** No address found, adding 192.168.1.1 at id 1 No key found, adding it at id 2 No Reboot counter found, adding it at id 3 Id: 4 not found, adding it Longarray not found, adding it as id 4 Reboot counter history: ...0 Oldest reboot counter: 0 Rebooting in ...5...4...3...2...1 ***** Booting Zephyr OS v1.12.0-rc1-176-gf091be783 ***** Entry: 1, Address: 192.168.1.1 Id: 2, Key: ff fe fd fc fb fa f9 f8 Id: 3, Reboot_counter: 1 Id: 4, Data: DATA Id: 5, Longarray: 0 1 2 3 4 5 6 7 8 9 a b c d e f 10 11 12 13 14 15 16 17 18 Reboot counter history: ...1...0 Oldest reboot counter: 0 Rebooting in ...5...4...3...2...1

第一次启动所有条目都是 "not found, adding it",第二次启动则直接打印出读回的各条目值,且Reboot_counter为 1——这就是"重启后成功读取"的判定依据。历史读取(nvs_read_hist)在重启倒计时开始时输出,示例中Reboot counter history: ...1...0表示该 id 有两条历史值,最新是 1。当计数器达到CONFIG_NVS_SAMPLE_MAX_REBOOT后,示例会把它重置为 0 并停止重启(打印 "Reboot counter reached max value.")。

排查与限制

  • MPU fault 或 API 返回-ETIMEDOUT:文档明确给出原因——NVS 使用片内 SoC flash 且启用 MPU 时,flash 驱动需要 MPU 对 flash 的 RWX 访问,由CONFIG_MPU_ALLOW_FLASH_WRITE控制。若该选项未启用,NVS 访问片内 flash 时会产生 MPU fault;单线程应用中直接表现为 fault,多线程应用中可能被其他线程拦截,此时 NVS API 返回-ETIMEDOUT。示例的 prj.conf 因此启用了该选项。
  • 不要事后开启数据 CRC:文档明确警告,对已存在且未启用 CRC 的 NVS 内容开启CONFIG_NVS_DATA_CRC会使所有既有数据失效;且数据 CRC 只在整段读取时校验,部分读取不校验。
  • flash 寿命与 sector 规划:文档给出预期寿命估算式(以分钟计):SECTOR_COUNT * SECTOR_SIZE * PAGE_ERASES / (NS * (DS+8)),其中NS为每分钟存储请求数、DS为数据字节数、DS+8即数据加 metadata。预期寿命偏短时,文档给出的对策是增大SECTOR_COUNTSECTOR_SIZE,而不是改变写入频率。
  • 删除后的条目:被nvs_delete删除(或 sector 回收时历史被清理)的旧值无法再用nvs_read读到,但可能仍可用nvs_read_hist读到历史副本,直到该 sector 被擦除。

如果你的配置项较多、需要按名字而不是数字 id 管理,Zephyr 还提供服务层之上的 Settings 服务(基于存储设备组织 key-string 结构),可作为 NVS 之后的延伸阅读。

【免费下载链接】zephyrPrimary Git Repository for the Zephyr Project. Zephyr is a new generation, scalable, optimized, secure RTOS for multiple hardware architectures.项目地址: https://gitcode.com/GitHub_Trending/ze/zephyr

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询