ESP32 IDF开发 驱动篇⑩ 存储NVS高级应用和自定义分区表


别迷路-导航栏
快速导航找到你想要的(文章目录)

此篇文章如果对你有用,请点赞收藏,您的支持就是博主坚持的动力。

1、博主写这篇技术文章的目的:

(1)、了解 分区比的作用;
(2)、掌握 自定义分区表;
(3)、学会 自定义NVS分区的大小;
(3)、学会 对多个NVS分区的读写操作;

2、ESP32 分区表的概述

在“ESP32 IDF开发 驱动篇⑨ 存储NVS的基本应用”章节我们已经介绍了nvs的介绍和基本操作,并且留下了许多疑问。下面就来对这些疑问一一揭开神秘的面纱。
(1)分区表的介绍
分区表文件位于: esp-idf\components\partition_table文件下, 该路径下所有的.csv文件都是用来对Flash分区进行配置如下图:
在这里插入图片描述
无OTA分区:partitions_singleapp.csv、partitions_singleapp_coredump.csv
双OTA分区:partitions_two_ota.csv、partitions_two_ota_coredump.csv
打开四个分区表:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

分析分区表中的内容
Name:字段可以是任何有意义的名称,但不能超过 16 个字符(之后的内容将被截断)。该字段对 ESP32 并不是特别重要;
Type:字段可以指定为 app (0) 或者 data (1),也可以直接使用数字 0-254(或者十六进制 0x00-0xFE)。注意,0x00-0x3F 不得使用(预留给 esp-idf 的核心功能)。
如果您的应用程序需要保存数据,请在 0x40-0xFE 内添加一个自定义分区类型。
注意,启动加载器将忽略 app (0) 和 data (1) 以外的其他分区类型。
SubType:字段长度为 8 bit,内容与具体 Type 有关。目前,esp-idf 仅仅规定了 “app” 和 “data” 两种子类型。
1、当 Type 定义为 app 时,SubType 字段可以指定为 factory (0),ota_0 (0x10) … ota_15 (0x1F) 或者 test (0x20)。
· factory (0) 是默认的 app 分区。启动加载器将默认加载该应用程序。但如果存在类型为 data/ota 分区,则启动加载器将加载 data/ota 分区中的数据,进而判断启动哪个 OTA 镜像文件。
· OTA 升级永远都不会更新 factory 分区中的内容。
·如果您希望在 OTA 项目中预留更多 flash,可以删除 factory 分区,转而使用 ota_0 分区。
· ota_0 (0x10) … ota_15 (0x1F) 为 OTA 应用程序分区,启动加载器将根据 OTA 数据分区中的数据来决定加载哪个 OTA 应用程序分区中的程序。在使用 OTA 功能时,应用程序应至少拥有 2 个 OTA 应用程序分区(ota_0 和 ota_1) 。
· test (0x2) 为预留 app 子类型,用于工厂测试流程。如果没有其他有效 app 分区,test 将作为备选启动分区使用。也可以在每次启动时配置启动加载器读取 GPIO,如果 GPIO 被拉低则启动该分区。
2、当 Type 定义为 data 时,SubType 字段可以指定为 ota (0),phy (1),nvs (2) 或者 nvs_keys (4)。
· ota (0) 即 OTA 数据分区 ,用于存储当前所选的 OTA 应用程序的信息。这个分区的大小需要设定为 0x2000。
· phy (1) 分区用于存放 PHY 初始化数据,从而保证可以为每个设备单独配置 PHY,而非必须采用固件中的统一 PHY 初始化数据。
·默认配置下,phy 分区并不启用,而是直接将 phy 初始化数据编译至应用程序中,从而节省分区表空间(直接将此分区删掉)。
· 如果需要从此分区加载 phy 初始化数据,请打开项目配置菜单(menuconfig),并且能 CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION 选项。此时,您还需要手动将 phy 初始化数据烧至设备 flash(esp-idf 编译系统并不会自动完成该操作)。
· nvs (2) 是专门给 非易失性存储 (NVS) API 使用的分区。
·用于存储每台设备的 PHY 校准数据(注意,并不是 PHY 初始化数据)。
·用于存储 Wi-Fi 数据(如果使用了 esp_wifi_set_storage(WIFI_STORAGE_FLASH) 初始化函数)。
· NVS API 还可以用于其他应用程序数据。
·强烈建议您应为 NVS 分区分配至少 0x3000 字节空间。
·如果使用 NVS API 存储大量数据,请增加 NVS 分区的大小(默认是 0x6000 字节)。
·nvs_keys (4) 是 NVS 秘钥分区。
·用于存储加密密钥(如果启用了 NVS 加密 功能)。
·此分区应至少设定为 4096 字节。
其它数据子类型已预留给 esp-idf 未来使用。
Offset和size:分区若偏移地址为空,则会紧跟着前一个分区之后开始;若为首个分区,则将紧跟着分区表开始。
app 分区的偏移地址必须要与 0x10000 (64K) 对齐,如果将偏移字段留空,gen_esp32part.py 工具会自动计算得到一个满足对齐要求的偏移地址。如果 app 分区的偏移地址没有与 0x10000 (64K) 对齐,则该工具会报错。
app 分区的大小和偏移地址可以采用十进制数、以 0x 为前缀的十六进制数,且支持 K 或 M 的倍数单位(分别代表 1024 和 1024*1024 字节)。
如果您希望允许分区表中的分区采用任意起始偏移量 (CONFIG_PARTITION_TABLE_OFFSET),请将分区表(CSV 文件)中所有分区的偏移字段都留空。注意,此时,如果您更改了分区表中任意分区的偏移地址,则其他分区的偏移地址也会跟着改变。这种情况下,如果您之前还曾设定某个分区采用固定偏移地址,则可能造成分区表冲突,从而导致报错。
Flags:当前仅支持 encrypted 标记。如果 Flags 字段设置为 encrypted,且已启用 Flash Encryption 功能,则该分区将会被加密。
app 分区始终会被加密,不管 Flags 字段是否设置。

