ESP32-IDF 非易失存储 NVS

目录

  • 零、前言
  • 一、基本介绍
    • 1、配置结构体
      • 1.1 nvs_entry_info_t
    • 2、常用 API
      • 2.1 nvs_flash_init
      • 2.2 nvs_flash_init_partition
      • 2.3 nvs_flash_init_partition_ptr
      • 2.4 nvs_flash_erase
      • 2.5 nvs_flash_erase_partition
      • 2.6 nvs_flash_erase_partition_ptr
      • 2.7 nvs_flash_generate_keys
      • 2.8 nvs_set_i8
        • 2.8.1 nvs_set_blob
      • 2.9 nvs_get_u8
        • 2.9.1 nvs_get_blob/nvs_get_str
      • 2.10 nvs_open
      • 2.11 nvs_find_key
      • 2.12 nvs_commit
      • 2.13 nvs_close
      • 2.14 nvs_entry_find_in_handle
      • 2.15 nvs_entry_next
      • 2.16 nvs_entry_info
      • 2.17 nvs_release_iterator
    • 3、枚举类型
      • 3.1 nvs_open_mode_t
      • 3.2 nvs_type_t
  • 二、NVS
    • 1、底层存储
    • 2、键值对
    • 3、命名空间
    • 4、NVS 迭代器
    • 5、NVS 分区生成程序
  • 三、实例操作
    • 1、例一——读写操作实验
    • 2、例二——blob 的写入和读取


零、前言

NVS(Non-volatile storage,非易失存储),意思是掉电后能依然能持久化保存数据。在我们应用 NVS 时,一般用于存储一些配置数据、状态数据等,一般不会用来存储存放大量的数据量。

在嵌入式系统中,NVS 主要是在 Flash 进行键值对的存储。举个例子,假设我们要把东西存到 Flash 中,按照底层的操作习惯,我们要先指定一个地址,然后对这个地址执行擦除操作,然后才能写入;读取的时候也需要根据这个地址,然后指定读取长度。如果我们要存的项比较多,又在代码中比较分散,我们对 Flash 的地址就很难管理。因为我们很难知道要存的内容与其他地址有没冲突,会不会误擦除。存在诸多问题。所以需要一个机制,方便帮我们把这些检测判断活都干了,不需要我们指定地址操作。文件系统就是这样的,但 NVS 操作更加轻量级。

在 NVS 中,我们要存一个值,我们不需要指定地址,但需要指定一个“键” key,然后我们在这个“键”索引下存我们的值 value。假设我们要存 WIFI 的 SSID 和 pasword,我们可以在 NVS 中这样定义:

key = ssid,value = testwifi
key = password,value = 12345678
  • 在键名 ssid 下,我们存的值是 testwifi
  • 在键名 password 下,我们存的值是 12345678。

因此键名 key 一般不会修改,经常修改的是 value。

在 ESP32 中对于 NVS 的操作,还需要指定一个命名空间,是因为还考虑了一种情况,在各个不同的功能模块中,键名是有可能取到一样的,比如对于 WIFI 模块,存在一个 password 键名,对于管理员模块,可能也存在一个 password 键名,这样有可能就造成了重复,程序无法按我们的意思进行。如果我们增加了一个命名空间进行隔离,那么键名有重复也不怕,比如说在 WIFI 模块中,我们指定一个命名空间”wifi”,在此命名空间下有 ssid 和 password 键名;在管理员模块,我们指定一个命名空间”manager”,在此命名空间下有 password 键名,这两组命名空间互不干扰。

一、基本介绍

1、配置结构体

1.1 nvs_entry_info_t

存储从 nvs_entry_info() 中获取到的条目信息:

typedef struct {char namespace_name[NVS_NS_NAME_MAX_SIZE];  /*!< Namespace to which key-value belong */char key[NVS_KEY_NAME_MAX_SIZE];            /*!< Key of stored key-value pair */nvs_type_t type;                            /*!< Type of stored key-value pair */
} nvs_entry_info_t;

2、常用 API

2.1 nvs_flash_init

esp_err_t nvs_flash_init(void)
  • 作用
    • 初始化默认 NVS 分区。默认 NVS 分区是在分区表中标记为 “nvs” 的分区。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NO_FREE_PAGES NVS:存储是否不包含空页(如果 NVS 分区被截断,则可能会发生这种情况)
    • ESP_ERR_NOT_FOUND:如果在分区表中找不到标签为 “nvs” 的分区
    • ESP_ERR_NO_MEM:无法为内部结构分配内存

