欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 财经 > 产业 > ESP32-C3 入门笔记06:存储 API - 非易失性存储库 (NVS) (读/写/删除数据)

ESP32-C3 入门笔记06:存储 API - 非易失性存储库 (NVS) (读/写/删除数据)

2025/6/29 1:04:59 来源:https://blog.csdn.net/Naiva/article/details/143617721  浏览:    关键词:ESP32-C3 入门笔记06:存储 API - 非易失性存储库 (NVS) (读/写/删除数据)

在这里插入图片描述

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

在 ESP32-C3 上使用 ESP-IDF 5.2.3 版本框架开发时,如果要实现 EEPROM 掉电存储功能,可以使用 NVS (Non-Volatile Storage) 库。ESP32 并没有直接支持 EEPROM,但是 NVS 库提供了类似 EEPROM 的功能,它可以用来保存少量的非易失性数据,存储内容在设备掉电后不会丢失。

在这里插入图片描述

在这里插入图片描述

应用示例

nvs_rw_value

演示如何读取及写入 NVS 单个整数值。

此示例中的值表示 ESP32 模组重启次数。NVS 中数据不会因为模组重启而丢失,因此只有将这一值存储于 NVS 中,才能起到重启次数计数器的作用。

该示例也演示了如何检测读取/写入操作是否成功,以及某个特定值是否在 NVS 中尚未初始化。诊断程序以纯文本形式提供,有助于追踪程序流程,及时发现问题。

#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"void app_main(void)
{// 初始化 NVS(非易失性存储),用于存储数据esp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// 如果 NVS 分区被截断(没有足够的空间)或版本不匹配,则需要擦除分区并重新初始化ESP_ERROR_CHECK(nvs_flash_erase());  // 擦除分区err = nvs_flash_init();  // 重新初始化 NVS}ESP_ERROR_CHECK( err );  // 检查是否初始化成功// 打开名为 "storage" 的 NVS 存储区,NVS_READWRITE 表示读写权限printf("\n");printf("Opening Non-Volatile Storage (NVS) handle... ");nvs_handle_t my_handle;err = nvs_open("storage", NVS_READWRITE, &my_handle); // 打开NVSif (err != ESP_OK) {// 如果打开 NVS 存储区失败,输出错误信息printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));} else {// 如果打开成功,输出提示printf("Done\n");// 从 NVS 读取 "restart_counter" 的值printf("Reading restart counter from NVS ... ");int32_t restart_counter = 0;  // 如果没有初始化,值默认为 0err = nvs_get_i32(my_handle, "restart_counter", &restart_counter);// 读取NVSswitch (err) {case ESP_OK:  // 读取成功printf("Done\n");printf("Restart counter = %" PRIu32 "\n", restart_counter);  // 打印计数器值break;case ESP_ERR_NVS_NOT_FOUND:  // 如果没有找到 "restart_counter" 键(第一次运行)printf("The value is not initialized yet!\n");break;default :  // 其他错误printf("Error (%s) reading!\n", esp_err_to_name(err));}// 更新 "restart_counter" 的值printf("Updating restart counter in NVS ... ");restart_counter++;  // 增加计数器err = nvs_set_i32(my_handle, "restart_counter", restart_counter);  // 写入NVSprintf((err != ESP_OK) ? "Failed!\n" : "Done\n");// 提交更改到 NVS 存储printf("Committing updates in NVS ... ");err = nvs_commit(my_handle);  // 提交NVSprintf((err != ESP_OK) ? "Failed!\n" : "Done\n");// 关闭 NVS 存储区,释放资源nvs_close(my_handle); // 关闭 NVS}printf("\n");// 模拟重启过程,倒计时 10 秒后重启模块for (int i = 10; i >= 0; i--) {printf("Restarting in %d seconds...\n", i);vTaskDelay(1000 / portTICK_PERIOD_MS);  // 延迟 1 秒}printf("Restarting now.\n");fflush(stdout);  // 刷新输出缓冲区esp_restart();  // 重启 ESP32
}

输出效果

在这里插入图片描述

