ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)_esp入门教学

183 阅读9分钟

在示例中,第二个参数应该是表示打开的区域是可以读也可以写的 ,只读的是NVS_READONLY

在这里插入图片描述

3、读写操作,使用nvs_get_*(*号表示不同的数据类型,比如nvs_get_i32nvs_get_u16) 读操作,使用nvs_set_*进行写操作:

在这里插入图片描述

在这里插入图片描述

在示例中对于读写应用如下:

在这里插入图片描述

4、写入值后,需要条用nvs_commit函数确保值写入成功。

在这里插入图片描述

5、关闭NVS,完成写入后,使用nvs_close关闭。

在这里插入图片描述

2、示例测试

2.1 基础示例测试

在官方例程中,我们参考的示例程序有2个,如下图:

在这里插入图片描述

示例nvs_rw_value:

在我们上面介绍NVS的使用步骤中的举例,就是用的nvs_rw_value工程,基本的工程没什么好修改的,测试的结果如下,每次重启 restart_counter 的值就会增加1,如下图:

在这里插入图片描述

示例nvs_rw_blob:

第二个工程测试效果如下:

在这里插入图片描述

先看了测试效果,我们来简单说明一下源码,第一个函数save_restart_counter函数,和示例nvs_rw_blob基本一样,不多说。

我们来看第二个函数save_run_time,在这个函数中,我们使用了一个nvs_get_blobnvs_set_blob的函数,注意到他们都有一个 void*类型得参数,表面这两个nvs的操作能够适用于任意类型的数据。

在这里插入图片描述

上面的save_run_time函数,我们直接上添加了注释的源码:

/\* Save new run time value in NVS
 by first reading a table of previously saved values
 and then adding the new value at the end of the table.
 Return an error if anything goes wrong
 during this process.
 \*/
esp\_err\_t save\_run\_time(void)
{
    nvs\_handle\_t my_handle;
    esp\_err\_t err;

    // Open 正常的操作步骤,打开nvs,第一个命名空间,读写,句柄名称
    err = nvs\_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK) return err;

    /\* Read the size of memory space required for blob
 unsigned int required\_size
 读nvs,读取键值对为 "run\_time" 处的内容放入 变量required\_size
 \*/
    size\_t required_size = 0;  // value will default to 0, if not set yet in NVS
    /\*先使用一次nvs\_get\_blob函数,但是第三个参数输出地址使用的是NULL,
 表示读出的数据不保存,因为这里使用只是为了看一下 "run\_time" 处是否
 有数据,只是先读一下数据,看一下读完以后 required\_size 还是不是0
 \*/
    err = nvs\_get\_blob(my_handle, "run\_time", NULL, &required_size);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;

    /\* Read previously saved blob if available
 这里申请一块地址,定义为 run\_time ,地址上存放的示uint32\_t数据,大小为
 required\_size大小 + sizeof(uint32\_t) 
 \*/
    uint32\_t\* run_time = malloc(required_size + sizeof(uint32\_t));
    /\*
 如果上面读到的 required\_size 大于0 ,说明"run\_time" 处以前是保存过数据的
 那么,就先读出来,保存在刚才申请的 地址 run\_time 处(第3个参数)。
 \*/
    if (required_size > 0) {
        err = nvs\_get\_blob(my_handle, "run\_time", run_time, &required_size);
        if (err != ESP_OK) {
            free(run_time);
            return err;
        }
    }
    /\* 
 Write value including previously saved blob if available
 不管 required\_size 有过还是没有过,进行这步操作,都会使得 required\_size 增加
 增加 sizeof(uint32\_t) 大小,因为示例本意示重启一次,计数必须加一次
 \*/
    required_size += sizeof(uint32\_t);
    /\*
 每一次保存的是使用数组形式保存数据:类似于 uint32\_t run\_time[] 数组
 给数组赋值
 \*/
    run_time[required_size / sizeof(uint32\_t) - 1] = xTaskGetTickCount() \* portTICK_PERIOD_MS;
    /\*
 最后把需要保存的数组处理完成以后
 调用 nvs\_set\_blob 函数进行保存
 \*/ 
    err = nvs\_set\_blob(my_handle, "run\_time", run_time, required_size);
    free(run_time);

    if (err != ESP_OK) return err;

    // Commit
    err = nvs\_commit(my_handle);
    if (err != ESP_OK) return err;

    // Close
    nvs\_close(my_handle);
    return ESP_OK;
}

搞清楚了上面这个函数,nvs_rw_blob示例基本就没问题了,接下来的print_what_saved(void)函数中,使用了不同的 get 函数,从同一个命名空间的不同 键值处取出了不同的数据(这个具体下一节我们会做测试),其他的倒没什么特别的:

在这里插入图片描述

2.2 数据的删除

前面说过,NVS其实也就是在 Flash 空间上开辟了一块区域,那么这块区域肯定示有地址的,只是 ESP32-C3 使用 NVS方式,对使用者而言内存地址是不透明的,只是通过命名空间 和键值对自动分配(下一节会说明)。既然有保存,那么也得知道删除,因为不删除,数据可能会一直存在与那个地址空间。

