ESP32-IDF 非易失存储 NVS

十九、【ESP32全栈开发指南——NVS(非易失性存储)接口使用】 ESP32的NVS(非易失性存储)库是专为在SPI Flash中存储键值对数据设计的核心组件。它支持整数、字符串和二进制数据,采用命名空间隔离数据,具有磨损均衡和掉电安全特性。主要API包括初始化(nvs_flash_init)、擦除(nvs_flash_erase)、打开命名空间(nvs_open)和读写操作。适用于存储配置参数、Wi-Fi凭证等中小规模数据,但不适合大文件存储。使用时需注意分区管理,修改分区表后需擦除Flash。 阅读详情


零、前言

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_first 或 esp_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_first 或 esp_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_first 或 esp_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_str 和 nvs_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_mode:NVS_READWRITE 或 NVS_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 获得的存储句柄。不能使用以只读方式打开的句柄。
    • type:nvs_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_find 或 nvs_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_find 或 nvs_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_t、int8_t、uint16_t、int16_t、uint32_t、int32_t、uint64_t 和 int64_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_next 和 nvs_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);
    }
    else
        ESP_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 的读取类似,不再重复演示。

ESP32 通过NVS存储WiFi账号和密码至Flash 在ESP32板子重启之后,自动从Flash中寻找已经存储好的WiFi账号和秘密,连接到网络。要想能够很好的理解如何将WiFi的账号和密码如何写入到Flash的什么地方,首先需要了解下ESP32中Flash的特点。 阅读详情

相关推荐

ESP32-C3嵌入式开发:ESP-IDF架构、OTA升级与NVS配置实战

嵌入式物联网设备开发中,基于RISC-V架构的MCU(如ESP32-C3)正成为低功耗、高集成度终端的主流选择。其核心依赖于成熟的IoT开发框架(如ESP-IDF),该框架深度整合FreeRTOS实时内核、Wi-Fi/BLE协议栈及安全OTA机制,显著提升固件可靠性与远程维护能力。在工程实践中,非易失存储(NVS)被广泛用于用户配置持久化,兼顾磨损均衡与AES加密安全;而事件驱动的Wi-Fi状态机与指数退避重连策略,则保障了弱网环境下的连接鲁棒性。本文围绕ESP32-C3平台,详解ESP-IDF项目结构、安

weixin_33773084的博客 39

esp32-nvs

然后,我们使用 strncpy() 函数将新的字符串值写入 my_string 变量,使用 nvs_set_str() 函数将其写入 “my_string” 名称下的 NVS,并使用 nvs_commit() 函数保存更改。您可以使用 nvs_get_xxx() 和 nvs_set_xxx() 系列函数中的各种数据类型来存储您需要的数据,并通过 nvs_commit() 函数将数据更改保存到 NVS。使用 nvs_get_xxx() 系列函数读取 NVS 中的值。用于向 NVS 中写入相应类型的值。

TKSY123的博客 2062

ESP32 Web配网项目深度拆解:从SoftAP、DNS劫持到前端页面,一次讲清

本文深入解析ESP32 Web配网项目的核心技术,包括SoftAP与Station双模网络、DNS劫持实现自动跳转配置页、前后端交互设计以及NVS存储配置数据的最佳实践。通过代码示例和实测数据,详细讲解如何构建高效可靠的WiFi配网系统,适用于各类物联网设备开发。

weixin_28684825的博客 155

ESP32学习笔记(23)——NVS(非易失性存储)接口使用

一、简介 非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据。 NVS适合存储一些小数据,如果对象占用空间比较大,使用负载均衡的FAT文件系统。 如果NVS分区被截断,比如更改分区表布局的时候,应该擦除分区内容。可以使用 idf.py erase_flash 命令擦除flash上全部的内容。 NVS 的操作对象为键值对,其中键是 ASCII 字符串,当前支持最大键长为 15 个字符,值可以为以下几种类型: 整数型: uint8_t、int8_t、uint16_t、int16_t、

Leung的博客 1万+

ESP32墨水屏配网失败根因与NVS初始化修复

嵌入式Wi-Fi配网是物联网终端联网的关键环节,其可靠性依赖于非易失存储(NVS)的正确初始化与事件驱动模型的时序协同。在ESP32平台中,NVS分区未按规范提前初始化,极易导致SmartConfig凭证写入失败、设备重启失联等问题。本文结合墨水屏阅读器工程实践,剖析NVS初始化时序错误、上下文不匹配及资源泄漏三大典型缺陷,阐明FreeRTOS任务调度与ESP-IDF组件初始化顺序的技术约束,并给出静态初始化、原子写入与事件解耦的可复用修复范式,适用于各类资源受限的e-Paper、电子标签及低功耗IoT终端