注释概述:

  • 初始化 NVS: 初始化非易失性存储(NVS),并确保存储区没有问题(例如需要擦除)。
  • 打开 NVS 存储区: 打开 "storage" 存储区以便进行读写操作。
  • 读取数据: 读取存储区中的 restart_counter 键的值。如果数据没有找到,则返回默认值 0,并提示用户。
  • 写入数据: 更新 restart_counter 的值,并写入 NVS 存储区。
  • 提交更改: 调用 nvs_commit() 确保所有更改都被写入闪存。
  • 关闭 NVS: 关闭存储区,释放资源。
  • 重启过程: 模拟设备重启,在重启前倒计时并打印信息,最后调用 esp_restart() 实际重启设备。

这段代码展示了如何在 ESP32 上使用 NVS(非易失性存储)来读取和写入数据,特别是一个“重启计数器”的例子。它演示了如何初始化 NVS、读取存储的数据、更新计数器、提交修改并关闭 NVS 存储区。以下是代码逐步解释:

代码解析

1. 初始化 NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// NVS 分区被截断,可能需要擦除以便重新初始化ESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();
}
ESP_ERROR_CHECK( err );
  • nvs_flash_init() 初始化 NVS 存储。第一次调用时,它会加载存储区域。如果存储区域有问题或不兼容,会抛出 ESP_ERR_NVS_NO_FREE_PAGESESP_ERR_NVS_NEW_VERSION_FOUND 错误。此时,我们会擦除存储并重新初始化。
2. 打开 NVS 存储区
nvs_handle_t my_handle;
err = nvs_open("storage", NVS_READWRITE, &my_handle);
if (err != ESP_OK) {printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
} else {printf("Done\n");
}
  • 使用 nvs_open() 打开名为 "storage" 的 NVS 存储区,并提供读写权限 (NVS_READWRITE)。
  • 如果打开失败,输出错误信息。
3. 读取数据
int32_t restart_counter = 0;
err = nvs_get_i32(my_handle, "restart_counter", &restart_counter);
switch (err) {case ESP_OK:printf("Done\n");printf("Restart counter = %" PRIu32 "\n", restart_counter);break;case ESP_ERR_NVS_NOT_FOUND:printf("The value is not initialized yet!\n");break;default:printf("Error (%s) reading!\n", esp_err_to_name(err));
}
  • 使用 nvs_get_i32() 读取存储在 "restart_counter" 键下的整数。如果读取成功,会输出当前计数器的值。如果没有找到数据(第一次读取),会输出“值尚未初始化”提示。
4. 写入数据
restart_counter++;
err = nvs_set_i32(my_handle, "restart_counter", restart_counter);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
  • 更新 restart_counter 的值,并使用 nvs_set_i32() 写入新的计数值。
5. 提交修改
err = nvs_commit(my_handle);
printf((err != ESP_OK) ? "Failed!\n" : "Done\n");
  • 使用 nvs_commit() 确保对 NVS 的修改被写入闪存。调用此函数后,数据会持久化到存储中。
6. 关闭 NVS 存储区
nvs_close(my_handle);
  • 完成操作后,使用 nvs_close() 关闭 NVS 存储区句柄,释放资源。
7. 重启模块
for (int i = 10; i >= 0; i--) {printf("Restarting in %d seconds...\n", i);vTaskDelay(1000 / portTICK_PERIOD_MS);
}
printf("Restarting now.\n");
fflush(stdout);
esp_restart();
  • 输出一个倒计时,等待 10 秒后重启模块。esp_restart() 会触发模块重启,重新加载应用。

总结:

  • 这段代码展示了如何使用 ESP32 的 NVS 存储来保存一个重启计数器的值,并在设备重启后读取和更新该值。
  • 使用 nvs_flash_init() 来初始化存储,nvs_open() 打开存储区,nvs_get_*nvs_set_* 用于读写数据,nvs_commit() 用于提交更改,nvs_close() 关闭存储。
  • 该代码可以在 ESP32 中用于保存小型配置数据,并在掉电或重启后恢复数据。

如果你想在实际项目中使用这段代码,可以根据需要修改数据键名和数据类型,扩展功能以满足你的需求。


nvs_rw_blob

演示如何读取及写入 NVS 单个整数值和 BLOB(二进制大对象),并在 NVS 中存储这一数值,即便 ESP32 模组重启也不会消失。

  • value - 记录 ESP32 模组软重启次数和硬重启次数。

  • blob - 内含记录模组运行次数的表格。此表格将被从 NVS 读取至动态分配的 RAM 上。每次手动软重启后,表格内运行次数即增加一次,新加的运行次数被写入 NVS。下拉 GPIO0 即可手动软重启。