2.2 nvs_flash_init_partition

esp_err_t nvs_flash_init_partition(const char *partition_label)
  • 参数
    • partition_label:分区的标签。不得超过 16 个字符。
  • 作用
    • 初始化指定分区的 NVS 闪存。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NO_FREE_PAGES NVS:存储是否不包含空页(如果 NVS 分区被截断,则可能会发生这种情况)
    • ESP_ERR_NO_MEM:无法为内部结构分配内存

2.3 nvs_flash_init_partition_ptr

esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition)
  • 参数
    • partition:指向 ESP 分区 API 获取的分区的指针。
  • 作用
    • 初始化分区指针指定的分区的 NVS 闪存存储。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NO_FREE_PAGES NVS:存储是否不包含空页(如果 NVS 分区被截断,则可能会发生这种情况)
    • ESP_ERR_NO_MEM:无法为内部结构分配内存

2.4 nvs_flash_erase

esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition)
  • 参数
    • partition:指向 ESP 分区 API 获取的分区的指针。
  • 作用
    • 擦除默认 NVS 分区(带有标签 “nvs” 的分区)的所有内容。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NO_FREE_PAGES NVS:存储是否不包含空页(如果 NVS 分区被截断,则可能会发生这种情况)

如果分区已初始化,则此函数首先 deinit 它。之后,必须再次初始化分区才能使用。

2.5 nvs_flash_erase_partition

esp_err_t nvs_flash_erase_partition(const char *part_name)
  • 参数
    • part_name:应擦除的分区的名称 (标签)
  • 作用
    • 擦除指定的 NVS 分区。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NO_FREE_PAGES NVS:存储是否不包含空页(如果 NVS 分区被截断,则可能会发生这种情况)

如果分区已初始化,则此函数首先 deinit 它。之后,必须再次初始化分区才能使用。

2.6 nvs_flash_erase_partition_ptr

esp_err_t nvs_flash_erase_partition_ptr(const esp_partition_t *partition)
  • 参数
    • partition:指向 ESP 分区 API 获取的分区的指针。
  • 作用
    • 擦除指定自定义分区的所有内容。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NO_FREE_PAGES NVS:存储是否不包含空页(如果 NVS 分区被截断,则可能会发生这种情况)
    • ESP_ERR_INVALID_ARG:分区为 NULL

如果分区已初始化,则此函数首先 deinit 它。之后,必须再次初始化分区才能使用。

2.7 nvs_flash_generate_keys

esp_err_t nvs_flash_generate_keys(const esp_partition_t *partition, nvs_sec_cfg_t *cfg)
  • 参数
    • partition:向使用 esp_partition_find_firstesp_partition_get 获取的分区结构的指针。必须为非 NULL。
    • cfg:指向 nvs 安全配置结构的指针。指针必须为非 NULL。生成的键将填充到此结构中。
  • 作用
    • 擦除指定自定义分区的所有内容。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_INVALID_ARG:partition 或 cfg 为 NULL

2.8 nvs_set_i8

esp_err_t nvs_set_i8(nvs_handle_t handle, const char *key, int8_t value)
  • 参数
    • handle:向使用 esp_partition_find_firstesp_partition_get 获取的分区结构的指针。必须为非 NULL。
    • key:键名称。最大长度为 (NVS_KEY_NAME_MAX_SIZE-1) 个字符。不应为空。
    • value:要设置的值。
  • 作用
    • 为给定键设置 int8_t
  • 返回值
    • ESP_OK:成功
    • ESP_FAIL:如果存在内部错误;很可能是由于 NVS 分区损坏(仅当禁用 NVS 断言检查时)
    • ESP_ERR_NVS_INVALID_HANDLE:句柄是否已关闭或为 NULL
    • ESP_ERR_NVS_READ_ONLY:如果存储句柄以只读方式打开
    • ESP_ERR_NVS_INVALID_NAME:key name 不满足 constraints
    • ESP_ERR_NVS_NOT_ENOUGH_SPACE:如果底层存储中没有足够的空间来保存值
    • ESP_ERR_NVS_REMOVE_FAILED:值是否因闪存写入操作失败而未更新。但是,该值已写入,并且 update 将在重新初始化 nvs 后完成,前提是 flash 操作不会再次失败。

nvs_set_*:类似的还有 u8、i16、u16、i32、u32、i64、u64、str

