esp32-idf eclipse 分区表(partition table / NVS)的读写demo

前言:

分区表(Partition Table)和 NVS(Non-Volatile Storage)是 ESP-IDF 中用于存储数据的两种不同机制。

  1. 分区表(Partition Table):

    • 分区表定义了将 Flash 存储器划分为不同逻辑分区的方式。每个分区都具有特定的大小、起始地址和类型,可以用于存储不同类型的数据,例如应用程序、文件系统、配置数据等。
    • 分区表是在编译时定义的,通常由 partitions.csv 或 partitions_singleapp.csv 文件指定。这些文件描述了每个分区的信息,如分区名称、大小、起始地址等。
    • 分区表在 ESP32 系统启动时被加载到内存中,ESP-IDF 使用分区表来确定各个分区的位置和属性。通过分区表,可以访问和管理不同分区中的数据。
  2. NVS(Non-Volatile Storage):

    • NVS 是一种轻量级的键值对存储系统,用于在 Flash 存储器中保存和检索数据。它提供了一种简单的方式来存储应用程序的配置数据、状态信息等。
    • NVS 使用了一个称为 "nvs" 的特定分区,该分区被用作存储键值对数据的容器。每个键值对由一个唯一的键和相应的值组成。
    • 与其他分区不同,NVS 分区不需要在分区表中显式声明。它是 ESP-IDF 自动创建和管理的特殊分区。
    • 通过 NVS API(如 nvs_get()nvs_set() 等),应用程序可以对 NVS 进行读写操作,方便地存储和检索数据。

总结:

  • 分区表定义了 Flash 存储器中不同分区的划分和属性,用于存储各种类型的数据。
  • NVS 是一种简单的键值对存储系统,用于在 Flash 存储器中存储和检索数据,它使用一个特定的分区。
  • 分区表和 NVS 是 ESP-IDF 中不同的存储机制,用于不同的数据存储需求。分区表用于管理整个 Flash 存储器的划分,而 NVS 则提供了一种简单的方式来存储和检索数据。


partition table 示例代码:

#include <string.h>
#include <assert.h>
#include "esp_partition.h"
#include "esp_log.h"

static const char *TAG = "partition_table--->";