该示例也演示了如何执行诊断程序以检测读取/写入操作是否成功。

以下是对该代码的详细注释:

#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "driver/gpio.h"#define STORAGE_NAMESPACE "storage"  // 定义NVS存储空间的命名空间#if CONFIG_IDF_TARGET_ESP32C3
#define BOOT_MODE_PIN GPIO_NUM_9  // 根据不同目标板选择GPIO引脚(ESP32-C3使用GPIO_NUM_9)
#else
#define BOOT_MODE_PIN GPIO_NUM_0  // 默认使用GPIO_NUM_0(通常用于启动模式选择)
#endif //CONFIG_IDF_TARGET_ESP32C3/* 保存模块重启计数到NVS中首先读取已保存的计数值,然后递增计数器如果过程中出现错误,则返回错误*/
esp_err_t save_restart_counter(void)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS存储,进行读写操作err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);if (err != ESP_OK) return err;// 读取重启计数器,如果未初始化,默认为0int32_t restart_counter = 0;err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;// 增加重启计数器restart_counter++;err = nvs_set_i32(my_handle, "restart_conter", restart_counter);if (err != ESP_OK) return err;// 提交更改到NVSerr = nvs_commit(my_handle);if (err != ESP_OK) return err;// 关闭NVS存储nvs_close(my_handle);return ESP_OK;
}
/** 保存新的运行时间值到NVS中
*   首先读取之前保存的时间表,然后将新的值添加到表末尾
*   如果过程中出现错误,则返回错误
**/
esp_err_t save_run_time(void)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS存储,进行读写操作err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);if (err != ESP_OK) return err;// 读取之前保存的 "run_time" Blob数据大小//读nvs,读取键值对为 "run_time" 处的内容放入变量 required_sizesize_t required_size = 0;/*先读取1次但第3个参数输出地址使用的是 NULL,表示读出的数据不保存,因为这里使用只是为了看一下 "run_time" 处是否有数据,只是先读一下数据,看一下读完以后 required_size 还是不是”0“*/err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);// 读取NVSif (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;//这里申请一块地址,定义为 run_time ,地址上存放的示 uint32_t 数据,大小为:required_size 大小 + sizeof(uint32_t) uint32_t* run_time = malloc(required_size + sizeof(uint32_t));  // 为新的Blob数据分配空间// 读取之前保存的 Blob 数据(如果有的话)就先读出来,保存在刚才申请的 地址 run_time 处(第3个参数)。if (required_size > 0) {err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);// 读取NVS 保存在 run_timeif (err != ESP_OK) {free(run_time);return err;}}required_size += sizeof(uint32_t);// 增加一个新的时间戳后所需要的总内存大小(4字节)// 写入新的运行时间,并追加到已保存的Blob数据末尾 // 数组形式保存数据:类似于 uint32_t run_time[] 数组给数组赋值run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;err = nvs_set_blob(my_handle, "run_time", run_time, required_size);// 写入NVSfree(run_time);  // 释放内存if (err != ESP_OK) return err;// 提交更改到NVSerr = nvs_commit(my_handle);// 提交NVSif (err != ESP_OK) return err;// 关闭NVS存储nvs_close(my_handle); // 关闭NVSreturn ESP_OK;
}/* 从NVS中读取并打印重启计数器和运行时间表如果过程中出现错误,则返回错误*/
esp_err_t print_what_saved(void)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS存储,进行读写操作err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle); // 打开 NVSif (err != ESP_OK) return err;// 读取并打印重启计数器int32_t restart_counter = 0;err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);// 读取 NVSif (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;printf("Restart counter = %" PRIu32 "\n", restart_counter);// 读取并打印运行时间的Blob数据size_t required_size = 0;err = nvs_get_blob(my_handle, "run_time", NULL, &required_size); // 读取 NVSif (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;printf("Run time:\n");if (required_size == 0) {printf("Nothing saved yet!\n");} else {uint32_t* run_time = malloc(required_size);err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);// 读取 NVSif (err != ESP_OK) {free(run_time);return err;}// 打印每个保存的运行时间for (int i = 0; i < required_size / sizeof(uint32_t); i++) {printf("%d: %" PRIu32 "\n", i + 1, run_time[i]);}free(run_time);  // 释放内存}// 关闭NVS存储nvs_close(my_handle);// 关闭 NVSreturn ESP_OK;
}void app_main(void)
{// 初始化 NVS(非易失性存储)esp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// 如果NVS分区被截断,或者版本不匹配,则擦除分区并重新初始化ESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();}ESP_ERROR_CHECK( err );// 3.打印NVS中的保存数据err = print_what_saved();if (err != ESP_OK) printf("Error (%s) reading data from NVS!\n", esp_err_to_name(err));// 1.保存重启计数到NVSerr = save_restart_counter();if (err != ESP_OK) printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));// 配置GPIO引脚gpio_reset_pin(BOOT_MODE_PIN);gpio_set_direction(BOOT_MODE_PIN, GPIO_MODE_INPUT);/* 监测BOOT_MODE_PIN(GPIO0)引脚状态如果GPIO0为低电平持续超过1000ms,则保存运行时间并重启*/while (1) {if (gpio_get_level(BOOT_MODE_PIN) == 0) {vTaskDelay(1000 / portTICK_PERIOD_MS);  // 等待1秒钟if(gpio_get_level(BOOT_MODE_PIN) == 0) {  // 如果引脚仍为低电平//2. 保存运行时间并重启err = save_run_time();if (err != ESP_OK) printf("Error (%s) saving run time blob to NVS!\n", esp_err_to_name(err));printf("Restarting...\n");fflush(stdout);esp_restart();  // 重启设备}}vTaskDelay(200 / portTICK_PERIOD_MS);  // 每200ms检查一次}
}

