- 音视频
- 音频处理
- 嵌入式
- 物联网
- 语音
【免费下载链接】esp-adf
Espressif Advanced Development Framework for Multimedia Applications
本文围绕 ESP-ADF(Espressif Advanced Development Framework)中的esp_config_manager组件及其官方示例config_manager_example展开,讲解如何在嵌入式设备上以「主/备双槽 + CRC 校验 + 默认值合并」的方式可靠地保存多组应用配置。读完本文,你将掌握该组件的完整使用流程:如何创建配置组、绑定 NVS/SPIFFS/raw flash 三种存储后端、切换自测模式验证掉电恢复与结构体升级场景,以及如何解读运行日志与定位故障。
例程概览:它解决什么问题
esp_config_manager是一个面向嵌入式场景的配置管理组件,其核心定位是让配置数据在掉电、写入被中断、结构体/schema 扩展等情况下依然「可用、可恢复、可演进」。组件 README 明确列出了它的关键能力(见 组件 README):
- 主/备双槽存储(Primary + Backup);
- 记录头校验(
magic / schema_version / payload_len / crc32); - 自动加载回退(primary → backup → defaults);
- 支持自定义
merge_fn的默认值合并(用于迁移/演进); - 同步的主槽修复(把 backup 复制回 primary);
- 可插拔存储后端(NVS / filesystem / raw flash / 自定义);
- 可选加解密钩子(
encrypt/decrypt)。
官方示例config_manager_example(例程 README)演示了如何用该组件在持久化介质中维护audio、lifecycle、net 三个相互独立的配置组:首次启动加载默认值并写入存储,随后修改字段、保存、再次读取,并打印每个配置的来源(defaults / primary / backup)。
典型场景
该例程适用于需要可靠本地配置保存的产品,例如:
- 音频参数(音量、亮度、profile 名等);
- 启动计数(如 boot counter)、会话标志位;
- 网络相关标志位(接口名、MTU、路由标识);
- 需要兼容结构体扩展的配置升级场景(新增字段时旧配置仍可加载并补齐默认值)。
例程运行机制(工作流程)
例程的完整流程如下(源码见 config_manager_example.c 中的app_main()):
- 初始化 NVS;当选择 filesystem 存储后端时,同时初始化并挂载 SPIFFS;
- 创建
audio、lifecycle、net三个独立配置组; - 将每个配置组绑定到选定的存储介质(通过
esp_config_storage_init_nvs()/init_fs()/init_flash()打开后端句柄); - 调用
esp_config_manager_load()读取配置; - 修改每个配置组中的一个字段,调用
esp_config_manager_save()保存,再次读取以验证持久化结果; - 释放 manager handle 与 storage handle,打印
Done。
在app_main()的开头还包含标准的 NVS 初始化容错逻辑:当nvs_flash_init()返回ESP_ERR_NVS_NO_FREE_PAGES或ESP_ERR_NVS_NEW_VERSION_FOUND时,先nvs_flash_erase()再重新初始化;当选择 FS 后端时,则通过esp_vfs_spiffs_register()挂载storage分区到/spiffs(max_files=8、format_if_mount_failed=true),退出前再esp_vfs_spiffs_unregister()。
深入底层:记录格式与双槽存储布局
理解例程行为的前提是掌握组件的底层存储格式。每个配置组维护两个逻辑槽(见 esp_config_storage.h):
ESP_CONFIG_SLOT_PRIMARY:主副本;ESP_CONFIG_SLOT_BACKUP:冗余副本。
每个槽保存一个二进制 blob(加密之前),格式为「16 字节私有记录头 + 变长 payload」:
+--------------------------------------------------------------+ | private record header (16 bytes) | | - magic (uint32_t) | | - schema_version (uint32_t) | | - payload_len (uint32_t) | | - crc32 (uint32_t) | +--------------------------------------------------------------+ | payload (variable length) | +--------------------------------------------------------------+在源码 esp_config_manager.c 中,magic 定义为0x30666765u,crc32的计算覆盖schema_version + payload_len + payload三个部分(crc_record(),基于esp_rom_crc32_le)。加载时若magic或 CRC 校验失败,该槽即被视为无效记录。
加载回退与主槽修复
esp_config_manager_load()在源码中的实际执行流程(esp_config_manager.c):
- 读取 primary 槽;成功则直接返回,
info.source = ESP_CONFIG_LOAD_SOURCE_PRIMARY; - 若 primary 缺失(
ESP_ERR_NOT_FOUND)或 CRC/magic 校验失败(ESP_ERR_INVALID_CRC),尝试读取 backup 槽; - 若 backup 有效:把 backup 内容同步写回 primary 完成修复,
info.source = ESP_CONFIG_LOAD_SOURCE_BACKUP,且info.primary_repair_scheduled = true; - 若两槽均无效:回退到默认值,先经
merge_fn(默认是前缀覆盖)构造运行时配置,再写回 primary 与 backup 两个槽,info.source = ESP_CONFIG_LOAD_SOURCE_DEFAULTS。
esp_config_manager_save()则通过write_both_slots()把同一份记录同时写入 primary 与 backup,任一槽写失败即返回ESP_FAIL。加载来源可通过esp_config_load_info_t::source区分:
ESP_CONFIG_LOAD_SOURCE_PRIMARYESP_CONFIG_LOAD_SOURCE_BACKUPESP_CONFIG_LOAD_SOURCE_DEFAULTS
例程中的print_audio()/print_lifecycle()/print_net()正是根据该枚举打印日志中的from primary / from backup / from defaults字样。
默认值合并:面向结构体升级的演进机制
当 primary 与 backup 都无效时,管理器会用default_config重建配置。esp_config_manager_cfg_t中的merge_fn提供自定义合并入口(原型见 esp_config_manager.h)。
若merge_fn为NULL,组件使用内置的前缀覆盖策略(default_merge(),esp_config_manager.c):
- 输出缓冲区先以
default_config整体初始化; - 再用已加载 payload 覆盖前
min(loaded_len, default_size)个字节。
这通常能保留结构体「尾部新增字段」的默认值——当旧 payload 比新结构体短时,新字段自动落回默认值。但需要注意:当新旧sizeof相同、仅字段语义变化时,旧 blob 的填充字节(padding)可能覆盖新字段。因此对于持久化结构体,建议使用packed布局或编写自定义merge_fn做显式迁移。
例程的 audio 配置组正是这一机制的演示对象(merge_tail_new_fields自测项):
typedef struct __attribute__((packed)) { uint32_t volume_percent; /*!< 音量 0–100 */ uint32_t feature_flags; /*!< 功能位掩码 */ char profile_name[16]; /*!< 标签 */ uint32_t brightness; /*!< UI 亮度 */ uint8_t volume_level; /*!< 离散电平 */ uint8_t array[5]; /*!< 演示字节数组 */ } example_audio_config_t;自测中会构造一份「只写到volume_level之前(28 字节)」的旧 payload 存入存储,再加载,验证volume_level与array能从默认值正确补齐。三个配置组的 schema 版本分别为:audio2、lifecycle11、net12(见例程中的s_group_desc)。
可选加解密钩子
组件允许通过esp_config_crypto_ops_t注入加解密流程:
- 保存时:先打包「头 + payload」,调用
encrypt()后再写入; - 加载时:先读出 blob,调用
decrypt(),再解析头与 CRC。
若不提供crypto,数据以明文存储;encrypt/decrypt完全由用户实现,因此可以接入任意算法或安全元件/HSM 流程。例程中的example_encrypt()/example_decrypt()是恒等透传实现(直接 memcpy),用于演示钩子接线方式:
static esp_err_t example_encrypt(void *ctx, const uint8_t *in, size_t in_len, uint8_t *out, size_t out_size, size_t *out_len) { if (out_size < in_len) { return ESP_ERR_NO_MEM; } memcpy(out, in, in_len); *out_len = in_len; return ESP_OK; }配置项crypto_extra_size表示密文相对明文记录(16 字节头 + 至多default_size的 payload)可能多出的字节数,内部缓冲区按「明文大小 + 该值」分配;不使用encrypt时必须将其设为0(esp_config_manager_init()中会强制校验这一点,见 esp_config_manager.c)。
存储后端绑定:NVS / SPIFFS / raw flash / 自定义
底层持久化由esp_config_storage_ops_t定义,只有三个回调(esp_config_storage.h):
read(ctx, slot, buf, inout_len)write(ctx, slot, buf, len)erase(ctx, slot)(可选)
用esp_config_storage_init(ops, ctx, &handle)绑定后端(ctx不会被拷贝,必须存活到esp_config_storage_deinit(handle)之后),并把得到的 handle 传给esp_config_manager_cfg_t.storage。组件已内置三个适配器:
- NVS:
esp_config_storage_nvs_ops(esp_config_storage_nvs.c); - Filesystem(SPIFFS/FATFS/LittleFS 等):
esp_config_storage_fs_ops(esp_config_storage_fs.c); - Raw flash 分区:
esp_config_storage_flash_ops(esp_config_storage_flash.c)。
NVS 后端:namespace + 双 blob key
每个配置组占用一个独立 NVS namespace,槽位映射到两个 blob key。例程中三个配置组使用三个隔离 namespace:
static esp_config_storage_nvs_t s_nvs_groups[CFG_EX_NUM_GROUPS] = { {.nvs_namespace = "cfg_g1", .key_primary = "main", .key_backup = "bak"}, {.nvs_namespace = "cfg_g2", .key_primary = "main", .key_backup = "bak"}, {.nvs_namespace = "cfg_g3", .key_primary = "main", .key_backup = "bak"}, };NVS 适配器读写最终落到nvs_set_blob()/nvs_get_blob()并显式nvs_commit()。
Filesystem 后端:/spiffs双文件
primary 与 backup 分别对应一个绝对路径文件。例程映射如下:
static esp_config_storage_fs_t s_fs_groups[CFG_EX_NUM_GROUPS] = { {.path_primary = "/spiffs/g1_pri.bin", .path_backup = "/spiffs/g1_bak.bin"}, {.path_primary = "/spiffs/g2_pri.bin", .path_backup = "/spiffs/g2_bak.bin"}, {.path_primary = "/spiffs/g3_pri.bin", .path_backup = "/spiffs/g3_bak.bin"}, };值得注意的实现细节:FS 后端写入时先写一个同路径的.tmp文件,再rename()替换目标文件,以保证同一卷上的基本原子性(见 esp_config_storage_fs.c)。
Raw flash 后端:专用 data 分区
每个槽对应一个独立的data类型分区(通过 label 查找)。分区内布局为「小端uint32_t长度字段 + blob 数据」,写入时先整分区擦除再写(见 esp_config_storage_flash.c)。例程映射:
static esp_config_storage_flash_t s_flash_groups[CFG_EX_NUM_GROUPS] = { {.label_primary = "cfg_pri", .label_backup = "cfg_bak"}, {.label_primary = "cfg2_pri", .label_backup = "cfg2_bak"}, {.label_primary = "cfg3_pri", .label_backup = "cfg3_bak"}, };自定义后端
你可以为任意介质(外部 EEPROM、远程 KV 等)自行实现ops,通过esp_config_storage_init()绑定后传入esp_config_manager_cfg_t.storage。单元测试 test_esp_config_manager.c 中就用一个内存 mock(mock_ram_ctx_t,可注入 primary/backup 读写失败)作为自定义后端来验证回退与修复逻辑。
环境配置与硬件要求
硬件要求
- ESP-IDF 支持的 Espressif 开发板,例如 ESP32 或 ESP32-S3。例程的 pytest 目标芯片为
esp32s3(见 pytest_esp_config_manager_simple.py)。
默认 IDF 分支
例程支持 ESP-IDFrelease/v5.5 及以后的分支;ADF 仓库默认使用 ESP-IDF release/v5.5。
构建准备
若尚未配置 ESP-IDF 环境,在 ESP-IDF 根目录执行:
./install.sh . ./export.sh进入例程工程目录(注意实际目录名为config_manager_example):
cd $ADF_PATH/components/esp_config_manager/examples/config_manager_example该目录下的顶层 CMakeLists.txt 通过include($ENV{IDF_PATH}/tools/cmake/project.cmake)引入工程构建;main/CMakeLists.txt声明依赖nvs_flash esp_config_manager spiffs,且idf_component.yml以override_path: ../../../指向组件源码,因此无需额外拉取依赖。
编译与烧录
- 编译示例程序:
idf.py build- 烧录程序并运行 monitor 查看串口输出(替换
PORT为实际端口名):
idf.py -p PORT flash monitor- 退出调试界面使用
Ctrl-]。
项目配置:切换存储后端与自测模式
存储后端选择
默认存储方式为 NVS,定义在 config_manager_example.c:
#define EXAMPLE_STORAGE_NVS 1 #define EXAMPLE_STORAGE_FS 2 #define EXAMPLE_STORAGE_FLASH 3 /* 选择存储介质(编辑本行)。FS/FLASH 后端需要 partitions.csv + sdkconfig.defaults。 */ #define EXAMPLE_CFG_STORAGE_BACKEND EXAMPLE_STORAGE_NVS如需测试其他后端,修改EXAMPLE_CFG_STORAGE_BACKEND为下列之一:
| 宏 | 说明 |
|---|---|
EXAMPLE_STORAGE_NVS | 每个配置组使用独立 NVS namespace,各存 primary/backup 两个 blob key |
EXAMPLE_STORAGE_FS | primary 与 backup 文件保存到/spiffs,使用 partitions.csv 中的storageSPIFFS 分区 |
EXAMPLE_STORAGE_FLASH | primary/backup 分别保存到partitions.csv中定义的 raw data 分区 |
例程的 sdkconfig.defaults 已启用自定义分区表(CONFIG_PARTITION_TABLE_CUSTOM=y、CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv"),可满足 SPIFFS 与 raw flash 后端演示所需的分区布局。自定义分区表内容如下:
| 分区名 | 类型 | 子类型 | 偏移 | 大小 | 用途 |
|---|---|---|---|---|---|
nvs | data | nvs | 0x9000 | 0x6000 | NVS 主区域 |
phy_init | data | phy | 0xf000 | 0x1000 | PHY 校准数据 |
factory | app | factory | 0x10000 | 0x100000 | 应用固件 |
cfg_pri | data | 0x99 | 自动 | 0x2000 | audio 组主槽 |
cfg_bak | data | 0x99 | 自动 | 0x2000 | audio 组备槽 |
cfg2_pri | data | 0x99 | 自动 | 0x1000 | lifecycle 组主槽 |
cfg2_bak | data | 0x99 | 自动 | 0x1000 | lifecycle 组备槽 |
cfg3_pri | data | 0x99 | 自动 | 0x1000 | net 组主槽 |
cfg3_bak | data | 0x99 | 自动 | 0x1000 | net 组备槽 |
storage | data | spiffs | 自动 | 0xBC000 | SPIFFS 文件系统 |
自测模式
如需运行较完整的自测流程(而不只是短演示),在 config_manager_example.c 中设置:
#define CFG_EX_RUN_SELFTEST 1自测模式会对每个配置组执行「擦除存储 → 验证默认值恢复 → 保存/加载往返 → 破坏主槽 → 验证备份回退与主槽修复」,并对 audio 组额外验证 packed 布局与尾字段合并。
运行效果与日志解读
烧录后例程自动运行。首次启动时,各配置组从默认值加载并写入存储;后续启动则优先从 primary 槽读取,并在上次保存值的基础上继续变化。
普通演示日志(默认 NVS 后端)
I (325) CFG_MANAGER_EXAMPLE: Storage backend: NVS (3 config groups) I (345) CFG_MANAGER_EXAMPLE: [audio] from defaults repair=0 I (345) CFG_MANAGER_EXAMPLE: vol=42 flags=0x00000001 profile=default bri=80 lvl=10 array=esp1 I (355) CFG_MANAGER_EXAMPLE: [audio] from primary repair=0 I (355) CFG_MANAGER_EXAMPLE: vol=43 flags=0x00000001 profile=default bri=80 lvl=0 array=esp1 I (365) CFG_MANAGER_EXAMPLE: --- group audio done --- I (375) CFG_MANAGER_EXAMPLE: [lifecycle] from defaults — boot_count=0 session_flags=0x0100 I (375) CFG_MANAGER_EXAMPLE: array=esp2 I (385) CFG_MANAGER_EXAMPLE: [lifecycle] from primary — boot_count=1 session_flags=0x0100 I (395) CFG_MANAGER_EXAMPLE: --- group lifecycle done --- I (405) CFG_MANAGER_EXAMPLE: [net] from defaults — route_tag=0 if=sta0 mtu=1500 I (415) CFG_MANAGER_EXAMPLE: [net] from primary — route_tag=305419896 if=sta0 mtu=1500 I (425) CFG_MANAGER_EXAMPLE: --- group net done --- I (425) CFG_MANAGER_EXAMPLE: Done日志解读:
- audio 组:首次从 defaults 读到
vol=42,修改后保存,二次加载从 primary 读到vol=43、lvl=0(源码中volume_level = (volume_level + 1) % 11,即 10 → 0); - lifecycle 组:
boot_count从默认 0 变为 1,演示「启动计数」类配置的跨重启累积; - net 组:
route_tag从 0 变为0x12345678(305419896),演示网络相关标志位。
自测模式日志
开启 self-test 后,关键日志如下:
I (325) CFG_MANAGER_EXAMPLE: ======== selftest start (backend=NVS, 3 groups) ======== I (345) CFG_MANAGER_EXAMPLE: [TEST] g0.packed_layout PASS I (365) CFG_MANAGER_EXAMPLE: [TEST] g0.load_defaults_after_wipe PASS — ESP_OK I (385) CFG_MANAGER_EXAMPLE: [TEST] g0.save_load_roundtrip PASS — ESP_OK I (405) CFG_MANAGER_EXAMPLE: [TEST] g0.fallback_to_backup_crc PASS — ESP_OK I (425) CFG_MANAGER_EXAMPLE: [TEST] g0.primary_repaired_after_load PASS — ESP_OK I (445) CFG_MANAGER_EXAMPLE: [TEST] g0.merge_tail_new_fields PASS I (465) CFG_MANAGER_EXAMPLE: ======== selftest end ========各测试项含义(对应源码run_selftests_audio()与run_selftests_short_group()):
| 测试项 | 验证内容 |
|---|---|
g0.packed_layout | example_audio_config_t的sizeof恰为 34 字节(packed 布局生效) |
g0.wipe_storage | 三种后端下都能成功擦除存储 |
g0.load_defaults_after_wipe | 双槽被擦除后,加载返回默认值且source = DEFAULTS |
g0.save_load_roundtrip | 修改字段(vol=55、lvl=7)保存后能原样读回,且来源为 primary |
g0.corrupt_primary_blob | 能向主槽写入垃圾数据制造损坏 |
g0.fallback_to_backup_crc | 主槽 CRC 损坏时回退到 backup,且触发主槽修复 |
g0.primary_repaired_after_load | 再次加载时从修复后的 primary 成功读到数据 |
g0.merge_tail_new_fields | 旧 payload 缺少新字段时,尾部新字段从默认值补齐 |
g1/g2.defaults、g1/g2.u32_roundtrip | 其余配置组的默认值加载与 u32 往返验证 |
CI 侧的 pytest_esp_config_manager_simple.py 会逐条匹配上述 PASS 日志,任一 FAIL 都会导致集成测试失败,因此这些日志既是演示输出,也是自动化验收断言。
故障排除
SPIFFS 或 raw flash 后端加载失败
请确认构建时使用了sdkconfig.defaults(启用CONFIG_PARTITION_TABLE_CUSTOM),并且自定义partitions.csv已生效。这两种后端分别需要storage分区以及cfg_pri、cfg_bak、cfg2_pri、cfg2_bak、cfg3_pri、cfg3_bak等 raw config 分区,缺少任一分区都会导致加载失败(raw flash 适配器会以「partition not found」拒绝读写)。
重新烧录后配置值没有恢复默认
普通应用烧录不会清除 NVS 数据,因此二次烧录后各配置组仍从 primary 槽读回上次保存的值。如需重新观察「首次启动恢复默认值」的流程,先执行:
idf.py erase-flashself-test 日志出现FAIL
- 先执行
idf.py erase-flash后重新运行,排除残留脏数据干扰; - 若使用 flash 后端,同时确认自定义分区表已生效,且
partitions.csv中的每个 raw config 分区都存在; - 若使用 FS 后端,确认 SPIFFS 分区
storage已正确挂载(日志中应无 SPIFFS 挂载错误)。
总结
esp_config_manager通过「双槽冗余写入 + 16 字节 CRC 记录头 + primary→backup→defaults 三级回退 + 同步主槽修复」的组合,为 ESP32 系列芯片上的本地配置提供了可靠的持久化保障;config_manager_example则以三个配置组、三种存储后端、可切换的自测模式,完整展示了该组件在产品中的典型接法。无论你的场景是音频参数、启动计数、网络标志位,还是需要兼容结构体扩展的配置升级,都可以直接以此例程为蓝本,替换其中的配置结构体与后端绑定即可落地使用。例程原文还列出了官方技术支持渠道(esp32.com 论坛与问题反馈入口),详细支持信息可参考例程 README 原文。
- 音视频
- 音频处理
- 嵌入式
- 物联网
- 语音
【免费下载链接】esp-adf
Espressif Advanced Development Framework for Multimedia Applications
相关推荐
ESP-ADF 配置管理组件 esp_config_manager 完全指南:双副本冗余、CRC 校验与默认值合并实战
ESP ADF 配置管理组件 esp_config_manager 完全指南:双副本冗余、CRC 校验与默认值合并实战 导读 esp_config_manage
音视频音频处理嵌入式物联网语音esp-adf 配置管理器 esp_config_manager 深入解析:双槽冗余存储、CRC 校验与默认值合并机制
esp adf 配置管理器 esp_config_manager 深入解析:双槽冗余存储、CRC 校验与默认值合并机制 导读 esp_config_manage
音视频音频处理嵌入式物联网语音signal-hook:Rust中安全处理Unix信号的终极指南 🚀
signal hook:Rust中安全处理Unix信号的终极指南 🚀 signal hook 是一个专为Rust语言设计的Unix信号处理库,它为开发者提供了
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考