ESP-IDF LP Core GPIO 唤醒测试指南:基于双板联测的深睡眠 GPIO 唤醒验证方案
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
本指南围绕 ESP-IDF 中lp_core_gpio_wakeup_tests测试应用展开,讲解如何在支持 LP Core(低功耗协处理器)的 ESP32-C5 / ESP32-C6 / ESP32-P4 芯片上,利用双板(wakee + waker)协作的方式验证“深睡眠下外部 GPIO 下降沿唤醒 LP Core,再由 LP Core 唤醒主处理器(HP)”的完整链路。读完本文,你将掌握该测试应用的硬件接线方式、Unity 多阶段用例的组织逻辑、LP Core 侧唤醒程序的写法,以及如何用 pytest 的generic_multi_device模式在本地复现整个唤醒时序验证。
测试应用概述与适用芯片
lp_core_gpio_wakeup_tests是 ESP-IDF 低功耗子系统(ULP/LP Core)下的一个专项测试应用,位于 components/ulp/test_apps/lp_core/lp_core_gpio_wakeup_tests,其唯一目的是验证 LP Core 的 GPIO 唤醒功能在深睡眠场景下的正确性——包括真实的唤醒触发、虚假唤醒(spurious wakeup)的抑制,以及唤醒后主处理器对唤醒原因链的核实。
该测试应用通过 README 中的支持矩阵声明了三个受支持的目标芯片:
| Supported Targets | ESP32-C5 | ESP32-C6 | ESP32-P4 |
|---|
从 pytest 脚本中的目标过滤条件可以确认,能够运行该测试的目标必须同时满足四个 SoC 能力(见 pytest_lp_core_gpio_wakeup.py 中的soc_filtered_targets):
SOC_LP_CORE_SUPPORTED == 1:芯片带 LP Core 协处理器;SOC_RTCIO_PIN_COUNT > 0:存在可用于唤醒的 RTC GPIO;SOC_DEEP_SLEEP_SUPPORTED == 1:支持深睡眠;SOC_RTC_GPIO_EDGE_WAKEUP_SUPPORTED == 1:RTC GPIO 支持边沿唤醒。
测试原理:一次完整的外部 GPIO 下降沿唤醒链路
README 明确指出,该测试应用复现了gpio_wakeup示例(LP Core GPIO 唤醒示例)的完整流程,并将其拆解为四个可验证的步骤:
- 主处理器(HP)侧准备:HP 配置唤醒 GPIO,将编译好的 LP Core 程序加载进 LP 核内存并启动,随后进入深睡眠;
- 空闲高电平保持:waker 板将共享 GPIO 维持在空闲高电平并保持足够长时间,用于捕获虚假唤醒——如果 wakee 板在下降沿到来之前就提前重启,说明唤醒逻辑存在误触发问题;
- 下降沿触发唤醒:外部(waker 板)产生下降沿,LP Core 被 GPIO 边沿唤醒。LP Core 程序记录本次 LP 唤醒原因,然后唤醒主处理器,并清除 GPIO 中断状态;
- 唤醒原因验证:HP 从深睡眠恢复后,验证两件事——系统级 ULP 唤醒原因(
ESP_SLEEP_WAKEUP_ULP)是否置位,以及 LP Core 记录的唤醒原因是否为LP_IO(LP GPIO 唤醒源)。
这四个步骤恰好对应了实际产品中“低功耗待机 → 外部事件触发 → 协处理器快速响应 → 唤醒主控处理业务”的典型低功耗工作模式,因此该测试不仅验证功能正确性,也具备很强的工程参考价值。
硬件搭建:双板(generic_multi_device)接线方案
该测试是一个generic_multi_device(多设备)测试,需要两块同型号开发板配合完成,两块板的唤醒 GPIO 必须短接在一起。
- Wakee(DUT 0,被唤醒板):运行一个多阶段(multi-stage)Unity 测试用例。它先进入深睡眠,随后在外部下降沿到达后被唤醒,并校验 ULP 唤醒原因;
- Waker(DUT 1,唤醒板):运行两个独立的 Unity 测试用例,分别把共享 GPIO 驱动为高电平和低电平,从而为 wakee 板提供“空闲高电平”和“下降沿”两种外部信号。
两块板之间默认使用的唤醒引脚为GPIO2,该默认值由 Kconfig.projbuild 中的TEST_LP_GPIO_WAKEUP_PIN配置项提供,其注释要求 waker 板上必须是同一个 GPIO 编号并配置为输出:
menu "LP GPIO Wakeup Test Configuration" config TEST_LP_GPIO_WAKEUP_PIN int "GPIO pin wired between wakee and waker boards" default 2 help RTC GPIO pin used for LP core GPIO wakeup on the sleeping board. The same pin number must be wired to the GPIO output on the waker board. endmenu接线时需要注意:wakee 一侧使用的必须是RTC GPIO(即支持rtc_gpio_*API 的引脚),因为深睡眠阶段只有 RTC 域保持供电,普通 GPIO 无法在深睡眠中感知外部电平变化。
源码剖析:wakee 板的唤醒配置与多阶段用例
wakee 侧的全部逻辑位于 test_lp_gpio_wakeup.c,分为三个阶段:配置唤醒 GPIO、加载并启动 LP 程序、进入深睡眠。
唤醒 GPIO 的 RTC 配置
static void configure_wakee_gpio(void) { rtc_gpio_init(TEST_WAKEUP_PIN); rtc_gpio_set_direction(TEST_WAKEUP_PIN, RTC_GPIO_MODE_INPUT_ONLY); rtc_gpio_pulldown_dis(TEST_WAKEUP_PIN); rtc_gpio_pullup_en(TEST_WAKEUP_PIN); rtc_gpio_wakeup_enable(TEST_WAKEUP_PIN, GPIO_INTR_NEGEDGE); }关键点:引脚被初始化为 RTC GPIO 输入模式,关闭下拉、使能上拉(确保 waker 板断开或未驱动时电平不会悬空),并以GPIO_INTR_NEGEDGE(下降沿)作为唤醒触发条件。上拉配置还保证了空闲电平为高——这与 waker 板“空闲高电平”策略相互印证。
加载并启动 LP Core 程序
static void load_and_start_lp_core_program(void) { TEST_ESP_OK(ulp_lp_core_load_binary(lp_core_gpio_wakeup_bin_start, lp_core_gpio_wakeup_bin_end - lp_core_gpio_wakeup_bin_start)); ulp_lp_core_cfg_t cfg = { .wakeup_source = ULP_LP_CORE_WAKEUP_SOURCE_LP_IO, }; TEST_ESP_OK(ulp_lp_core_run(&cfg)); }LP 程序二进制通过ulp_embed_binary宏嵌入到应用镜像中(见 main/CMakeLists.txt),符号lp_core_gpio_wakeup_bin_start/_end即其起止地址。ulp_lp_core_cfg_t.wakeup_source被设置为ULP_LP_CORE_WAKEUP_SOURCE_LP_IO,声明 LP Core 在深睡眠中由 LP GPIO 事件唤醒。ulp_lp_core_run的底层实现位于 components/ulp/lp_core/lp_core/lp_core_utils.c,它负责配置唤醒源并使 LP Core 进入等待状态。
多阶段用例:深睡眠重启后的状态延续
static void lp_gpio_wakeup_wakee_init(void) { configure_wakee_gpio(); load_and_start_lp_core_program(); unity_wait_for_signal(GPIO_IDLE_HIGH_SIGNAL); TEST_ESP_OK(esp_sleep_enable_ulp_wakeup()); esp_deep_sleep_start(); TEST_FAIL_MESSAGE("Should have entered deep sleep"); } static void lp_gpio_wakeup_wakee_after_wake(void) { TEST_ASSERT(esp_sleep_get_wakeup_causes() & BIT(ESP_SLEEP_WAKEUP_ULP)); printf("LP wakeup cause: 0x%" PRIx32 "\n", ulp_lp_wakeup_cause); TEST_ASSERT_EQUAL_HEX32(LP_CORE_LL_WAKEUP_SOURCE_LP_IO, ulp_lp_wakeup_cause); } TEST_CASE_MULTIPLE_STAGES( "LP GPIO wakeup wakee from deep sleep", "[lp_core][lp_gpio_wakeup][test_env=generic_multi_device][timeout=120][deepsleep]", lp_gpio_wakeup_wakee_init, lp_gpio_wakeup_wakee_after_wake);这里使用了 Unity 的TEST_CASE_MULTIPLE_STAGES机制:因为深睡眠会把芯片重启回 Unity 菜单,单个函数无法同时覆盖“入睡前”和“唤醒后”两个阶段,必须拆成wakee_init(入睡前)和after_wake(唤醒后)两个 stage。唤醒后 HP 侧做两项断言:
esp_sleep_get_wakeup_causes()中包含ESP_SLEEP_WAKEUP_ULP位,证明本次深睡眠唤醒由 ULP/LP Core 触发;- LP 核侧全局变量
ulp_lp_wakeup_cause的值等于LP_CORE_LL_WAKEUP_SOURCE_LP_IO,证明 LP Core 自身记录到的唤醒源确实是 LP GPIO。
测试用例的 tag 中还包含test_env=generic_multi_device,这正是 pytest 选择双板测试环境、并把用例路由到对应 DUT 的依据。
源码剖析:LP Core 侧唤醒程序
LP 核侧程序非常精简,位于 lp_core/test_main_gpio_wakeup.c:
#include "ulp_lp_core_utils.h" #include "ulp_lp_core_gpio.h" #include "hal/lp_core_ll.h" uint32_t lp_wakeup_cause; int main(void) { lp_wakeup_cause = ulp_lp_core_get_wakeup_cause(); ulp_lp_core_wakeup_main_processor(); ulp_lp_core_gpio_clear_intr_status(); return 0; }它依次完成三件事,正好对应 README 第 3 步的描述:
ulp_lp_core_get_wakeup_cause()读取并保存本次唤醒原因到全局变量lp_wakeup_cause。该变量被声明为普通uint32_t全局符号,LP 与 HP 共享内存,因此 HP 侧代码(test_lp_gpio_wakeup.c)可以直接以ulp_lp_wakeup_cause的形式访问它;ulp_lp_core_wakeup_main_processor()唤醒处于深睡眠的主处理器;ulp_lp_core_gpio_clear_intr_status()清除 GPIO 中断状态位,防止下次唤醒因残留中断标志而误触发。相关 API 声明位于 ulp_lp_core_gpio.h,唤醒原因读取与唤醒源判断逻辑则在 ulp_lp_core_utils.h 与 lp_core_utils.c 中——lp_core_utils.c中通过lp_core_ll_get_wakeup_source() & LP_CORE_LL_WAKEUP_SOURCE_LP_IO来判定是否由 LP IO 唤醒。
源码剖析:waker 板的电平驱动
waker 板运行两个普通 Unity 用例,分别把共享 GPIO 拉高、拉低:
static void configure_waker_gpio_output(void) { gpio_config_t io_conf = { .pin_bit_mask = (1ULL << TEST_WAKEUP_PIN), .mode = GPIO_MODE_OUTPUT, .pull_down_en = GPIO_PULLDOWN_DISABLE, .pull_up_en = GPIO_PULLUP_DISABLE, .intr_type = GPIO_INTR_DISABLE, }; TEST_ESP_OK(gpio_config(&io_conf)); } static void lp_gpio_wakeup_waker_idle_high(void) { configure_waker_gpio_output(); TEST_ESP_OK(gpio_set_level(TEST_WAKEUP_PIN, 1)); } static void lp_gpio_wakeup_waker_wake_low(void) { configure_waker_gpio_output(); TEST_ESP_OK(gpio_set_level(TEST_WAKEUP_PIN, 0)); }注意 waker 板使用的是通用 GPIO 驱动(driver/gpio.h的gpio_config),因为它不进入睡眠,无需 RTC GPIO 能力;而 wakee 板使用的是driver/rtc_io.h的rtc_gpio_*API。两个用例的 tag 同样带有test_env=generic_multi_device,pytest 会依据用例名中的idle high/wake low片段区分两者。
pytest 自动化:完整的唤醒时序编排
整个双板时序由 pytest_lp_core_gpio_wakeup.py 编排,其关键流程与 README 描述严格对应:
- 从 Unity 菜单中筛选出 1 个 multi_stage 用例(wakee)和 2 个 normal 用例(waker 的
idle high与wake low),并校验 wakee 用例确实包含 2 个子阶段; - 对两块板分别执行硬复位(
hard_reset),保证从干净状态开始; - 先在 waker 板上运行
idle high用例,把共享 GPIO 拉高; - 在 wakee 板上运行 stage 0(入睡前阶段),并等待它打印
Waiting for signal: [gpio idle high]!信号行——该信号由 HP 侧unity_wait_for_signal(GPIO_IDLE_HIGH_SIGNAL)发出,表示 wakee 已确认 GPIO 处于空闲高电平; - 虚假唤醒检测:用极短的
NO_EARLY_WAKE_TIMEOUT = 2.0秒探测 wakee 是否提前回到 Unity 菜单。如果探测到了,直接pytest.fail('Wakee rebooted before the waker drove the GPIO falling edge')——说明在下降沿到来之前就发生了唤醒,判定为虚假唤醒;若 2 秒内无响应(超时)则视为通过,说明 wakee 稳定停留在深睡眠中; - 在 waker 板上运行
wake low用例,把 GPIO 拉低,形成下降沿; - 等待 wakee 唤醒并回到 Unity 菜单(
_get_ready(TEST_TIMEOUT),120 秒超时),然后运行 stage 1(唤醒后验证阶段)并检查 Unity 断言输出。
该脚本通过@pytest.mark.parametrize('count', [2], indirect=True)声明需要 2 个 DUT,并通过@pytest.mark.generic_multi_device标记测试类型,本地运行时即可用-m generic_multi_device匹配。
本地运行:从编译到双板联测
按照 README 提供的方式,在本地复现该测试需要按顺序执行以下命令:
cd components/ulp/test_apps/lp_core/lp_core_gpio_wakeup_tests idf.py set-target esp32p4 idf.py build pytest --target esp32p4 -m generic_multi_device几点实操提示:
- 目标选择:
esp32p4可替换为 README 支持矩阵中的esp32c5或esp32c6(示例中以 ESP32-P4 演示); - 构建依赖配置:测试应用默认通过 sdkconfig.defaults 启用 LP Core 相关配置:
CONFIG_ULP_COPROC_ENABLED=y、CONFIG_ULP_COPROC_TYPE_LP_CORE=y,并预留 12000 字节的 LP 核内存(CONFIG_ULP_COPROC_RESERVE_MEM=12000);同时关闭了任务看门狗(CONFIG_ESP_TASK_WDT_INIT=n),避免深睡眠流程中看门狗干扰。CI 环境下的配置在 sdkconfig.ci.defaults 中补充; - 串口连接:
generic_multi_device模式要求 pytest 能同时访问两块板的串口(通常通过 USB 转串口芯片连接到 PC),并在测试框架中正确配置两个 DUT 的串口设备; - 换引脚:若 GPIO2 不适用于你的硬件(例如已被占用或不可用),可通过
menuconfig修改TEST_LP_GPIO_WAKEUP_PIN,但必须确保该引脚在 wakee 板上是 RTC GPIO,且两板接线同步更换。
验证价值与延伸参考
从工程角度看,这个测试应用的价值不仅在于“功能能跑通”,更在于它把低功耗唤醒场景中最容易踩坑的两个问题纳入了自动化回归:
- 虚假唤醒:通过“空闲高电平保持 + 2 秒提前唤醒探测”的组合,确保 GPIO 上拉配置、中断使能时机、LP 程序加载顺序都正确,不会因电平抖动或配置时序问题在下降沿到来前就误唤醒;
- 唤醒原因链路一致性:HP 侧断言
ESP_SLEEP_WAKEUP_ULP,LP 侧记录LP_CORE_LL_WAKEUP_SOURCE_LP_IO,两层唤醒原因必须同时正确,保证“谁唤醒了谁”在软件语义上是自洽的。
如果你要基于此模式开发自己的低功耗唤醒产品,可以直接复用这里的三块资产:LP Core 侧唤醒程序的四行骨架(记录原因 → 唤醒 HP → 清中断)、HP 侧rtc_gpio_*+esp_sleep_enable_ulp_wakeup的入睡前配置序列,以及 pytest 中“先拉高、防早醒、再拉低”的双板时序编排。更深层的 LP Core 编程接口可继续阅读 ulp_lp_core_utils.h 与 ulp_lp_core_gpio.h,而唤醒源判定与寄存器操作的底层实现在 lp_core_utils.c 与 hal 层lp_core_ll头文件中。
【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考