通用步骤

[English]

本文档总结了 ESP-IDF 中 非易失性存储(NVS)的通用实现流程,涵盖其基础说明、初始化步骤以及相关执行流程。

通过掌握本文档内容,开发者能够快速理解有关 NVS 关键逻辑,为后续示例学习提供统一参考。

非易失性存储(NVS)

概述

NVS(Non-Volatile Storage)是 ESP 提供的非易失性存储机制,保存在其中的数据即使设备断电或重启后仍然不会丢失。它为开发者提供了一种轻量级、可靠的方式来存储设备配置、状态信息或标记类数据。NVS 的设计理念是简单、结构化和持久化,核心特点是以键值对存储数据,并通过命名空间进行组织和隔离,从而避免不同模块之间的数据冲突。NVS 通过内部页管理机制保证数据安全,并提供标准 API 来进行读写、更新和删除操作。

Flash 存储特性

NVS 的数据最终存储在 ESP 芯片的 Flash 上,因此 Flash 的特性直接影响 NVS 的使用:

  • 按页擦写:Flash 数据以页为单位操作,每页通常为 4 KB 或 16 KB,写入前必须先擦除整页。

  • 擦写寿命有限:每个存储单元的擦写次数有限(例如 10 万次),频繁擦写会加速 Flash 损耗。

  • 页管理机制:NVS 写入或更新数据时,如果页空间不足,会触发垃圾回收,将有效数据搬到新页并擦除旧页,以保证数据完整性。

由于这些特性,NVS 不适合存储大文件或频繁更新的数据,例如音频文件、日志文件等,否则容易造成 Flash 提前损坏和性能下降。NVS 更适合保存少量、结构简单的数据,例如:

  • 配置信息:Wi-Fi SSID、密码、蓝牙配对信息等。

  • 用户设置:亮度、音量、语言偏好等。

  • 状态信息:例如上一次运行的模式、计数器值等。

键值对(Key-Value)

NVS 的核心存储单元是键值对,每条数据都以键值对形式存储,从而实现高效且安全的查找、更新和删除操作。每个键值对由两个部分组成:

  • :类似标签或名称,为字符串类型,用于唯一标识某条数据,长度有限(最长 15 个字符)。

  • :实际要保存的数据,可存储多种数据类型,包括整数(int32_t、int64_t)、字符串或二进制数据(blob)。

写入新数据时,NVS 会找到空闲页空间存储新值。更新已有键时,旧值将被标记为无效,并将新值写入空闲位置,保证数据安全。键值对的概念使 NVS 可以灵活管理不同类型的数据,并且通过唯一键的索引可以快速访问数据。其他说明可参考 键值对

命名空间(Namespace)

命名空间是 NVS 用于隔离不同模块数据的机制,每个命名空间独立管理自己的键值集合,以避免不同模块使用相同键名导致冲突,同时提供逻辑分组,便于管理和访问。

在 Flash 内部,NVS 会根据命名空间分配页进行管理。写入或更新数据时,会定位到对应命名空间所在的页;如果页满,则触发垃圾回收,将有效数据搬到新页并擦除旧页,从而保证数据完整性和安全性。其他说明可参考 命名空间

NVS 通用步骤

NVS 初始化

  1. 初始化非易失性存储

  • 调用 nvs_flash_init() 初始化 NVS,完成对存储介质的准备和挂载。相关参数说明请参考 非易失性存储库 API

  1. 检查返回值

  • 及时发现初始化过程中可能出现的问题,比如 NVS 分区已满或版本不匹配等异常情况,避免程序在后续读写数据时出现错误,导致功能异常甚至崩溃。

  • 若返回异常,调用 nvs_flash_erase() 擦除 NVS 分区。相关参数说明请参考 非易失性存储库 API

    • ESP_ERR_NVS_NO_FREE_PAGES:表示 NVS 分区已满,没有可用的空闲页,无法写入新数据。

    • ESP_ERR_NVS_NEW_VERSION_FOUND:表示当前 NVS 分区的数据版本与库期望版本不匹配,可能需要升级或格式化分区。

  • 擦除分区后需要重新调用初始化函数,并再次检查返回值,确保初始化成功。

打开 NVS 句柄

  • 创建 nvs_handle_t 类型的句柄变量,用于后续存储 NVS 句柄。相关说明可参考 非易失性存储库

  • 调用 nvs_open() 获取指定命名空间的 NVS 句柄,用于对其中的键值对进行读写操作。相关使用及参数说明可参考 非易失性存储库

..note:

获取句柄时,如果指定的命名空间不存在,``nvs_open()`` 会在默认分区中自动创建该命名空间,并返回访问句柄;如果命名空间已存在,则直接返回其访问句柄。

数据读写

整数值读写

  • 调用 nvs_set_i32() 将整数值写入指定命名空间的缓存中。相关使用及参数说明可参考 非易失性存储库

  • 调用 nvs_get_i32() 读取指定命名空间中指定键对应的整数值。相关使用及参数说明可参考 非易失性存储库

字符串读写

  • 调用 nvs_set_str() 将字符串写入指定命名空间的缓存中。相关使用及参数说明可参考 非易失性存储库

  • 调用 nvs_get_str() 读取指定命名空间中指定键对应的字符串值,相关使用及参数说明可参考 非易失性存储库。读取过程中需要调用两次该函数,分别用于:

    • 获取目标字符串在 NVS 中的实际长度(包括结尾 \0),以便动态分配足够的内存。

    • 获取实际字符串数据并保存至分配的内存中,确保数据完整且安全。。

..note:

调用 ``nvs_set_*()`` 写入数据时,数据并不是立即写入闪存,而是先存放在 RAM 中的缓存或 NVS 内部管理的数据结构里。这样做可以减少闪存写操作,延长闪存寿命,并提高写入效率。

查询 NVS 键值

使用迭代器遍历指定命名空间下的所有键,并根据需求执行相应操作。具体说明可查看 NVS 迭代器

删除 NVS 键值

提交更改

  • 调用 nvs_commit() 提价更改,确保将对 NVS 的更改写入闪存存储。相关使用及参数说明可参考 非易失性存储库

..note:

将之前通过 ``nvs_set_*()`` 写入的所有数据真正写入闪存,保证数据持久化。如果不调用 ``nvs_commit()``,即使程序运行结束或断电,RAM 中的数据不会保存到闪存,写入会丢失。

关闭 NVS 句柄

  • 调用 nvs_close() 关闭 NVS 句柄,释放资源。相关使用及参数说明可参考 非易失性存储库