2.8.1 nvs_set_blob
esp_err_t nvs_set_blob(nvs_handle_t handle, const char *key, const void *value, size_t length)

相比其它几个 get 函数,要注意一下:

  • value:要设置的值。
  • length: 要设置的二进制值的长度,以字节为单位;最大长度为 508000 字节或(分区大小的 97.6% - 4000)字节,以较低者为准。

2.9 nvs_get_u8

esp_err_t nvs_get_u8(nvs_handle_t handle, const char *key, uint8_t *out_value)
  • 参数
    • handle:向使用 esp_partition_find_firstesp_partition_get 获取的分区结构的指针。必须为非 NULL。
    • key:键名称。最大长度为 (NVS_KEY_NAME_MAX_SIZE-1) 个字符。不应为空。
    • value:存储要获取的值。
  • 作用
    • 获取给定键 uint8_t
  • 返回值
    • ESP_OK:成功
    • ESP_FAIL:如果存在内部错误;很可能是由于 NVS 分区损坏(仅当禁用 NVS 断言检查时)
    • ESP_ERR_NVS_INVALID_HANDLE:句柄是否已关闭或为 NULL
    • ESP_ERR_NVS_READ_ONLY:如果存储句柄以只读方式打开
    • ESP_ERR_NVS_INVALID_NAME:key name 不满足 constraints
    • ESP_ERR_NVS_NOT_ENOUGH_SPACE:如果底层存储中没有足够的空间来保存值
    • ESP_ERR_NVS_REMOVE_FAILED:值是否因闪存写入操作失败而未更新。但是,该值已写入,并且 update 将在重新初始化 nvs 后完成,前提是 flash 操作不会再次失败。

nvs_get_*:类似的还有 u8、i16、u16、i32、u32、i64、u64、str、blob

2.9.1 nvs_get_blob/nvs_get_str
esp_err_t nvs_get_blob(nvs_handle_t handle, const char *key, void *out_value, size_t *length)
esp_err_t nvs_get_str(nvs_handle_t handle, const char *key, char *out_value, size_t *length)

相比其它几个 get 函数,要注意一下:

  • out_value:指向输出值的指针。对于 nvs_get_strnvs_get_blob 可能为 NULL,在这种情况下,所需长度将在 length 参数中返回。
  • length: 一个非零指针,指向保存 out_value 长度的变量。
    • 如果 out_value 为 NULL,则将设置为保存该值所需的长度。
    • 如果 out_value 不为零,则将设置为写入值的实际长度。对于 nvs_get_str 来说,nvs_get_blob 还包括零终止符。

2.10 nvs_open

esp_err_t nvs_open(const char *namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle)
  • 参数
    • namespace_name:Namespace 名称。最大长度为 (NVS_KEY_NAME_MAX_SIZE-1) 个字符。不应为空。
    • open_modeNVS_READWRITENVS_READONLY。如果 NVS_READONLY,将打开仅用于读取的句柄。此 handle 的所有写入请求都将被拒绝。
    • out_handle:如果成功(返回代码为零),则在此参数中返回 handle。
  • 作用
    • 从默认 NVS 分区中打开具有给定命名空间的非易失性存储。
  • 返回值
    • ESP_OK:成功
    • ESP_FAIL:如果存在内部错误;很可能是由于 NVS 分区损坏(仅当禁用 NVS 断言检查时)
    • ESP_ERR_NVS_INVALID_HANDLE:句柄是否已关闭或为 NULL
    • ESP_ERR_NVS_READ_ONLY:如果存储句柄以只读方式打开
    • ESP_ERR_NVS_INVALID_NAME:key name 不满足 constraints
    • ESP_ERR_NVS_NOT_ENOUGH_SPACE:如果底层存储中没有足够的空间来保存值
    • ESP_ERR_NVS_REMOVE_FAILED:值是否因闪存写入操作失败而未更新。但是,该值已写入,并且 update 将在重新初始化 nvs 后完成,前提是 flash 操作不会再次失败。

esp_err_t nvs_open_from_partition(const char *part_name, const char *namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle) 类似,不过是从指定分区打开具有给定命名空间的非易失性存储器。

2.11 nvs_find_key

