ESP-IDF 杂项系统 API 实战指南:软件复位、复位原因、堆内存、MAC 地址与版本信息
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
本指南以 ESP-IDF 官方文档 Miscellaneous System APIs 为骨架,系统讲解 ESP-IDF 提供的一组"小而关键"的系统级接口:软件复位与关机回调、复位原因查询、堆内存状态获取、MAC 地址的读取/自定义/派生规则、芯片与 SDK 版本信息、调试辅助工具以及应用版本管理。读完本文,你将掌握这些 API 的调用方式、底层原理与典型使用场景,并能直接应用于自己的 ESP-IDF 工程。
软件复位(Software Reset)
ESP-IDF 提供了esp_restart()函数用于执行芯片的软件复位。调用该函数后,程序停止执行,CPU 被复位(在 ESP32 上为双核同时复位,见 esp_system.h 中对 "Restart PRO and APP CPUs" 的说明),随后由 bootloader 重新加载应用并再次启动执行。
函数原型(位于 components/esp_system/include/esp_system.h):
void esp_restart(void) __attribute__((__noreturn__));该函数声明为noreturn,调用后不会返回。需要注意:
- 可从 PRO 与 APP CPU 任意一侧调用;
- 复位成功后,CPU 复位原因将被记录为
SW_CPU_RESET(对应枚举ESP_RST_SW); - 除 Wi-Fi、BT、UART0、SPI1 和传统定时器外,外设不会复位。
关机回调(Shutdown Handler)
与atexit类似的机制由esp_register_shutdown_handler()提供:注册的回调会在esp_restart()触发的重启之前被自动调用,适合用于保存关键状态、关闭外设或输出日志。对应接口定义同样位于 esp_system.h:
typedef void (*shutdown_handler_t)(void); esp_err_t esp_register_shutdown_handler(shutdown_handler_t handle); esp_err_t esp_unregister_shutdown_handler(shutdown_handler_t handle);返回值含义:ESP_OK成功;ESP_ERR_INVALID_ARG传入 NULL;ESP_ERR_INVALID_STATE处理器重复注册/注销;ESP_ERR_NO_MEM内存分配失败。注册数量仅受可用堆内存限制。
从源码结构还可以看到一种静态(链接期)注册方式ESP_SHUTDOWN_HANDLER_REGISTER(fn, prio)(esp_system.h):它通过链接器段esysev_shdn按优先级排序分发,低优先级值先执行,且总是先于动态注册的处理器执行。若业务上对回调执行顺序有确定性要求,应优先使用该宏。
复位原因(Reset Reason)
应用可能因多种原因启动或重启,调用esp_reset_reason()可获取最近一次的复位原因,返回类型为esp_reset_reason_t。完整枚举定义见 esp_system.h:
| 枚举值 | 含义 |
|---|---|
ESP_RST_UNKNOWN | 无法确定复位原因 |
ESP_RST_POWERON | 上电事件复位 |
ESP_RST_EXT | 外部引脚复位(ESP32 不适用) |
ESP_RST_SW | 通过esp_restart的软件复位 |
ESP_RST_PANIC | 异常/恐慌导致的软件复位 |
ESP_RST_INT_WDT | 中断看门狗导致的复位(软件或硬件) |
ESP_RST_TASK_WDT | 任务看门狗复位 |
ESP_RST_WDT | 其他看门狗复位 |
ESP_RST_DEEPSLEEP | 退出深度睡眠后的复位 |
ESP_RST_BROWNOUT | 掉电(欠压)复位(软件或硬件) |
ESP_RST_SDIO | 通过 SDIO 复位 |
ESP_RST_USB | USB 外设复位 |
ESP_RST_JTAG | JTAG 复位 |
ESP_RST_EFUSE | eFuse 错误导致的复位 |
ESP_RST_PWR_GLITCH | 检测到电源毛刺 |
ESP_RST_CPU_LOCKUP | CPU 锁死(双重异常)导致的复位 |
典型用途:在app_main()启动时判断设备是被看门狗踢复位还是异常崩溃后复位,从而决定是否进入恢复流程或输出诊断信息。
堆内存状态(Heap Memory)
ESP-IDF 提供两个与堆内存相关的函数(esp_system.h):
esp_get_free_heap_size():返回当前可用堆内存大小(字节)。注意返回值可能大于能够分配的最大连续内存块;esp_get_minimum_free_heap_size():返回应用生命周期内曾经出现过的最小可用堆内存大小,可用于评估内存峰值压力。
此外,esp_get_free_internal_heap_size()返回内部(片上)堆的可用大小。需要说明的是:ESP-IDF 支持多堆与多种能力(capability)属性,上述函数返回的是可通过malloc家族分配的内存大小。更细粒度的堆能力分配(如heap_caps_malloc)请参考 Heap Memory Allocation(中文版见 docs/zh_CN/api-reference/system/mem_alloc.rst)。
MAC 地址(MAC Address)
MAC 地址相关 API 允许查询和自定义不同网络接口(Wi-Fi、Bluetooth、Ethernet 等)的 MAC 地址。在 ESP-IDF 中,各网络接口的 MAC 地址都由**一个基准 MAC 地址(base MAC)**计算派生而来。默认使用乐鑫(Espressif)出厂时预烧录在芯片 eFuse 中的基准 MAC 地址。类型枚举esp_mac_type_t定义在 esp_mac.h,包括:
ESP_MAC_WIFI_STA:Wi-Fi Station(6 字节)ESP_MAC_WIFI_SOFTAP:Wi-Fi SoftAP(6 字节)ESP_MAC_BT:蓝牙(6 字节)ESP_MAC_ETH:以太网(6 字节)ESP_MAC_IEEE802154:IEEE 802.15.4(8 字节,需CONFIG_SOC_IEEE802154_SUPPORTED=y)ESP_MAC_BASE:用于派生其他 MAC 的基准 MAC(6 字节)ESP_MAC_EFUSE_FACTORY:乐鑫出厂烧录的 MAC_FACTORY eFuse(6 字节)ESP_MAC_EFUSE_CUSTOM:客户可烧录的 MAC_CUSTOM eFuse(6 字节)ESP_MAC_EFUSE_EXT:IEEE 802.15.4 扩展字段(2 字节,需支持 802.15.4)
获取指定接口 MAC 使用esp_read_mac(),其内部先取得 base MAC,再按派生算法计算对应接口的 MAC(esp_mac.h)。
接口 MAC 派生规则表
不同芯片的派生规则不同,下表汇总了常见配置(base_mac, +N表示末字节加 N):
支持 4 个全局管理 MAC 的芯片(默认 4 个,含 ESP32/ESP32-C3/S3 等,Wi-Fi + BT 全功能):
| 接口 | 4 个全局管理 MAC(默认) | 2 个全局管理 MAC |
|---|---|---|
| Wi-Fi Station | base_mac | base_mac |
| Wi-Fi SoftAP | base_mac, +1 | 本地管理 MAC(由 Station MAC 派生) |
| Bluetooth | base_mac, +2 | base_mac, +1 |
| Ethernet | base_mac, +3 | 本地管理 MAC(由蓝牙 MAC 派生) |
ESP32-S2(默认 2 个,无蓝牙):
| 接口 | 2 个全局管理 MAC(默认) | 1 个全局管理 MAC |
|---|---|---|
| Wi-Fi Station | base_mac | base_mac |
| Wi-Fi SoftAP | base_mac, +1 | 本地管理 MAC(由 Station MAC 派生) |
| Ethernet | 本地管理 MAC(由 SoftAP MAC 派生) | 本地管理 MAC(由 base+1 派生,不推荐) |
ESP32-S3(默认 2 个,eFuse 仅提供 2 个全局管理 MAC):
| 接口 | 2 个全局管理 MAC(默认) | 4 个全局管理 MAC |
|---|---|---|
| Wi-Fi Station | base_mac | base_mac |
| Wi-Fi SoftAP | 本地管理 MAC(由 Station MAC 派生) | base_mac, +1 |
| Bluetooth | base_mac, +1 | base_mac, +2 |
| Ethernet | 本地管理 MAC(由蓝牙 MAC 派生) | base_mac, +3 |
警告:ESP32-S3 的 eFuse 只提供 2 个全局管理 MAC 地址。只有在使用客户提供的自定义 base MAC 范围(见下文)时才应选择 "4 个" 选项;若使用默认乐鑫 eFuse base MAC 却选择 4 个,SoftAP 与 Ethernet 将占用该芯片未分配的全局 MAC 槽位(base+1/+3),可能与蓝牙 MAC 冲突。
ESP32-H2/H21/H4(默认 1 个,无 Wi-Fi,支持 802.15.4):
| 接口 | MAC 地址(1 个全局管理,默认) |
|---|---|
| IEEE 802.15.4 | 由 base_mac 与 MAC_EXT 派生的 EUI-64:base_mac[0:2] ‖ MAC_EXT ‖ base_mac[3:5],MAC_EXT 默认为ff:fe |
| Bluetooth | base_mac |
说明:H2/H21/H4 的 eFuse 中提供 1 个全局管理 MAC(MAC_FACTORY)加 MAC_EXT 字段。蓝牙直接复用 base MAC,不加偏移——因为这些芯片没有 Wi-Fi,无需第二个全局 MAC 槽位。
ESP32-P4(默认 1 个):
| 接口 | MAC 地址(1 个全局管理,默认) |
|---|---|
| Ethernet | base_mac |
说明:ESP32-P4 上
CONFIG_ESP32P4_UNIVERSAL_MAC_ADDRESSES固定为 1 个全局管理 MAC。
上述表项由CONFIG_{TARGET}_UNIVERSAL_MAC_ADDRESSES配置决定(对应源码中的CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES,见 esp_mac.h 中UNIVERSAL_MAC_ADDR_NUM的定义)。另外,即使芯片没有集成以太网 MAC(如 ESP32-C 系列),仍可计算以太网 MAC 地址,但只能用于 SPI 以太网等外部以太网设备,参考 esp_eth。
自定义接口 MAC(Custom Interface MAC)
如果不想使用由 base MAC 派生的地址,可以调用esp_iface_mac_addr_set(mac, type)覆盖指定接口的 MAC。被覆盖的接口在 base MAC 改变后不受影响。底层实现见 esp_mac.h。
自定义 Base MAC(Custom Base MAC)
默认 base MAC 预烧录在 eFuse BLK1(ESP32 为 BLK0)。如需改用自定义 base MAC:
- 在初始化任何网络接口或调用
esp_read_mac之前,调用esp_iface_mac_addr_set(mac, ESP_MAC_BASE)(或旧 APIesp_base_mac_addr_set()); - 自定义 MAC 可存储在任何受支持的存储介质中(如 flash、NVS);
- 分配自定义 base MAC 时要保证派生出的各接口 MAC 互不重叠,并依据上文表格通过
CONFIG_{TARGET}_UNIVERSAL_MAC_ADDRESSES配置可派生的全局 MAC 数量。
esp_base_mac_addr_set()的两个注意点(esp_mac.h):
- base MAC 必须是单播 MAC(首字节最低位必须为 0);
- 如果不使用有效 OUI,应设置 "本地管理" 位(首字节 bit 值 0x02)以避免冲突。
官方还提示:也可在网络初始化后调用esp_netif_set_mac()设置具体接口使用的 MAC,但推荐使用上述 base MAC 方案,以避免原始 MAC 地址在被替换前短暂出现在网络中。
eFuse 中的自定义 MAC(Custom MAC Address in eFuse)
从 eFuse 读取自定义 MAC 时,可调用辅助函数esp_efuse_mac_get_custom()(esp_mac.h),或用esp_read_mac(mac, ESP_MAC_EFUSE_CUSTOM)。该 MAC 存储在 eFuse BLK3,且假定采用以下存储格式:
ESP32(BLK3 含版本与 CRC 校验):
| 字段 | 位数 | 位范围 | 说明 |
|---|---|---|---|
| Version | 8 | 191:184 | 0 无效,其他有效 |
| Reserved | 128 | 183:56 | — |
| MAC address | 48 | 55:8 | — |
| MAC address CRC | 8 | 7:0 | CRC-8-CCITT,多项式 0x07 |
注意:ESP32 若启用了 3/4 编码方案(3/4 coding scheme),该块所有 eFuse 字段必须同时烧录。
esp_efuse_mac_get_custom()在 ESP32 上会校验版本与 CRC,异常时返回ESP_ERR_INVALID_VERSION/ESP_ERR_INVALID_CRC。
其他芯片(BLK3,无版本/CRC 字段):
| 字段 | 位数 | 位范围 |
|---|---|---|
| MAC address | 48 | 200:248 |
注意:非 ESP32 芯片的 BLK3 烧录使用 RS 编码(RS-coding),所有 eFuse 字段必须同时烧录。
拿到自定义 eFuse MAC 后,将其设置为 base MAC 有两种方式:
- 旧 API:调用
esp_base_mac_addr_set(); - 新 API:调用
esp_iface_mac_addr_set(mac, ESP_MAC_BASE)。
本地管理与全局管理 MAC(Local vs Universal)
芯片出厂时预烧录了足够覆盖所有内部接口的全局管理(universally administered)MAC 地址。但使用自定义 MAC 方案时,可能无法为所有接口分配全局管理 MAC,此时会分配本地管理(locally administered)MAC 地址——这类地址仅适用于单个本地网络。
esp_derive_local_mac()(esp_mac.h)在内部完成从全局到本地 MAC 的派生,规则如下:
- 在全局 MAC 首字节设置 U/L 位(bit 值 0x2),得到本地 MAC;
- 若该位在传入的 "全局" MAC 中已置位(即传入的其实已是本地 MAC),则将首字节与 0x4 做异或,保证得到不同的本地 MAC。
实战示例:base_mac_address
官方示例 examples/system/base_mac_address 演示了完整的读取、设置与派生流程,核心代码在 base_mac_address_example_main.c:
- 通过
esp_read_mac(base_mac_addr, ESP_MAC_EFUSE_CUSTOM)从 eFuse BLK3 读取自定义 base MAC;读取失败时可按配置回退到ESP_MAC_EFUSE_FACTORY(出厂 MAC)或直接abort(); - 也支持
CONFIG_BASE_MAC_STORED_OTHER_EXTERNAL_STORAGE选项,从外部存储(flash/EEPROM)模拟读取 base MAC; - 用
esp_iface_mac_addr_set(base_mac_addr, ESP_MAC_BASE)设置 base MAC; - 依次用
esp_read_mac(..., ESP_MAC_WIFI_STA / ESP_MAC_WIFI_SOFTAP / ESP_MAC_BT / ESP_MAC_ETH)读取各接口派生 MAC 并打印; - 演示了用
esp_iface_mac_addr_set(base_mac_addr, ESP_MAC_ETH)覆盖 Ethernet MAC(末字节 +6); - 在支持 802.15.4 的芯片上,还读取了
ESP_MAC_EFUSE_EXT(2 字节)与ESP_MAC_IEEE802154(8 字节 EUI-64)。
芯片版本(Chip Version)
esp_chip_info()函数填充esp_chip_info_t结构体,提供芯片修订版本(revision)、CPU 核心数以及芯片已启用功能的位掩码等信息。接口头文件为 esp_chip_info.h,可用于在运行时适配不同芯片特性(如根据核心数决定任务分配策略、根据 feature 掩码判断是否支持特定外设)。
SDK 版本(SDK Version)
esp_get_idf_version()返回用于编译当前应用的 ESP-IDF 版本字符串,与构建系统中的IDF_VER变量一致,格式一般为git describe的输出(如v6.2.0-xxx-gxxxxxxxxx),能区分开发版、预发布版与正式版。
该函数声明于 esp_idf_version.h。当前仓库的版本宏为:ESP_IDF_VERSION_MAJOR=6、ESP_IDF_VERSION_MINOR=2、ESP_IDF_VERSION_PATCH=0(esp_idf_version.h)。
编译期版本判断宏:
ESP_IDF_VERSION_MAJOR/ESP_IDF_VERSION_MINOR/ESP_IDF_VERSION_PATCH:分别表示主/次/修订版本号(整数);ESP_IDF_VERSION_VAL(major, minor, patch):将版本号编码为整数用于比较,实现为(major << 16) | (minor << 8) | (patch);ESP_IDF_VERSION:当前版本编码后的整数值。
典型用法(与文档示例一致):
#include "esp_idf_version.h" #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 0, 0) // 启用 ESP-IDF v4.0 起才有的功能 #endif调试辅助工具(Debug Helpers)
esp_debug_helpers.h(位于 components/esp_system/include/esp_debug_helpers.h)提供运行时调试与栈回溯输出的工具:
esp_backtrace_print():打印当前栈回溯;esp_backtrace_print_all_tasks():打印所有任务的栈回溯;esp_backtrace_get_start()与esp_backtrace_get_next_frame():手动迭代回溯帧。
这些 API 在诊断崩溃、看门狗超时或异常控制流时非常有用。例如在看门狗复位前注册一个 shutdown handler,调用esp_backtrace_print_all_tasks()输出各任务栈,即可定位卡死位置。
应用版本(App Version)
应用版本存储在esp_app_desc_t结构体中(定义见 esp_app_desc.h)。该结构位于 DROM 段,从二进制文件起始处有一个固定偏移,紧随esp_image_header_t与esp_image_segment_header_t之后。其中version字段为字符串类型,最大长度 32 字符。
如何设置应用版本
- 手动设置:在工程的
CMakeLists.txt中,在include project.cmake之前设置set(PROJECT_VER "0.1.0.1"); - 从配置项读取:若启用
CONFIG_APP_PROJECT_VER_FROM_CONFIG,则使用CONFIG_APP_PROJECT_VER的值; - 自动推断:未设置
PROJECT_VER时,依次尝试$(PROJECT_PATH)/version.txt文件、git describe命令;若都不可用,PROJECT_VER将回退为"1"。
应用可通过esp_app_get_description()或esp_ota_get_partition_description()获取该版本信息(后者用于读取 OTA 分区中的应用描述),常用于 OTA 升级时的版本比对、启动时打印固件版本号等场景。
小结
本文覆盖了 ESP-IDF 杂项系统 API 的六大方面:
- 软件复位:
esp_restart()与关机回调esp_register_shutdown_handler()/ESP_SHUTDOWN_HANDLER_REGISTER; - 复位原因:
esp_reset_reason()与 15 种esp_reset_reason_t枚举; - 堆内存:
esp_get_free_heap_size()、esp_get_minimum_free_heap_size(); - MAC 地址:
esp_read_mac()、esp_base_mac_addr_set()、esp_iface_mac_addr_set()、esp_derive_local_mac()、esp_efuse_mac_get_custom(),以及各芯片的派生规则表与本地/全局管理 MAC 机制; - 版本信息:
esp_chip_info()、esp_get_idf_version()与编译期版本宏; - 调试与应用版本:栈回溯工具与
PROJECT_VER的优先级链。
对应 API 的完整参考可继续查阅 esp_system、esp_mac、esp_idf_version、esp_chip_info、esp_debug_helpers 与 esp_app_desc 头文件,并通过 base_mac_address 示例 快速上手实践。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考