(二十)ESP32-S3之NVS
NVS(Non-volatile storage,非易失存储),意思是掉电后能依然能持久化保存数据。在我们应用 NVS 时,一般用于存储一些配置数据、状态数据等,一般不会用来存储存放大量的数据量。在嵌入式系统中,NVS 主要是在 Flash 进行键值对的存储。举个例子,假设我们要把东西存到 Flash 中,按照底层的操作习惯,我们要先指定一个地址,然后对这个地址执行擦除操作,然后才能写入;读取的时
NVS简介
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
因此键名 key 一般不会修改,经常修改的是 value。
在 ESP32 中对于 NVS 的操作,还需要指定一个命名空间,是因为还考虑了一种情况,在各个不同的功能模块中,键名是有可能取到一样的,比如对于 WIFI 模块,存在一个 password 键名,对于管理员模块,可能也存在一个 password 键名,这样有可能就造成了重复,程序无法按我们的意思进行。如果我们增加了一个命名空间进行隔离,那么键名有重复也不怕,比如说在 WIFI 模块中,我们指定一个命名空间”wifi”,在此命名空间下有 ssid 和 password 键名;在管理员模块,我们指定一个命名空间”manager”,在此命名空间下有 password 键名,这两组命名空间互不干扰。
NVS的api函数
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;
nvs_flash_init
初始化默认 NVS 分区。默认 NVS 分区是在分区表中标记为 “nvs” 的分区
esp_err_t nvs_flash_init(void)
nvs_flash_init_partition
初始化指定分区的 NVS 闪存
esp_err_t nvs_flash_init_partition(const char *partition_label)
nvs_flash_init_partition_ptr
初始化分区指针指定的分区的 NVS 闪存存储
esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition)
nvs_flash_erase
擦除默认 NVS 分区(带有标签 “nvs” 的分区)的所有内容
esp_err_t nvs_flash_init_partition_ptr(const esp_partition_t *partition)
nvs_open
从默认 NVS 分区中打开具有给定命名空间的非易失性存储
esp_err_t nvs_open(const char *namespace_name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle)
nvs_set_blob
为给定键设置值
esp_err_t nvs_set_blob(nvs_handle_t handle, const char *key, const void *value, size_t length)
nvs_get_blob
获取给定键值
esp_err_t nvs_get_blob(nvs_handle_t handle, const char *key, void *out_value, size_t *length)
nvs_commit
将任何待处理的更改写入非易失性存储
esp_err_t nvs_commit(nvs_handle_t handle)
nvs_close
关闭存储 handle 并释放所有已分配的资源
void nvs_close(nvs_handle_t handle)
blob 的读取(以及 str 的读取)有一点小技巧,因为事先读取的时候,我们并不知道要读取的 blob 数据的长度,所以我们可以利用 nvs_get_blob 的功能,先将第三个参数设为 NULL,读取出数据的长度,再次调用 nvs_get_blob,就可以获取到 blob 数据了
#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));
}
}

为武汉地区的开发者提供学习、交流和合作的平台。社区聚集了众多技术爱好者和专业人士,涵盖了多个领域,包括人工智能、大数据、云计算、区块链等。社区定期举办技术分享、培训和活动,为开发者提供更多的学习和交流机会。
更多推荐
所有评论(0)