From 80023f79e0327aa422da4ecbfebd2ca4f02c0c2e Mon Sep 17 00:00:00 2001 From: mikecarper Date: Sat, 17 Oct 2026 21:41:47 -0700 Subject: [PATCH] Fix ESP32-C6 GPIO hold capability and add cross-platform smoke builds --- .github/workflows/run-unit-tests.yml | 10 ++++++++++ src/helpers/ESP32GpioHold.h | 9 ++++++++- test/test_esp32_gpio_hold.py | 29 ++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 1 deletion(-) diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 9e315a806..046989e84 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -88,6 +88,16 @@ jobs: --firmware-version v1.17.1.10-halo-keymind-cascade-dev --radio-preset usa-cascade-fixed --profile cascade --require-ota + cross-platform-smoke: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Build Environment + uses: ./.github/actions/setup-build-environment + - name: Compile RP2040 contact storage and Arduino 3 ESP32-C6 room services + # One process: PlatformIO environments share this checkout's build tree. + run: pio run -e Xiao_rp2040_companion_radio_usb -e LilyGo_Tlora_C6_room_server_ + management-reports: runs-on: ubuntu-latest steps: diff --git a/src/helpers/ESP32GpioHold.h b/src/helpers/ESP32GpioHold.h index 93431e4db..56e3ffbba 100644 --- a/src/helpers/ESP32GpioHold.h +++ b/src/helpers/ESP32GpioHold.h @@ -72,7 +72,8 @@ inline esp_err_t configureHeldOutput(gpio_num_t pin, uint32_t level) { // Explicitly select a known output before latching it. RTC pads can also be // retained for packet-wake sleep (e.g. W12 FEM rails). Non-RTC/digital pads -// require the terminal-only commit below; never globally freeze live pads. +// on older chips require the terminal-only commit below; chips with per-pad +// deep-sleep retention hold directly. Never globally freeze live pads. // The IDF GPIO API selects the correct RTC/digital hold implementation. inline esp_err_t holdOutputForDeepSleep(gpio_num_t pin, uint32_t level) { esp_err_t result = configureHeldOutput(pin, level); @@ -92,15 +93,21 @@ inline esp_err_t releaseOutputAfterDeepSleep(gpio_num_t pin, uint32_t level) { } inline void resetTerminalDeepSleepHolds() { +#if !defined(SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP) || !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP gpio_deep_sleep_hold_dis(); +#endif terminalDigitalHoldPending() = false; } inline void commitTerminalDeepSleepHolds() { + // Chips such as C6 retain individual gpio_hold_en pads during deep sleep + // and do not expose the global auto-hold API. Older chips need auto-hold. // IDF4.4's digital auto-hold affects all digital pads DURING deep sleep. // Arm it only after every virtual shutdown hook and transport quiescence, // immediately before terminal deep sleep; never in runtime or light sleep. +#if !defined(SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP) || !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP if (terminalDigitalHoldPending()) gpio_deep_sleep_hold_en(); +#endif } } // namespace esp32 diff --git a/test/test_esp32_gpio_hold.py b/test/test_esp32_gpio_hold.py index 1275b692a..cd1589e71 100644 --- a/test/test_esp32_gpio_hold.py +++ b/test/test_esp32_gpio_hold.py @@ -77,15 +77,21 @@ inline int gpio_hold_dis(int pin) { if(!api_result) pads[pin].hold=false; return api_result; } +#if !SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP inline void gpio_deep_sleep_hold_en() { deep_hold=true; ++deep_hold_en_calls; } inline void gpio_deep_sleep_hold_dis() { deep_hold=false; ++deep_hold_dis_calls; } +#endif inline void simulate_deep_sleep() { for(int pin=0;pin<64;++pin) { bool rtc=false; #if SOC_RTCIO_INPUT_OUTPUT_SUPPORTED rtc=rtc_gpio_is_valid_gpio(pin); #endif +#if SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP + if(!rtc && !pads[pin].hold) { pads[pin].output=false; pads[pin].retained=-1; } +#else if(!rtc && !deep_hold) { pads[pin].output=false; pads[pin].retained=-1; } +#endif } } ''' @@ -249,6 +255,29 @@ int main(){ mesh::esp32::resetTerminalDeepSleepHolds(); femShutdown(); void femShutdown(){ assert(mesh::esp32::holdOutputForDeepSleep(42,1)==ESP_OK); } ''') + def test_single_pad_deep_sleep_hold_omits_unavailable_global_apis(self): + # IDF5.3 C6 headers omit the global APIs entirely. Per-pad holds are + # retained directly, including across reset until the pin is released. + self.compile_run(r''' +#include +int main() { + using namespace mesh::esp32; + resetTerminalDeepSleepHolds(); + assert(!terminalDigitalHoldPending() && deep_hold_dis_calls==0); + assert(holdOutputForDeepSleep(18,1)==ESP_OK); + commitTerminalDeepSleepHolds(); + assert(!deep_hold && deep_hold_en_calls==0); + simulate_deep_sleep(); + assert(physical(18)==1); + pads[18].latch=0; pads[18].output=false; + resetTerminalDeepSleepHolds(); + assert(deep_hold_dis_calls==0); + assert(releaseOutputAfterDeepSleep(18,1)==ESP_OK); + assert(!pads[18].hold && physical(18)==1); +} +''', caps=1, defines=('SOC_GPIO_SUPPORT_HOLD_SINGLE_IO_IN_DSLP=1', + 'CONFIG_IDF_TARGET_ESP32C6=1')) + def test_only_terminal_sleep_commits_after_virtual_shutdown_and_wake_setup(self): production=(ROOT/'src/helpers/ESP32Board.cpp').read_text() sleep=body(production,'void ESP32Board::enterDeepSleep(uint32_t secs)')