save_run_time()函数

怎么理解下面代码?

    required_size += sizeof(uint32_t); run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;怎么理解 

这两行代码在处理 NVS 存储的 Blob 数据时,用于更新存储的运行时间列表并添加新的时间戳。具体来看,它们的作用是:

required_size += sizeof(uint32_t);

这一行的作用是 增加空间 来存储一个新的时间戳。我们逐步分析这行代码:

  1. required_size 变量最初保存了从 NVS 中读取的 Blob 数据 的大小。在之前的代码中,nvs_get_blob() 会返回当前存储的数据的大小,并将其保存在 required_size 变量中。

  2. sizeof(uint32_t) 是每个时间戳占用的内存空间大小(4 字节,uint32_t 是 32 位无符号整数类型)。因此,required_size += sizeof(uint32_t); 的意思是:为即将添加的新时间戳增加空间,即让 required_size 表示增加一个新的时间戳后所需要的总内存大小。

  3. 例如,如果 required_size 原来表示存储了 3 个时间戳的数据(3 × 4 字节 = 12 字节),那么通过 required_size += sizeof(uint32_t) 后,required_size 会增加 4 字节,表示需要为新添加的时间戳留出空间。此时 required_size 变为 16 字节。

run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;

这一行是将新的时间戳添加到 run_time 数组中。

  1. run_time 是一个动态分配的内存数组,大小为 required_size 字节。由于每个时间戳占用 sizeof(uint32_t) 字节(4 字节),因此可以使用 required_size / sizeof(uint32_t) 来计算出当前 run_time 数组中已经存储了多少个时间戳。

  2. required_size / sizeof(uint32_t) 会返回当前数组中已经存储的时间戳的数量。假设 required_size 的当前值是 16 字节,那么此时 required_size / sizeof(uint32_t) 就等于 4,表示当前数组中已经存储了 4 个时间戳。

  3. run_time[required_size / sizeof(uint32_t) - 1] 这个索引就是新时间戳将要存储的位置。假设 required_size 为 16 字节(4 个 uint32_t 时间戳),那么 required_size / sizeof(uint32_t) - 1 会等于 3,即新时间戳将被存储在 run_time[3](第四个位置)。

  4. xTaskGetTickCount() * portTICK_PERIOD_MS 会返回从系统启动以来的运行时间(单位为毫秒)。xTaskGetTickCount() 返回的是一个以 ticks 为单位的值,而 portTICK_PERIOD_MS 是一个常量,它表示每个系统 tick 的时间(单位为毫秒)。乘积的结果就是当前的运行时间,单位为毫秒。

  5. run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS; 就是将新的时间戳(系统启动以来的时间)存储在 run_time 数组的最后一个位置。