void app_main(void)
{
    /*
    * This example uses the partition table from ../partitions_example.csv. For reference, its contents are as follows:
    *
    *  nvs,        data, nvs,      0x9000,  0x6000,
    *  phy_init,   data, phy,      0xf000,  0x1000,
    *  factory,    app,  factory,  0x10000, 1M,
    *  storage,    data, ,             , 0x40000,
    */
/*
	typedef struct {
	    esp_flash_t* flash_chip;            !< SPI flash chip on which the partition resides
	    esp_partition_type_t type;          !< partition type (app/data)
	    esp_partition_subtype_t subtype;    !< partition subtype
	    uint32_t address;                   !< starting address of the partition in flash
	    uint32_t size;                      !< size of the partition, in bytes
	    char label[17];                     !< partition label, zero-terminated ASCII string
	    bool encrypted;                     !< flag is set to true if partition is encrypted
	} esp_partition_t;
*/
    // Find the partition map in the partition table
    const esp_partition_t *partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_ANY, "storage"); //三个参数:分区类型、分区子类型和分区标签.在分区表中找到名为 "storage" 的分区
    assert(partition != NULL);

    static char store_data[] = "ESP-IDF Partition Operations Example (Read, Erase, Write)";  //写入分区的数据
    static char read_data[sizeof(store_data)]; 												 //存储从分区读取的数据。

    // Erase entire partition
    memset(read_data, 0xFF, sizeof(read_data));
    ESP_ERROR_CHECK(esp_partition_erase_range(partition, 0, partition->size));         		//擦除整个分区。这里使用 memset() 函数将 read_data 数组填充为 0xFF,表示将整个分区内容擦除。

    // Write the data, starting from the beginning of the partition
    ESP_ERROR_CHECK(esp_partition_write(partition, 0, store_data, sizeof(store_data)));    //将 store_data 写入分区。这里将数据写入分区的起始位置,大小为 sizeof(store_data),即存储数据的长度。
    ESP_LOGI(TAG, "Written data: %s", store_data);

    // Read back the data, checking that read data and written data match
    ESP_ERROR_CHECK(esp_partition_read(partition, 0, read_data, sizeof(read_data)));       //从分区中读取数据,并通过 memcmp() 函数比较读取的数据和写入的数据是否相同。如果比较结果为 0,则说明读取的数据与写入的数据一致
    assert(memcmp(store_data, read_data, sizeof(read_data)) == 0);
    ESP_LOGI(TAG, "Read data: %s", read_data);

    // Erase the area where the data was written. Erase size shoud be a multiple of SPI_FLASH_SEC_SIZE
    // and also be SPI_FLASH_SEC_SIZE aligned
    ESP_ERROR_CHECK(esp_partition_erase_range(partition, 0, SPI_FLASH_SEC_SIZE));         //擦除之前写入的数据所占据的区域。这里擦除的大小为 SPI_FLASH_SEC_SIZE,它需要是 SPI_FLASH_SEC_SIZE 的倍数,并且与 SPI_FLASH_SEC_SIZE 对齐。

    // Read back the data (should all now be 0xFF's)
    memset(store_data, 0xFF, sizeof(read_data));
    ESP_ERROR_CHECK(esp_partition_read(partition, 0, read_data, sizeof(read_data)));      //读取分区中的数据,并通过比较来验证数据是否已被擦除(全为 0xFF)。
    assert(memcmp(store_data, read_data, sizeof(read_data)) == 0);

    ESP_LOGI(TAG, "Erased data");

    ESP_LOGI(TAG, "Example end");
}

partition table 运行结果:


NVS库的使用

1.概述

非易失性存储(Non-volatile storage) 简称 NVS, 乐鑫使用一套 NVS 库将键值对保存在 SPI flash 中。NVS 库可以使用 readwriteerase API 操作 flash 的一部分, 该库使用 data 类型和 nvs 子类型的所有分区。 应用程序可以使用 nvs_openAPI 选用 nvs 表中的分区或通过nvs_open_from_part API 指定其名称后使用其他分区。

2.NVS 可以保存的类型
无符号整型: uint8_t,uint16_t,uint32_t,uint64_t

有符号整型: int8_t, int16_t,int32_t,int64_t

字符串: 必须以 0 结尾, 因为需要知道字符串的长度, 以便保存。

二进制数据: 可变长。

暂不支持浮点数保存

字符串和二进制数据目前仅限于 1984 字节。 对于字符串, 这包括空终止符.

3. NVS 的命名空间

为了缓解不同组件之间的密钥名称之间的潜在冲突, NVS 将每个键值对分配给一个名称空间, 类似数据库中的表。 名称空间名称遵循与键名相同的规则, 即最多 15 个字符。 命名空间名 称 在 nvs_open 或 nvs_open_from_part 调 用 中 指 定 。 随 后 调 用 的 nvs_read_* ,nvs_write_*和 nvs_commit 将返回不透明句柄。 这样, 句柄与名称空间相关联, 并且键名不会与其他名称空间中的相同名称相冲突。

在不同 NVS 分区中具有相同名称的名称空间被视为单独的名称空间。

4.NVS 优势

接口更加安全

相比较于 spi_flash_read 和 spi_flash_write 等接口, NVS 不直接操作 address. 对于终端用户而已, 更加安全.

例如: 应用复杂一点, 容易 spi_flash_write(address, src, size) 不小心写到同一个地址, 或地址写覆盖, 而导致长时间 debug

接口使用接近用户习惯

NVS 接口类似于电脑上操作文件一样:

打开文件(nvs_open), 写文件(nvs_set_xxx), 保存文件(nvs_commit), 关闭文件(nvs_close)