esp_err_t nvs_find_key(nvs_handle_t handle, const char *key, nvs_type_t *out_type)
  • 参数
    • namespace_name:通过 nvs_open 获得的存储句柄。
    • key:键名称。最大长度为 (NVS_KEY_NAME_MAX_SIZE-1) 个字符。不应为空。
    • out_type:指向输出变量的指针,该变量填充了 NVS 条目的数据类型,以防找到 key 的情况。可能为 NULL,则不提供相应的数据类型。
  • 作用
    • 查找具有给定键名称的键值对。
  • 返回值
    • ESP_OK:成功
    • ESP_FAIL:如果存在内部错误;很可能是由于 NVS 分区损坏(仅当禁用 NVS 断言检查时)
    • ESP_ERR_NVS_INVALID_HANDLE:句柄是否已关闭或为 NULL
    • ESP_ERR_NVS_READ_ONLY:如果存储句柄以只读方式打开
    • ESP_ERR_NVS_INVALID_NAME:key name 不满足 constraints
    • ESP_ERR_NVS_NOT_ENOUGH_SPACE:如果底层存储中没有足够的空间来保存值
    • ESP_ERR_NVS_REMOVE_FAILED:值是否因闪存写入操作失败而未更新。但是,该值已写入,并且 update 将在重新初始化 nvs 后完成,前提是 flash 操作不会再次失败。

请注意,如果找到密钥,函数可能同时指示密钥的存在以及 NVS 条目的数据类型。

nvs_erase_key 是擦除具有给定键名称的键值对。

esp_err_t nvs_erase_key(nvs_handle_t handle, const char *key)

nvs_erase_all 是擦除命名空间中的所有键值对。

esp_err_t nvs_erase_all(nvs_handle_t handle)

2.12 nvs_commit

esp_err_t nvs_commit(nvs_handle_t handle)
  • 参数
    • handle:通过 nvs_open 获得的存储句柄。不能使用以只读方式打开的句柄。
  • 作用
    • 将任何待处理的更改写入非易失性存储。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_INVALID_HANDLE:句柄是否已关闭或为 NULL

设置任何值后,必须调用 nvs_commit() 以确保将更改写入非易失性存储。单个 implementations 可能会在其他时间写入 storage,但不能保证这一点。

2.13 nvs_close

void nvs_close(nvs_handle_t handle)
  • 参数
    • handle:要关闭的存储 handle
  • 作用
    • 关闭存储 handle 并释放所有已分配的资源。

2.14 nvs_entry_find_in_handle

esp_err_t nvs_entry_find_in_handle(nvs_handle_t handle, nvs_type_t type, nvs_iterator_t *output_iterator)
  • 参数
    • handle:通过 nvs_open 获得的存储句柄。不能使用以只读方式打开的句柄。
    • typenvs_type_t 值之一。
    • output_iterator:设置为有效的迭代器以枚举找到的所有条目。
      • 如果未找到指定条件的条目,则设置为 NULL。
      • 如果发生除 ESP_ERR_INVALID_ARG 以外的任何其他错误,output_iterator 也为 NULL。
      • 如果发生 ESP_ERR_INVALID_ARG,则不会更改 output_iterator
      • 如果通过此函数获得有效的迭代器,则必须在不再使用时使用 nvs_release_iterator 释放它,除非返回 ESP_ERR_INVALID_ARG
  • 作用
    • 创建迭代器以根据句柄和类型枚举 NVS 条目。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_INVALID_HANDLE:句柄是否已关闭或为 NULL
    • ESP_ERR_NVS_NOT_FOUND:未找到指定条件的元素
    • ESP_ERR_NO_MEM:在分配内部结构期间内存是否已耗尽。
    • ESP_ERR_INVALID_ARG:如果 output_iterator 参数为 NULL。注意:如果 ESP_ERR_INVALID_ARG 已被退回,请不要释放 output_iterator

2.15 nvs_entry_next

esp_err_t nvs_entry_next(nvs_iterator_t *iterator)
  • 参数
    • iterator :从 nvs_entry_findnvs_entry_find_in_handle 函数获得的迭代器。必须为非 NULL。
      • 如果发生除 ESP_ERR_INVALID_ARG 之外的任何错误,则 iterator 设置为 NULL。
      • 如果出现 ESP_ERR_INVALID_ARG,则不会更改 iterator。
  • 作用
    • 将迭代器前进到与迭代器条件匹配的下一个项目。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_NVS_NOT_FOUND:未找到指定条件的元素
    • ESP_ERR_INVALID_ARG:如果有参数为 NULL。注意:如果 ESP_ERR_INVALID_ARG 已被退回,请不要释放 output_iterator

