diff --git a/src/configuration.h b/src/configuration.h
index 53ae30d51d2..1824f018813 100644
--- a/src/configuration.h
+++ b/src/configuration.h
@@ -143,12 +143,6 @@ along with this program. If not, see .
#define SX126X_MAX_POWER 22
#endif
-#ifdef USE_GC1109_PA
-// Power Amps are often non-linear, so we can use an array of values for the power curve
-#define NUM_PA_POINTS 22
-#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
-#endif
-
#ifdef RAK13302
#define NUM_PA_POINTS 22
#define TX_GAIN_LORA 7, 8, 8, 8, 8, 8, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 8
diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp
index 553cd4ee5bf..f7db20d30f6 100644
--- a/src/mesh/SX126xInterface.cpp
+++ b/src/mesh/SX126xInterface.cpp
@@ -6,7 +6,7 @@
#ifdef ARCH_PORTDUINO
#include "PortduinoGlue.h"
#endif
-#if defined(USE_GC1109_PA) && defined(ARCH_ESP32)
+#if defined(USE_LORA_FEM) && defined(ARCH_ESP32)
#include
#include
#endif
@@ -56,41 +56,53 @@ template bool SX126xInterface::init()
pinMode(SX126X_POWER_EN, OUTPUT);
#endif
-#if defined(USE_GC1109_PA)
- // GC1109 FEM chip initialization
+#if defined(USE_LORA_FEM)
+ // FEM chip initialization (GC1109 / SKY66122-11)
// 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.
+ // On deep sleep wake (ESP32 with LORA_FEM_POWER), power and CSD are held HIGH
+ // by RTC latch. We configure GPIO registers before releasing the hold so the
+ // pad transitions atomically from held-HIGH to register-HIGH with no glitch.
+
+#ifdef LORA_FEM_POWER
+ pinMode(LORA_FEM_POWER, OUTPUT);
+ digitalWrite(LORA_FEM_POWER, HIGH);
+#if SOC_RTCIO_HOLD_SUPPORTED
+ rtc_gpio_hold_dis((gpio_num_t)LORA_FEM_POWER);
+#endif
+ // LDO startup delay — on cold boot, wait for supply to stabilise before
+ // driving CSD/CPS/CTX. On deep sleep wake the LDO was held on via RTC latch.
#if defined(ARCH_ESP32)
- if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED) {
+ if (esp_sleep_get_wakeup_cause() == ESP_SLEEP_WAKEUP_UNDEFINED)
delayMicroseconds(1000);
- }
#else
delayMicroseconds(1000);
#endif
+#endif
+
+#ifdef LORA_FEM_CTX
+ // MCU-driven CTX: drive LOW first to prevent transient TX mode while
+ // CSD/CPS are being enabled (no pull-downs on RAK13302).
+ pinMode(LORA_FEM_CTX, OUTPUT);
+ digitalWrite(LORA_FEM_CTX, LOW);
+#endif
+
+ pinMode(LORA_FEM_CSD, OUTPUT);
+ digitalWrite(LORA_FEM_CSD, HIGH);
+#if SOC_RTCIO_HOLD_SUPPORTED
+ rtc_gpio_hold_dis((gpio_num_t)LORA_FEM_CSD);
+#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);
+ pinMode(LORA_FEM_CPS, OUTPUT);
+#ifdef LORA_FEM_CTX
+ // MCU-driven CTX: CPS stays HIGH for both TX and RX
+ digitalWrite(LORA_FEM_CPS, HIGH);
+#else
+ // DIO2-driven CTX: CPS selects PA mode, start in RX-ready
+ digitalWrite(LORA_FEM_CPS, LOW);
+#endif
- // 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
- pinMode(LORA_PA_TX_EN, OUTPUT);
- digitalWrite(LORA_PA_TX_EN, LOW); // Start in RX-ready state
+ delay(1); // FEM settling time
#endif
#ifdef RF95_FAN_EN
@@ -197,9 +209,9 @@ template bool SX126xInterface::init()
// Undocumented SX1262 register patch recommended by Heltec/Semtech for improved RX sensitivity.
// Sets bit 0 of register 0x8B5.
if (module.SPIsetRegValue(0x8B5, 0x01, 0, 0) == RADIOLIB_ERR_NONE) {
- LOG_INFO("Applied SX1262 register 0x8B5 patch for GC1109 RX improvement");
+ LOG_INFO("Applied SX1262 register 0x8B5 patch for FEM RX improvement");
} else {
- LOG_WARN("Failed to apply SX1262 register 0x8B5 patch for GC1109");
+ LOG_WARN("Failed to apply SX1262 register 0x8B5 patch");
}
#if 0
@@ -419,14 +431,14 @@ template bool SX126xInterface::sleep()
digitalWrite(SX126X_POWER_EN, LOW);
#endif
-#if defined(USE_GC1109_PA)
- /*
- * Do not switch the power on and off frequently.
- * After turning off LORA_PA_EN, the power consumption has dropped to the uA level.
- * // digitalWrite(LORA_PA_POWER, LOW);
- */
- digitalWrite(LORA_PA_EN, LOW);
- digitalWrite(LORA_PA_TX_EN, LOW);
+#if defined(USE_LORA_FEM)
+#ifdef LORA_FEM_CTX
+ digitalWrite(LORA_FEM_CTX, LOW);
+#endif
+ digitalWrite(LORA_FEM_CPS, LOW);
+ digitalWrite(LORA_FEM_CSD, LOW);
+ // LORA_FEM_POWER intentionally left on — avoids frequent power cycling
+ // and deep sleep code in sleep.cpp re-latches CSD if LNA wake is needed
#endif
return true;
}
@@ -486,13 +498,25 @@ template void SX126xInterface::resetAGC()
startReceive();
}
-/** Control PA mode for GC1109 FEM - CPS pin selects full PA (txon=true) or bypass mode (txon=false) */
+/** Control FEM PA mode.
+ * - With MCU-driven CTX (LORA_FEM_CTX defined), txon=true selects the TX path and txon=false selects the RX path.
+ * - With DIO2-driven CTX (no LORA_FEM_CTX), this toggles CPS (bypass vs active path); TX/RX path is controlled by the radio.
+ */
template void SX126xInterface::setTransmitEnable(bool txon)
{
-#if defined(USE_GC1109_PA)
- digitalWrite(LORA_PA_POWER, HIGH); // Ensure LDO is on
- digitalWrite(LORA_PA_EN, HIGH); // CSD=1: Chip enabled
- digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); // CPS: 1=full PA, 0=bypass (for RX, CPS is don't care)
+#if defined(USE_LORA_FEM)
+#ifdef LORA_FEM_POWER
+ digitalWrite(LORA_FEM_POWER, HIGH);
+#endif
+ digitalWrite(LORA_FEM_CSD, HIGH);
+#ifdef LORA_FEM_CTX
+ // MCU-driven CTX: CPS always HIGH, toggle CTX for TX/RX
+ digitalWrite(LORA_FEM_CPS, HIGH);
+ digitalWrite(LORA_FEM_CTX, txon ? HIGH : LOW);
+#else
+ // DIO2-driven CTX: toggle CPS for PA mode
+ digitalWrite(LORA_FEM_CPS, txon ? HIGH : LOW);
+#endif
#endif
}
diff --git a/src/sleep.cpp b/src/sleep.cpp
index 8470e9273c7..751d3acc015 100644
--- a/src/sleep.cpp
+++ b/src/sleep.cpp
@@ -163,11 +163,15 @@ 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
+#if defined(USE_LORA_FEM)
+ // Skip 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)
+ if (false
+#ifdef LORA_FEM_POWER
+ || i == LORA_FEM_POWER
+#endif
+ || i == LORA_FEM_CSD)
continue;
#endif
if (rtc_gpio_is_valid_gpio((gpio_num_t)i))
@@ -567,15 +571,15 @@ void enableLoraInterrupt()
gpio_pullup_en((gpio_num_t)LORA_CS);
#endif
-#if defined(USE_GC1109_PA)
- // 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),
+#if defined(USE_LORA_FEM) && defined(LORA_FEM_POWER)
+ // Keep FEM powered during deep sleep so LNA remains active for RX wake.
+ // Set POWER and CSD 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);
+ digitalWrite(LORA_FEM_POWER, HIGH);
+ rtc_gpio_hold_en((gpio_num_t)LORA_FEM_POWER);
+ digitalWrite(LORA_FEM_CSD, HIGH);
+ rtc_gpio_hold_en((gpio_num_t)LORA_FEM_CSD);
+ gpio_pulldown_en((gpio_num_t)LORA_FEM_CPS);
#endif
LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h
index 1c1168d9459..8f9e7248971 100644
--- a/variants/esp32s3/heltec_v4/variant.h
+++ b/variants/esp32s3/heltec_v4/variant.h
@@ -47,15 +47,20 @@
// CSD (pin 4) -> GPIO2: Chip enable (HIGH=on, LOW=shutdown)
// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass)
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
-#define USE_GC1109_PA
-#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
-#define LORA_PA_EN 2 // CSD - GC1109 chip enable (HIGH=on)
-#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
+#define USE_LORA_FEM
+#define LORA_FEM_POWER 7 // VFEM_Ctrl - LDO power enable
+#define LORA_FEM_CSD 2 // Chip enable (HIGH=on, LOW=shutdown)
+#define LORA_FEM_CPS 46 // PA mode (HIGH=full PA, LOW=bypass)
+// CTX handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH)
// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp
// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46
+// Power Amps are often non-linear, so we use an array of values for the power curve
+#define NUM_PA_POINTS 22
+#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
+
#if HAS_TFT
#define USE_TFTDISPLAY 1
#endif
diff --git a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
index 0ca2dfc033f..fa8069fb949 100644
--- a/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
+++ b/variants/esp32s3/heltec_wireless_tracker_v2/variant.h
@@ -91,11 +91,16 @@
// CSD (pin 4) -> GPIO4: Chip enable (HIGH=on, LOW=shutdown)
// CPS (pin 5) -> GPIO46: PA mode select (HIGH=full PA, LOW=bypass)
// VCC0/VCC1 -> Vfem via U3 LDO, controlled by GPIO7
-#define USE_GC1109_PA
-#define LORA_PA_POWER 7 // VFEM_Ctrl - GC1109 LDO power enable
-#define LORA_PA_EN 4 // CSD - GC1109 chip enable (HIGH=on)
-#define LORA_PA_TX_EN 46 // CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass)
+#define USE_LORA_FEM
+#define LORA_FEM_POWER 7 // VFEM_Ctrl - LDO power enable
+#define LORA_FEM_CSD 4 // Chip enable (HIGH=on, LOW=shutdown)
+#define LORA_FEM_CPS 46 // PA mode (HIGH=full PA, LOW=bypass)
+// CTX handled by DIO2 via SX126X_DIO2_AS_RF_SWITCH
// GC1109 FEM: TX/RX path switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH)
// GPIO46 is CPS (PA mode), not TX control - setTransmitEnable() handles it in SX126xInterface.cpp
-// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46
\ No newline at end of file
+// Do NOT use SX126X_TXEN/RXEN as that would cause double-control of GPIO46
+
+// Power Amps are often non-linear, so we use an array of values for the power curve
+#define NUM_PA_POINTS 22
+#define TX_GAIN_LORA 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 11, 10, 10, 9, 9, 8, 7
\ No newline at end of file
diff --git a/variants/nrf52840/rak3401_1watt/variant.h b/variants/nrf52840/rak3401_1watt/variant.h
index 80b09cf695f..35b82ac0a78 100644
--- a/variants/nrf52840/rak3401_1watt/variant.h
+++ b/variants/nrf52840/rak3401_1watt/variant.h
@@ -158,8 +158,23 @@ static const uint8_t SCK = PIN_SPI_SCK;
#define SX126X_BUSY (9)
#define SX126X_RESET (4)
-#define SX126X_POWER_EN (21)
-// DIO2 controlls an antenna switch and the TCXO voltage is controlled by DIO3
+/*
+ * SKY66122-11 Front-End Module control (RAK13302 1W module)
+ *
+ * CSD (P0.24) — Chip enable: HIGH to power up the FEM, LOW for shutdown (<1 μA)
+ * CPS (P0.21) — Path select: HIGH = active (required for TX and RX), LOW = shutdown
+ * CTX (P0.31) — TX/RX select + 5 V boost enable:
+ * HIGH = TX mode (boost on, PA energized at 5 V)
+ * LOW = RX mode (boost off, LNA on 3.3 V)
+ *
+ * R25 (DIO2→CTX) is NC by default on the RAK13302, so the MCU must drive CTX directly.
+ */
+#define USE_LORA_FEM
+#define LORA_FEM_CSD (24) // Chip enable
+#define LORA_FEM_CPS (21) // Path select (HIGH for both TX and RX)
+#define LORA_FEM_CTX (31) // TX/RX select + 5V boost (MCU-driven, R25 NC)
+
+// Configure SX1262 DIO2 as RF-switch control output (not connected on this module, R25 NC)
#define SX126X_DIO2_AS_RF_SWITCH
#define SX126X_DIO3_TCXO_VOLTAGE 1.8