打开文件(nvs_open), 读取文件(nvs_get_xxx), 关闭文件(nvs_close)

擦写均衡, 使 flash 寿命更长

NVS 在操作少量数据上, NVS 分区更大时, 擦写均衡表现的更为明显.

例如: flash 一个 sector 为 4KB, NVS 分配大小为一个 sector, 写同一个 64 Bytes 数据到 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 分区更大, 存储信息少时, 表现的更为明显.

注意事项:

  ESP32_学习笔记(一)NVS的操作(存储和读取大数组)(为什么存入数据成功,读取却为零的原因)_arduino esp32 定义最大数组-优快云博客

5.api函数

esp_err_t  nvs_flash_init void 

初始化默认的NVS分区。

该API初始化默认的NVS分区。默认的NVS分区是在分区表中标记为“ nvs”的分区。

返回

  • 如果存储已成功初始化,则为ESP_OK。
  • ESP_ERR_NVS_NO_FREE_PAGES如果NVS存储器不包含空页(如果NVS分区被截断,则可能发生)
  • 如果在分区表中找不到带有标签“ nvs”的分区,则为ESP_ERR_NOT_FOUND
  • 来自基础闪存存储驱动程序的错误代码之一

esp_err_t nvs_flash_init_partition(const char *partition_label)

初始化指定分区的NVS闪存。

返回

  • 如果存储已成功初始化,则为ESP_OK。
  • ESP_ERR_NVS_NO_FREE_PAGES如果NVS存储器不包含空页(如果NVS分区被截断,则可能发生)
  • 如果在分区表中找不到指定的分区,则为ESP_ERR_NOT_FOUND
  • 来自基础闪存存储驱动程序的错误代码之一

参量

  • [in] partition_label:分区的标签。不得超过16个字符。

esp_err_t  nvs_flash_erase void 

擦除默认的NVS分区。

擦除默认NVS分区(带有标签“ nvs”的分区)的所有内容。

注意

如果分区已初始化,则此函数首先将其初始化。之后,必须再次初始化分区才能使用。

返回

  • ESP_OK成功
  • 如果分区表中没有标记为“ nvs”的NVS分区,则为ESP_ERR_NOT_FOUND
  • 万一取消初始化失败(不应发生),则会出现其他错误

esp_err_t nvs_set_i8(nvs_handle_thandleconst char *key, int8_t value)

给定键的设定值

给定其名称,该功能家族为键设置值。请注意,直到调用nvs_commit函数,才会更新实际存储。

返回

  • ESP_OK,如果值设置成功
  • 如果句柄已关闭或为NULL,则为ESP_ERR_NVS_INVALID_HANDLE
  • ESP_ERR_NVS_READ_ONLY如果存储句柄被打开为只读
  • 如果键名不满足约束,则为ESP_ERR_NVS_INVALID_NAME
  • ESP_ERR_NVS_NOT_ENOUGH_SPACE,如果基础存储中没有足够的空间来保存该值
  • ESP_ERR_NVS_REMOVE_FAILED如果由于闪存写入操作失败而未更新该值。但是,已写入该值,并且只要闪存操作不会再次失败,更新将在nvs重新初始化后完成。
  • 如果字符串值太长,则为ESP_ERR_NVS_VALUE_TOO_LONG

参量

  • [in] handle:从nvs_open函数获得的句柄。以只读方式打开的句柄不能使用。
  • [in] key:密钥名称。最大长度为(NVS_KEY_NAME_MAX_SIZE-1)个字符。不应该是空的。
  • [in] value:要设置的值。对于字符串,最大长度(包括空字符)为4000字节。

esp_err_tnvs_set_u8(nvs_handle_thandleconst char *key, uint8_t value)

esp_err_tnvs_set_i16(nvs_handle_thandleconst char *key, int16_t value)

esp_err_tnvs_set_u16(nvs_handle_thandleconst char *key, uint16_t value)

esp_err_tnvs_set_i32(nvs_handle_thandleconst char *key, int32_t value)

