ESP-IDF+vscode开发ESP32第十三讲——NVS
目录一、NVS梳理1.1 分区 (Partition)NVS 的专属“仓库”1.2 页面 (Page)仓库里的“货架”1.3 条目 (Entry)货架上的“最小存储格”1.4 键值对 (Key-Value Pair)实际存放的“货物”1.5 命名空间 (Namespace)货物的“分类文件夹”二、完善工程1 nvs_basics.c2 nvs_basics.h3 mian.c三、添加官方nvs控制台命令四、结果展示五、nvs分区生成程序5.1 使用函数烧录5.2 使用python工具烧录5.3 结果展示前言NVSNon-Volatile Storage非易失性存储是一个数据储存库。它的核心作用就是在 Flash 中持久化地存储键值对格式的数据。它非常适合存储那些不经常更改、但需要长期保留的配置数据。例如wifi和蓝牙的各种配网凭据。本文使用的开发板是微雪的ESP32-P4-Module-DEV-KIT。ESP-IDF版本是6.0.1。基于第一章的工程模板。来实现NVS库的各种操作。一、NVS梳理NVSNon-Volatile Storage非易失性存储是 ESP32 中用于在 Flash 中持久化保存数据的官方库。它本质上是一个轻量级的键值对存储系统。但是各种名词、概念较多容易产生误解。这里我从宏观到微观的层级来梳理这些概念。1.1 分区 (Partition)NVS 的专属“仓库”NVS 的数据最终是存储在 ESP32 的 Flash 上的。 ESP32 的 Flash使用一个分区表对各个类型的数据空间进行管理其中就有也必须有一个专门的分区给 NVS 使用。在partitions.csv分区表中类型Type为data子类型SubType为nvs的分区。默认通常是 24KB (0x6000)。如果你的设备需要存储大量配置可以在分区表中手动调大这个数值。它是 NVS 库操作的物理边界所有的 NVS 数据都只能在这个划定的 Flash 区域内读写。ESP32 的 Flash实际分区表如下关于各种分区的解释可以去看《分区表》前面工程《ESP-IDFvscode开发ESP32第九讲——I2S工程1》也涉及到分区表的使用。# ESP-IDF Partition Table # Name , Type, SubType, Offset , Size , Flags nvs , data, nvs , 0x9000 , 0x6000, phy_init , data, phy , 0xf000 , 0x1000, factory , app , factory, 0x10000, 10M , ,1.2 页面 (Page)仓库里的“货架”NVS 并不是把数据杂乱无章地堆在分区里而是将分区划分成了若干个固定大小的“页面”。大小通常一个页面的大小等于 Flash 的一个物理扇区即 4096 字节 (4KB)。状态管理每个页面都有自己的生命周期状态如空、活跃、写满、擦除中。磨损均衡NVS 采用日志式的写入方式。当前活跃的页面写满后系统会自动切换到下一个空白页面继续写入。这种机制避免了频繁擦写同一个物理地址极大地延长了 Flash 的使用寿命。1.3 条目 (Entry)货架上的“最小存储格”页面进一步被划分为更小的单元称为“条目”。大小每个条目固定为 32 字节。作用它是 NVS 存储数据的最小物理单位。无论是存一个小小的整数还是存一段很长的字符串都会占用至少一个条目。空间计算如果你发现 NVS 经常报“空间不足”往往是因为条目被耗尽了。例如在一个 24KB 的 NVS 分区中大约只有几千个条目可供使用。1.4 键值对 (Key-Value Pair)实际存放的“货物”这是开发者在写代码时最直接接触的概念。NVS 的所有数据操作都是基于键值对的。键 (Key)数据的名字ASCII 字符串最大长度不能超过 15 个字符。值 (Value)实际要存储的数据。支持多种类型整数如int8_t,int32_t,uint64_t等字符串以\0结尾最大约 4000 字节二进制大对象BLOB用于存结构体、数组等最大约 50 万字节存储逻辑当你写入一个键值对时NVS 会根据数据的大小占用 1 个或多个连续的“条目”来存放它。1.5 命名空间 (Namespace)货物的“分类文件夹”当我们需要存入大量键值对很有可能有重名的键为了防止不同功能模块的键名Key发生冲突NVS 引入了命名空间的概念。作用相当于电脑里的“文件夹”。你可以把 Wi-Fi 的配置放在名为wifi的命名空间里把传感器的校准数据放在sensor的命名空间里。隔离性即使两个命名空间里都有叫config的键它们也是完全独立、互不干扰的。限制命名空间的名称同样不能超过 15 个字符。到这所有NVS的储存结构我们清楚了下面就可以尝试去使用NVS了。二、完善工程创建组件nvs_basics在cmakelists中添加依赖声明REQUIRES nvs_flash1 nvs_basics.c#include stdio.h #include nvs_basics.h static const char *TAG NVS_BASICS; typedef struct { uint8_t id; char name[32]; float values[2]; uint32_t flags; int16_t counts[2]; bool active; } test_data_t; void nvs_init(void) { esp_err_t err; err nvs_flash_init(); if (err ESP_ERR_NVS_NO_FREE_PAGES ) { ESP_LOGW(TAG, 分区中没有了空页面); return; } else if (err ESP_ERR_NOT_FOUND ) { ESP_LOGW(TAG, 没有找到NVS分区); return; } nvs_handle_t storage_handle; ESP_ERROR_CHECK(nvs_open(storage, NVS_READWRITE_PURGE , storage_handle)); ESP_ERROR_CHECK(nvs_set_i32(storage_handle, num, 123456789)); ESP_ERROR_CHECK(nvs_set_str(storage_handle, str, Hello, ESP32!)); test_data_t test_data { .id 123, .name Test Sample, .values {3.14f, 2.718f}, .flags 0xABCD1234, .counts {-100, 100}, .active true }; err nvs_set_blob(storage_handle, test_data, test_data, sizeof(test_data_t)); int32_t num; ESP_ERROR_CHECK(nvs_get_i32(storage_handle, num, num)); ESP_LOGI(TAG, 从NVS读取到的整数: %d, num); size_t len 0; err nvs_get_str(storage_handle, str, NULL, len); if(err ESP_OK){ char* message malloc(len); // 分配足够的内存来存储字符串 ESP_ERROR_CHECK(nvs_get_str(storage_handle, str, message, len)); ESP_LOGI(TAG, 从NVS读取到的字符串: %s, message); free(message); } size_t required_size 0; err nvs_get_blob(storage_handle, test_data, NULL, required_size); if(err ESP_OK){ test_data_t* test_data malloc(required_size); ESP_ERROR_CHECK(nvs_get_blob(storage_handle, test_data, test_data, required_size)); ESP_LOGI(TAG, 从NVS读取到的测试数据: ID%d, Name%s, test_data-id, test_data-name); free(test_data); } nvs_iterator_t it; err nvs_entry_find(nvs, storage, NVS_TYPE_ANY, it); if(err ESP_OK){ do { nvs_entry_info_t info; ESP_ERROR_CHECK(nvs_entry_info(it, info)); ESP_LOGI(TAG, 命名空间%s, 找到的键: %s, 类型: %x, info.namespace_name, info.key, info.type); } while (nvs_entry_next(it) ESP_OK); nvs_release_iterator(it); } nvs_stats_t nvs_stats; ESP_ERROR_CHECK(nvs_get_stats(nvs, nvs_stats)); ESP_LOGI(TAG, NVS统计信息 - 已使用条目: %d, 空闲条目%d, 可用条目: %d, 总条目: %d, 命名空间数量: %d, nvs_stats.used_entries, nvs_stats.free_entries, nvs_stats.available_entries, nvs_stats.total_entries, nvs_stats.namespace_count); ESP_ERROR_CHECK(nvs_commit(storage_handle)); nvs_close(storage_handle); //ESP_ERROR_CHECK(nvs_flash_deinit()); }2 nvs_basics.h#ifndef NVS_BASICS_H #define NVS_BASICS_H #include stdio.h // 输入输出函数 #include string.h // 字符串处理函数 #include esp_log.h // ESP32日志函数 #include FreeRTOS/FreeRTOS.h // FreeRTOS函数 #include FreeRTOS/task.h // FreeRTOS任务管理函数 #include FreeRTOS/semphr.h // FreeRTOS信号量管理函数 #include nvs_flash.h // NVS Flash函数 void nvs_init(void); #endif // NVS_BASICS_H3 mian.c#include stdio.h #include user.h #include nvs_basics.h void app_main(void) { CONSOLE_REPL_INIT(); // 初始化控制台REPL环境 nvs_init(); while(1) { vTaskDelay(pdMS_TO_TICKS(1000)); } }以上实现了分区初始化接着向默认分区存入了一个int32、string和blob数据接着读取以上数据查看是否正确最后对分区所有信息进行了一个检索。代码使用比较简单就不讲解了。各种函数的定义去查看官方文档或《ESP32实用API指南3》。三、添加官方nvs控制台命令打开esp安装目录例如我的是《E:\esp\v6.0.1\esp-idf\examples\system\console\advanced\components》可以看到共有三个控制台命令文件夹其中system在第二章新建工程模板中就已经添加了现在继续添加nvs。找到user组件的idf_component.yml新增nvs的路径依赖如下。version: 1.0.0 dependencies: cmd_system: path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_system cmd_nvs: path: ${IDF_PATH}/examples/system/console/advanced/components/cmd_nvs接着在user.c中的控制台初始化程序中注册nvs相关命令。/*--------------------------------------------------------------------------*/ /** * brief console REPL init * param[in] void * note * return void */ /*--------------------------------------------------------------------------*/ void CONSOLE_REPL_INIT(void) { esp_console_repl_config_t repl_config ESP_CONSOLE_REPL_CONFIG_DEFAULT(); // 使用默认REPL配置 esp_console_repl_t *repl NULL; #if CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG esp_console_dev_usb_serial_jtag_config_t usb_serial_jtag_config ESP_CONSOLE_DEV_USB_SERIAL_JTAG_CONFIG_DEFAULT(); // 使用默认USB串行JTAG配置 ESP_ERROR_CHECK(esp_console_new_repl_usb_serial_jtag(usb_serial_jtag_config, repl_config, repl)); // 创建USB串行JTAG REPL环境 #else esp_console_dev_uart_config_t uart_config ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT(); // 使用默认UART配置 ESP_ERROR_CHECK(esp_console_new_repl_uart(uart_config, repl_config, repl)); // 创建UART REPL环境 #endif ESP_ERROR_CHECK(esp_console_start_repl(repl)); // 启动REPL环境 esp_console_register_help_command(); // 注册帮助命令 register_system(); // 注册系统常用命令 register_nvs(); // 注册NVS相关命令 // linenoiseSetDumbMode(1); // 设置linenoise为简单模式适用于串行终端 }这样就完成了四、结果展示以上是我们对nvs的测试结果。使用help命令可以发现新增了很多nvs的命令注意这些命令操控的分区首先要在代码中使用nvs_flash_init进行初始化。五、nvs分区生成程序从上文可以看到我们很容易向nvs分区中写入和读取数据。但是如果我们现在准备了大量的键值对需要存入那么这种代码程序的写入方式就很低效。于是我们需要使用NVS 分区生成程序。NVS 分区生成程序根据 CSV 文件中的键值对生成二进制文件。首先需要自定义一个CSV文件储存需要烧录分区的键值对。例如nvs_data.cvs内容如下key,type,encoding,value my_app,namespace,, wifi_ssid,data,string,MyHomeWiFi wifi_pass,data,string,password123 device_id,data,u32,10086关于文件格式见《CSV文件格式》说明分区生成程序不会对重复键进行检查而将数据同时写入这两个重复键中。请注意不要使用同名的键新页面创建后前一页的空白处不会再写入数据。CSV 文件中的字段须按次序排列以优化内存暂不支持 64 位数据类型。注意前面我们说的分区表也是CSV格式文件但是作用不相同。对比维度分区表 (Partition Table)NVS 数据 CSV核心作用定义 Flash 存储空间的物理划分地址、大小、类型定义要写入 NVS 分区的具体键值对数据处理时机编译阶段由构建系统解析生成partition-table.bin烧录前由nvs_partition_gen.py解析生成数据.bin必需列名Name,Type,SubType,Offset,Size,Flagskey,type,encoding,value内容示例nvs, data, nvs, 0x9000, 0x6000,wifi_ssid, data, string, MyWiFi定义好nvs_data.cvs之后需要将其中的键值对烧录进nvs分区提供了两者方法分别是直接使用函数nvs_create_partition_image通过 CMake 创建分区二进制文件和手动调用nvs_partition_gen.py工具5.1使用函数烧录打开组件nvs_basics的中的CMakeLists.txt文件修改如下idf_component_register(SRCS nvs_basics.c INCLUDE_DIRS include REQUIRES nvs_flash) # 分区名nvs分区数据文件nvs_data.csv分区类型FLASH_IN_PROJECT nvs_create_partition_image(nvs nvs_data.csv FLASH_IN_PROJECT)nvs_create_partition_image宏命令原型如下nvs_create_partition_image(partition csv [FLASH_IN_PROJECT] [DEPENDS dep1 dep2 ...])参数必填说明partition✅分区名称必须与partitions.csv中的 Name 字段完全一致如nvs、my_nvscsv✅NVS 数据源 CSV 文件的路径支持绝对路径或相对于当前CMakeLists.txt的路径FLASH_IN_PROJECT❌添加此标志后执行idf.py flash时会自动烧录该 NVS 分区到芯片。不加则只生成.bin文件但不烧录DEPENDS❌指定额外依赖项。当这些文件发生变化时触发 NVS 镜像重新生成。常用于 CSV 引用了外部二进制文件的场景注如果csv文件和CMakeLists.txt文件在同一个路径文件下csv可直接填入csv文件名5.2 使用python工具烧录首先找到nvs_partition_gen.py的地址例如E:\esp\v6.0.1\esp-idf\components\nvs_flash\nvs_partition_generator将nvs_partition_gen.py文件粘贴到工程根目录下接着就可以使用python命令了命令使用见《NVS 分区生成程序 - ESP32-P4 - — ESP-IDF 编程指南 v6.0.1 文档》5.3 结果展示可以看出nvs_data.cvs所有键值对都写入了nvs分区中
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2638300.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!