From b9319fa761473854b6482938c0b45a7dc1cd653c Mon Sep 17 00:00:00 2001 From: GlassOnTin Date: Fri, 27 Mar 2026 17:08:05 +0000 Subject: [PATCH] Fix deep sleep: remove PMU enableSleep, fix GPIO handling, add shared helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three critical fixes: - Remove PMU->enableSleep() which bricked AXP2101 I2C across resets - Remove I2C pins (GPIO 2/3) from OPEN_DRAIN list — gpio_reset_pin() already sets INPUT mode, and OPEN_DRAIN persists in battery-backed RTC domain, corrupting the I2C bus - Use esp_reset_reason() == ESP_RST_DEEPSLEEP for reliable cold boot vs timer wake discrimination Extracted twatch_enter_deep_sleep() shared helper used by both sleep_now() and beacon_wake_cycle(). Adds co5300_sleep() call before SPI.end() to properly shut down display controller. Tested: sleep via long-press, wake via button, radio reconnects, display re-initialises, I2C bus intact across multiple cycles. --- Power.h | 4 +- RNode_Firmware.ino | 147 +++++++++++++++++++++------------------------ 2 files changed, 73 insertions(+), 78 deletions(-) diff --git a/Power.h b/Power.h index a68ed43..6455fdd 100644 --- a/Power.h +++ b/Power.h @@ -70,7 +70,9 @@ float pmu_temperature = PMU_TEMP_MIN-1; PMU->disableSystemVoltageMeasure(); PMU->disableTSPinMeasure(); - PMU->enableSleep(); + // DO NOT call PMU->enableSleep() — it puts the AXP2101 into a + // persistent sleep mode that survives battery-backed resets and + // disables its I2C slave interface, bricking the bus. // Disable peripheral rails (NOT ALDO2 — causes 600µA anomaly!) power_sd(false); diff --git a/RNode_Firmware.ino b/RNode_Firmware.ino index f3d0616..2d729f0 100644 --- a/RNode_Firmware.ino +++ b/RNode_Firmware.ino @@ -274,6 +274,16 @@ void setup() { #if BOARD_MODEL == BOARD_TWATCH_ULT xl9555_init(); xl9555_enable_lora_antenna(); + + // Beacon timer wakeup: if we woke from deep sleep via timer, + // take the fast path — init GPS/LoRa only, transmit, sleep again. + // esp_reset_reason() reliably distinguishes deep sleep from cold boot. + #if HAS_GPS == true + if (esp_reset_reason() == ESP_RST_DEEPSLEEP && + esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_TIMER) { + beacon_wake_cycle(); // Does not return + } + #endif #endif #if HAS_BLUETOOTH || HAS_BLE == true @@ -1984,12 +1994,61 @@ void loop() { } -#if BOARD_MODEL == BOARD_TWATCH_ULT && HAS_GPS == true +#if BOARD_MODEL == BOARD_TWATCH_ULT +// Shared deep sleep entry for T-Watch Ultra. +// Safely shuts down peripherals and enters ESP32 deep sleep. +// Does not return — device reboots on wake. +void twatch_enter_deep_sleep(bool beacon_timer) { + // 1. Put display controller into sleep mode (must happen before SPI.end) + #if HAS_DISPLAY + co5300_sleep(); + #endif + + // 2. Gate display VCI power and disable haptics via XL9555 + xl9555_sleep_prepare(); + + // 3. Disable PMU peripheral rails (no PMU->enableSleep — that bricks I2C!) + pmu_prepare_sleep(); + + // 4. Close communication buses + #if HAS_GPS + gps_serial.end(); + #endif + Serial1.end(); + SPI.end(); + Wire.end(); + + // 5. Reset unused GPIOs to INPUT (minimal leakage) + // DO NOT touch I2C pins (GPIO 2/3) — external pullups, and setting + // them to OPEN_DRAIN persists across battery-backed resets, bricking I2C. + const uint8_t sleep_pins[] = { + DISP_D0, DISP_D1, DISP_D2, DISP_D3, + DISP_SCK, DISP_CS, DISP_TE, DISP_RST, + RTC_INT, NFC_INT, SENSOR_INT, NFC_CS, + I2S_BCLK, I2S_WCLK, I2S_DOUT, SD_CS, + pin_mosi, pin_miso, pin_sclk, pin_cs, + PIN_GPS_TX, PIN_GPS_RX, PIN_GPS_PPS, + pin_reset, pin_busy, pin_dio, + }; + for (auto p : sleep_pins) { + gpio_reset_pin((gpio_num_t)p); // Resets to INPUT, clears any drive + } + + // 6. Configure wakeup sources + esp_sleep_enable_ext1_wakeup(1ULL << PMU_IRQ, ESP_EXT1_WAKEUP_ANY_LOW); + if (beacon_timer) { + esp_sleep_enable_timer_wakeup((uint64_t)BEACON_INTERVAL_MS * 1000ULL); + } + + // 7. Enter deep sleep (does not return) + esp_deep_sleep_start(); +} + +#if HAS_GPS == true // Minimal boot path for beacon timer wakeup. // Inits only GPS + LoRa, waits for fix, transmits beacon, sleeps again. -// Called from setup() before BLE/display init. Does not return. +// Called from setup() on timer wake. Does not return. void beacon_wake_cycle() { - // GPS is not yet initialized at this point — init it now gps_setup(); // Load beacon crypto config from EEPROM @@ -2011,44 +2070,17 @@ void beacon_wake_cycle() { delay(100); } - // Attempt beacon transmission if we have a fix if (gps_has_fix) { - last_host_activity = 0; // ensure beacon_update doesn't think host is active - last_beacon_tx = 0; // force immediate beacon + last_host_activity = 0; + last_beacon_tx = 0; beacon_update(); } - // Go back to sleep with timer for next beacon cycle - gps_serial.end(); stopRadio(); - xl9555_sleep_prepare(); - pmu_prepare_sleep(); - Serial1.end(); - SPI.end(); - Wire.end(); - - // Set GPIOs to open drain - const uint8_t sleep_pins[] = { - DISP_D0, DISP_D1, DISP_D2, DISP_D3, - DISP_SCK, DISP_CS, DISP_TE, DISP_RST, - RTC_INT, NFC_INT, SENSOR_INT, NFC_CS, - I2S_BCLK, I2S_WCLK, I2S_DOUT, SD_CS, - I2C_SDA, I2C_SCL, - pin_mosi, pin_miso, pin_sclk, pin_cs, - PIN_GPS_TX, PIN_GPS_RX, PIN_GPS_PPS, - pin_reset, pin_busy, pin_dio, - }; - for (auto p : sleep_pins) { - gpio_reset_pin((gpio_num_t)p); - pinMode(p, OPEN_DRAIN); - } - - // Timer wakeup for next beacon, also allow PMU button to wake - esp_sleep_enable_timer_wakeup((uint64_t)BEACON_INTERVAL_MS * 1000ULL); - esp_sleep_enable_ext1_wakeup(1ULL << PMU_IRQ, ESP_EXT1_WAKEUP_ANY_LOW); - esp_deep_sleep_start(); + twatch_enter_deep_sleep(true); // Sleep with beacon timer } #endif +#endif void sleep_now() { #if HAS_SLEEP == true @@ -2078,51 +2110,12 @@ void sleep_now() { #endif #if BOARD_MODEL == BOARD_TWATCH_ULT - // T-Watch Ultra deep sleep sequence - // Following LilyGo's proven power-down order - - #if HAS_GPS - gps_serial.end(); - #endif - - // XL9555: disable display and haptics - xl9555_sleep_prepare(); - - // PMU: disable peripheral rails, enable sleep mode - pmu_prepare_sleep(); - - // Close all buses - Serial1.end(); - SPI.end(); - Wire.end(); - - // Set all unused GPIOs to OPEN_DRAIN to prevent current leaks - const uint8_t sleep_pins[] = { - DISP_D0, DISP_D1, DISP_D2, DISP_D3, - DISP_SCK, DISP_CS, DISP_TE, DISP_RST, - RTC_INT, NFC_INT, SENSOR_INT, NFC_CS, - I2S_BCLK, I2S_WCLK, I2S_DOUT, SD_CS, - I2C_SDA, I2C_SCL, - pin_mosi, pin_miso, pin_sclk, pin_cs, - PIN_GPS_TX, PIN_GPS_RX, PIN_GPS_PPS, - pin_reset, pin_busy, pin_dio, - }; - for (auto p : sleep_pins) { - gpio_reset_pin((gpio_num_t)p); - pinMode(p, OPEN_DRAIN); - } - - // Always allow PMU button wakeup - esp_sleep_enable_ext1_wakeup(1ULL << PMU_IRQ, ESP_EXT1_WAKEUP_ANY_LOW); - - // If in beacon mode, also set timer wakeup for next beacon cycle #if HAS_GPS == true - if (beacon_mode_active) { - esp_sleep_enable_timer_wakeup((uint64_t)BEACON_INTERVAL_MS * 1000ULL); - } + bool use_beacon_timer = beacon_mode_active; + #else + bool use_beacon_timer = false; #endif - - esp_deep_sleep_start(); + twatch_enter_deep_sleep(use_beacon_timer); #else esp_sleep_enable_ext0_wakeup(PIN_WAKEUP, WAKEUP_LEVEL);