weixin_30820933的博客 303

ESP32嵌入式固件骨架:基于tcMenu的工程级基础库

嵌入式固件开发中,菜单系统与底层硬件管理常面临跨芯片适配、多任务协同、非易失存储可靠性及低功耗唤醒等共性挑战。tcMenu作为轻量跨平台菜单框架,需结合FreeRTOS任务调度、NVS持久化、看门狗协同与HAL抽象才能落地为工业级解决方案。该技术栈支撑人机交互界面快速构建,同时保障长期运行稳定性与电源效率,广泛适用于温控仪、环境监测节点、HMI终端等ESP32-WROOM/S2/S3产品场景。bamboitEsp32Base正是面向这一需求沉淀的工程化基线,深度融合tcMenu与ESP-IDF运行时服务。

weixin_42594427的博客 68

ESP32 IDF环境下EEPROM模拟驱动详解

在ESP32 IDF开发环境中,EEPROM模拟并非硬件外设,而是基于Flash分区实现的轻量级非易失存储方案。本文手把手带你配置分区表、调用nvs_flash_init初始化NVS,并用key-value方式安全读写数据,避开Flash擦写寿命陷阱。结合ESP32 IDF官方API与实战踩坑经验,让掉电保存更稳更可靠。

weixin_42613018的博客 237

ESP32墨水屏固件更新:NVS持久化与配网状态同步

非易失存储(NVS)是嵌入式系统中保障配置数据可靠性的关键技术,其核心在于写入原子性、Flash寿命管理与事件驱动的一致性校验。在ESP32等资源受限平台,NVS常用于WiFi凭证、设备参数等关键配置的持久化,但异步写入与状态机断裂易引发‘配网成功却丢失SSID’等典型故障。本文聚焦NVS事务设计原理与工程实践价值,解析如何通过nvs_commit强同步、事件闭环监控及生命周期管理,构建高鲁棒性配置存储方案,并延伸至墨水屏阅读器等IoT终端的固件升级场景。

weixin_30820933的博客 189

ESP32项目实战:用NVS存储WiFi密码,并实现多任务自动扫描连接

本文详细介绍了如何利用ESP-IDF框架开发ESP32智能设备的WiFi管理系统,实现通过NVS安全存储多个WiFi密码,并采用多任务自动扫描与连接策略。内容涵盖项目架构设计、NVS存储高级应用、WiFi扫描与连接策略、多任务协同与事件处理等关键技术点,为物联网设备开发提供实用解决方案。

weixin_30684743的博客 414

ESP32分区表深度解析:如何优化你的Flash空间分配(含NVS、OTA分区实战)

本文深入解析ESP32分区表的核心机制与优化策略,涵盖NVS和OTA分区的实战配置。通过详细的分区表结构分析和实际案例,帮助开发者合理分配Flash空间,提升系统稳定性和扩展性,特别适合物联网设备开发者参考。

weixin_29189373的博客 326

【ESP32】打造全网最强esp-idf基础教程——13.ESP32中的NVS

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

Hello_kitty_ling的博客 2998

ESP32 EEPROM数据存储:从零实现操作指南

手把手带你用ESP32操作EEPROM,完成初始化、读写与擦除全流程。聚焦ESP32硬件特性与API调用细节,避开常见地址越界和写入寿命陷阱,让嵌入式数据持久化真正落地。

weixin_35906794的博客 387

ESP32 IDF开发 驱动篇⑩ 存储NVS高级应用和自定义分区表

在“ESP32 IDF开发 驱动篇⑨ 存储NVS的基本应用”章节我们已经介绍了nvs的介绍和基本操作,并且留下了许多疑问。下面就来对这些疑问一一揭开神秘的面纱。(1)分区表的介绍分区表文件位于: esp-idf\components\partition_table文件下, 该路径下所有的.csv文件都是用来对Flash分区进行配置如下图:无OTA分区:partitions_singleapp.csv、partitions_singleapp_coredump.csv。

lu330274924的博客 2489

ESP32开发--使用NVS存储数据

