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

  • 6
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

物联网程序猿

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值