(2)分区表的使用
双OTA分区将在 【ESP32 IDF开发 应用篇⑱ 空中升级OTA】讲解
Nvs:保存的是用户操作nvs api设置的一些数据
phy_init:区是RF 的数据
factory:应用程序
coredump:存放的是系统运行过程中产生的一些错误快照,当系统崩溃时产出的错处都保存在coredump去,从而可以在随后在 PC 上分析失败的原因。(后续将以实例讲解怎么使用)
otadata:存放启动应用程序的参数
ota_0:升级程序的APP1
ota_1:升级程序的APP2

这篇主要介绍“nvs”默认使用的是partitions_singleapp.csv分区表,从表中可以看到nvs的大小为0x6000 =
24K

3、NVS库的介绍

有关NVS API基本函数在“ESP32 IDF开发 驱动篇⑨ 存储NVS的基本应用”篇已经介绍过了
在这里我们只介绍几个重要的函数

esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode,nvs_handle_t *out_handle);

从这个函数就可以发现他并没有指定使用哪一个“nvs”,打开源文件

extern "C" esp_err_t nvs_open(const char* name, nvs_open_mode_t open_mode, nvs_handle_t *out_handle)
{
    return nvs_open_from_partition(NVS_DEFAULT_PART_NAME, name, open_mode, out_handle);
}
#define NVS_DEFAULT_PART_NAME               "nvs"   /*!< Default partition name of the NVS partition in the partition table *

nvs_open函数默认使用的“nvs”分区

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。那么在这个过程中我们并不知道要读取的数据长度究竟是多少,所以我们需要传入一个长度的指针进去。
参数介绍:

  * @param out_value 指向输出值的指针。对于nvs_get_str和nvs_get_blob,可能为NULL,所需长度的将在length参数中返回。
  * @param [inout] length 指向变量out_value长度的非零指针。
  *如果out_value为零,则将其设置为长度必须保持该值。 如果out_value不是零,将设置为实际长度值

每次写入之后必须使用 nvs_commit提交

4、软件设计

(1)、初始化nvs

nvs_flash_init()

(2)、打开nvs并创建一个表,以读写的方式打开

nvs_open(“test_list”, NVS_READWRITE, &my_handle);

(3)、根据键值对读写字符串、数组、结构体等数据

nvs_get_str、nvs_get_blob、nvs_set_str、nvs_set_blob

(4)、提交nvs

err = nvs_commit(my_handle);

(4)、关闭nvs

nvs_close(my_handle);

5、实例分析