请注意,在此调用后,迭代器的任何副本都将无效。

2.16 nvs_entry_info

esp_err_t nvs_entry_info(const nvs_iterator_t iterator, nvs_entry_info_t *out_info)
  • 参数
    • iterator: 从 nvs_entry_findnvs_entry_find_in_handle 函数获取的 Iterator。必须为非 NULL。
    • out_info:将条目信息复制到的结构体。
  • 作用
    • 将任何待处理的更改写入非易失性存储。
  • 返回值
    • ESP_OK:成功
    • ESP_ERR_INVALID_ARG:如果有参数为 NULL。注意:如果 ESP_ERR_INVALID_ARG 已被退回,请不要释放 output_iterator

2.17 nvs_release_iterator

void nvs_release_iterator(nvs_iterator_t iterator)
  • 参数
    • iterator:从 nvs_entry_find 或 nvs_entry_find_in_handle 或 nvs_entry_next 函数获得的 Release 迭代器。允许 NULL 参数。
  • 作用
    • 释放迭代器。

3、枚举类型

3.1 nvs_open_mode_t

打开类型:

typedef enum {NVS_READONLY,  /*!< Read only */NVS_READWRITE  /*!< Read and write */
} nvs_open_mode_t;

3.2 nvs_type_t

数据类型:

typedef enum {NVS_TYPE_U8    = 0x01,  /*!< Type uint8_t */NVS_TYPE_I8    = 0x11,  /*!< Type int8_t */NVS_TYPE_U16   = 0x02,  /*!< Type uint16_t */NVS_TYPE_I16   = 0x12,  /*!< Type int16_t */NVS_TYPE_U32   = 0x04,  /*!< Type uint32_t */NVS_TYPE_I32   = 0x14,  /*!< Type int32_t */NVS_TYPE_U64   = 0x08,  /*!< Type uint64_t */NVS_TYPE_I64   = 0x18,  /*!< Type int64_t */NVS_TYPE_STR   = 0x21,  /*!< Type string */NVS_TYPE_BLOB  = 0x42,  /*!< Type blob */NVS_TYPE_ANY   = 0xff   /*!< Must be last */
} nvs_type_t;

二、NVS

1、底层存储

NVS 库通过调用 esp_partition() 使用主 flash 的部分空间,即类型为 data 且子类型为 nvs 的所有分区。应用程序可调用 nvs_open() 选择使用带有 nvs 标签的分区,也可以通过调用 nvs_open_from_partition() 选择使用指定名称的任意分区。

如果 NVS 分区被截断(例如,更改分区表布局时),则应擦除分区内容。可以使用 ESP-IDF 构建系统中的 idf.py erase-flash 命令擦除 flash 上的所有内容。

2、键值对

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

  • 整数型:uint8_tint8_tuint16_tint16_tuint32_tint32_tuint64_tint64_t
  • 以 0 结尾的字符串;
  • 可变长度的二进制数据 (BLOB)

注意,字符串值当前上限为 4000 字节,其中包括空终止符。BLOB 值上限为 508,000 字节或分区大小的 97.6% 减去 4000 字节,以较低值为准。

键必须唯一。为现有的键写入新值时,会将旧的值及数据类型更新为写入操作指定的值和数据类型。

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

3、命名空间

为了减少不同组件之间键名的潜在冲突,NVS 将每个键值对分配给一个命名空间。命名空间的命名规则遵循键名的命名规则:

  • 键名最多可占 15 个字符
  • 单个 NVS 分区最多只能容纳 254 个不同的命名空间

命名空间的名称在调用 nvs_open()nvs_open_from_partition 中指定,调用后将返回一个不透明句柄,用于后续调用 nvs_get_*nvs_set_*nvs_commit 函数。这样,一个句柄关联一个命名空间,键名便不会与其他命名空间中相同键名冲突。请注意,不同 NVS 分区中具有相同名称的命名空间将被视为不同的命名空间。

4、NVS 迭代器

迭代器允许根据指定的分区名称、命名空间和数据类型轮询 NVS 中存储的键值对。

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

  • nvs_entry_find:创建一个不透明句柄,用于后续调用 nvs_entry_nextnvs_entry_info 函数;
  • nvs_entry_next:让迭代器指向下一个键值对;
  • nvs_entry_info:返回每个键值对的信息。

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