ESP32基础系列 ESP32初体验之环境搭建 ESP32外设学习之GPIO ESP32外设学习之UART ESP32开发之给模块连上网络 ESP32的一键配网与Airkiss配网 ESP32使用NVS存储数据 ESP32一键配网+自动重连 ESP32之Cmake的介绍与组件的添加 ESP32之为配网模式添加按键和指示灯 本章目录 一.NVS的介绍 二.ESP32的分区简单介绍 三.NVS存储例程解析 四.将例程修改为读写wifi名称和密码 一.NVS的介绍 先来看看官网文.

啤酒的博客 1万+

ESP32学习笔记 -- ESP32-S3使用NVS存储数据

非易失性存储(Non-Volatile Storage)简称NVS,是用来保存一些设备断电后不能丢失的数据,乐鑫的ESP32-S3芯片使用了一套NVS库函数对外置的SPI FLASH进行读、写、擦除等操作。

EmbedIoT Studio 3706

ESP32 中NVS基本原理与实现

NVS(非易失存储)是嵌入式系统中用于持久化存储键值对数据的轻量级解决方案。ESP32通过NVS实现了配置数据的高效管理,采用命名空间隔离机制(如"wifi"和"manager")避免键名冲突。其操作流程包括:初始化NVS分区、打开命名空间获取句柄、执行读写操作(支持字符串和二进制数据,最大4000字节),最后提交更改确保数据持久化。典型API包括nvs_open()、nvs_set/get_str()、nvs_set/get_blob()和nvs_commit()。使用时需注意:首次需初始化NVS分区,写

嵌入式技术开发 243

ESP32 非易失性存储器NVS,实现数据掉电存储(ESP-IDF)

NVS即Non-volatile storage,是一种非易失性存储技术,用于在嵌入式系统中保存持久化数据。它主要用于在flash存储器中存储键值格式的数据,提供了一种简单且有效的方法来保存和读取配置信息、状态数据、用户设置等应用程序数据。NVS在设备重新启动或断电后能够恢复状态,因此非常适合保存需要长期存储的数据。

weixin_49337111的博客 5596

Gromacs实战:从蛋白质-小分子对接到分子动力学模拟全流程解析

本文详细解析了使用Gromacs进行蛋白质-小分子复合物分子动力学模拟的全流程。从处理AutoDock等对接软件的结果文件开始,逐步指导如何为蛋白质和小分子分别生成拓扑文件,并解决力场合并的核心挑战。文章涵盖了构建模拟盒子、添加溶剂离子、能量最小化、平衡模拟以及关键的轨迹分析步骤,为计算生物学和药物设计研究者提供了一套从静态对接到动态评估的完整实战指南。

weixin_29251337的博客 1218

ESP32 IDF NVS离线存储

在之前学习桌面小屏幕的时候,发现老师在为了能够使用https修改wifi密码和账号并保证下一次启动mcu能够自动加载wifi使用了一个叫做nvs的离线存储功能。

m0_73991989的博客 1237

从淬火实验到仿真验证:用ANSYS APDL复现钢球冷却温度变化(含动画生成教程)

本文详细介绍了如何利用ANSYS APDL进行淬火过程的瞬态热分析仿真,以复现钢球冷却温度变化。内容涵盖从实验数据校准材料参数、构建高保真模型、设置非线性瞬态求解,到提取节点温度曲线和生成温度云图动画的全流程。通过APDL命令流实现自动化,为工程研发和学术研究提供高效、可靠的仿真验证方法。

pz8901234的博客 791

COMSOL模拟CO2驱替CH4:多物理场耦合建模与工程应用详解

在非常规天然气开发与碳封存工程中,多孔介质内的渗流、吸附与岩石变形相互耦合,使得CO2驱替CH4的产能预测和CO2封存评估无法仅靠经验公式完成。以达西定律描述裂隙流动、以扩展Langmuir方程刻画竞争吸附、用应力-吸附-渗透率关系关联煤岩变形,是多物理场耦合数值模拟的核心路径。COMSOL Multiphysics凭借内置渗流、固体力学与组分传递接口,能够将这些机制整合为可参数化扫描的仿真模型,为注气压力、井距、渗透率演化等方案设计提供量化依据。围绕CO2驱替CH4模拟的建模流程、参数取法与结果判读,可形

weixin_29574585的博客 158
上一篇: ESP32-IDF 分区表
下一篇: 静态链接过程分析
Projectsauron
博客等级 码龄4年 2587粉丝 100原创
评论 27
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值