复制上一个工程改名字为 idf_nvs2即可 文件名字改为 idf_nvs2.C makefile文件也改成 PROJECT_NAME := idf_nvs2即可,然后复制一下代码测试。
添加自定的分区“nvs_1”,在原分区表上添加如下分区,大小自定义这里是4000,
然后在程序里面将默认分区定义为修改后的分区

#define NVS_DEFAULT_PART_NAME “nvs_1”

在这里插入图片描述
如果要同时使用两个分区,就需要使用

nvs_open_from_partition(NVS_DEFAULT_PART_NAME, name, open_mode, out_handle);

这个函数来打开多个分区即可。

/**********************************************************************
*               文件名:            idf_nvs2.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 = "NVS2_";

nvs_handle my_handle;   
uint8_t nvs_Array[100]={0};
typedef struct _msgData
{
    char data1[10];
    uint8_t data2;  
}MSG_DATA;
MSG_DATA msg_data;      //从机信号机制
/***********************************************************************
* 函数:  
* 描述:   主函数
* 参数:
* 返回: 无
* 备注:
************************************************************************/
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=0;
        char strval[50];
        ESP_LOGI(TAG, "NVS open OK");

        //字符串
        //如果len长度为0,则读取的是数据的长度,保存在len中,参数说明参考API介绍
        //如果len长度不为为0,则读取的是实际数据
        err = nvs_get_str (my_handle, "str_key", NULL, &len);
        if(err==ESP_OK) ESP_LOGI(TAG, "str_key read size = %d", len);      
        err = nvs_get_str (my_handle, "str_key", strval, &len);
        if(err==ESP_OK) ESP_LOGI(TAG, "str_key read data = %s", strval);

        //读出大数据,数组
        err = nvs_get_blob(my_handle, "blob_arrray", NULL, &len);
        if (err == ESP_OK ) ESP_LOGI(TAG, "blob_arrray read size = %d", len);     
        err = nvs_get_blob(my_handle, "blob_arrray", nvs_Array, &len);
        if (err == ESP_OK ) ESP_LOGI(TAG, "blob_arrray read data = %s", nvs_Array);   

        //读出结构体
        err = nvs_get_blob(my_handle, "blob_struct", NULL, &len);
        if (err == ESP_OK ) ESP_LOGI(TAG, "blob_struct read size = %d", len);   
        err = nvs_get_blob(my_handle, "blob_struct", &msg_data, &len);
        if (err == ESP_OK )
        {
            //从结构体msg_data.data1地址开始后面的数据全部输出,所以msg_data.data2的数据也被输出
            ESP_LOGI(TAG, "blob_struct read data1 = %s", msg_data.data1);  
            ESP_LOGI(TAG, "blob_struct read data2 = %s", &msg_data.data2);  
        } 

        //写入大数据,数组
        for (int i=0;i<10;i++) nvs_Array[i] = i+'0';
        for (int i=10;i<100;i++) nvs_Array[i] = 'A';
        err = nvs_set_blob(my_handle, "blob_arrray", nvs_Array, 100);
        if (err != ESP_OK) ESP_LOGE(TAG, "blob_arrray set Error");

        //写入大数据,结构体
        for (int i=0;i<10;i++) msg_data.data1[i] = i+'0';//转换成字符
        msg_data.data2 = 'B';
        err = nvs_set_blob(my_handle, "blob_struct", &msg_data, sizeof(msg_data));
        if (err != ESP_OK) ESP_LOGE(TAG, "blob_struct set Error");
        //写字符串
        err = nvs_set_str (my_handle, "str_key", "str_value test ok!");
        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、以下是调试的结果:

make menuconfig
在Partition Table->Partition Table (Single factory app, no OTA)

在这里插入图片描述
编译过程首先编译的就是分区表,我们在make menuconfig时选择的分区表
在这里插入图片描述
系统启动可以看到我们添加的分区“nvs_1”大小4000

在这里插入图片描述
在这里插入图片描述
更多分区变的讲解,请点赞收藏,【ESP32 IDF开发 系统篇⑪ 系统启动流程及硬件复位问题分析】详细讲解分区的启动流程。

所有文章源代码:https://download.csdn.net/download/lu330274924/88518092

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

物联网程序猿

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

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

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

打赏作者

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

抵扣说明:

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

余额充值