这个页面的 最新开发版本 可能比这个发布的 2.1.0rc1 版本新。

NVS

概述

非易失性存储(NVS) 是一种使用 flash 来存储 key-value 键值对数据的功能模块,本文将介绍 NVS 的一些基本概念。

基本原理

NVS 库通过调用 wm_nvs API 使用主 flash 的部分空间,具体使用 flash 分区表中名称为 nvs 的分区来存储数据。

备注

NVS 最适合存储一些较小的数据,大数据的存储可以考虑文件系统方式。

键值对

NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持的最大长度为 15 个字符。存储值可以是以下几种类型:

  • 整数型: uint8_tint8_tuint16_tint16_tuint32_tint32_tuint64_tint64_t

  • 字符串:以 \0 结尾的字符串;

  • 二进制数据:可变长度的二进制数据 (BLOB);

  • 浮点数: double 类型的浮点数;

警告

存储字符串类型的数据时,会把末尾的 \0 字符也进行存储,即实际存储的长度为 strlen 函数获取到的长度加 1; 在使用接口获取字符串时, 给的 buffer 长度需要比实际字符个数多 1,否则会返回无效参数。

备注

当前 flash 每个 sector 大小为 4096 字节,NVS 的存储不能跨 sector 存储,默认每个 sector 块头部 20 字节用于标记 sector 的使用情况,每个数据 item 有24字节的头部, 所以无论存储的是字符串或者二进制数据,其大小都不能超过 4052 字节。

备注

虽然 NVS 提供了整数、浮点数的读写,但项目中把整数、浮点数转字符串去存储也是可以的。

键值必须唯一,为现有的键写入新值时,会将旧的值及数据类型和值替换。

读取值时会执行数据类型检查。如果读取操作预期的数据类型与对应键的数据类型不匹配,则返回错误。

分组

NVS 不支持分组功能,如果用户需要把键值分组,建议可以把一组键值加一个分组前缀,如 wifi.ssid 。迭代器的遍历接口支持按前缀遍历, 指定前缀遍历就可以获取到该前缀的全部键值对。

NVS迭代器

迭代器允许用于遍历 NVS 中的数据,可以指定获取某个键值,或者指定前缀获取一组键值,不指定时,遍历所有键值。

使用以下函数,可执行相关操作:

  • wm_nvs_entry_find:创建一个不透明迭代器句柄,并让迭代器指向第一条记录,用于后续调用 wm_nvs_entry_next , wm_nvs_entry_infowm_nvs_entry_data 函数;

  • wm_nvs_entry_next:让迭代器指向下一个键值对;

  • wm_nvs_entry_info:返回当前键值对的信息;

  • wm_nvs_entry_data:返回当前键值对的数据;

  • wm_nvs_release_iterator:释放迭代器句柄;

总的来说,所有通过 wm_nvs_entry_find() 获得的迭代器(包括 NULL 迭代器)都必须使用 wm_nvs_release_iterator() 释放。

实现功能

为了用户使用的多样性,我们能实现多种功能:

  • NVS 初始化:

    wm_nvs_init

  • 字符串写入与读取:

    wm_nvs_set_str, wm_nvs_get_str

  • 二进制数据写入与读取:

    wm_nvs_set_blob, wm_nvs_get_blob

  • 8 位、16 位、32 位、64 位整数的写入与读取:

    wm_nvs_set_i8, wm_nvs_get_i8

    wm_nvs_set_i16, wm_nvs_get_i16

    wm_nvs_set_i32, wm_nvs_get_i32

    wm_nvs_set_i64, wm_nvs_get_i64

    wm_nvs_set_u8, wm_nvs_get_u8

    wm_nvs_set_u16, wm_nvs_get_u16

    wm_nvs_set_u32, wm_nvs_get_u32

    wm_nvs_set_u64, wm_nvs_get_u64

  • 浮点数的读写:

    wm_nvs_set_floatwm_nvs_get_float

  • 遍历 NVS 数据项并打印:

    wm_nvs_print

  • 获取数据项的类型与大小:

    wm_nvs_get_info

  • NVS 数据项删除:

    wm_nvs_del

  • NVS 重置:

    wm_nvs_reset

  • NVS 遍历接口:

    wm_nvs_entry_find : 创建迭代器,让迭代器指向第一条记录

    wm_nvs_entry_next : 移动到下一条记录

    wm_nvs_entry_info : 根据迭代器获取记录的键值,类型,数据长度

    wm_nvs_entry_data : 根据迭代器获取记录的数据

    wm_nvs_release_iterator : 释放迭代器

  • 支持 磨损平衡

  • 支持 掉电保护

  • 支持 Hash快速读写

备注

wm_nvs_reset 执行后,将格式化 nvs 的分区,所有存储数据将清空,请谨慎使用。

配置方法

在项目中要调整 NVS 大小时,可以调整分区表中名称为 nvs 的分区,起始地址和大小都可以调整。具体配置如下:

# name,         offset,        size,       flag
nvs,            0x1F0000,      0x8000,     0x0

详细配置请参考 分区表 章节。

备注

在数据写满时需要对已经删除数据进行回收,为预防回收过程断电导致数据丢失,需要一个专门的 sector 做回收使用,所以实际可用空间会少于 4K,size 大小至少要配置 2 个 sector, 即至少 0x2000。

模块配置

  • HASH 表配置

    Hash 表是加快读写而创建的,初始化时会扫描 NVS 存储分区,在内存中建立一个地址偏移表,在读写时,把键值通过 Hash 计算转换成 索引下标,直接从表中得到存储该键值的偏移地址,这样就可以快速的读取到值。初始Hash表入口数为 53 个,后面可以根据写入数据条数 进行扩展,当存储的数据条数超过 75% 时,会动态扩充 Hash 表的长度,每次增加大概 50 个入口。 每个入口占用 3 个字节,关闭 Hash 表能节省的内存空间,节省大小大概为存储数量 * 4,但代价是每次读写要遍历各个 sector。

    CONFIG_NVS_HASH_ENABLED 可以配置是否使用 Hash 表,默认是开启的,Hash 表能大大提升读写速度,不建议关闭。

  • 键值长度配置

    键值长度使用 CONFIG_NVS_ITEM_NAME_MAX_SIZE 配置,默认配置 16,由于要存储结尾的 0,实际字符串最长支持 15 个字符,项目需要更长的键值名称 时可以增大该配置。

备注

修改该键值长度配置后,历史存储数据将丢失。

应用实例

使用 NVS 基本示例请参照 examples/storage