From 044505d08453515f7afb526679228215ee0865fd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sat, 20 Dec 2025 18:06:22 +0100 Subject: [PATCH 1/5] Fix handling RX/TX for Hetlec v4 FEM The root cause was that RadioLib didn't know about the external GC1109 RF switch. Without the SX126X_RXEN/TXEN definitions, RadioLib never called setRfSwitchPins(), so it couldn't automatically manage PA_TX_EN during RX/TX transitions. --- variants/heltec_v4/HeltecV4Board.cpp | 28 +++++++++++++++++++--------- variants/heltec_v4/platformio.ini | 2 ++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index f143db36f..388ebc56b 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -7,15 +7,23 @@ void HeltecV4Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // ---- GC1109 RF FRONT END CONFIGURATION ---- + // The Heltec V4 uses a GC1109 FEM chip with integrated PA and LNA + // RF switch control: PA_TX_EN LOW = RX path (LNA), HIGH = TX path (PA) + + // PA_POWER: Power enable for GC1109 chip (always on) pinMode(P_LORA_PA_POWER, OUTPUT); - digitalWrite(P_LORA_PA_POWER,HIGH); + digitalWrite(P_LORA_PA_POWER, HIGH); + // PA_EN: Main enable for GC1109 (must be HIGH for both RX and TX) rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN,HIGH); - pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN,LOW); + digitalWrite(P_LORA_PA_EN, HIGH); + // PA_TX_EN: RF switch control (LOW=RX/LNA, HIGH=TX/PA) + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN, LOW); // Default to RX mode + // ------------------------------------------- periph_power.begin(); @@ -32,13 +40,13 @@ void HeltecV4Board::begin() { } void HeltecV4Board::onBeforeTransmit(void) { - digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on - digitalWrite(P_LORA_PA_TX_EN,HIGH); + digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on + digitalWrite(P_LORA_PA_TX_EN, HIGH); // Switch to TX path (PA) } void HeltecV4Board::onAfterTransmit(void) { - digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off - digitalWrite(P_LORA_PA_TX_EN,LOW); + digitalWrite(P_LORA_PA_TX_EN, LOW); // Switch back to RX path (LNA) + digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off } void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { @@ -50,7 +58,9 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep (PA_EN=HIGH, PA_TX_EN=LOW for RX mode) + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index c26a5bc69..d0f3a43e7 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -31,6 +31,8 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_RXEN=RADIOLIB_NC ; No separate RX enable pin + -D SX126X_TXEN=46 ; TX enable pin (P_LORA_PA_TX_EN) controls RF switch -D PIN_GPS_RX=38 -D PIN_GPS_TX=39 -D PIN_GPS_RESET=42 From 27360114281f2ef161d9ca790f2d028f6a30f66f Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Sun, 21 Dec 2025 12:39:30 +0100 Subject: [PATCH 2/5] Apply same fix for Heltec tracker v2 --- .../HeltecTrackerV2Board.cpp | 27 +++++++++++++------ variants/heltec_tracker_v2/platformio.ini | 2 ++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 4975d5cde..a620e7868 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -6,14 +6,23 @@ void HeltecTrackerV2Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + // ---- GC1109 RF FRONT END CONFIGURATION ---- + // The Heltec Tracker V2 uses a GC1109 FEM chip with integrated PA and LNA + // RF switch control: PA_TX_EN LOW = RX path (LNA), HIGH = TX path (PA) + + // PA_POWER: Power enable for GC1109 chip (always on) pinMode(P_LORA_PA_POWER, OUTPUT); - digitalWrite(P_LORA_PA_POWER,HIGH); + digitalWrite(P_LORA_PA_POWER, HIGH); + // PA_EN: Main enable for GC1109 (must be HIGH for both RX and TX) rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN,HIGH); + digitalWrite(P_LORA_PA_EN, HIGH); + + // PA_TX_EN: RF switch control (LOW=RX/LNA, HIGH=TX/PA) pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN,LOW); + digitalWrite(P_LORA_PA_TX_EN, LOW); // Default to RX mode + // ------------------------------------------- periph_power.begin(); @@ -30,13 +39,13 @@ void HeltecTrackerV2Board::begin() { } void HeltecTrackerV2Board::onBeforeTransmit(void) { - digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED on - digitalWrite(P_LORA_PA_TX_EN,HIGH); + digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on + digitalWrite(P_LORA_PA_TX_EN, HIGH); // Switch to TX path (PA) } void HeltecTrackerV2Board::onAfterTransmit(void) { - digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED off - digitalWrite(P_LORA_PA_TX_EN,LOW); + digitalWrite(P_LORA_PA_TX_EN, LOW); // Switch back to RX path (LNA) + digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off } void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { @@ -48,7 +57,9 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); //It also needs to be enabled in receive mode + // Hold GC1109 FEM pins during sleep (PA_EN=HIGH, PA_TX_EN=LOW for RX mode) + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index 61ccd4f4b..af4a28d12 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -26,6 +26,8 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_RXEN=RADIOLIB_NC ; No separate RX enable pin + -D SX126X_TXEN=46 ; TX enable pin (P_LORA_PA_TX_EN) controls RF switch -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=0 From 2171bfd5dede7b1e5f7b366701225c8a804d48da Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Tue, 23 Dec 2025 22:02:58 +0100 Subject: [PATCH 3/5] Let SX1262 DIO2 handle the RF switching and hold all pins during sleep --- .../HeltecTrackerV2Board.cpp | 47 ++++++++++++++----- variants/heltec_tracker_v2/platformio.ini | 14 +++--- variants/heltec_v4/HeltecV4Board.cpp | 47 ++++++++++++++----- variants/heltec_v4/platformio.ini | 10 ++-- 4 files changed, 80 insertions(+), 38 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index a620e7868..19a7e35b9 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -8,20 +8,37 @@ void HeltecTrackerV2Board::begin() { // ---- GC1109 RF FRONT END CONFIGURATION ---- // The Heltec Tracker V2 uses a GC1109 FEM chip with integrated PA and LNA - // RF switch control: PA_TX_EN LOW = RX path (LNA), HIGH = TX path (PA) - - // PA_POWER: Power enable for GC1109 chip (always on) + // RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna + // Measured net TX gain (non-linear due to PA compression): + // +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out) + // +10dB at 16-17dBm input + // +9dB at 18-19dBm input + // +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max) + // Control logic (from GC1109 datasheet): + // Shutdown: CSD=0, CTX=X, CPS=X + // Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) + // Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA) + // Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) + // Pin mapping: + // CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) + // 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 LDO, controlled by GPIO7 + + // VFEM_Ctrl (GPIO7): Power enable for GC1109 LDO + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER, HIGH); - // PA_EN: Main enable for GC1109 (must be HIGH for both RX and TX) + // CSD (GPIO4): Chip enable - must be HIGH to enable GC1109 rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN, HIGH); - // PA_TX_EN: RF switch control (LOW=RX/LNA, HIGH=TX/PA) + // CPS (GPIO46): PA mode - LOW for RX (don't care), HIGH during TX for full PA + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_TX_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN, LOW); // Default to RX mode + digitalWrite(P_LORA_PA_TX_EN, LOW); // Start in RX-ready state // ------------------------------------------- periph_power.begin(); @@ -39,13 +56,15 @@ void HeltecTrackerV2Board::begin() { } void HeltecTrackerV2Board::onBeforeTransmit(void) { - digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on - digitalWrite(P_LORA_PA_TX_EN, HIGH); // Switch to TX path (PA) + digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on + digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS=1: Enable full PA mode (+30dBm) + // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) } void HeltecTrackerV2Board::onAfterTransmit(void) { - digitalWrite(P_LORA_PA_TX_EN, LOW); // Switch back to RX path (LNA) - digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off + digitalWrite(P_LORA_PA_TX_EN, LOW); // CPS=0: Back to RX mode (CPS=X for RX) + digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off + // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) } void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { @@ -57,9 +76,11 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - // Hold GC1109 FEM pins during sleep (PA_EN=HIGH, PA_TX_EN=LOW for RX mode) - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); + // Hold GC1109 FEM pins during sleep for RX wake capability + // State: CSD=1, CTX=0 (DIO2), CPS=0 -> Receive LNA mode + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); // VFEM_Ctrl - keep LDO powered + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); // CSD=1 - chip enabled + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); // CPS=0 - RX mode (don't care) if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_tracker_v2/platformio.ini b/variants/heltec_tracker_v2/platformio.ini index af4a28d12..21fe2325a 100644 --- a/variants/heltec_tracker_v2/platformio.ini +++ b/variants/heltec_tracker_v2/platformio.ini @@ -17,17 +17,17 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=4 - -D P_LORA_PA_TX_EN=46 ;enable tx - -D LORA_TX_POWER=10 ;If it is configured as 10 here, the final output will be 22 dbm. - -D MAX_LORA_TX_POWER=22 ;Max SX1262 output + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - GC1109 LDO power enable + -D P_LORA_PA_EN=4 ; CSD - GC1109 chip enable (HIGH=on) + -D P_LORA_PA_TX_EN=46 ; CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) + -D LORA_TX_POWER=10 ; 10dBm + 11dB gain = ~21dBm output (see gain curve above) + -D MAX_LORA_TX_POWER=22 ; Max SX1262 output -> ~28dBm at antenna -D SX126X_DIO2_AS_RF_SWITCH=true -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D SX126X_RXEN=RADIOLIB_NC ; No separate RX enable pin - -D SX126X_TXEN=46 ; TX enable pin (P_LORA_PA_TX_EN) controls RF switch + ; GC1109 FEM: TX/RX switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH) + ; GPIO46 is CPS (PA mode), not TX control - do not use for RF switching -D PIN_BOARD_SDA=5 -D PIN_BOARD_SCL=6 -D PIN_USER_BTN=0 diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 388ebc56b..bb70d412d 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -9,20 +9,37 @@ void HeltecV4Board::begin() { // ---- GC1109 RF FRONT END CONFIGURATION ---- // The Heltec V4 uses a GC1109 FEM chip with integrated PA and LNA - // RF switch control: PA_TX_EN LOW = RX path (LNA), HIGH = TX path (PA) - - // PA_POWER: Power enable for GC1109 chip (always on) + // RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna + // Measured net TX gain (non-linear due to PA compression): + // +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out) + // +10dB at 16-17dBm input + // +9dB at 18-19dBm input + // +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max) + // Control logic (from GC1109 datasheet): + // Shutdown: CSD=0, CTX=X, CPS=X + // Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) + // Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA) + // Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) + // Pin mapping: + // CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) + // 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 + + // VFEM_Ctrl (GPIO7): Power enable for GC1109 LDO (U3) + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); pinMode(P_LORA_PA_POWER, OUTPUT); digitalWrite(P_LORA_PA_POWER, HIGH); - // PA_EN: Main enable for GC1109 (must be HIGH for both RX and TX) + // CSD (GPIO2): Chip enable - must be HIGH to enable GC1109 rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); pinMode(P_LORA_PA_EN, OUTPUT); digitalWrite(P_LORA_PA_EN, HIGH); - // PA_TX_EN: RF switch control (LOW=RX/LNA, HIGH=TX/PA) + // CPS (GPIO46): PA mode - LOW for RX (don't care), HIGH during TX for full PA + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_TX_EN); pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN, LOW); // Default to RX mode + digitalWrite(P_LORA_PA_TX_EN, LOW); // Start in RX-ready state // ------------------------------------------- periph_power.begin(); @@ -40,13 +57,15 @@ void HeltecV4Board::begin() { } void HeltecV4Board::onBeforeTransmit(void) { - digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on - digitalWrite(P_LORA_PA_TX_EN, HIGH); // Switch to TX path (PA) + digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on + digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS=1: Enable full PA mode (+30dBm) + // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) } void HeltecV4Board::onAfterTransmit(void) { - digitalWrite(P_LORA_PA_TX_EN, LOW); // Switch back to RX path (LNA) - digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off + digitalWrite(P_LORA_PA_TX_EN, LOW); // CPS=0: Back to RX mode (CPS=X for RX) + digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off + // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) } void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { @@ -58,9 +77,11 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - // Hold GC1109 FEM pins during sleep (PA_EN=HIGH, PA_TX_EN=LOW for RX mode) - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); + // Hold GC1109 FEM pins during sleep for RX wake capability + // State: CSD=1, CTX=0 (DIO2), CPS=0 -> Receive LNA mode + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); // VFEM_Ctrl - keep LDO powered + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); // CSD=1 - chip enabled + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); // CPS=0 - RX mode (don't care) if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index d0f3a43e7..44c4f00a1 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -17,9 +17,9 @@ build_flags = -D P_LORA_SCLK=9 -D P_LORA_MISO=11 -D P_LORA_MOSI=10 - -D P_LORA_PA_POWER=7 ;power en - -D P_LORA_PA_EN=2 - -D P_LORA_PA_TX_EN=46 ;enable tx + -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - GC1109 LDO power enable + -D P_LORA_PA_EN=2 ; CSD - GC1109 chip enable (HIGH=on) + -D P_LORA_PA_TX_EN=46 ; CPS - GC1109 PA mode (HIGH=full PA, LOW=bypass) -D PIN_BOARD_SDA=17 -D PIN_BOARD_SCL=18 -D PIN_USER_BTN=0 @@ -31,8 +31,8 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 - -D SX126X_RXEN=RADIOLIB_NC ; No separate RX enable pin - -D SX126X_TXEN=46 ; TX enable pin (P_LORA_PA_TX_EN) controls RF switch + ; GC1109 FEM: TX/RX switching is handled by DIO2 -> CTX pin (via SX126X_DIO2_AS_RF_SWITCH) + ; GPIO46 is CPS (power save), not TX control - do not use for RF switching -D PIN_GPS_RX=38 -D PIN_GPS_TX=39 -D PIN_GPS_RESET=42 From d46624661fd74a5ff0f35f6cdb2d22939af9ed11 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 24 Dec 2025 12:44:04 +0100 Subject: [PATCH 4/5] Don't hold CPS pin, it's not RTC --- variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp | 6 +++--- variants/heltec_v4/HeltecV4Board.cpp | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index 19a7e35b9..aa28f9fd5 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -36,7 +36,7 @@ void HeltecTrackerV2Board::begin() { digitalWrite(P_LORA_PA_EN, HIGH); // CPS (GPIO46): PA mode - LOW for RX (don't care), HIGH during TX for full PA - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_TX_EN); + // Note: GPIO46 is NOT an RTC GPIO, so no rtc_gpio_hold_dis needed pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN, LOW); // Start in RX-ready state // ------------------------------------------- @@ -77,10 +77,10 @@ void HeltecTrackerV2Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); // Hold GC1109 FEM pins during sleep for RX wake capability - // State: CSD=1, CTX=0 (DIO2), CPS=0 -> Receive LNA mode + // State: CSD=1, CTX=0 (DIO2), CPS=X -> Receive LNA mode rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); // VFEM_Ctrl - keep LDO powered rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); // CSD=1 - chip enabled - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); // CPS=0 - RX mode (don't care) + // Note: GPIO46 (CPS) is NOT an RTC GPIO, cannot hold - but CPS is don't care for RX if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index bb70d412d..3bce97309 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -37,7 +37,7 @@ void HeltecV4Board::begin() { digitalWrite(P_LORA_PA_EN, HIGH); // CPS (GPIO46): PA mode - LOW for RX (don't care), HIGH during TX for full PA - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_TX_EN); + // Note: GPIO46 is NOT an RTC GPIO, so no rtc_gpio_hold_dis needed pinMode(P_LORA_PA_TX_EN, OUTPUT); digitalWrite(P_LORA_PA_TX_EN, LOW); // Start in RX-ready state // ------------------------------------------- @@ -78,10 +78,10 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); // Hold GC1109 FEM pins during sleep for RX wake capability - // State: CSD=1, CTX=0 (DIO2), CPS=0 -> Receive LNA mode + // State: CSD=1, CTX=0 (DIO2), CPS=X -> Receive LNA mode rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); // VFEM_Ctrl - keep LDO powered rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); // CSD=1 - chip enabled - rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_TX_EN); // CPS=0 - RX mode (don't care) + // Note: GPIO46 (CPS) is NOT an RTC GPIO, cannot hold - but CPS is don't care for RX if (pin_wake_btn < 0) { esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet From dd5e868f70df3a85f9dc576bbe4e4b2f22c817dd Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Wed, 24 Dec 2025 14:26:37 +0100 Subject: [PATCH 5/5] Don't touch pin 46 except when transmitting. Set pinmode output/input too to make sure --- .../HeltecTrackerV2Board.cpp | 73 ++++++++---------- variants/heltec_v4/HeltecV4Board.cpp | 74 ++++++++----------- 2 files changed, 60 insertions(+), 87 deletions(-) diff --git a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp index aa28f9fd5..159e87625 100644 --- a/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp +++ b/variants/heltec_tracker_v2/HeltecTrackerV2Board.cpp @@ -6,43 +6,7 @@ void HeltecTrackerV2Board::begin() { pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive - // ---- GC1109 RF FRONT END CONFIGURATION ---- - // The Heltec Tracker V2 uses a GC1109 FEM chip with integrated PA and LNA - // RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna - // Measured net TX gain (non-linear due to PA compression): - // +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out) - // +10dB at 16-17dBm input - // +9dB at 18-19dBm input - // +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max) - // Control logic (from GC1109 datasheet): - // Shutdown: CSD=0, CTX=X, CPS=X - // Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) - // Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA) - // Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) - // Pin mapping: - // CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) - // 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 LDO, controlled by GPIO7 - - // VFEM_Ctrl (GPIO7): Power enable for GC1109 LDO - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - pinMode(P_LORA_PA_POWER, OUTPUT); - digitalWrite(P_LORA_PA_POWER, HIGH); - - // CSD (GPIO4): Chip enable - must be HIGH to enable GC1109 - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, HIGH); - - // CPS (GPIO46): PA mode - LOW for RX (don't care), HIGH during TX for full PA - // Note: GPIO46 is NOT an RTC GPIO, so no rtc_gpio_hold_dis needed - pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN, LOW); // Start in RX-ready state - // ------------------------------------------- - - periph_power.begin(); - + // Check if waking from deep sleep esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); @@ -50,21 +14,44 @@ void HeltecTrackerV2Board::begin() { startup_reason = BD_STARTUP_RX_PACKET; } + // Release RTC holds - pins retain their state, no need to reconfigure rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } else { + // Cold boot: Configure GC1109 FEM pins + // Control logic (from GC1109 datasheet): + // Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) + // Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) + // Pin mapping: CTX->DIO2 (auto), CSD->GPIO4, CPS->GPIO46, VFEM->GPIO7 + + // VFEM_Ctrl (GPIO7): Power enable for GC1109 LDO + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER, HIGH); + + // CSD (GPIO4): Chip enable - must be HIGH for GC1109 to work + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, HIGH); } + + periph_power.begin(); + + // Note: GPIO46 (CPS) is a strapping pin - do NOT configure it here. + // TX handlers are fully responsible for GPIO46 (see onBeforeTransmit/onAfterTransmit) } void HeltecTrackerV2Board::onBeforeTransmit(void) { - digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on - digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS=1: Enable full PA mode (+30dBm) - // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) + // GPIO46 is a strapping pin - only drive it when actively transmitting + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS=1: Enable full PA mode + digitalWrite(P_LORA_TX_LED, HIGH); } void HeltecTrackerV2Board::onAfterTransmit(void) { - digitalWrite(P_LORA_PA_TX_EN, LOW); // CPS=0: Back to RX mode (CPS=X for RX) - digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off - // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) + digitalWrite(P_LORA_PA_TX_EN, LOW); + pinMode(P_LORA_PA_TX_EN, INPUT); // Release strapping pin + digitalWrite(P_LORA_TX_LED, LOW); } void HeltecTrackerV2Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 3bce97309..18caeca15 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -3,47 +3,10 @@ void HeltecV4Board::begin() { ESP32Board::begin(); - pinMode(PIN_ADC_CTRL, OUTPUT); digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive - // ---- GC1109 RF FRONT END CONFIGURATION ---- - // The Heltec V4 uses a GC1109 FEM chip with integrated PA and LNA - // RF path: SX1262 -> GC1109 PA -> Pi attenuator -> Antenna - // Measured net TX gain (non-linear due to PA compression): - // +11dB at 0-15dBm input (e.g., 10dBm in -> 21dBm out) - // +10dB at 16-17dBm input - // +9dB at 18-19dBm input - // +7dB at 21dBm input (e.g., 21dBm in -> 28dBm out max) - // Control logic (from GC1109 datasheet): - // Shutdown: CSD=0, CTX=X, CPS=X - // Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) - // Transmit bypass: CSD=1, CTX=1, CPS=0 (~1dB loss, no PA) - // Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) - // Pin mapping: - // CTX (pin 6) -> SX1262 DIO2: TX/RX path select (automatic via SX126X_DIO2_AS_RF_SWITCH) - // 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 - - // VFEM_Ctrl (GPIO7): Power enable for GC1109 LDO (U3) - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - pinMode(P_LORA_PA_POWER, OUTPUT); - digitalWrite(P_LORA_PA_POWER, HIGH); - - // CSD (GPIO2): Chip enable - must be HIGH to enable GC1109 - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN, HIGH); - - // CPS (GPIO46): PA mode - LOW for RX (don't care), HIGH during TX for full PA - // Note: GPIO46 is NOT an RTC GPIO, so no rtc_gpio_hold_dis needed - pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN, LOW); // Start in RX-ready state - // ------------------------------------------- - - periph_power.begin(); - + // Check if waking from deep sleep esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); @@ -51,21 +14,44 @@ void HeltecV4Board::begin() { startup_reason = BD_STARTUP_RX_PACKET; } + // Release RTC holds - pins retain their state, no need to reconfigure rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } else { + // Cold boot: Configure GC1109 FEM pins + // Control logic (from GC1109 datasheet): + // Receive LNA: CSD=1, CTX=0, CPS=X (17dB gain, 2dB NF) + // Transmit PA: CSD=1, CTX=1, CPS=1 (full PA enabled) + // Pin mapping: CTX->DIO2 (auto), CSD->GPIO2, CPS->GPIO46, VFEM->GPIO7 + + // VFEM_Ctrl (GPIO7): Power enable for GC1109 LDO + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER, HIGH); + + // CSD (GPIO2): Chip enable - must be HIGH for GC1109 to work + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, HIGH); } + + periph_power.begin(); + + // Note: GPIO46 (CPS) is a strapping pin - do NOT configure it here. + // TX handlers are fully responsible for GPIO46 (see onBeforeTransmit/onAfterTransmit) } void HeltecV4Board::onBeforeTransmit(void) { - digitalWrite(P_LORA_TX_LED, HIGH); // Turn TX LED on - digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS=1: Enable full PA mode (+30dBm) - // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) + // GPIO46 is a strapping pin - only drive it when actively transmitting + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS=1: Enable full PA mode + digitalWrite(P_LORA_TX_LED, HIGH); } void HeltecV4Board::onAfterTransmit(void) { - digitalWrite(P_LORA_PA_TX_EN, LOW); // CPS=0: Back to RX mode (CPS=X for RX) - digitalWrite(P_LORA_TX_LED, LOW); // Turn TX LED off - // CTX (TX/RX path) handled by SX1262 DIO2 -> GC1109 CTX (hardware) + digitalWrite(P_LORA_PA_TX_EN, LOW); + pinMode(P_LORA_PA_TX_EN, INPUT); // Release strapping pin + digitalWrite(P_LORA_TX_LED, LOW); } void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) {