esp_err_tnvs_set_u32(nvs_handle_thandleconst char *key, uint32_t value)

esp_err_tnvs_set_i64(nvs_handle_thandleconst char *key, int64_t value)

esp_err_tnvs_set_u64(nvs_handle_thandleconst char *key, uint64_t value)

esp_err_tnvs_set_str(nvs_handle_thandleconst char *keyconst char *value)

esp_err_tnvs_get_i8(nvs_handle_thandleconst char *key, int8_t *out_value)

获得给定密钥的价值

这些函数在给定键名的情况下检索键的值。如果键不存在,或者请求的变量类型与设置值时使用的类型不匹配,则返回错误。

发生任何错误时,不会修改out_value。

所有函数都希望out_value是指向给定类型的已分配变量的指针。

// Example of using nvs_get_i32:

int32_t max_buffer_size = 4096; // default value

esp_err_t err = nvs_get_i32(my_handle, "max_buffer_size", &max_buffer_size);

assert(err == ESP_OK || err == ESP_ERR_NVS_NOT_FOUND);

// if ESP_ERR_NVS_NOT_FOUND was returned, max_buffer_size will still

// have its default value.

返回

  • ESP_OK,如果成功检索到值
  • ESP_ERR_NVS_NOT_FOUND如果请求的密钥不存在
  • 如果句柄已关闭或为NULL,则为ESP_ERR_NVS_INVALID_HANDLE
  • 如果键名不满足约束,则为ESP_ERR_NVS_INVALID_NAME
  • 如果长度不足以存储数据,则输入ESP_ERR_NVS_INVALID_LENGTH

参量

  • [in] handle:从nvs_open函数获得的句柄。
  • [in] key:密钥名称。最大长度为(NVS_KEY_NAME_MAX_SIZE-1)个字符。不应该是空的。
  • out_value:指向输出值的指针。对于nvs_get_str和nvs_get_blob可能为NULL,在这种情况下,将在length参数中返回所需的长度。