一般情况下,nvs_entry_find()nvs_entry_next() 会将给定的迭代器设置为 NULL 或为一个有效的迭代器。但如果出现参数错误(如返回 ESP_ERR_NVS_NOT_FOUND),给定的迭代器不会被修改。因此,在调用 nvs_entry_find() 之前最好将迭代器初始化为 NULL,这样可以避免在释放迭代器之前进行复杂的错误检查。

5、NVS 分区生成程序

NVS 分区生成程序帮助生成 NVS 分区二进制文件,可使用烧录程序将二进制文件单独烧录至特定分区。烧录至分区上的键值对由 CSV 文件提供。

可以直接使用函数 nvs_create_partition_image 通过 CMake 创建分区二进制文件,无需手动调用 nvs_partition_gen.py 工具:

nvs_create_partition_image(<partition> <csv> [FLASH_IN_PROJECT] [DEPENDS  dep dep dep ...])
  • 位置参数
    • partition:NVS 分区名
    • csv:解析的 CSV 文件路径
  • 可选参数
    • FLASH_IN_PROJECT:NVS 分区名
    • DEPENDS:指定命令依赖的文件

在没有指定 FLASH_IN_PROJECT 的情况下,也支持生成分区镜像,不过此时需要使用 idf.py <partition>-flash 手动进行烧录。举个例子,如果分区名为 nvs,则需使用的命令为 idf.py nvs-flash

目前,仅支持从组件中的 CMakeLists.txt 文件调用 nvs_create_partition_image,且此选项仅适用于非加密分区。

三、实例操作

1、例一——读写操作实验

本例中向 NVS 分区中的 storage 命名空间写入数据,并读出数据,并验证了 NVS 分区的掉电不丢失的特性:

void app_main(void)
{// 初始化 NVS 分区esp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();}ESP_ERROR_CHECK( err );printf("\n");printf("Opening Non-Volatile Storage (NVS) handle... ");nvs_handle handle;err = nvs_open("storage", NVS_READWRITE, &handle);  // 打开 NVS 分区if (err != ESP_OK) {printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));} else {printf("Done\n");printf("Reading restart counter from NVS ... ");int32_t restart_counter = 0;err = nvs_get_i32(handle, "restart_counter", &restart_counter);  // 读取数据switch (err) {case ESP_OK:printf("Done\n");printf("Restart counter = %" PRIu32 "\n", restart_counter);break;case ESP_ERR_NVS_NOT_FOUND:printf("The value is not initialized yet!\n");break;default:printf("Error (%s) reading!\n", esp_err_to_name(err));}printf("Updating restart counter in NVS ... ");restart_counter++;err = nvs_set_i32(handle, "restart_counter", restart_counter);  // 写入数据printf((err != ESP_OK) ? "Failed!\n" : "Done\n");printf("Committing updates to NVS ... ");err = nvs_commit(handle);  // 更新数据printf((err != ESP_OK) ? "Failed!\n" : "Done\n");nvs_close(handle);}printf("\n");for (int i = 10; i >= 0; --i) {printf("Restarting in %d seconds... \n", i);vTaskDelay(1000 / portTICK_PERIOD_MS);}printf("Restarting now. \n");fflush(stdout);esp_restart();
}


2、例二——blob 的写入和读取

blob 的读取(以及 str 的读取)有一点小技巧,因为事先读取的时候,我们并不知道要读取的 blob 数据的长度,所以我们可以利用 nvs_get_blob 的功能,先将第三个参数设为 NULL,读取出数据的长度,再次调用 nvs_get_blob,就可以获取到 blob 数据了:

size_t required_size = 0;  
err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);  // required_size 保存了 blob 数据的长度
err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);
#define NVS_NAMESPACE "storage"
#define NVS_KEY       "password"const char *TAG = "NVS_TEST";static esp_err_t write_nvs_blob(const char* namespace, const char* key,uint8_t* value, size_t len)
{nvs_handle_t nvs_handle;esp_err_t ret;ESP_ERROR_CHECK(nvs_open(namespace, NVS_READWRITE, &nvs_handle));ESP_LOGI(TAG, "Write NVS: %s", value);ret = nvs_set_blob(nvs_handle, key, value, len);ESP_ERROR_CHECK(nvs_commit(nvs_handle));nvs_close(nvs_handle);return ret;
}static void read_nvs_blob(const char* namespace,const char* key,uint8_t *value,int maxlen)
{nvs_handle_t nvs_handle;esp_err_t ret_val = ESP_FAIL;size_t required_size = 0;ESP_ERROR_CHECK(nvs_open(namespace, NVS_READONLY, &nvs_handle));ret_val = nvs_get_blob(nvs_handle, key, NULL, &required_size);if (ret_val == ESP_OK && required_size <= maxlen){ESP_ERROR_CHECK(nvs_get_blob(nvs_handle, key, value, &required_size));ESP_LOGI(TAG,"Read NVS: %s", value);}elseESP_LOGI(TAG, "Read fail");nvs_close(nvs_handle);
}void app_main(void)
{esp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {ESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();}ESP_ERROR_CHECK( err );uint8_t blob_buf[20] = "abcd1234";// 写入write_nvs_blob(NVS_NAMESPACE,NVS_KEY, blob_buf, 8); // 读取read_nvs_blob(NVS_NAMESPACE, NVS_KEY, blob_buf, sizeof(blob_buf));while (1) {vTaskDelay(pdMS_TO_TICKS(1000));}
}