举个例子:

假设现在 required_size = 12 字节(表示当前存储了 3 个时间戳,每个时间戳 4 字节)。现在我们要添加一个新的时间戳:

  • 第一步,required_size += sizeof(uint32_t); 会使 required_size 变为 16 字节,表示需要为新时间戳腾出空间。

  • 第二步,run_time[required_size / sizeof(uint32_t) - 1] 会计算出当前数组的最后一个索引。在这个例子中,required_size / sizeof(uint32_t) 等于 16 / 4 = 4,减去 1 后得到索引 3。所以新的时间戳会被写入 run_time[3]

  • 最后,xTaskGetTickCount() * portTICK_PERIOD_MS 计算出当前的运行时间,并将其存储在 run_time[3] 位置。

总结:

  1. required_size += sizeof(uint32_t); 是为了为新添加的时间戳增加内存空间。
  2. run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS; 将当前的时间戳(系统启动以来的时间)存储在 run_time 数组的最后一个位置,确保数据结构按顺序保存每次运行的时间。

print_what_saved()函数

在这里插入图片描述


输出效果

在这里插入图片描述

在这里插入图片描述

注释概述:

  1. save_restart_counter: 用于读取并增加设备的重启计数器。每次重启时,计数器都会递增并保存到NVS中。
  2. save_run_time: 用于记录设备的运行时间,将每次运行的时间戳保存在一个数组(Blob)中,每次保存时追加新数据。
  3. print_what_saved: 用于读取并打印NVS中保存的数据,包括重启计数器和运行时间数组。
  4. GPIO控制: 检查 BOOT_MODE_PIN(默认GPIO0)的状态,如果引脚为低电平持续超过1000ms,则保存运行时间并重启设备。

代码通过NVS提供的读写功能来保存模块的重启计数和运行时信息,并根据GPIO引脚状态控制设备是否保存数据并重启。


清除数据

在 ESP-IDF v5.2.3 中,清除 NVS 数据可以使用 nvs_erase_keynvs_erase_allnvs_flash_erase 三个主要函数。以下是每种方法的介绍和操作步骤:

1. 清除单个键值:nvs_erase_key

  • 功能:删除 NVS 中指定的键值对。
  • 使用场景:当您只需要清除某个特定键的数据时。

示例代码

#include "nvs_flash.h"
#include "nvs.h"void erase_key_example()
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS命名空间err = nvs_open("storage", NVS_READWRITE, &my_handle);if (err != ESP_OK) {printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));return;}// 删除指定键值err = nvs_erase_key(my_handle, "my_key");if (err == ESP_OK) {printf("Key erased successfully\n");} else {printf("Error (%s) erasing key!\n", esp_err_to_name(err));}// 提交删除操作err = nvs_commit(my_handle);nvs_close(my_handle);
}

2. 清除整个命名空间:nvs_erase_all

  • 功能:删除特定命名空间中的所有键值。
  • 使用场景:当需要清除命名空间中的所有键值数据,而不影响其他命名空间的数据。

示例代码

#include "nvs_flash.h"
#include "nvs.h"void erase_all_keys_in_namespace()
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS命名空间err = nvs_open("storage", NVS_READWRITE, &my_handle);if (err != ESP_OK) {printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));return;}// 删除命名空间中的所有键值err = nvs_erase_all(my_handle);if (err == ESP_OK) {printf("All keys erased successfully\n");} else {printf("Error (%s) erasing all keys!\n", esp_err_to_name(err));}// 提交删除操作err = nvs_commit(my_handle);nvs_close(my_handle);
}

3. 清除整个 NVS 分区:nvs_flash_erase

  • 功能:擦除整个 NVS 分区,清空所有存储在 NVS 中的数据。
  • 使用场景:当需要重置所有 NVS 数据到出厂状态时使用。
  • 注意:此操作会清空所有命名空间的数据。

示例代码