esp_err_t nvs_get_u8(nvs_handle_t句柄constchar * key,uint8_t * out_value 

esp_err_t nvs_get_i16(nvs_handle_t句柄constchar * key,int16_t * out_value 

esp_err_t nvs_get_u16(nvs_handle_t句柄constchar * key,uint16_t * out_value 

esp_err_t nvs_get_i32(nvs_handle_t句柄constchar * key,int32_t * out_value 

esp_err_t nvs_get_u32(nvs_handle_t句柄constchar * key,uint32_t * out_value 

esp_err_t nvs_get_i64(nvs_handle_t句柄constchar * key,int64_t * out_value 

esp_err_t nvs_get_u64(nvs_handle_t句柄constchar * key,uint64_t * out_value 

esp_err_tnvs_get_str(nvs_handle_thandleconst char *key, char *out_value, size_t *length)

获得给定密钥的价值

这些函数在给定键的情况下检索条目的数据。如果键不存在,或者请求的变量类型与设置值时使用的类型不匹配,则返回错误。

发生任何错误时,不会修改out_value。

所有函数都希望out_value是指向给定类型的已分配变量的指针。

nvs_get_str和nvs_get_blob函数支持WinAPI样式的长度查询。要获取存储值所需的大小,请使用零out_value和非零长度指针来调用nvs_get_str或nvs_get_blob。length参数指向的变量将设置为所需的长度。对于nvs_get_str,此长度包括零终止符。当使用非零out_value调用nvs_get_str和nvs_get_blob时,length必须非零,并且必须指向out_value中可用的长度。建议将nvs_get / set_str用于零终止的C字符串,将nvs_get / set_blob用于任意数据结构。

// Example (without error checking) of using nvs_get_str to get a string into dynamic array:

size_t required_size;

nvs_get_str(my_handle, "server_name", NULL, &required_size);

char* server_name = malloc(required_size);

nvs_get_str(my_handle, "server_name", server_name, &required_size);

// Example (without error checking) of using nvs_get_blob to get a binary data

into a static array:

uint8_t mac_addr[6];

size_t size = sizeof(mac_addr);

nvs_get_blob(my_handle, "dst_mac_addr", mac_addr, &size);

返回

  • ESP_OK,如果成功检索到值
  • ESP_ERR_NVS_NOT_FOUND如果请求的密钥不存在
  • 如果句柄已关闭或为NULL,则为ESP_ERR_NVS_INVALID_HANDLE
  • 如果键名不满足约束,则为ESP_ERR_NVS_INVALID_NAME
  • 如果长度不足以存储数据,则输入ESP_ERR_NVS_INVALID_LENGTH

参量

  • [in] handle:从nvs_open函数获得的句柄。
  • [in] key:密钥名称。最大长度为(NVS_KEY_NAME_MAX_SIZE-1)个字符。不应该是空的。
  • out_value:指向输出值的指针。对于nvs_get_str和nvs_get_blob可能为NULL,在这种情况下,将在length参数中返回所需的长度。
  • [inout] length:指向保存out_value长度的变量的非零指针。如果out_value为零,则将其设置为保持该值所需的长度。如果out_value不为零,则将其设置为写入值的实际长度。对于nvs_get_str,它包括零终止符。

esp_err_t nvs_openconstchar * namenvs_open_mode_t open_modenvs_handle_t * out_handle 

从默认NVS分区打开具有给定名称空间的非易失性存储。

多个内部ESP-IDF和第三方应用程序模块可以将其键值对存储在NVS模块中。为了减少键名上的可能冲突,每个模块可以使用其自己的名称空间。默认的NVS分区是在分区表中标记为“ nvs”的分区。

返回

  • 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
  • 基础存储驱动程序中的其他错误代码

参量

  • [in] name:命名空间名称。最大长度为(NVS_KEY_NAME_MAX_SIZE-1)个字符。不应该是空的。
  • [in] open_mode:NVS_READWRITE或NVS_READONLY。如果为NVS_READONLY,将打开一个只读句柄。该句柄将拒绝所有写请求。
  • [out] out_handle:如果成功(返回码为零),则将在此参数中返回handle。

esp_err_t  nvs_commitnvs_handle_t handle 

将所有未决的更改写入非易失性存储。

设置任何值后,必须调用nvs_commit()以确保将更改写入非易失性存储。各个实现可以在其他时间写入存储,但这不能保证。

返回

  • ESP_OK,如果更改已成功写入
  • 如果句柄已关闭或为NULL,则为ESP_ERR_NVS_INVALID_HANDLE
  • 基础存储驱动程序中的其他错误代码

参量

  • [in] handle:通过nvs_open获得的存储句柄。以只读方式打开的句柄不能使用。

void nvs_close(nvs_handle_thandle)

关闭存储句柄并释放所有分配的资源。

一旦不再使用nvs_open打开的每个句柄,应调用该函数。关闭句柄可能不会自动将更改写入非易失性存储。必须使用nvs_commit函数明确地完成此操作。在句柄上调用此函数后,将不再使用该句柄。

参量

  • [in] handle:要关闭的存储句柄

NVS 示例代码:

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "nvs_flash.h"
#include "nvs.h"

void app_main(void)
{
    // Initialize NVS
    esp_err_t err = nvs_flash_init();
    if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
        // NVS partition was truncated and needs to be erased
        // Retry nvs_flash_init
        ESP_ERROR_CHECK(nvs_flash_erase());
        err = nvs_flash_init();
    }
    ESP_ERROR_CHECK( err );

    // Open
    printf("\n");
    printf("Opening Non-Volatile Storage (NVS) handle... ");
    nvs_handle_t my_handle;
    err = nvs_open("storage", NVS_READWRITE, &my_handle); //打开NVS存储,并将句柄保存在my_handle变量中。STORAGE_NAMESPACE是NVS存储的命名空间
    if (err != ESP_OK) {
        printf("Error (%s) opening NVS handle!\n", esp_err_to_name(err));
    } else {
        printf("Done\n");

        // Read
        printf("Reading restart counter from NVS ... ");
        int32_t restart_counter = 0; // value will default to 0, if not set yet in NVS
        err = nvs_get_i32(my_handle, "restart_counter", &restart_counter); //从NVS中读取键为"restart_counter"的整数值,并将结果保存在restart_counter变量中。
//        err = nvs_get_i32(my_handle, "132", &restart_counter);
        switch (err) {
            case ESP_OK:
                printf("Done\n");
                printf("Restart counter = %d\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));
        }

        // Write
        printf("Updating restart counter in NVS ... ");
        restart_counter++;
        err = nvs_set_i32(my_handle, "restart_counter", restart_counter); //将更新后的二进制数据写入 NVS 中。
        printf((err != ESP_OK) ? "Failed!\n" : "Done\n");

        // Commit written value.
        // After setting any values, nvs_commit() must be called to ensure changes are written
        // to flash storage. Implementations may write to storage at other times,
        // but this is not guaranteed.
        printf("Committing updates in NVS ... ");
        err = nvs_commit(my_handle);                                     //提交已写入的值到 NVS 中。
        printf((err != ESP_OK) ? "Failed!\n" : "Done\n");

        // Close
        nvs_close(my_handle);                                            //关闭 NVS 句柄。
    }

    printf("\n");

    // Restart module
    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();
}

NVS 运行结果:

xecuting action: flash Serial port /dev/ttyACM0 Connecting.... Detecting chip type... ESP32-S3 Running ninja in directory /home/jichu/esp32/lvgl_display/build Executing "ninja flash"... [1/1] cd /home/jichu/esp32/lvgl_displa...isplay/build/bootloader/bootloader.bin Bootloader binary size 0x5240 bytes. 0x2dc0 bytes (36%) free. [2/5] Linking CXX executable lvgl_display.elf FAILED: lvgl_display.elf : && /home/jichu/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20230928/xtensa-esp-elf/bin/xtensa-esp32s3-elf-g++ -mlongcalls -Wl,--cref -Wl,--defsym=IDF_TARGET_ESP32S3=0 -Wl,--Map=/home/jichu/esp32/lvgl_display/build/lvgl_display.map -Wl,--no-warn-rwx-segments -fno-rtti -fno-lto -Wl,--gc-sections -Wl,--warn-common -T esp32s3.peripherals.ld -T esp32s3.rom.ld -T esp32s3.rom.api.ld -T esp32s3.rom.libgcc.ld -T esp32s3.rom.newlib.ld -T esp32s3.rom.version.ld -T memory.ld -T sections.ld CMakeFiles/lvgl_display.elf.dir/project_elf_src_esp32s3.c.obj -o lvgl_display.elf -L/home/jichu/esp32/esp-idf/components/soc/esp32s3/ld -L/home/jichu/esp32/esp-idf/components/esp_rom/esp32s3/ld -L/home/jichu/esp32/lvgl_display/build/esp-idf/esp_system/ld -L/home/jichu/esp32/esp-idf/components/esp_phy/lib/esp32s3 -L/home/jichu/esp32/esp-idf/components/esp_coex/lib/esp32s3 -L/home/jichu/esp32/esp-idf/components/esp_wifi/lib/esp32s3 -L/home/jichu/esp32/esp-idf/components/bt/controller/lib_esp32c3_family/esp32s3 esp-idf/xtensa/libxtensa.a esp-idf/esp_ringbuf/libesp_ringbuf.a esp-idf/efuse/libefuse.a esp-idf/esp_mm/libesp_mm.a esp-idf/driver/libdriver.a esp-idf/esp_pm/libesp_pm.a esp-idf/mbedtls/libmbedtls.a esp-idf/esp_bootloader_format/libesp_bootloader_format.a esp-idf/esp_app_format/libesp_app_format.a esp-idf/bootloader_support/libbootloader_support.a esp-idf/esp_partition/libesp_partition.a esp-idf/app_update/libapp_update.a esp-idf/spi_flash/libspi_flash.a esp-idf/pthread/libpthread.a esp-idf/esp_system/libesp_system.a esp-idf/esp_rom/libesp_rom.a esp-idf/hal/libhal.a esp-idf/log/liblog.a esp-idf/heap/libheap.a esp-idf/soc/libsoc.a esp-idf/esp_hw_support/libesp_hw_support.a esp-idf/freertos/libfreertos.a esp-idf/newlib/libnewlib.a esp-idf/cxx/libcxx.a esp-idf/esp_common/libesp_common.a esp-idf/esp_timer/libesp_timer.a esp-idf/app_trace/libapp_trace.a esp-idf/esp_event/libesp_event.a esp-idf/nvs_flash/libnvs_flash.a esp-idf/esp_phy/libesp_phy.a esp-idf/vfs/libvfs.a esp-idf/lwip/liblwip.a esp-idf/esp_netif/libesp_netif.a esp-idf/wpa_supplicant/libwpa_supplicant.a esp-idf/esp_coex/libesp_coex.a esp-idf/esp_wifi/libesp_wifi.a esp-idf/bt/libbt.a esp-idf/unity/libunity.a esp-idf/cmock/libcmock.a esp-idf/console/libconsole.a esp-idf/http_parser/libhttp_parser.a esp-idf/esp-tls/libesp-tls.a esp-idf/esp_adc/libesp_adc.a esp-idf/esp_eth/libesp_eth.a esp-idf/esp_gdbstub/libesp_gdbstub.a esp-idf/esp_hid/libesp_hid.a esp-idf/tcp_transport/libtcp_transport.a esp-idf/esp_http_client/libesp_http_client.a esp-idf/esp_http_server/libesp_http_server.a esp-idf/esp_https_ota/libesp_https_ota.a esp-idf/esp_lcd/libesp_lcd.a esp-idf/protobuf-c/libprotobuf-c.a esp-idf/protocomm/libprotocomm.a esp-idf/esp_local_ctrl/libesp_local_ctrl.a esp-idf/espcoredump/libespcoredump.a esp-idf/wear_levelling/libwear_levelling.a esp-idf/sdmmc/libsdmmc.a esp-idf/fatfs/libfatfs.a esp-idf/json/libjson.a esp-idf/mqtt/libmqtt.a esp-idf/nvs_sec_provider/libnvs_sec_provider.a esp-idf/perfmon/libperfmon.a esp-idf/spiffs/libspiffs.a esp-idf/touch_element/libtouch_element.a esp-idf/usb/libusb.a esp-idf/wifi_p
03-14
<think>好的,我现在要处理用户提供的关于ESP32-S3项目编译失败的日志。首先,我需要仔细阅读日志内容,找出关键错误信息。用户执行了`ninja flash`命令,但在链接阶段出现了问题,具体是`FAILED: lvgl_display.elf`,说明生成可执行文件时失败。 首先,我注意到链接器命令中使用了`xtensa-esp32s3-elf-g++`,这可能涉及到C++的链接问题。常见的失败原因可能包括内存不足、库缺失或版本不兼容、符号未定义等。用户的环境是ESP-IDF,可能使用了LVGL库,所以需要检查相关依赖是否正确配置。 日志中的错误没有直接显示具体的错误信息,这可能是因为输出被截断或者需要更详细的编译日志。因此,我应该建议用户增加编译的详细程度,例如运行`ninja -v flash`来获取更多信息。此外,检查是否有未定义的引用(undefined reference)或库路径问题。 另一个可能性是项目的sdkconfig配置不正确,特别是对于ESP32-S3的配置,例如PSRAM设置、分区表大小等。用户需要确认`sdkconfig`中是否选择了正确的芯片型号,以及LVGL相关配置是否适配ESP32-S3。 内存不足也是一个常见问题,尤其是在链接阶段。Bootloader的大小显示为0x5240字节,剩余36%的空间,这可能没问题,但主应用程序可能超出了可用内存。需要检查分区表是否合理,以及应用程序的代码量和内存需求。 还有可能是工具链的问题,比如xtensa-esp32s3-elf-g++的版本是否与ESP-IDF版本兼容。用户使用的ESP-IDF版本是否支持所选的LVGL版本?需要确认版本兼容性,并尝试重新安装或更新工具链。 此外,检查CMakeLists.txt和组件依赖是否正确,特别是LVGL组件是否被正确包含,路径是否正确。如果用户自定义了组件,可能存在路径错误或编译选项冲突。 最后,建议用户清理构建目录(删除build文件夹)并重新编译,有时候旧的构建文件会导致链接错误。如果问题依旧,查看更详细的日志或尝试简化项目以隔离问题。</think>您在尝试通过 `ninja flash` 命令烧录 ESP32-S3 固件时遇到了链接阶段的编译错误。以下是问题分析和解决步骤: --- ### **错误定位** 日志显示关键错误: ```bash FAILED: lvgl_display.elf : && /home/jichu/.espressif/tools/.../xtensa-esp32s3-elf-g++ ... (链接命令) ``` 此错误表明在生成最终可执行文件 `lvgl_display.elf` 时,链接器 (`xtensa-esp32s3-elf-g++`) 失败。 --- ### **可能原因** 1. **内存不足** - Bootloader 剩余空间:$$ 0x5240 \text{ bytes ()} - 0x2dc0 \text{ bytes (剩余)} = 0x2480 \text{ bytes (已用)} $$ - 主程序 (`lvgl_display.elf`) 可能超出芯片 Flash 或 RAM 限制。 2. **依赖库缺失或冲突** - 检查是否缺少 LVGL 相关库(如 `lvgl/liblvgl.a`)。 - 确保所有组件(如 Wi-Fi、SPIFFS)在 `sdkconfig` 中正确配置。 3. **符号未定义 (Undefined Reference)** - 常见于未实现函数、头文件未包含或库未链接。 4. **工具链版本不兼容** - ESP-IDF 版本与 LVGL 组件或第三方库存在兼容性问题。 --- ### **解决步骤** #### 1. **启用详细日志** 重新编译并输出详细错误信息: ```bash ninja -v flash | tee build.log ``` 检查 `build.log` 中具体的未定义符号或路径错误。 --- #### 2. **检查内存配置** - **分区表**:确认 `partitions.csv` 中 `app` 分区大小是否足够。 - **sdkconfig**:通过 `idf.py menuconfig` 检查: - **Component config → LVGL → Memory settings**(调整 LVGL 缓冲区大小)。 - **Serial flasher config → Flash Size** 是否匹配硬件。 --- #### 3. **验证依赖关系** - **组件路径**:确保 LVGL 组件路径正确(`CMakeLists.txt` 中 `EXTRA_COMPONENT_DIRS`)。 - **库链接**:在 `CMakeLists.txt` 中添加缺失库: ```cmake target_link_libraries(${PROJECT_NAME} PRIVATE lvgl) ``` --- #### 4. **清理并重新编译** 清除旧构建文件: ```bash rm -rf build idf.py build ``` --- #### 5. **缩小问题范围** - **最小化代码**:注释掉 LVGL 初始化代码,逐步排查功能模块。 - **更新工具链**:重新安装 ESP-IDF 或更新至稳定版本(如 v5.1)。 --- ### **扩展建议** - **使用官方例程**:从 `esp-idf/examples/peripherals/lcd/lvgl` 测试基础功能。 - **内存分析工具**:通过 `idf.py size-components` 查看各组件内存占用。 如果仍有问题,请提供 `build.log` 中的具体错误信息,以便进一步诊断!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

枕上

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

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

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

打赏作者

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

抵扣说明:

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

余额充值