ESP32 IDF开发 驱动篇⑨ 存储NVS的基本应用
别迷路-导航栏
快速导航找到你想要的(文章目录)
此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。
1、博主写这篇技术文章的目的:
(1)、了解 NVS 概念;
(2)、掌握 NVS 库函数接口;
(3)、掌握编写 NVS 存储多种类型;
2、ESP32 NVS的概述
(1)NVS的介绍
非易失性存储(Non-volatile storage)简称 NVS,乐鑫使用一套 NVS 库将键值对保
存在 SPI flash 中。
NVS 库可以使用 read、write、erase 的 API 操作 flash 的一部分,在系统分区表中该库使用 data 类型和 nvs 子类型的所有分区。应用程序可以使用 nvs_open API 选用 nvs 表中的分区或通过nvs_open_from_part API 指定其名称后使用其他分区。
注:NVS分区表将在下一章讲解
(2)、NVS 的命名空间
为了缓解不同组件之间的密钥名称之间的潜在冲突,NVS 将每个键值对分配给一个名称空间,类似数据库中的表。名称空间名称遵循与键名相同的规则,即最多 15 个字符。命名空间名 称在 nvs_open 或 nvs_open_from_part 调 用中 指 定 。随 后 调用 的 nvs_read_* ,nvs_write_*和 nvs_commit 将返回不透明句柄。这样,句柄与名称空间相关联,并且键名不会与其他名称空间中的相同名称相冲突。
在不同 NVS 分区中具有相同名称的名称空间被视为单独的名称空间。
注:添加NVS分区将在下一章讲解
(3)、存储结构
相比较于 spi_flash_read 和 spi_flash_write 等接口, NVS 不直接操作 address.
对于终端用户而已, 更加安全。
擦写均衡, 使 flash 寿命更长:NVS 在操作少量数据上, NVS 分区更大时, 擦写均衡表现的更为明显。
例如: flash 一个 sector 为 4KB, NVS 分配大小为一个 sector, 写同一个 64Bytes 数据到 flash, 分别比较 spi_flash_xxx 和 nvs 写 64 次spi_flash_write: 每次写 flash 前, 需擦除 flash. 对应: 64 次擦除 flash, 64次写 flash 。
nvs: nvs 内部有擦写均衡, 有标志位记录当前有效存储. 如第一次擦除 sector, 再写 sector 0-63 Byte, 第二次写 sector 64-127 Bytes, 第 64 次(4KB/64Bytes) 写完 sector 最后一个 64 Byte. 对应: 1 次擦除 flash, 64 次写 flash这样 NVS 减少 64 倍擦除操作, 对 flash 寿命有较大提升。在 NVS 分区更大, 存储信息少时, 表现的更为明显.
3、NVS库的介绍
有关NVS详细函数请参esp-idf\components\nvs_flash\include\nvs.h
在这里我只做几个重要经常使用的API函数讲解
NVS库函数主要讲解:读、写、擦除函数。
NVS初始化:
/**
* @brief 初始化默认的NVS分区。
*此API初始化默认的NVS分区。 默认的NVS分区
*是在分区表中标记为“ nvs”的那个。
* @return
*-ESP_OK,如果存储已成功初始化。
*-ESP_ERR_NVS_NO_FREE_PAGES(如果NVS存储器不包含空页)
*(如果NVS分区被截断,则可能会发生)
*-如果在分区表中未找到带有标签“ nvs”的分区,则为ESP_ERR_NOT_FOUND */
esp_err_t nvs_flash_init(void);
擦除NVS分区:
/**
* @brief 清除默认的NVS分区
*
*此功能删除默认NVS分区的所有内容(一个带有标签“ nvs”的)
*
* @return
*-ESP_OK成功
*-如果ESP_ERR_NOT_FOUND中没有标签为“ nvs”的NVS分区
*/
esp_err_t nvs_flash_erase(void);
当存在多个NVS分区时,输入nvs名称擦除指定的nvs分区:
/**
* @brief 擦除指定的NVS分区
*此功能删除指定NVS分区的所有内容
* @param [in] part_name要删除的分区的名称(标签)
* @return
*-ESP_OK成功
*-如果没有指定名称的NVS分区,则为ESP_ERR_NOT_FOUND
*在分区表中
*/
esp_err_t nvs_flash_erase_partition(const char *part_name);
打开默认“nvs”NVS分区并创建一个表名:
/**
* @brief 从默认的NVS分区打开具有给定名称空间的非易失性存储
*默认的NVS分区是分区中标记为“ nvs”的分区
* @param [in]名称命名空间名称。最大长度15个字符。不能是空。
* @param [in] open_mode NVS_READWRITE或NVS_READONLY。
* @param [out] out_handle如果成功(返回码为零),则句柄为
*在此参数中返回。
* @return
*-ESP_OK,如果存储句柄已成功打开
*-如果未初始化存储驱动程序,则为ESP_ERR_NVS_NOT_INITIALIZED
*-如果未找到带有标签“ nvs”的分区,则为ESP_ERR_NVS_PART_NOT_FOUND
*-ESP_ERR_NVS_NOT_FOUND id名称空间尚不存在,并且
*模式为NVS_READONLY
*-如果名称空间名称不满足约束条件,则为ESP_ERR_NVS_INVALID_NAME
*-来自底层存储驱动程序的其他错误代码
*/
esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle);
NVS读函数:
/**
* @param [in]句柄从nvs_open函数获得的句柄。
* @param [in]键键名。最大长度 15个字符。不应该是空的。
* @param out_value指向输出值的指针。
*对于nvs_get_str和nvs_get_blob,可能为NULL
*所需长度的情况将在length参数中返回。
* @返回
*-ESP_OK,如果成功检索到值
*-ESP_ERR_NVS_NOT_FOUND如果请求的密钥不存在
*-ESP_ERR_NVS_INVALID_HANDLE(如果句柄已关闭或为NULL)
*-如果键名不满足约束条件,则为ESP_ERR_NVS_INVALID_NAME
*-如果长度不足以存储数据,则为ESP_ERR_NVS_INVALID_LENGTH
*/
esp_err_t nvs_get_str (nvs_handle_t handle, const char* key, char* out_value, size_t* length);
esp_err_t nvs_get_blob(nvs_handle_t handle, const char* key, void* out_value, size_t* length);
NVS写函数:
esp_err_t nvs_set_i8 (nvs_handle_t handle, const char* key, int8_t value);
esp_err_t nvs_set_u8 (nvs_handle_t handle, const char* key, uint8_t value);
esp_err_t nvs_set_i16 (nvs_handle_t handle, const char* key, int16_t value);
esp_err_t nvs_set_u16 (nvs_handle_t handle, const char* key, uint16_t value);
esp_err_t nvs_set_i32 (nvs_handle_t handle, const char* key, int32_t value);
esp_err_t nvs_set_u32 (nvs_handle_t handle, const char* key, uint32_t value);
esp_err_t nvs_set_i64 (nvs_handle_t handle, const char* key, int64_t value);
esp_err_t nvs_set_u64 (nvs_handle_t handle, const char* key, uint64_t value);
esp_err_t nvs_set_str (nvs_handle_t handle, const char* key, const char* value);
esp_err_t nvs_set_blob(nvs_handle_t handle, const char* key, const void* value, size_t length);
NVS提交函数:写入之后,必须提交才能写入NVS
esp_err_t nvs_commit(nvs_handle_t handle);
4、软件设计
(1)、初始化nvs
nvs_flash_init()
(2)、打开nvs并创建一个表,以读写的方式打开
nvs_open(“test_list”, NVS_READWRITE, &my_handle);
(3)、根据键值对读写数据
nvs_get_i8(my_handle, “nvs_i8”, &nvs_i8);
nvs_set_i8(my_handle, “nvs_i8”, nvs_i8);
(4)、提交nvs
err = nvs_commit(my_handle);
(5)、关闭nvs
nvs_close(my_handle);
5、实例分析
复制上一个工程改名字为 idf_nvs即可 文件名字改为 idf_nvs.C makefile文件也改成 PROJECT_NAME := idf_nvs即可,然后复制一下代码测试。
/**********************************************************************
* 文件名: idf_nvs.c
* 创建人:
* 创建日期:
* 修改人:
* 修改日期:
* 版本号: V1.1
* 备注:
* 公司:
********************************************************************/
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "nvs.h"
#include "esp_log.h"
#include "nvs_flash.h"
static const char *TAG = "NVS_";
nvs_handle my_handle;
int8_t nvs_i8;
uint8_t nvs_u8;
/***********************************************************************
* 函数:
* 描述: 主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
void app_main()
{
//初始化NVS
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
//发现新版本
//擦除
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
//打开,类似数据库的表
err = nvs_open("test_list", NVS_READWRITE, &my_handle);
if (err != ESP_OK)
{
ESP_LOGE(TAG, "opening NVS Error (%s)!\n", esp_err_to_name(err));
}
else
{
size_t len = 50;
char strval[50];
ESP_LOGI(TAG, "NVS open OK");
//读取数据,根据键值名
err = nvs_get_i8(my_handle, "nvs_i8", &nvs_i8);
if(err==ESP_OK) ESP_LOGI(TAG, "nvs_i8 = %d\n", nvs_i8);
err = nvs_get_u8(my_handle, "nvs_u8", &nvs_u8);
if(err==ESP_OK) ESP_LOGI(TAG, "nvs_u8 = %d\n", nvs_u8);
//字符串
err = nvs_get_str (my_handle, "str_key", strval, &len);
if(err==ESP_OK) ESP_LOGI(TAG, "str_key = %s\n", strval);
nvs_i8 += 1;
nvs_u8 += 1;
err = nvs_set_i8(my_handle, "nvs_i8", nvs_i8);
if(err!=ESP_OK) ESP_LOGE(TAG, "nvs_i8 set Error");
err = nvs_set_u8(my_handle, "nvs_u8", nvs_u8);
if(err!=ESP_OK) ESP_LOGE(TAG, "nvs_u8 set Error");
//写字符串
err = nvs_set_str (my_handle, "str_key", "str_value test");
if(err!=ESP_OK) ESP_LOGE(TAG, "str_key set Error");
//提交,必须提交才能写入NVS
err = nvs_commit(my_handle);
if(err!=ESP_OK) ESP_LOGE(TAG, "nvs_commit Error");
}
//关闭
nvs_close(my_handle);
//复位倒计时
for (int i = 10; i >= 0; i--) {
printf("Restarting in %d seconds...\n", i);
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
esp_restart();
}
6、以下是调试的结果:
从系统的提示信息可以看出nvs的分区开始地址从0x9000;大小为0x6000。从上面的例程我们会发现几个在项目中可能会遇到的疑问:
1、在哪里指定分区,怎么添加多个分区,并且访问。
2、在使用这两个函数时
esp_err_t nvs_get_str (nvs_handle_t handle, const char* key, char* out_value, size_t* length);
esp_err_t nvs_get_blob(nvs_handle_t handle, const char* key, void* out_value, size_t* length);
如果我们不知道存储在key键值中的数据长度该怎么办
3、在上面的例程中都是单个字节,如果要存储一个结构体或是大数组该怎么做
具体解决办法将在下一章讲解。
所有文章源代码:https://download.csdn.net/download/lu330274924/88518092