
ESP-IDF NVS Bootloader 指南在 Bootloader 中安全读取 NVS 数据的简化只读 API【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf导读本文围绕 ESP-IDF 官方开发框架中为自定义 bootloader 代码提供的 NVSNon-Volatile Storage非易失性存储只读 API 展开。由于 bootloader 运行环境的限制自定义 bootloader 代码无法直接使用完整的 NVS API因此 NVS 提供了一套简化 API支持在 bootloader 阶段读取 NVS 数据甚至解密读取加密的 NVS 分区。读完本文你将掌握nvs_bootloader_read()等 API 的用法、输入输出结构体约定、错误码语义、加密 NVS 分区的解密流程以及如何通过官方示例examples/storage/nvs/nvs_bootloader在真实工程中落地这套能力。概述为什么 Bootloader 需要一套独立的 NVS 只读 API在 ESP-IDF 中NVS 是应用层持久化键值存储的标准方案。但在 bootloader 运行环境中存在诸多限制无完整堆管理、运行时间短、代码尺寸受限等因此自定义 bootloader 代码不能直接使用完整的 NVS API。为此NVS 提供了一套简化 API其核心约束与能力如下依据官方文档 docs/en/api-reference/storage/nvs_bootloader.rst 及头文件 components/nvs_flash/include/nvs_bootloader.h只读访问该 API 仅支持读取 NVS 数据不支持写入。数据类型覆盖支持读取除 blob 之外的所有 NVS 数据类型即NVS_TYPE_U8/U16/U32/U64、NVS_TYPE_I8/I16/I32/I64、NVS_TYPE_FLOAT、NVS_TYPE_DOUBLE和NVS_TYPE_STR。该约束在私有头文件 components/nvs_flash/private_include/nvs_bootloader_private.h 中由NVS_BOOTLOADER_IS_SUPPORTED_TYPE()宏定义体现——它对NVS_TYPE_BLOB返回 false。一次调用读取多条记录一次nvs_bootloader_read()调用可以同时读取多个 NVS entry。跨命名空间可以在同一次调用中从同一个 NVS 分区内的不同命名空间读取值。固定 8 字节占位API 使用一个输入输出结构体数组作为从 NVS 读回数据的占位符单个标量值的占位大小固定为最多 8 字节。字符串需自备缓冲区由于 bootloader 中堆内存分配受限读取字符串类型的 entry 时调用方必须提供缓冲区及其长度。从源码结构看components/nvs_flash/src/nvs_bootloader.c该实现直接基于esp_partition_read()逐页、逐条目解析 NVS 分区的物理格式而不是复用应用层的 NVS 库这正是它能运行在 bootloader 精简环境中的根本原因。核心数据结构占位符、联合体与输入输出列表所有公开类型定义在 components/nvs_flash/include/nvs_bootloader.h 中共三层结构。字符串占位符nvs_bootloader_str_value_placeholder_ttypedef struct { char* buff_ptr; /** 指向缓冲区字符串和结尾的 \0 将被读入此处 */ size_t buff_len; /** 缓冲区长度字节 */ } nvs_bootloader_str_value_placeholder_t;由于 bootloader 中不能依赖堆分配读取字符串时由调用方在栈上或静态区准备缓冲区并把指针和长度填入该结构。值占位联合体nvs_bootloader_value_placeholder_ttypedef union { uint8_t u8_val; /** 无符号 8 位整数 */ int8_t i8_val; /** 有符号 8 位整数 */ uint16_t u16_val; /** 无符号 16 位整数 */ int16_t i16_val; /** 有符号 16 位整数 */ uint32_t u32_val; /** 无符号 32 位整数 */ int32_t i32_val; /** 有符号 32 位整数 */ uint64_t u64_val; /** 无符号 64 位整数 */ int64_t i64_val; /** 有符号 64 位整数 */ float float_val; /** IEEE 754 单精度浮点数 */ double double_val; /** IEEE 754 双精度浮点数 */ nvs_bootloader_str_value_placeholder_t str_val; /** 字符串缓冲区信息 */ } nvs_bootloader_value_placeholder_t;联合体大小为 16 字节str_val成员决定而单个标量值实际占用不超过 8 字节与文档所述固定大小、最多 8 字节一致。输入输出列表项nvs_bootloader_read_list_ttypedef struct { const char* namespace_name; /** 输入。entry 所在的命名空间 */ const char* key_name; /** 输入。entry 的键名 */ nvs_type_t value_type; /** 输入。期望读取的数据类型 */ esp_err_t result_code; /** 输出。本 entry 的结果码 */ nvs_bootloader_value_placeholder_t value; /** 输入/输出。读回值的占位符 */ uint8_t namespace_index; /** 输出。命名空间索引内部变量勿使用 */ } nvs_bootloader_read_list_t;调用前需要填充namespace_name、key_name和value_type若读取字符串还需在value.str_val中提供缓冲区指针与长度。调用后result_code会反映每条记录的具体结果value在成功时携带读回的数据。nvs_bootloader_read()批量只读入口函数原型esp_err_t nvs_bootloader_read(const char* partition_name, const size_t read_list_count, nvs_bootloader_read_list_t read_list[]);返回值与逐条结果码的两种语义该函数的整体返回值与每条记录的result_code是两套信息必须区分解读整体返回ESP_OK所有输入参数通过校验读取流程正常执行完毕。此时每条记录的result_code可能是ESP_OK找到该 entryvalue成员已填充数据这是value被填充的唯一情况ESP_ERR_NVS_TYPE_MISMATCH找到 entry但请求的数据类型与 NVS 中实际存储的类型不一致ESP_ERR_NVS_NOT_FOUND未找到该数据ESP_ERR_INVALID_SIZE找到的字符串长度超过str_val.buff_len提供的空间。整体返回ESP_ERR_INVALID_ARG参数校验失败至少一条记录的输入参数不合法。此时逐条result_code可能是ESP_ERR_NVS_NOT_FOUND该条参数校验通过默认值表示无问题ESP_ERR_NVS_INVALID_NAMEnamespace_name为 NULL 或过长ESP_ERR_NVS_KEY_TOO_LONGkey_name为 NULL 或过长ESP_ERR_INVALID_SIZENVS_TYPE_STR的缓冲区长度str_val.buff_len为 0 或超过最大值NVS_CONST_STR_LEN_MAX_SIZEESP_ERR_INVALID_ARG请求了不支持的数据类型如NVS_TYPE_BLOB。除此之外整体返回值还可能是ESP_ERR_NVS_INVALID_NAME分区名过长或为 NULLESP_ERR_NVS_PART_NOT_FOUND分区表中找不到该分区ESP_ERR_NVS_CORRUPT_KEY_PART/ESP_ERR_NVS_WRONG_ENCRYPTION与加密相关的问题ESP_ERR_INVALID_STATE分区尺寸错误、页头不一致/entry 不一致、存在多个 ACTIVE 页、页处于 INVALID 状态等ESP_ERR_NO_MEM无法分配执行所需的内存底层存储的技术性错误。参数校验的源码依据在 components/nvs_flash/src/nvs_bootloader.c 的nvs_bootloader_check_parameters()中可以看到具体的校验逻辑partition_name为 NULL 或超过NVS_PART_NAME_MAX_SIZE时返回ESP_ERR_NVS_INVALID_NAMEread_list为 NULL 或read_list_count为 0 时返回ESP_ERR_INVALID_ARG逐条检查命名空间名、键名长度分别以NVS_NS_NAME_MAX_SIZE - 1、NVS_KEY_NAME_MAX_SIZE - 1为上限、数据类型是否受支持、字符串缓冲区的长度与指针是否有效对单 entry 类型NVS_BOOTLOADER_TYPE_FITS_SINGLE_ENTRY会先清空value占位符。内部读取流程五步走从nvs_bootloader_read()实现components/nvs_flash/src/nvs_bootloader.c看读取过程分为五个步骤校验所有输入参数如有问题返回错误并填充逐条result_code通过esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_NVS, partition_name)查找分区找不到返回ESP_ERR_NVS_PART_NOT_FOUND遍历分区所有页统计处于 ACTIVE 与 FREEING 状态的页数量若 FREEING 页多于 1 个或 ACTIVE 页多于 1 个返回ESP_ERR_INVALID_STATE若存在 FREEING 页则在后续读取中跳过 ACTIVE 页保证一致性读取各页中命名空间条目为read_list中的每条请求匹配namespace_index再次遍历页按命名空间索引、键名和类型匹配并读回键值对。实现采用了访问者模式visitor pattern通过nvs_bootloader_visit_pages()统一遍历页分别用nvs_bootloader_page_visitor_get_page_states()、nvs_bootloader_page_visitor_get_namespaces()、nvs_bootloader_page_visitor_get_key_value_pairs()三个访问者函数完成上述三个阶段见 components/nvs_flash/private_include/nvs_bootloader_private.h 中的声明。字符串读取与 CRC32 校验对字符串类型的读取components/nvs_flash/src/nvs_bootloader.c做了两件额外的事情读取字符串数据体时先检查str_val.buff_len是否不小于存储的字符串长度不足则返回ESP_ERR_INVALID_SIZE读回数据后计算 CRC32esp_rom_crc32_le并与 entry 元数据中存储的 CRC32 比对不一致按未找到处理技术上视为不一致。另外nvs_bootloader_partition_read_string_value()中体现了 bootloader 环境下 flash 读取的特殊约束esp_partition_read()内部调用bootloader_flash_read()要求源地址、长度和目标地址均字对齐因此实现用 32 字节对齐的中间缓冲区BOOTLOADER_FLASH_READ_LEN分块读取components/nvs_flash/src/nvs_bootloader.c。读取加密 NVS 分区五步解密工作流当 NVS 分区采用 nvs_encryption 文档所述的加密方案flash encryption 方案或 HMAC 方案时bootloader API 同样支持解密读取。启用解密的完整流程为根据所选 NVS 加密方案填充 NVS 安全配置结构体nvs_sec_cfg_t详细见 nvs_encryption用nvs_bootloader_read_security_cfg()读取指定安全方案设置的安全配置用nvs_bootloader_secure_init()以读取到的安全配置初始化 NVS flash 分区用nvs_bootloader_read()执行 NVS 读取操作用nvs_bootloader_secure_deinit()反初始化并清除 NVS flash 分区的安全配置。三个加密相关 API 说明esp_err_t nvs_bootloader_read_security_cfg(nvs_sec_scheme_t *scheme_cfg, nvs_sec_cfg_t* cfg); esp_err_t nvs_bootloader_secure_init(const nvs_sec_cfg_t *sec_cfg); void nvs_bootloader_secure_deinit(void);nvs_bootloader_read_security_cfg()从指定的安全方案读取加密密钥配置。返回ESP_OK读取成功、ESP_ERR_INVALID_ARGscheme_cfg或cfg为 NULL、ESP_FAIL密钥读取过程失败。其实现直接调用scheme_cfg-nvs_flash_read_cfg回调见 components/nvs_flash/src/nvs_bootloader.c。nvs_bootloader_secure_init()用给定的nvs_sec_cfg_t初始化内部 NVS 安全上下文。实现中调用nvs_bootloader_xts_aes_init()/nvs_bootloader_xts_aes_setkey()设置 AES-256-XTS 解密密钥并置位分区已加密标志components/nvs_flash/src/nvs_bootloader.c。注意一旦执行nvs_bootloader_secure_init()在nvs_bootloader_secure_deinit()清除内部上下文之前nvs_bootloader_read()只能正确读取使用该nvs_sec_cfg_t加密的 NVS 分区。nvs_bootloader_secure_deinit()释放 XTS-AES 上下文并清除加密标志components/nvs_flash/src/nvs_bootloader.c。加密实现的底层细节从源码可确认以下实现事实加解密使用AES-256-XTSNVS_KEY_SIZE为 32即 AES-256见 components/nvs_flash/src/nvs_bootloader.c相关实现在 components/nvs_flash/src/nvs_bootloader_xts_aes.c 与 components/nvs_flash/src/nvs_bootloader_aes.c。由于 mbedtlsmbedtls_aes_crypt_xts()在缓冲区长度不是 16 的倍数时存在原地加解密计算缺陷源码注释引用了 Mbed-TLS issue实现采用分块处理先按 16 对齐的长度处理再把剩余数据复制到 16 字节缓冲区处理当前按 32 字节块XTS_AES_PROCESS_BLOCK_LEN操作components/nvs_flash/src/nvs_bootloader.c。解密单元data unit由相对地址构造把src_offset低 4 字节拷贝进 16 字节的data_unit后执行解密components/nvs_flash/src/nvs_bootloader.c这与 NVS 加密时按扇区/偏移生成 tweak 的约定对应。在 bootloader 构建中若启用了CONFIG_MBEDTLS_USE_CRYPTO_ROM_IMPL_BOOTLOADERnvs_bootloader_secure_init()会先调用mbedtls_rom_osi_functions_init_bootloader()以启用 ROM 中的 mbedtls 实现应用构建中则由 mbedtls 组件的构造函数完成同样的初始化。HMAC 方案的补充说明官方文档特别注明当使用 HMAC 方案时上述工作流可以在不使能任何 NVS 加密配置项CONFIG_NVS_ENCRYPTION、CONFIG_NVS_SEC_KEY_PROTECTION_SCHEME下的CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC、以及CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID的情况下直接用nvs_flash_secure_init()API 加密默认及自定义 NVS 分区该说明位于文档中SOC_HMAC_SUPPORTED条件块内仅对支持 HMAC 的芯片生效。官方示例nvs_bootloader代码示例位于examples/storage/nvs/nvs_bootloader属于examples/storage/nvs目录其目的正是演示如何在 bootloader 构建中使用这套简化只读 API。示例结构├── CMakeLists.txt ├── main │ ├── CMakeLists.txt │ └── main.c # 普通应用仅打印消息后结束 ├── bootloader_components │ └── nvs_bootloader_example │ ├── CMakeLists.txt │ ├── include/nvs_bootloader_example_utils.h │ └── src │ ├── nvs_bootloader_example.c # 主测试实现详细注释均在此 │ └── nvs_bootloader_example_utils.c # 日志输出辅助函数 ├── nvs_data.csv # NVS 分区的初始内容即待读取数据 ├── README.md ├── pytest_nvs_bootloader.py # 自动化测试 ├── sdkconfig.ci.default / sdkconfig.ci.nvs_enc_flash_enc / sdkconfig.ci.nvs_enc_hmac └── main/nvs_encrypted.bin、main/nvs_encrypted_hmac.bin、main/encryption_keys.bin、main/nvs_enc_hmac_key.bin # 预生成加密分区与密钥如何接入自定义 bootloader示例中的 bootloader 代码位于bootloader_components/nvs_bootloader_example在 bootloader 运行期间执行。它使用bootloader hooks 技术扩展默认 bootloader在 components/bootloader/subproject/main/bootloader_hooks.h 中定义了弱符号bootloader_before_init()与bootloader_after_init()由 components/bootloader/subproject/main/bootloader_start.c 在 bootloader 初始化前/后调用。示例的主函数是bootloader_after_init()定义于 nvs_bootloader_example.c。注意 components/bootloader/subproject/main/CMakeLists.txt 中通过-u bootloader_hooks_include强制链接包含 hooks 的符号保证自定义组件被链接进 bootloader。示例还定义了bootloader_hooks_include()空函数来确保整个文件及其符号被链接器保留。示例的数据准备nvs_data.csv示例 NVS 分区的初始内容定义在 nvs_data.csvkey,type,encoding,value sunny_day,namespace,, u8,data,u8,255 i8,data,i8,-128 u16,data,u16,65535 i16,data,i16,-20000 u32,data,u32,4294967295 i32,data,i32,-2147483648 string_10_chars,data,string,Text_67890 string_66_chars,data,string,Text_67890_Text_67890_Text_67890_0_Text_67890_Text_67890_Text_6789 cloudy_day,namespace,, i8,data,i8,-13其中sunny_day命名空间覆盖了 U8/I8/U16/I16/U32/I32 及长短两个字符串cloudy_day用于演示一次调用跨命名空间读取。string_66_chars特意设计为长度不是 XTS-AES 块大小整倍数的数据用于验证加密场景下的分块处理逻辑。三类演示场景与运行日志示例中的bootloader_after_init()依次构造三组read_list[]详见 nvs_bootloader_example.c通过log_request_call_read_evaluate_output()调用nvs_bootloader_read()并打印结果场景一传入非法参数整体返回ESP_ERR_INVALID_ARG请求中包含正确的请求默认ESP_ERR_NVS_NOT_FOUND、过长的命名空间名ESP_ERR_NVS_INVALID_NAME、过长的键名ESP_ERR_NVS_KEY_TOO_LONG、不支持的NVS_TYPE_BLOBESP_ERR_INVALID_ARG、缓冲区长度为 0 的字符串ESP_ERR_INVALID_SIZE、缓冲区指针为 NULL 的字符串ESP_ERR_INVALID_SIZE。场景二参数合法但结果含错误整体返回ESP_OK逐条报错包含类型不匹配ESP_ERR_NVS_TYPE_MISMATCH如请求 I8 而存储为 U8、键名拼写错误ESP_ERR_NVS_NOT_FOUND、命名空间拼写错误ESP_ERR_NVS_NOT_FOUND、缓冲区过小的字符串ESP_ERR_INVALID_SIZE、正常读取ESP_OK以及重复请求同一键——API 不支持重复读取第二次请求返回ESP_ERR_NVS_NOT_FOUND。场景三全部成功读取整体与逐条均为ESP_OK混合多种数据类型与命名空间I (1665) nvs_bootloader_example: Data read from NVS partition I (1666) nvs_bootloader_example_utils: 0 ESP_OK sunny_day u8 U8 255 I (1666) nvs_bootloader_example_utils: 1 ESP_OK sunny_day i32 I32 -2147483648 I (1667) nvs_bootloader_example_utils: 2 ESP_OK cloudy_day i8 I8 -13 I (1668) nvs_bootloader_example_utils: 3 ESP_OK sunny_day u32 U32 4294967295 I (1668) nvs_bootloader_example_utils: 4 ESP_OK sunny_day i16 I16 -20000 I (1669) nvs_bootloader_example_utils: 5 ESP_OK sunny_day string_10_chars STR Text_67890运行结束后 bootloader 跳转到正常应用打印User application is loaded and running.。构建与运行idf.py build idf.py flash monitor正常输出应包含上述三块日志。示例还提供了自动化测试 pytest_nvs_bootloader.py 以及 bootloader 测试应用 components/nvs_flash/test_apps_bootloader/main/test_nvs_bootloader.c 和 test_encrypted_nvs_bootloader.c可作为回归验证参考。加密 NVS 示例flash encryption 与 HMAC 两种方案的完整配置示例同时支持读取加密 NVS 分区。启用 NVS 加密后应用需要按前文五步工作流在bootloader_after_init()中完成安全配置的读取与初始化。示例代码中nvs_bootloader_example.c根据 Kconfig 选择安全方案HMAC 方案CONFIG_NVS_SEC_KEY_PROTECT_USING_HMAC使用NVS_SEC_PROVIDER_CFG_HMAC_DEFAULT()初始化配置调用nvs_sec_provider_register_hmac()注册方案flash encryption 方案CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENC使用NVS_SEC_PROVIDER_CFG_FLASH_ENC_DEFAULT()初始化配置要求分区表中存在nvs_keys子类型分区调用nvs_sec_provider_register_flash_enc()注册方案随后统一调用nvs_bootloader_read_security_cfg()读取密钥配置、nvs_bootloader_secure_init()初始化安全上下文读完后调用nvs_bootloader_secure_deinit()清理。生成加密 NVS 分区若修改了nvs_data.csv可用 NVS 分区生成工具 nvs_partition_gen.py 重新生成加密分区flash encryption 方案python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/nvs_encrypted.bin 0x6000 --inputkey $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/encryption_keys.binHMAC 方案python nvs_partition_gen.py encrypt $IDF_PATH/examples/storage/nvs/nvs_bootloader/nvs_data.csv $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/nvs_encrypted_hmac.bin 0x6000 --keygen --key_protect_hmac --kp_hmac_inputkey $IDF_PATH/examples/storage/nvs/nvs_bootloader/main/nvs_enc_hmac_key.bin预生成的加密分区main/nvs_encrypted.bin、main/nvs_encrypted_hmac.bin由main/CMakeLists.txt在构建时随固件一并烧录。若选择 HMAC 方案需要先把 nvs_enc_hmac_key.bin 烧入 efuse。两种方案的构建配置idf.py set-target target # flash encryption 方案 cat sdkconfig.ci.nvs_enc_flash_enc sdkconfig # 或 HMAC 方案 cat sdkconfig.ci.nvs_enc_hmac sdkconfig idf.py build对应的 CI 配置文件内容如下sdkconfig.ci.nvs_enc_flash_enc启用CONFIG_NVS_ENCRYPTIONy、CONFIG_NVS_SEC_KEY_PROTECT_USING_FLASH_ENCy并配合CONFIG_PARTITION_TABLE_SINGLE_APP_ENCRYPTED_NVSy单应用 加密 NVS 分区表及开发模式的 flash 加密配置CONFIG_SECURE_FLASH_ENC_ENABLEDy、CONFIG_SECURE_FLASH_ENCRYPTION_MODE_DEVELOPMENTy等sdkconfig.ci.nvs_enc_hmac启用CONFIG_NVS_ENCRYPTIONy、CONFIG_NVS_SEC_KEY_PROTECT_USING_HMACy、CONFIG_NVS_SEC_HMAC_EFUSE_KEY_ID0使用 efuse 中的 BLOCK_KEY0 作为 HMAC 密钥。烧录与监控idf.py flash monitor输出应与未加密版本一致的三块日志。若采用 HMAC 方案需先用idf.py efuse-burn-key烧录 HMAC 密钥QEMU 下使用idf.py qemu efuse-burn-key BLOCK_KEY0 main/nvs_enc_hmac_key.bin HMAC_UP。用 QEMU 快速验证flash encryption 方案idf.py set-target qemu-supported-targets cat sdkconfig.ci.nvs_enc_flash_enc sdkconfig echo CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLEDn sdkconfig # 关闭 CI 专用的启动时 flash 加密要求 idf.py build idf.py qemu monitorHMAC 方案idf.py set-target qemu-supported-targets cat sdkconfig.ci.nvs_enc_hmac sdkconfig echo CONFIG_SECURE_FLASH_REQUIRE_ALREADY_ENABLEDn sdkconfig idf.py build idf.py qemu efuse-burn-key BLOCK_KEY0 main/nvs_enc_hmac_key.bin HMAC_UP idf.py qemu monitor典型应用场景与注意事项典型应用设备快速恢复。官方示例 README 给出了一个非常实际的场景——应用将设备当前状态/配置写入 NVS重启后在 bootloader 阶段即可读取 NVS 恢复设备上次状态而无需等待应用完整启动从而缩短设备恢复时间。使用注意事项汇总只读、无 blobbootloader 场景下不要尝试写入或读取 blob 类型字符串缓冲区自备必须提供合法、足够大的缓冲区长度需覆盖字符串内容和结尾\0不支持重复读取同一分区内同一 (namespace, key) 在单次调用中只能请求一次重复请求第二次会得到ESP_ERR_NVS_NOT_FOUND区分整体返回值与逐条result_code整体ESP_OK不代表所有记录都读到了逐条结果才是最终答案加密上下文的生命周期nvs_bootloader_secure_init()后只应读取使用同一安全配置加密的分区并在完成后及时nvs_bootloader_secure_deinit()清除密钥上下文HMAC 方案需先烧录 efuse 密钥否则密钥读取阶段将失败字对齐约束bootloader 下 flash 读取要求地址/长度/缓冲区字对齐底层实现已通过 32 字节分块读取处理用户只需提供普通缓冲区即可。总结NVS Bootloader API 是 ESP-IDF 在 bootloader 精简运行环境下为自定义 bootloader 提供的一把只读钥匙nvs_bootloader_read()支持批量、跨命名空间读取除 blob 外的所有 NVS 类型字符串读取配合自备缓冲区完成针对加密 NVS 分区nvs_bootloader_read_security_cfg()→nvs_bootloader_secure_init()→nvs_bootloader_read()→nvs_bootloader_secure_deinit()的四步组合配合 flash encryption 或 HMAC 两种方案让 bootloader 阶段也能安全解密读取。结合官方示例 nvs_bootloader 与头文件 nvs_bootloader.h开发者可以快速在自己的 bootloader 工程中实现启动即恢复的设备状态读取能力。【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考