From af518fbd2b256eaebb5b23841e3ca9d8892dd791 Mon Sep 17 00:00:00 2001 From: Wessel Date: Fri, 20 Feb 2026 23:12:54 +0100 Subject: [PATCH] Hold GC1109 FEM power during deep sleep for LNA RX wake (#9572) * Hold GC1109 PA_POWER and PA_EN during deep sleep for LNA RX wake Use rtc_gpio_hold_en to latch PA_POWER (LDO) and PA_EN (CSD) HIGH during deep sleep so the GC1109 LNA remains powered for wake-on-packet RX. Previously these pins used weak pull-ups which could lose state. On deep sleep wake, skip these pins in the blanket RTC hold release and instead release them in SX126xInterface::init() after GPIO registers are set HIGH first, avoiding a power glitch on the GC1109. Trade-off: ~6.5mA additional deep sleep current for significantly improved wake-on-packet RX sensitivity (~17dB). Reference: https://github.com/meshcore-dev/MeshCore/pull/1600 * Add LDO startup delay before GC1109 chip enable TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait 1ms for VBAT to stabilise before driving CSD/CPS, per GC1109 power-on sequence requirement. On deep sleep wake the LDO is held on via RTC latch so no delay is needed. --- src/mesh/SX126xInterface.cpp | 23 +++++++++++++++++++++++ src/sleep.cpp | 16 ++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 9dfc46bee..08ee2ff48 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -6,6 +6,10 @@ #ifdef ARCH_PORTDUINO #include "PortduinoGlue.h" #endif +#if defined(USE_GC1109_PA) && defined(ARCH_ESP32) +#include +#include +#endif #include "Throttle.h" @@ -55,14 +59,33 @@ template bool SX126xInterface::init() #if defined(USE_GC1109_PA) // GC1109 FEM chip initialization // See variant.h for full pin mapping and control logic documentation + // + // On deep sleep wake, PA_POWER and PA_EN are held HIGH by RTC latch (set in + // enableLoraInterrupt). We configure GPIO registers before releasing the hold + // so the pad transitions atomically from held-HIGH to register-HIGH with no + // power glitch. On cold boot the hold_dis is a harmless no-op. // VFEM_Ctrl (LORA_PA_POWER): Power enable for GC1109 LDO (always on) pinMode(LORA_PA_POWER, OUTPUT); digitalWrite(LORA_PA_POWER, HIGH); + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_POWER); + + // TLV75733P LDO has ~550us startup time (datasheet tSTR). On cold boot, wait + // for VBAT to stabilise before driving CSD/CPS, per GC1109 requirement: + // "VBAT must be prior to CSD/CPS/CTX for the power on sequence" + // On deep sleep wake the LDO was held on via RTC latch, so no delay needed. +#if defined(ARCH_ESP32) + if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) { + delayMicroseconds(1000); + } +#else + delayMicroseconds(1000); +#endif // CSD (LORA_PA_EN): Chip enable - must be HIGH to enable GC1109 for both RX and TX pinMode(LORA_PA_EN, OUTPUT); digitalWrite(LORA_PA_EN, HIGH); + rtc_gpio_hold_dis((gpio_num_t)LORA_PA_EN); // CPS (LORA_PA_TX_EN): PA mode select - HIGH enables full PA during TX, LOW for RX (don't care) // Note: TX/RX path switching (CTX) is handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH diff --git a/src/sleep.cpp b/src/sleep.cpp index 7c768d573..d42b9841a 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -162,6 +162,13 @@ void initDeepSleep() if (wakeCause != ESP_SLEEP_WAKEUP_UNDEFINED) { LOG_DEBUG("Disable any holds on RTC IO pads"); for (uint8_t i = 0; i <= GPIO_NUM_MAX; i++) { +#if defined(USE_GC1109_PA) + // Skip GC1109 FEM power pins - they are held HIGH during deep sleep to keep + // the LNA active for RX wake. Released later in SX126xInterface::init() after + // GPIO registers are set HIGH first, avoiding a power glitch. + if (i == LORA_PA_POWER || i == LORA_PA_EN) + continue; +#endif if (rtc_gpio_is_valid_gpio((gpio_num_t)i)) rtc_gpio_hold_dis((gpio_num_t)i); @@ -556,8 +563,13 @@ void enableLoraInterrupt() #endif #if defined(USE_GC1109_PA) - gpio_pullup_en((gpio_num_t)LORA_PA_POWER); - gpio_pullup_en((gpio_num_t)LORA_PA_EN); + // Keep GC1109 FEM powered during deep sleep so LNA remains active for RX wake. + // Set PA_POWER and PA_EN HIGH (overrides SX126xInterface::sleep() shutdown), + // then latch with RTC hold so the state survives deep sleep. + digitalWrite(LORA_PA_POWER, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_PA_POWER); + digitalWrite(LORA_PA_EN, HIGH); + rtc_gpio_hold_en((gpio_num_t)LORA_PA_EN); gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); #endif