示例和官方说明只是说明了如何使用 NVS 保存数据掉电不丢失,却没有针对性的说明如何清除数据。

我们通过nvs.h找到了两个函数:

esp\_err\_t nvs\_erase\_key(nvs\_handle\_t handle, const char\* key);

esp\_err\_t nvs\_erase\_all(nvs\_handle\_t handle);

使用这两个函数,我们测试一下,测试函数基于 示例nvs_rw_blob:,然后加入按键驱动:

ESP32-C3 学习测试(二、GPIO中断、按键驱动测试)

我们通过按键操作,对示例中保存的数据进行删除,在按键驱动中,我们修改一下代码:

static void button\_single\_click\_cb(void \*arg){
   
    uint8\_t \*num = (uint8\_t \*)arg;
    uint8\_t gpio_num = \*num;
    ESP\_LOGI(TAG, "BTN%d: BUTTON\_SINGLE\_CLICK\n", gpio_num);
    printf("nvs\_erase\_key test!\r\n");
    // nvs\_erase\_key(my\_handle,"restart\_conter");
    nvs\_handle\_t my_handle;

    // Open
    nvs\_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);

    nvs\_erase\_key(my_handle,"restart\_conter");

    // err = nvs\_commit(my\_handle);

    // nvs\_close(my\_handle);
}


图示如下:

在这里插入图片描述

测试效果如下图:

在这里插入图片描述

然后换一个 key 试一试:

在这里插入图片描述

最后测试一下nvs_erase_all,如下图:

在这里插入图片描述

2.3 命名空间,键值对

在上面例程中用到过命名空间,可以区分不同的存储区域,但是还有一个 key 参数,应该示键值对,感觉和命名空间一样,也是用来区分不同数据的,如图:

在这里插入图片描述

在前面的多个示例测试中,我们也多少对命名空间(函数中一般用参数const char* name表示)和键值对(函数中一般用参数const char* key表示)有一定理解,首先在示例中,使用了同一个命名空间,通过不同的键值对获取不同的数据:

在这里插入图片描述

针对 命名空间 和 键值对 做几个简单的测试,使用按钮连按,保存新的 命名空间的数值,在按键操作中保存新的数据:

在这里插入图片描述

这个数据也是上电的时候读取:

在这里插入图片描述

测试结果如下:

在这里插入图片描述
总结一下,基于前面的测试示例,画了一张图,如下:

在这里插入图片描述

2.4 字符串数据类型的保存

上面我们示例中使用的数据基本都是整形,虽然我们示例中使用了nvs_set_blob,也还是传入的整形数据,我们来测试下,保存字符串,数值类型的数据。

和上面的例程一样,通过按键操作保存一个字符串:

static void button\_long\_press\_start\_cb(void \*arg){
    uint8\_t \*num = (uint8\_t \*)arg;
    uint8\_t gpio_num = \*num;
    ESP\_LOGI(TAG, "BTN%d: BUTTON\_LONG\_PRESS\_START\n", gpio_num);

    char test_str[]="this is my test str,boom!";
    printf("nvs\_new\_name test!\r\n");
    nvs\_handle\_t my_handle;
    nvs\_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle);
    nvs\_set\_str(my_handle,"str\_test",test_str);
    nvs\_commit(my_handle);
    nvs\_close(my_handle);
}

在主函数中,新建一个读取函数:

esp\_err\_t my\_test\_str(void)
{
    nvs\_handle\_t my_handle;
    esp\_err\_t err;
    char get_char[30] = {0};
    // Open
    err = nvs\_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle);
    if (err != ESP_OK) return err;

    // Read
    size\_t required_size = 0;
    err = nvs\_get\_str(my_handle, "str\_test",NULL,&required_size);
    // err = nvs\_get\_i32(my\_handle, "str\_test", &restart\_counter);
    // if (err != ESP\_OK && err != ESP\_ERR\_NVS\_NOT\_FOUND) return err;
    // err = nvs\_get\_blob(my\_handle, "run\_time", NULL, &required\_size);
    if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;
   
    if (required_size > 0) {
        err = nvs\_get\_str(my_handle, "str\_test", get_char, &required_size);
        printf("test str is: %s \nsize is %d \n",get_char,required_size);
        if (err != ESP_OK) {
            return err;
        }
    }
    else{
        printf("no str data now!!!\n");
    }
    // Close
    nvs\_close(my_handle);
    return ESP_OK;
}

当然还是需要在app_main中调用my_test_str,测试效果如下:

在这里插入图片描述

在后期的 ESP32-C3 wifi 学习和使用的时候,保存的 SSID 和 密码 就会经常使用的 NVS 的字符串操作。

img img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上物联网嵌入式知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、电子书籍、讲解视频,并且后续会持续更新

如果你需要这些资料,可以戳这里获取