ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)
前面的7节课把开发板上基本的外设都测试过一边,接下来马上就要进入wifi和蓝牙应用的测试了
在此之前,还需要把掉电数据保存的功能给实现,在STM32中,可以使用内部的flash或者有些自带的EEPROM
在 ESP32-C3 上,使用非易失性存储 (NVS) 库的方式,进行简单数据的掉电保存
... 分区表章节添加分区表修改方法链接 2022/3/29
目录
- 前言
- 1、NVS基础介绍
- 1.1 基本介绍
- 1.2 分区表
- 1.3 NVS使用步骤
- 2、示例测试
- 2.1 基础示例测试
- 2.2 数据的删除
- 2.3 命名空间,键值对
- 2.4 字符串数据类型的保存
前言
接下来的 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_i32
、nvs_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_blob
和nvs_set_blob
的函数,注意到他们都有一个 void*
类型得参数,表面这两个nvs的操作能够适用于任意类型的数据。
上面的save_run_time
函数,我们直接上添加了注释的源码:
/* Save new run time value in NVSby first reading a table of previously saved valuesand then adding the new value at the end of the table.Return an error if anything goes wrongduring 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 blobunsigned 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;// Commiterr = nvs_commit(my_handle);if (err != ESP_OK) return err;// Closenvs_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;// Opennvs_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};// Openerr = nvs_open(TEST_NAMESPACE, NVS_READWRITE, &my_handle);if (err != ESP_OK) return err;// Readsize_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");}// Closenvs_close(my_handle);return ESP_OK;
}
当然还是需要在app_main
中调用my_test_str
,测试效果如下:
在后期的 ESP32-C3 wifi 学习和使用的时候,保存的 SSID 和 密码 就会经常使用的 NVS 的字符串操作。
ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)相关推荐
- ESP32-C3入门教程 基础篇⑪——Non-Volatile Storage (NVS) 非易失性存储参数的读写
文章目录 一.前言 二.NVS介绍 三.操作流程 3.1 读操作流程 3.2 写操作流程 四.关键函数 五.随机整数 读写示例 六.对象/数组 读写示例 七.总结 八.参考 一.前言 本文基于VS C ...
- ESP32-C3入门教程 基础篇②——GPIO口输入,按键的长按和短按
文章目录 一.前言 二.硬件准备 三.知识要点 3.1 GPIO使用 3.2 时钟节拍 四.参考例程 五.功能简述 六.源码实现 6.1 中断方式 6.2 定时扫描 七.源码详解 一.前言 本文基于V ...
- ESP32-C3入门教程 基础篇(三、UART模块 — 与Enocean无线模块串口通信)
测试第三课,ESP32-C3的串口通信测试 老样子,使用Enocean无线模块和ESP32-C3进行串口通信. 目录 前言 1.UART示例测试 1.1 UART 基础测试 1.2 与Enocean无 ...
- ESP32-C3入门教程 基础篇(六、TIMG 硬件定时器 与 软件定时器)
到了测试第6课,还没有玩过ESP32-C3的基本定时器,虽然FreeRTOS,可以使用软件定时器 但是软件定时器毕竟也有不适用的时候,这个在我FreeRTOS博文中有单独说明. 所以硬件定时器也得熟悉 ...
- ESP32-C3入门教程 基础篇(四、I2C总线 — 与SHT21温湿度传感器通讯)
测试第四课,了解ESP32-C3的 I2C 总线使用,与SHT21 温湿度传感器通讯 这一课把基础介绍放在前面,先看基本流程,再去修改代码 目录 前言 1. ESP32-C3 I2C基础介绍 1.1 ...
- 泰凌微8258入门教程 基础篇④——sig_mesh_sdk架构介绍
文章目录 一.前言 二.SDK文件架构 三.SDK Demo Project 四.vendor文件架构 4.1 common 4.2 Demo Project目录 五.产品类型定义 一.前言 本系列的 ...
- 泰凌微8258入门教程 基础篇⑤——发送数据流程
文章目录 一.Sig Mesh协议 二.Sig SDK 流程图 三.mesh_tx_cmd 四.增加Log 五.调试 一.Sig Mesh协议 二.Sig SDK 流程图 Created with R ...
- 【SQL Server】入门教程-基础篇(三)
目录
- 泰凌微8258入门教程 基础篇①——Bluetooth® SIG Mesh 快速上手
文章目录 一.前言 1.1 Telink Bluetooth® Mesh开发工具 1.2 Bluetooth®SIG mesh演示 1.3 基于SDK的演示二进制文件 二.基于APP的节点控制 2.1 ...
最新文章
- 知识驱动的推荐系统:现状与展望
- UVa1467 Installations(贪心)
- 【Mongodb】如何创建mongodb的replica set
- 《MyBatis技术原理与实战》之动态SQL
- 双向多点路由重分布--如何防止路由环路以及次优路径
- 2021牛客多校2 - WeChat Walk(分块)
- python调用shell用什么类_python脚本中调用shell命令
- python进行数据查询_如何进行python数据库查询?(实例解析)
- 部署System Center App Controller 2012 Service Pack 1 (6)
- jQuery图片水平滑动延迟加载动画
- Python 将 HTML 文件转成指定的编码
- 下载oracle环境变量失败,oracle instantClient 安装配置及Error: DPI-1047: Cannot locate a 64-bit Oracle Client...
- shapefile文件格式说明
- PostMan——安装使用教程(图文详解)
- 开发DSP硬件驱动程序的一种方法
- 2021年,小灰都读了哪些书?
- 跑车html5网页模板,html5代码画兰博基尼跑车,6不6?
- Cryptocell-712安全引擎概述
- Unity 绘制弹球和台球的运动轨迹
- excel中表格行高最大值是多少?如果超过了怎么调整?