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

前面的7节课把开发板上基本的外设都测试过一边,接下来马上就要进入wifi和蓝牙应用的测试了
在此之前,还需要把掉电数据保存的功能给实现,在STM32中,可以使用内部的flash或者有些自带的EEPROM
在 ESP32-C3 上,使用非易失性存储 (NVS)  库的方式,进行简单数据的掉电保存
... 分区表章节添加分区表修改方法链接                               2022/3/29

前言

接下来的 ESP32-C3 功能测试都是基于自己设计的开发板:

自己画一块ESP32-C3 的开发板(第一次使用立创EDA)(PCB到手)

开发环境是乐鑫官方的 ESP-IDF, 基于VScode插件搭建好的:

ESP32-C3 VScode开发环境搭建(基于乐鑫官方ESP-IDF——Windows和Ubuntu双环境)

因为是对ESP32-C3 内部 Flash的操作,所以这里我们不要用到其他外设。

1、NVS基础介绍

通俗的来说,NVS 就是在 flash 上分配的一块内存空间 ,提供给用户保存掉电不丢失的数据 。

本文主要主要的目的是基于官方的SDK,学会使用 NVS,相关的知识简单提一下,比如分区表等(后期需要应用到的时候再来详细说明)。

1.1 基本介绍

对于ESP32-C3 NVS的介绍,乐鑫的官网的说明链接如下:

乐鑫官方ESP32-C3 NVS部分说明

简单通过官方介绍了解一下,还是使用几张官方的图表示一下:

在这里插入图片描述
在这里插入图片描述

1.2 分区表

我们说NVS是内存上的一块区域,那么他是地址是多少? 大小是多少?

这就不得不提一下分区表相关的内容了,官方说明地址如下:

ESP32-C3 的 flash 分区表

根据官方介绍,我们列出这里需要用到的:

分区表中的每个条目都包括以下几个部分:Name(标签)、Type(app、data 等)、SubType 以及在 flash 中的偏移量(分区的加载地址)。

  • Type 字段可以指定为 app (0x00) 或者 data (0x01),也可以直接使用数字 0-254(或者十六进制 0x00-0xFE)。注意,0x00-0x3F 不得使用(预留给 esp-idf 的核心功能)。
  • SubType 字段长度为 8 bit,内容与具体分区 Type 有关。目前,esp-idf 仅仅规定了 “app” 和 “data” 两种分区类型的子类型含义。

在这里插入图片描述

在这里插入图片描述

所以根据上面分区表的介绍说明,对于我们使用的 ESP32-C3,芯片启动会自动打印系统信息,对应的 NVS 说明如下图:

在这里插入图片描述

对于NVS分区如何生成,请参考我的另一篇博文:

ESP32-C3入门教程 网络 篇(二、 Wi-Fi 配网 — Smart_config方式 和 BlueIF方式)

在博文最后面,因为默认的分区表满足不了需求,告知了如何修改分区表。

1.3 NVS使用步骤

本文的NVS测试,是基于默认的分区表,所以在使用过程,我们不需要再进行分区表的操作。

NVS所需要用到的API,在nvs_flash.h 文件中,路径为:esp-idf/components/nvs_flash/include/nvs_flash.h

1、初始化 NVS,使用函数nvs_flash_init

在示例中:

在这里插入图片描述

2、打开NVS,使用nvs_open函数:

在这里插入图片描述

在示例中,第二个参数应该是表示打开的区域是可以读也可以写的 ,只读的是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 的字符串操作。

  • 19
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
智慧校园整体解决方案是响应国家教育信息化政策,结合教育改革和技术创新的产物。该方案以物联网、大数据、人工智能和移动互联技术为基础,旨在打造一个安全、高效、互动且环保的教育环境。方案强调从数字化校园向智慧校园的转变,通过自动数据采集、智能分析和按需服务,实现校园业务的智能化管理。 方案的总体设计原则包括应用至上、分层设计和互联互通,确保系统能够满足不同用户角色的需求,并实现数据和资源的整合与共享。框架设计涵盖了校园安全、管理、教学、环境等多个方面,构建了一个全面的校园应用生态系统。这包括智慧安全系统、校园身份识别、智能排课及选课系统、智慧学习系统、精品录播教室方案等,以支持个性化学习和教学评估。 建设内容突出了智慧安全和智慧管理的重要性。智慧安全管理通过分布式录播系统和紧急预案一键启动功能,增强校园安全预警和事件响应能力。智慧管理系统则利用物联网技术,实现人员和设备的智能管理,提高校园运营效率。 智慧教学部分,方案提供了智慧学习系统和精品录播教室方案,支持专业级学习硬件和智能化网络管理,促进个性化学习和教学资源的高效利用。同时,教学质量评估中心和资源应用平台的建设,旨在提升教学评估的科学性和教育资源的共享性。 智慧环境建设则侧重于基于物联网的设备管理,通过智慧教室管理系统实现教室环境的智能控制和能效管理,打造绿色、节能的校园环境。电子班牌和校园信息发布系统的建设,将作为智慧校园的核心和入口,提供教务、一卡通、图书馆等系统的集成信息。 总体而言,智慧校园整体解决方案通过集成先进技术,不仅提升了校园的信息化水平,而且优化了教学和管理流程,为学生、教师和家长提供了更加便捷、个性化的教育体验。
esp32-c3入门教程 环境③——vs code ide快速入门是关于如何使用VS Code IDE进行ESP32-C3开发的教程。VS Code是一种功能强大且流行的集成开发环境,它提供了许多便捷工具和插件来简化代码编写和调试的过程。 首先,我们需要在电脑上安装VS Code。你可以从官方网站上下载并安装VS Code。 安装完成后,打开VS Code并点击"Extensions"图标,搜索并安装ESP-IDF插件。这个插件是为了和ESP32-C3开发框架进行集成而开发的。 接下来,我们需要配置ESP-IDF插件。首先,点击左侧的扩展图标,找到ESP-IDF插件并点击设置图标。然后,找到"espidf.espIdfPath"选项,将其设置为你的ESP-IDF框架的路径。 现在,我们可以开始创建一个新的ESP32-C3项目。点击"File"菜单,选择"New File",然后输入项目的名称。然后,点击"File"菜单中的"Save"来保存项目文件。 接下来,打开终端窗口,点击"Terminal"菜单,选择"New Terminal"。在终端中,输入以下命令来初始化ESP-IDF环境: idf.py set-target esp32c3 idf.py menuconfig 这将打开ESP-IDF的配置菜单,在这里你可以配置各种参数和选项。 配置完成后,我们可以开始编写和调试代码了。在VS Code中,点击左侧的扩展图标,找到ESP-IDF插件并点击设置图标。然后,点击"Build"按钮来构建项目。 构建完成后,我们可以使用VS Code的调试功能来调试代码。点击左侧的调试图标,然后点击"Add Configuration"按钮来添加一个调试配置。然后,点击"Debug"按钮来启动调试模式。 总之,使用VS Code IDE进行ESP32-C3开发是非常方便和高效的。它提供了许多工具和插件来简化开发流程,并提供了强大的调试功能来帮助我们调试代码。希望这个快速入门教程对你有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

矜辰所致

您们的鼓励是我奋斗的动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值