ESP32中的NVS
这几天的天气只有钱包的余额能让我冷静,好好活着,每天都有新的打击,写写博客压压惊。
一、什么是NVS?
NVS即Non-volatile storage,意思是非易失存储,也就是掉电后能依然能持久化保存数据。在我们应用NVS时,一般用于存储一些配置数据、状态数据等,一般不会用来存储存放大量的数据量。
在嵌入式系统中,NVS主要是在flash进行键值对的存储。那么什么是键值对?这边举一个例子方便大家理解,假设我们要把东西存到flash中,按照底层的操作习惯,我们要先指定一个地址,然后对这个地址执行擦除操作,然后才能写入;读取的时候也需要根据这个地址,然后指定读取长度。如果我们要存的项比较多,又在代码中比较分散,我们对flash的地址就很难管理。因为我们很难知道要存的内容与其他地址有没冲突,会不会误擦除。存在诸多问题。那如果有一个机制,方便帮我们把这些检测判断活都干了,不需要我们指定地址操作,那岂不是很好。有的人又说了,文件系统不就是这样吗?是的,文件系统是这样,但NVS操作更加轻量级。
在NVS中,我们要存一个值,我们不需要指定地址,但需要指定一个“键”,我们称为key,然后我们在这个“键”索引下存我们的值,也就是value。假设我们要存wifi的ssid和密码,我们可以在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键名,这两组命名空间互不干扰。
二、ESP32中NVS的使用
上一节介绍了NVS的基本概念,这一节我们看下esp-idf中给我们提供了什么接口。
//打开一个NVS存储操作
esp_err_t nvs_open(const char* namespace_name, /*命名空间名称*/
nvs_open_mode_t open_mode, /* 打开的模式*/
NVS_READONLY,只读
NVS_READWRITE 读写
nvs_handle_t *out_handle); /* 返回的nvs操作句柄
//向NVS中读取字符串数据
esp_err_t nvs_get_str (nvs_handle_t handle, /*nvs操作句柄*/
const char* key, /*键名*/
char* out_value, /*读取到的值*/
size_t* length) /*读取到的字符串长度*/
//向NVS中读取二进制数据
esp_err_t nvs_get_blob(nvs_handle_t handle, /*nvs操作句柄*/
const char* key, /*键名*/
void* out_value, /*返回的值
size_t* length); /*返回值的长度*/
//向NVS中写入字符串数据(最大4000字节)
esp_err_t nvs_set_str (nvs_handle_t handle, /*nvs操作句柄*/
const char* key, /*键名*/
const char* value); /*值*/
//向NVS中写入二进制数据(最大值:NVS分区大小*97.6%-4000)
esp_err_t nvs_set_blob(nvs_handle_t handle, /*nvs操作句柄
const char* key, /*键名*/
const void* value, /*值*/
size_t length); /*大小*/
//擦除对应命名空间下所有的nvs值
esp_err_t nvs_erase_all(
nvs_handle_t handle); /*nvs操作句柄*/
//在执行写操作后,为了确保写入到NVS区域中,需要调用此函数
esp_err_t nvs_commit(
nvs_handle_t handle); /*nvs操作句柄*/
//关闭NVS操作句柄,释放资源
void nvs_close(
nvs_handle_t handle); /*nvs操作句柄*/
以上操作有几点注意:
1)操作NVS的第一步就是调用nvs_open,获取到nvs句柄后才能执行其他操作
2)命名空间长度、key长度,默认是15字节
3)在执行写操作后,需要nvs_commit,才能确保写入到了nvs区域
在上一课程对分区表进行解析后,我们知道nvs默认的分区地址是0x9000开始,默认大小是0x6000,flash中这一段的区域是专门留给我们做nvs存储的。我们来看下具体例程部分源码,源码位于esp32-board/nvs中。
#define NVS_BOB_NAMESPACE "Bob" //namespace最长15字节
#define NVS_JOHN_NAMESPACE "John" //namespace最长15字节
#define NVS_AGE_KEY "age" //年龄键名
#define NVS_SEX_KEY "sex" //性别键名
void app_main(void)
{
//初始化NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
//NVS出现错误,执行擦除
ESP_ERROR_CHECK(nvs_flash_erase());
//重新尝试初始化
ESP_ERROR_CHECK(nvs_flash_init());
}
char read_buf[64];
size_t len = 0;
//以字节方式写入到NVS中
write_nvs_str(NVS_BOB_NAMESPACE,NVS_SEX_KEY,"female");
write_nvs_str(NVS_JOHN_NAMESPACE,NVS_SEX_KEY,"male");
//读取NVS_BOB_NAMESPACE命名空间中的SEX键值
len =read_nvs_str(NVS_BOB_NAMESPACE,NVS_SEX_KEY,read_buf,64);
if(len)
ESP_LOGI(TAG,"Read BOB SEX:%s",read_buf);
else
ESP_LOGI(TAG,"Read BOB SEX fail,please perform nvs_erase_key and try again");
//读取NVS_JOHN_NAMESPACE命名空间中的SEX键值
len =read_nvs_str(NVS_JOHN_NAMESPACE,NVS_SEX_KEY,read_buf,64);
if(len)
ESP_LOGI(TAG,"Read JOHN SEX:%s",read_buf);
else
ESP_LOGI(TAG,"Read JOHN SEX fail,please perform nvs_erase_key and try again");
uint8_t blob_buf[32];
blob_buf[0] = 19;
//以字节方式写入
write_nvs_blob(NVS_BOB_NAMESPACE,NVS_AGE_KEY,blob_buf,1);
blob_buf[0] = 23;
write_nvs_blob(NVS_JOHN_NAMESPACE,NVS_AGE_KEY,blob_buf,1);
//以字节方式读取
len = read_nvs_blob(NVS_BOB_NAMESPACE,NVS_AGE_KEY,blob_buf,32);
if(len)
ESP_LOGI(TAG,"Read BOB age:%d",blob_buf[0]);
else
ESP_LOGI(TAG,"Read BOB age fail,please perform nvs_erase_key and try again");
//以字节方式读取
len = read_nvs_blob(NVS_JOHN_NAMESPACE,NVS_AGE_KEY,blob_buf,32);
if(len)
ESP_LOGI(TAG,"Read JOHN age:%d",blob_buf[0]);
else
ESP_LOGI(TAG,"Read JOHN age fail,please perform nvs_erase_key and try again");
while(1)
{
vTaskDelay(pdMS_TO_TICKS(1000));
}
}
这个例程中,有两个命名空间”Bob”和”John”,这两个命名空间分别都有”age”和”sex”这两个键,这个例程可以看到,两组命名空间之间互不干扰,其他具体代码请看例程源码。
最后附上相关资料:
ESP32教程资料链接:
https://pan.baidu.com/s/1kCjD8yktZECSGmHomx_veg?pwd=q8er
提取码:q8er
配套源码下载地址:
esp32-board: esp32开发板配套的经典例程
鉴于实验需要开发板的支持,我也设计了一款ESP32开发板,包含部分传感器模块,1.69寸LCD高亮屏,Type-C一键下载,方便大家学习和做各种实验。开发板链接如下:
请大家多多支持。