#include "nvs_flash.h"void erase_entire_nvs_partition()
{esp_err_t err;// 擦除整个NVS分区err = nvs_flash_erase();if (err == ESP_OK) {printf("NVS partition erased successfully\n");} else {printf("Error (%s) erasing NVS partition!\n", esp_err_to_name(err));}// 重新初始化NVSerr = nvs_flash_init();if (err == ESP_OK) {printf("NVS initialized successfully\n");} else {printf("Error (%s) initializing NVS!\n", esp_err_to_name(err));}
}

总结

  • 使用 nvs_erase_key 清除单个键。
  • 使用 nvs_erase_all 清除整个命名空间的键值。
  • 使用 nvs_flash_erase 清除整个 NVS 分区。


参考资料

  • [1]【ESP-IDF 编程指南】非易失性存储库 (https://docs.espressif.com/projects/esp-idf/zh_CN/v4.3.2/esp32c3/api-reference/storage/nvs_flash.html)
  • [2]【ESP-IDF 编程指南】分区表(https://docs.espressif.com/projects/esp-idf/zh_CN/v4.3.2/esp32c3/api-guides/partition-tables.html)
  • [3]【CSDN@矜辰所致】ESP32-C3入门教程 基础篇(八、NVS — 非易失性存储库的使用)(https://blog.csdn.net/weixin_42328389/article/details/122703875)
#include <stdio.h>
#include <inttypes.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "driver/gpio.h"#define STORAGE_NAMESPACE "storage"  // 定义NVS存储空间的命名空间#if CONFIG_IDF_TARGET_ESP32C3
#define BOOT_MODE_PIN GPIO_NUM_9  // 根据不同目标板选择GPIO引脚(ESP32-C3使用GPIO_NUM_9)
#else
#define BOOT_MODE_PIN GPIO_NUM_0  // 默认使用GPIO_NUM_0(通常用于启动模式选择)
#endif //CONFIG_IDF_TARGET_ESP32C3/** 保存模块重启计数到NVS中
*   首先读取已保存的计数值,然后递增计数器
*   如果过程中出现错误,则返回错误
**/
esp_err_t save_restart_counter(void)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS存储,进行读写操作err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);if (err != ESP_OK) return err;// 读取重启计数器,如果未初始化,默认为0int32_t restart_counter = 0;err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;// 增加重启计数器restart_counter++;err = nvs_set_i32(my_handle, "restart_conter", restart_counter);if (err != ESP_OK) return err;// 提交更改到NVSerr = nvs_commit(my_handle);if (err != ESP_OK) return err;// 关闭NVS存储nvs_close(my_handle);return ESP_OK;
}/** 保存新的运行时间值到NVS中
*   首先读取之前保存的时间表,然后将新的值添加到表末尾
*   如果过程中出现错误,则返回错误
**/
esp_err_t save_run_time(void)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS存储,进行读写操作err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle);if (err != ESP_OK) return err;// 读取之前保存的 "run_time" Blob数据大小//读nvs,读取键值对为 "run_time" 处的内容放入变量 required_sizesize_t required_size = 0;/*先读取1次但第3个参数输出地址使用的是 NULL,表示读出的数据不保存,因为这里使用只是为了看一下 "run_time" 处是否有数据,只是先读一下数据,看一下读完以后 required_size 还是不是”0“*/err = nvs_get_blob(my_handle, "run_time", NULL, &required_size);// 读取NVSif (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;//这里申请一块地址,定义为 run_time ,地址上存放的示 uint32_t 数据,大小为:required_size 大小 + sizeof(uint32_t) uint32_t* run_time = malloc(required_size + sizeof(uint32_t));  // 为新的Blob数据分配空间// 读取之前保存的 Blob 数据(如果有的话)就先读出来,保存在刚才申请的 地址 run_time 处(第3个参数)。if (required_size > 0) {err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);// 读取NVS 保存在 run_timeif (err != ESP_OK) {free(run_time);return err;}}required_size += sizeof(uint32_t);// 增加一个新的时间戳后所需要的总内存大小(4字节)// 写入新的运行时间,并追加到已保存的Blob数据末尾 // 数组形式保存数据:类似于 uint32_t run_time[] 数组给数组赋值run_time[required_size / sizeof(uint32_t) - 1] = xTaskGetTickCount() * portTICK_PERIOD_MS;err = nvs_set_blob(my_handle, "run_time", run_time, required_size);// 写入NVSfree(run_time);  // 释放内存if (err != ESP_OK) return err;// 提交更改到NVSerr = nvs_commit(my_handle);// 提交NVSif (err != ESP_OK) return err;// 关闭NVS存储nvs_close(my_handle); // 关闭NVSreturn ESP_OK;
}// 用于清除单个键值
esp_err_t clear_erase_key(void){nvs_handle_t my_handle;esp_err_t err;// 打开NVS命名空间err = nvs_open("storage", NVS_READWRITE, &my_handle);if (err != ESP_OK) {printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));return ESP_FAIL;}// 删除指定键值// err = nvs_erase_key(my_handle, "my_key");err = nvs_erase_key(my_handle, "run_time");if (err == ESP_OK) {printf("Key erased successfully\n");} else {printf("Error (%s) erasing key!\n", esp_err_to_name(err));}// 提交删除操作err = nvs_commit(my_handle);// 关闭NVSnvs_close(my_handle);return ESP_OK;	
}/* 从NVS中读取并打印重启计数器和运行时间表如果过程中出现错误,则返回错误*/
esp_err_t print_what_saved(void)
{nvs_handle_t my_handle;esp_err_t err;// 打开NVS存储,进行读写操作err = nvs_open(STORAGE_NAMESPACE, NVS_READWRITE, &my_handle); // 打开 NVSif (err != ESP_OK) return err;// 读取并打印重启计数器int32_t restart_counter = 0;err = nvs_get_i32(my_handle, "restart_conter", &restart_counter);// 读取 NVSif (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;printf("Restart counter = %" PRIu32 "\n", restart_counter);// 读取并打印运行时间的Blob数据size_t required_size = 0;err = nvs_get_blob(my_handle, "run_time", NULL, &required_size); // 读取 NVSif (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return err;printf("Run time:\n");if (required_size == 0) {printf("Nothing saved yet!\n");} else {uint32_t* run_time = malloc(required_size);err = nvs_get_blob(my_handle, "run_time", run_time, &required_size);// 读取 NVSif (err != ESP_OK) {free(run_time);return err;}// 打印每个保存的运行时间for (int i = 0; i < required_size / sizeof(uint32_t); i++) {printf("%d: %" PRIu32 "\n", i + 1, run_time[i]);}free(run_time);  // 释放内存}// 关闭NVS存储nvs_close(my_handle);// 关闭 NVSreturn ESP_OK;
}void app_main(void)
{// 初始化 NVS(非易失性存储)esp_err_t err = nvs_flash_init();if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {// 如果NVS分区被截断,或者版本不匹配,则擦除分区并重新初始化ESP_ERROR_CHECK(nvs_flash_erase());err = nvs_flash_init();}ESP_ERROR_CHECK( err );// 3.打印NVS中的保存数据err = print_what_saved();if (err != ESP_OK) printf("Error (%s) reading data from NVS!\n", esp_err_to_name(err));// 删除"run_time" 数据err = clear_erase_key();if (err != ESP_OK) printf("Error (%s) clear “run_time” data from NVS!\n", esp_err_to_name(err));// 1.保存重启计数到NVSerr = save_restart_counter();if (err != ESP_OK) printf("Error (%s) saving restart counter to NVS!\n", esp_err_to_name(err));// 配置GPIO引脚gpio_reset_pin(BOOT_MODE_PIN);gpio_set_direction(BOOT_MODE_PIN, GPIO_MODE_INPUT);/* 监测BOOT_MODE_PIN(GPIO0)引脚状态如果GPIO0为低电平持续超过1000ms,则保存运行时间并重启*/while (1) {if (gpio_get_level(BOOT_MODE_PIN) == 0) {vTaskDelay(1000 / portTICK_PERIOD_MS);  // 等待1秒钟if(gpio_get_level(BOOT_MODE_PIN) == 0) {  // 如果引脚仍为低电平//2. 保存运行时间并重启err = save_run_time();if (err != ESP_OK) printf("Error (%s) saving run time blob to NVS!\n", esp_err_to_name(err));printf("Restarting...\n");fflush(stdout);esp_restart();  // 重启设备}}vTaskDelay(200 / portTICK_PERIOD_MS);  // 每200ms检查一次}
}

在这里插入图片描述

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词