SOC-ESP32S3部分:21-非易失性存储库
飞书文档https://x509p6c8to.feishu.cn/wiki/QB0Zw7GLeio4l4kyaWQcuQT3nZS
非易失性存储 (NVS) 库主要用于在 flash 中存储键值格式的数据。
它允许我们在芯片的闪存中存储和读取数据,即使在断电后,这些数据也不会丢失。
NVS 是 ESP32 flash(flash就是板子上的一个存储芯片)中的一个存储分区,我们可以在其中存储键值对(key-value pairs)。每个键值对都有一个唯一的键名(key name)和一个对应的值(value)。这种组合类似于哈希表的(key-value)对应结构。
初始化NVS
在开始使用NVS之前,需要先初始化整个NVS分区。通常在应用程序启动阶段完成这一操作
esp_err_t nvs_flash_init(void);
返回值
esp_err_t
表示函数执行的结果,通常为以下几种情况:
ESP_OK: 成功初始化 NVS(Non-Volatile Storage)闪存。
ESP_ERR_NVS_NO_FREE_PAGES: NVS 分区没有可用的页。
ESP_ERR_NVS_NEW_VERSION_FOUND: NVS 分区版本更新,需要格式化。
ESP_ERR_NVS_NOT_INITIALIZED: NVS 未初始化。
ESP_ERR_NVS_INVALID_STATE: NVS 已经初始化。
其他可能的错误代码,具体取决于底层实现。示例参考:
#include <nvs_flash.h>
void app_main()
{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);
}
使用 NVS 存储数据
我们可以使用 nvs_open 、nvs_set_* 、nvs_commit三个函数来存储数据。
nvs_open 函数用于打开一个 NVS 命名空间,并返回一个句柄,通过该句柄可以进行后续的读写操作。
esp_err_t nvs_open(const char *name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle);
参数
const char *name: NVS 命名空间的名称。命名空间用于组织存储的数据,类似于文件夹的概念。
nvs_open_mode_t open_mode: 打开命名空间的模式,可以是以下几种:
NVS_READONLY: 只读模式。
NVS_READWRITE: 读写模式。
nvs_handle_t *out_handle: 输出参数,用于返回打开的命名空间的句柄。通过这个句柄可以进行后续的读写操作。nvs_set_i32 函数用于将一个 32 位整数值存储到 NVS 中,使用指定的键名标识该数据。存储的数据可以通过该键名在后续的读取操作中检索。
esp_err_t nvs_set_i32(nvs_handle_t handle, const char *key, int32_t value);
参数
nvs_handle_t handle: 通过 nvs_open 函数获得的命名空间句柄。
const char *key: 要存储的数据的键名。键名用于标识存储的数据。
int32_t value: 要存储的 32 位整数值。nvs_commit 函数用于将对 NVS 命名空间所做的更改(如设置、删除键值对等)提交到闪存中。
在调用 nvs_set_i32 或其他设置函数后,必须调用 nvs_commit 以确保更改被持久化到 NVS 分区中。
如果不调用 nvs_commit,更改将不会保存到闪存中,下次读取时将不会看到这些更改
esp_err_t nvs_commit(nvs_handle_t handle);
参数
nvs_handle_t handle: 通过 nvs_open 函数获得的命名空间句柄。// 存储数据
nvs_handle_t my_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle); //打开命名空间
if (err == ESP_OK) {err = nvs_set_i32(my_handle, "restart_counter", 1);if (err == ESP_OK) {err = nvs_commit(my_handle);}nvs_close(my_handle);
}
在这个例子中,我们首先打开了一个名为 “storage” 的 NVS 名称空间,然后在其中存储了一个键名为 “restart_counter”、值为1的键值对。
而nvs_commit()函数的主要作用是将所有挂起的更改写入NVS。当你在NVS中设置一个键值对后,这个更改首先被存储在内存中。只有当你调用nvs_commit()函数时,这些更改才会被写入闪存。
所以,如果你在调用nvs_commit()函数之前重启了设备,那么你在NVS中设置的所有键值对都将丢失。因此,每次在NVS中设置键值对后,都应该调用nvs_commit()函数,以确保这些更改在设备重启后仍然存在。
当然,还有其它不同类型的数据存储接口
nvs_set_i8
功能: 存储 8 位整数。
esp_err_t nvs_set_i8(nvs_handle_t handle, const char *key, int8_t value);nvs_set_u8
功能: 存储 8 位无符号整数。
esp_err_t nvs_set_u8(nvs_handle_t handle, const char *key, uint8_t value);nvs_set_i16
功能: 存储 16 位整数。
esp_err_t nvs_set_i16(nvs_handle_t handle, const char *key, int16_t value);nvs_set_u16
功能: 存储 16 位无符号整数。
esp_err_t nvs_set_u16(nvs_handle_t handle, const char *key, uint16_t value);nvs_set_u32
功能: 存储 32 位无符号整数。
esp_err_t nvs_set_u32(nvs_handle_t handle, const char *key, uint32_t value);nvs_set_i64
功能: 存储 64 位整数。
esp_err_t nvs_set_i64(nvs_handle_t handle, const char *key, int64_t value);nvs_set_u64
功能: 存储 64 位无符号整数。
esp_err_t nvs_set_u64(nvs_handle_t handle, const char *key, uint64_t value);nvs_set_str
功能: 存储字符串。
esp_err_t nvs_set_str(nvs_handle_t handle, const char *key, const char *value);
参数:
handle: NVS 命名空间句柄。
key: 键名。
value: 要存储的字符串。nvs_set_blob
功能: 存储二进制数据块。
esp_err_t nvs_set_blob(nvs_handle_t handle, const char *key, const void *value, size_t length);
参数:
handle: NVS 命名空间句柄。
key: 键名。
value: 要存储的二进制数据块。
length: 数据块的长度(以字节为单位)。使用示例:// 存储不同类型的数据err = nvs_set_i8(my_handle, "int8_key", 10);ESP_ERROR_CHECK(err);err = nvs_set_u8(my_handle, "uint8_key", 20);ESP_ERROR_CHECK(err);err = nvs_set_i16(my_handle, "int16_key", 300);ESP_ERROR_CHECK(err);err = nvs_set_u16(my_handle, "uint16_key", 400);ESP_ERROR_CHECK(err);err = nvs_set_u32(my_handle, "uint32_key", 5000);ESP_ERROR_CHECK(err);err = nvs_set_i64(my_handle, "int64_key", 60000);ESP_ERROR_CHECK(err);err = nvs_set_u64(my_handle, "uint64_key", 70000);ESP_ERROR_CHECK(err);const char *str_value = "Hello, NVS!";err = nvs_set_str(my_handle, "str_key", str_value);ESP_ERROR_CHECK(err);uint8_t blob_value[] = {0x01, 0x02, 0x03, 0x04};size_t blob_length = sizeof(blob_value);err = nvs_set_blob(my_handle, "blob_key", blob_value, blob_length);ESP_ERROR_CHECK(err);
使用 NVS 读取数据
我们可以使用 nvs_get_* 函数来读取数据。例如,我们可以使用 nvs_get_i32 函数来读取一个整数:
nvs_get_i32 函数用于从 NVS 中读取一个 32 位整数值,使用指定的键名标识该数据。读取的数据存储在输出参数中,可以通过该参数访问。
esp_err_t nvs_get_i32(nvs_handle_t handle, const char *key, int32_t *out_value);
参数
nvs_handle_t handle: 通过 nvs_open 函数获得的命名空间句柄。const char *key: 要读取的数据的键名。键名用于标识存储的数据。int32_t *out_value: 输出参数,用于存储从 NVS 中读取的 32 位整数值。使用参考
// 检索数据
nvs_handle_t my_handle;
esp_err_t err = nvs_open("storage", NVS_READWRITE, &my_handle);
if (err == ESP_OK) {int32_t value;err = nvs_get_i32(my_handle, "restart_counter", &value);if (err == ESP_OK) {printf("Value = %d\n", value);}nvs_close(my_handle);
}
删掉NVS上的数据
esp_err_t nvs_flash_erase(void);
返回值
esp_err_t
表示函数执行的结果,通常为以下几种情况:
ESP_OK: 成功擦除 NVS 分区。
ESP_ERR_NVS_NOT_INITIALIZED: NVS 未初始化。
ESP_ERR_NVS_INVALID_STATE: NVS 处于无效状态。
其他可能的错误代码,具体取决于底层实现
最终参考程序:
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "esp_log.h"static const char *TAG = "NVS"; // 定义日志标签void app_main(void)
{// 初始化 NVSesp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// NVS 分区被截断,需要擦除// 重新初始化 nvs_flashESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();}ESP_ERROR_CHECK(err);// 打开命名空间ESP_LOGI(TAG, "打开非易失性存储 (NVS) 句柄...");nvs_handle_t my_handle;err = nvs_open("storage", NVS_READWRITE, &my_handle);if (err != ESP_OK) {ESP_LOGI(TAG, "打开 NVS 句柄时出错 (%s)!\n", esp_err_to_name(err));} else {// 读取重启计数器ESP_LOGI(TAG, "从 NVS 读取重启计数器 ... ");int32_t restart_counter = 0; // 如果 NVS 中未设置值,则默认为 0err = nvs_get_i32(my_handle, "restart_counter", &restart_counter);ESP_LOGI(TAG, "重启计数器 = %" PRIu32 "\n", restart_counter);// 更新重启计数器ESP_LOGI(TAG, "更新 NVS 中的重启计数器 ... ");restart_counter++;err = nvs_set_i32(my_handle, "restart_counter", restart_counter);// 提交写入的值。设置任何值后,必须调用 nvs_commit() 以确保更改写入闪存存储。ESP_LOGI(TAG, "提交 NVS 中的更改 ... ");err = nvs_commit(my_handle);// 关闭命名空间nvs_close(my_handle);}ESP_LOGI(TAG, "\n");// 重启模块for (int i = 10; i >= 0; i--) {ESP_LOGI(TAG, "将在 %d 秒后重启...\n", i);vTaskDelay(1000 / portTICK_PERIOD_MS);}ESP_LOGI(TAG, "现在重启。\n");fflush(stdout);esp_restart();
}