Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions src/configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -143,12 +143,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#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
Expand Down
108 changes: 66 additions & 42 deletions src/mesh/SX126xInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 <driver/rtc_io.h>
#include <esp_sleep.h>
#endif
Expand Down Expand Up @@ -56,41 +56,53 @@ template <typename T> bool SX126xInterface<T>::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
Expand Down Expand Up @@ -197,9 +209,9 @@ template <typename T> bool SX126xInterface<T>::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
Expand Down Expand Up @@ -419,14 +431,14 @@ template <typename T> bool SX126xInterface<T>::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;
}
Expand Down Expand Up @@ -486,13 +498,25 @@ template <typename T> void SX126xInterface<T>::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 <typename T> void SX126xInterface<T>::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
}

Expand Down
26 changes: 15 additions & 11 deletions src/sleep.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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);
Comment thread
weebl2000 marked this conversation as resolved.
#endif

LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1);
Expand Down
13 changes: 9 additions & 4 deletions variants/esp32s3/heltec_v4/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 10 additions & 5 deletions variants/esp32s3/heltec_wireless_tracker_v2/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
// 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
19 changes: 17 additions & 2 deletions variants/nrf52840/rak3401_1watt/variant.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down