From 1935846fb200f0e21468bb0b2da713018cac6e15 Mon Sep 17 00:00:00 2001 From: Wessel Nieboer Date: Thu, 26 Feb 2026 10:36:02 +0100 Subject: [PATCH] Update Heltec V4 code to support V4.3 with KCT8103L FEM - runtime auto-detection of v4.3 board (KCT8103L FEM) vs V4.0-V4.2 (GC1109) via GPIO2 pull level - different TX/RX path using PGIO5 for KCT8103L, GPIO46 CPS for GC1109 - hold both FEMs active for RX - report Heltec V4.3 in manufacturer name --- variants/heltec_v4/HeltecV4Board.cpp | 74 +++++++++++++++++++--------- variants/heltec_v4/HeltecV4Board.h | 2 + variants/heltec_v4/platformio.ini | 1 + 3 files changed, 54 insertions(+), 23 deletions(-) diff --git a/variants/heltec_v4/HeltecV4Board.cpp b/variants/heltec_v4/HeltecV4Board.cpp index 8186f2d4b..7e9ecb08e 100644 --- a/variants/heltec_v4/HeltecV4Board.cpp +++ b/variants/heltec_v4/HeltecV4Board.cpp @@ -3,49 +3,71 @@ void HeltecV4Board::begin() { ESP32Board::begin(); - pinMode(PIN_ADC_CTRL, OUTPUT); - digitalWrite(PIN_ADC_CTRL, LOW); // Initially inactive + digitalWrite(PIN_ADC_CTRL, LOW); - // Set up digital GPIO registers before releasing RTC hold. The hold latches - // the pad state including function select, so register writes accumulate - // without affecting the pad. On hold release, all changes apply atomically - // (IO MUX switches to digital GPIO with output already HIGH — no glitch). + // Power on FEM LDO — set registers before releasing RTC hold for + // atomic transition (no glitch on deep sleep wake). pinMode(P_LORA_PA_POWER, OUTPUT); - digitalWrite(P_LORA_PA_POWER,HIGH); + digitalWrite(P_LORA_PA_POWER, HIGH); rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); - pinMode(P_LORA_PA_EN, OUTPUT); - digitalWrite(P_LORA_PA_EN,HIGH); - rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); - pinMode(P_LORA_PA_TX_EN, OUTPUT); - digitalWrite(P_LORA_PA_TX_EN,LOW); - esp_reset_reason_t reason = esp_reset_reason(); if (reason != ESP_RST_DEEPSLEEP) { - delay(1); // GC1109 startup time after cold power-on + delay(1); // FEM startup time after cold power-on + } + + // Auto-detect FEM type via GPIO2 default pull level. + // GC1109 CSD: internal pull-down → reads LOW + // KCT8103L CSD: internal pull-up → reads HIGH + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_EN); + pinMode(P_LORA_PA_EN, INPUT); + delay(1); + is_kct8103l_ = (digitalRead(P_LORA_PA_EN) == HIGH); + + // CSD/enable: HIGH for both FEM types + pinMode(P_LORA_PA_EN, OUTPUT); + digitalWrite(P_LORA_PA_EN, HIGH); + + if (is_kct8103l_) { + // V4.3 — KCT8103L: CTX on GPIO5 controls TX/RX path + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_CTX); + pinMode(P_LORA_PA_CTX, OUTPUT); + digitalWrite(P_LORA_PA_CTX, LOW); // RX mode (LNA enabled) + } else { + // V4.2 — GC1109: CPS on GPIO46 controls PA mode + pinMode(P_LORA_PA_TX_EN, OUTPUT); + digitalWrite(P_LORA_PA_TX_EN, LOW); // RX bypass mode } periph_power.begin(); + if (reason == ESP_RST_DEEPSLEEP) { long wakeup_source = esp_sleep_get_ext1_wakeup_status(); - if (wakeup_source & (1 << P_LORA_DIO_1)) { // received a LoRa packet (while in deep sleep) + if (wakeup_source & (1 << P_LORA_DIO_1)) { startup_reason = BD_STARTUP_RX_PACKET; } - rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); } } 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); + if (is_kct8103l_) { + digitalWrite(P_LORA_PA_CTX, HIGH); // CTX: TX path + } else { + digitalWrite(P_LORA_PA_TX_EN, HIGH); // CPS: full 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_TX_LED, LOW); + if (is_kct8103l_) { + digitalWrite(P_LORA_PA_CTX, LOW); // CTX: RX path (LNA on) + } else { + digitalWrite(P_LORA_PA_TX_EN, LOW); // CPS: bypass + } } void HeltecV4Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { @@ -57,10 +79,16 @@ void HeltecV4Board::begin() { rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); - // Hold GC1109 FEM pins during sleep to keep LNA active for RX wake + // Hold FEM pins during sleep to keep LNA active for RX wake rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_EN); + if (is_kct8103l_) { + // Hold CTX LOW during deep sleep for RX wake (LNA enabled) + digitalWrite(P_LORA_PA_CTX, LOW); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_CTX); + } + 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 } else { @@ -96,8 +124,8 @@ void HeltecV4Board::begin() { const char* HeltecV4Board::getManufacturerName() const { #ifdef HELTEC_LORA_V4_TFT - return "Heltec V4 TFT"; + return is_kct8103l_ ? "Heltec V4.3 TFT" : "Heltec V4 TFT"; #else - return "Heltec V4 OLED"; + return is_kct8103l_ ? "Heltec V4.3 OLED" : "Heltec V4 OLED"; #endif } diff --git a/variants/heltec_v4/HeltecV4Board.h b/variants/heltec_v4/HeltecV4Board.h index 745e8d8f3..8a2524a84 100644 --- a/variants/heltec_v4/HeltecV4Board.h +++ b/variants/heltec_v4/HeltecV4Board.h @@ -20,4 +20,6 @@ class HeltecV4Board : public ESP32Board { uint16_t getBattMilliVolts() override; const char* getManufacturerName() const override ; +private: + bool is_kct8103l_ = false; // true = V4.3 (KCT8103L), false = V4.2 (GC1109) }; diff --git a/variants/heltec_v4/platformio.ini b/variants/heltec_v4/platformio.ini index 71ffc2e6a..b8aedb0ba 100644 --- a/variants/heltec_v4/platformio.ini +++ b/variants/heltec_v4/platformio.ini @@ -20,6 +20,7 @@ build_flags = -D P_LORA_PA_POWER=7 ; VFEM_Ctrl - Power on GC1109 -D P_LORA_PA_EN=2 ; PA CSD - Enable GC1109 -D P_LORA_PA_TX_EN=46 ; PA CPS - GC1109 TX PA full(High) / bypass(Low) + -D P_LORA_PA_CTX=5 ; KCT8103L CTX pin (V4.3 only, auto-detected) -D PIN_USER_BTN=0 -D PIN_VEXT_EN=36 -D PIN_VEXT_EN_ACTIVE=HIGH