str 的读取类似,不再重复演示。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/57148.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

element plus中menu菜单技巧

我在使用element plus的menu&#xff08;侧边栏&#xff09;组件的过程中遇到了一些问题&#xff0c;就是menu编写样式和路由跳转&#xff0c;下面给大家分享以下&#xff0c;我是怎么解决的。 1.页面效果 我要实现的网站布局是这样的&#xff1a; 侧边栏折叠以后的效果&#…

python爬虫快速入门之---Scrapy 从入门到包吃包住

python爬虫快速入门之—Scrapy 从入门到包吃包住 文章目录 python爬虫快速入门之---Scrapy 从入门到包吃包住一、scrapy简介1.1、scrapy是什么?1.2、Scrapy 的特点1.3、Scrapy 的主要组件1.4、Scrapy 工作流程1.5、scrapy的安装 二、scrapy项目快速入门2.1、scrapy项目快速创建…

详解equals底层原理

equals 方法是 Java 中用于比较两个对象是否“相等”的方法。在 Java 中&#xff0c;每个类都继承自 java.lang.Object 类&#xff0c;而 equals 方法正是定义在 Object 类中的一个方法。默认情况下&#xff0c;Object 类的 equals 方法比较的是两个对象的内存地址&#xff08;…

SQL 多表联查

SQL JOIN (w3school.com.cn) SQL join用于根据两个或多个表中的列之间的关系&#xff0c;从这些表中查询数据。 之前跟着老师学数据库的时候学过&#xff0c;最近又在比较频繁的使用&#xff0c;再复习一下。 Person表&#xff1a; Id_P &#xff1a;居民编号。主键 …

大数据开发基于Hadoop+springboot平台的岗位推荐系统

文章目录 前言项目介绍技术介绍功能介绍核心代码数据库参考 系统效果图文章目录 前言 文章底部名片&#xff0c;获取项目的完整演示视频&#xff0c;免费解答技术疑问 项目介绍 随着网络科学技术不断的发展和普及化&#xff0c;用户在寻找适合自己的信息管理系统时面临着越来…

成功解决pycharm软件中按住Ctrl+点击指定函数却不能跳转到对应库中的源代码

成功解决pycharm软件中按住Ctrl点击指定函数却不能跳转到对应库中的源代码 目录 解决问题 解决方法 解决问题 在pycharm软件中按住Ctrl点击指定函数却不能跳转到对应库中的源代码 解决方法

探索秘境:如何使用智能体插件打造专属的小众旅游助手『小众旅游探险家』

文章目录 摘要引言智能体介绍和亮点展示介绍亮点展示 已发布智能体运行效果智能体创意想法创意想法创意实现路径拆解 如何制作智能体可能会遇到的几个问题快速调优指南总结未来展望 摘要 本文将详细介绍如何使用智能体平台开发一款名为“小众旅游探险家”的旅游智能体。通过这…

个人健康系统|个人健康数据管理系统|基于小程序+java的个人健康数据管理系统设计与实现(源码+数据库+文档)

个人健康数据管理系统 目录 基于小程序java的个人健康数据管理系统设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师…

重构案例:将纯HTML/JS项目迁移到Webpack

我们已经了解了许多关于 Webpack 的知识&#xff0c;但要完全熟练掌握它并非易事。一个很好的学习方法是通过实际项目练习。当我们对 Webpack 的配置有了足够的理解后&#xff0c;就可以尝试重构一些项目。本次我选择了一个纯HTML/JS的PC项目进行重构&#xff0c;项目位于 GitH…

