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));
    }
}

Logo

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

更多推荐