web3学习-区块链基础知识

1.1 区块链技术简史 block chain 点对点的分布式交易系统 比特币协议并不是图灵完备的。 以太坊协议加入了智能合约&#xff0c;智能合约是以太坊协议与比特币协议的最大区别&#xff08;图灵完备&#xff09; 1.2、区块链设计哲学 去中心化 由于没有中心化的数据库作为…

记录一个容易混淆的 Spring Boot 项目配置文件问题

记录一个容易混淆的 Spring Boot 项目配置文件问题 去年&#xff0c;我遇到了这样一个问题&#xff1a; 在这个例子中&#xff0c;由于密码 password 以 0 开头&#xff0c;当它被 Spring Boot 的 bean 读取时&#xff0c;前导的 0 被自动去掉了。这导致程序无法正确读取密码。…

网盘直链下载神器NDM

工具介绍 ​Neat Download Manager分享一款网盘不限速神器,安装步骤稍微有一点繁琐,但实际体验下载速度飞快,个人实际体验还是非常不错的 NDM是一款免费且强大的下载工具。可以帮助你下载各种文件&#xff0c;还能够在多任务下载中保持出色的速度及其稳定性 通过网盘分享的文…

【MySQL核心面试题】MySQL 核心 - Explain 执行计划详解!

欢迎关注公众号 【11来了】&#xff08;文章末尾即可扫码关注&#xff09; &#xff0c;持续 中间件源码、系统设计、面试进阶相关内容 在我后台回复 「资料」 可领取 编程高频电子书&#xff01; 在我后台回复「面试」可领取 30w 字的硬核面试笔记&#xff01; 感谢你的关注&…

MySQL【知识改变命运】10

联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例&#xff1a;⼀个完整的联合查询的过程2.2.1. 确定参与查询的表&#xff0c;学⽣表和班级表2.2.2. 确定连接条件&#xff0c;student表中的class_id与class表中id列的值相等2.…

wordpress 子比主题美化 四宫格 多宫格 布局插件

wordpress 主题美化 四宫格 多宫格 布局插件&#xff08;只在子比主题上测试过&#xff0c;其它主题没测试&#xff09; A5资源网四宫格布局插件是一个功能丰富的WordPress插件,专为创建自适应的四宫格布局而设计。这个插件具有以下主要特点: 灵活的布局: 支持1到8个宫格的自定…

Springboot整合knife4j生成文档

前言 在开发过程中&#xff0c;接口文档是很重要的内容&#xff0c;用于前端对接口的联调&#xff0c;也用于给其他方使用。但是手写相对比较麻烦。 当然也有swagger之类的&#xff0c;但是界面没有那么友好。 官网&#xff1a; 整合步骤 整合依赖 需要根据版本进行&…

如何使用 pnpm 进行打补丁patch操作?推荐两个方法

前言 作为一个前端开发者&#xff0c;我们每天都在和各种各样的库和依赖打交道。node_modules 目录中存放着我们项目的各种依赖。我们有时需要对其中的一些依赖进行修改&#xff0c;比如修复某个 bug 或者增加某些自定义功能。这时候&#xff0c;给 node_modules 打补丁就显得…

为您的 WordPress 网站打造完美广告布局 A5广告单元格插件

一个为 WordPress 网站量身定制的强大工具,它将彻底改变您展示广告的方式 灵活多变的布局设计 A5 广告单元格插件的核心优势在于其无与伦比的灵活性。无论您是想要创建整齐的网格布局,还是希望打造独特的不规则设计,这款插件都能满足您的需求。 自定义网格数量&#xff1a;从 2…

androidStudio编译导致的同名.so文件冲突问题解决

files found with path lib/arm64-v8a/libserial_port.so from inputs: ...\build\intermediates\library_jni\debug\jni\arm64-v8a\libserial_port.so C:\Users\...\.gradle\caches\transforms-3\...\jni\arm64-v8a\XXX.so 解决方式如下&#xff1a; 1.将gradle缓存文件删…

TwinCAT3安装 Advanced Motion Pack库

文章目录 一.简介二.安装方式1. 下载地址2. 双击下载好的安装包3. 选择语言&#xff08;只有英文和德语&#xff09;4. 点击Next5. 选择Accept6. 填写公司和组织名称&#xff08;随意&#xff09;7. 点击Install8. 等待安装完成9. 点击Finish 一.简介 TF5420 TC3 Motion Pick-…