From da8e1556fca1fbc0e2bda4a73326617be7c90c3c Mon Sep 17 00:00:00 2001 From: MuddyPaws2 Date: Fri, 24 Jul 2026 19:02:12 -0500 Subject: [PATCH 1/7] Add initial variant and platformio configuration for Heltec WiFi LoRa 32 V4-R2 --- variants/esp32s3/heltec_v4_r2/pins_arduino.h | 58 ++++++++++ variants/esp32s3/heltec_v4_r2/platformio.ini | 107 +++++++++++++++++++ variants/esp32s3/heltec_v4_r2/variant.h | 86 +++++++++++++++ 3 files changed, 251 insertions(+) create mode 100644 variants/esp32s3/heltec_v4_r2/pins_arduino.h create mode 100644 variants/esp32s3/heltec_v4_r2/platformio.ini create mode 100644 variants/esp32s3/heltec_v4_r2/variant.h diff --git a/variants/esp32s3/heltec_v4_r2/pins_arduino.h b/variants/esp32s3/heltec_v4_r2/pins_arduino.h new file mode 100644 index 00000000000..5f439a45ab3 --- /dev/null +++ b/variants/esp32s3/heltec_v4_r2/pins_arduino.h @@ -0,0 +1,58 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 4; +static const uint8_t SCL = 3; + +static const uint8_t SS = 8; +static const uint8_t MOSI = 10; +static const uint8_t MISO = 11; +static const uint8_t SCK = 9; + +static const uint8_t A0 = 1; +static const uint8_t A1 = 2; +static const uint8_t A2 = 3; +static const uint8_t A3 = 4; +static const uint8_t A4 = 5; +static const uint8_t A5 = 6; +static const uint8_t A6 = 7; +static const uint8_t A7 = 8; +static const uint8_t A8 = 9; +static const uint8_t A9 = 10; +static const uint8_t A10 = 11; +static const uint8_t A11 = 12; +static const uint8_t A12 = 13; +static const uint8_t A13 = 14; +static const uint8_t A14 = 15; +static const uint8_t A15 = 16; +static const uint8_t A16 = 17; +static const uint8_t A17 = 18; +static const uint8_t A18 = 19; +static const uint8_t A19 = 20; + +static const uint8_t T1 = 1; +static const uint8_t T2 = 2; +static const uint8_t T3 = 3; +static const uint8_t T4 = 4; +static const uint8_t T5 = 5; +static const uint8_t T6 = 6; +static const uint8_t T7 = 7; +static const uint8_t T8 = 8; +static const uint8_t T9 = 9; +static const uint8_t T10 = 10; +static const uint8_t T11 = 11; +static const uint8_t T12 = 12; +static const uint8_t T13 = 13; +static const uint8_t T14 = 14; + +static const uint8_t Vext = 36; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini new file mode 100644 index 00000000000..f7395d7a24b --- /dev/null +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -0,0 +1,107 @@ +; Heltec WiFi LoRa 32 V4-R2 — ESP32-S3 + SX1262 +; External 2.8" ILI9341V SPI display (240x320) + FT6336G capacitive touch (I2C) +; No on-board LCD screen +; +; Display SPI wiring (SPI3_HOST): +; GPIO47 → SCK GPIO33 → MOSI GPIO26 → MISO +; GPIO34 → LCD_CS GPIO21 → LCD_RS GPIO48 → LCD_RST GPIO35 → LED/BL +; +; Touch I2C wiring (Wire, port 0): +; GPIO4 → CTP_SDA GPIO3 → CTP_SCL +; GPIO45 → CTP_RST not connected → CTP_INT (polling) + +[heltec_v4_r2_base] +extends = heltec_v4_base +build_flags = + ${heltec_v4_base.build_flags} + -D HELTEC_V4_R2 + -I variants/esp32s3/heltec_v4_r2 + +[env:heltec-v4-r2] +custom_meshtastic_hw_model = 110 +custom_meshtastic_hw_model_slug = HELTEC_V4 +custom_meshtastic_architecture = esp32-s3 +custom_meshtastic_actively_supported = true +custom_meshtastic_support_level = 2 +custom_meshtastic_display_name = Heltec V4 R2 ILI9341 Touch +custom_meshtastic_tags = Heltec +custom_meshtastic_requires_dfu = true +custom_meshtastic_partition_scheme = 16MB +custom_meshtastic_has_mui = true + +extends = heltec_v4_r2_base +build_flags = + ${heltec_v4_r2_base.build_flags} + ; ---- System ---- + -D CONFIG_ARDUHAL_LOG_COLORS + -D RADIOLIB_DEBUG_SPI=0 + -D RADIOLIB_DEBUG_PROTOCOL=0 + -D RADIOLIB_DEBUG_BASIC=0 + -D RADIOLIB_VERBOSE_ASSERT=0 + -D RADIOLIB_SPI_PARANOID=0 + -D CONFIG_DISABLE_HAL_LOCKS=1 + -D INPUTDRIVER_BUTTON_TYPE=0 + -D USE_LOG_DEBUG + -D LOG_DEBUG_INC=\"DebugConfiguration.h\" + -D USE_PACKET_API + ; ---- Display ---- + -D HAS_SCREEN=0 + -D HAS_TFT=1 + ; ---- LVGL / MUI ---- + -D LV_LVGL_H_INCLUDE_SIMPLE + -D LV_CONF_INCLUDE_SIMPLE + -D LV_COMP_CONF_INCLUDE_SIMPLE + -D LV_USE_SYSMON=0 + -D LV_USE_PROFILER=0 + -D LV_USE_PERF_MONITOR=0 + -D LV_USE_MEM_MONITOR=0 + -D LV_USE_LOG=0 + -D LV_BUILD_TEST=0 + ; ---- RAM / tile budget (2 MB PSRAM) ---- + -D MAP_TILES_GREY + -D RAM_SIZE=1432 + -D STBI_ARENA_SIZE=450000 + -D LV_CACHE_DEF_SIZE=0 + ; ---- LGFX Generic driver (device-ui) ---- + -D LGFX_DRIVER_TEMPLATE + -D LGFX_DRIVER=LGFX_GENERIC + -D GFX_DRIVER_INC=\"graphics/LGFX/LGFX_GENERIC.h\" + -D LGFX_CFG_HOST=SPI3_HOST + -D LGFX_PIN_SCK=47 + -D LGFX_PIN_MOSI=33 + -D LGFX_PIN_MISO=26 + -D LGFX_PIN_DC=21 + -D LGFX_PIN_CS=34 + -D LGFX_PIN_RST=48 + -D LGFX_PIN_BL=35 + -D LGFX_PANEL=ILI9341 + -D LGFX_SCREEN_WIDTH=240 + -D LGFX_SCREEN_HEIGHT=320 + -D VIEW_240x320 + -D DISPLAY_SET_RESOLUTION + -D DISPLAY_SIZE=240x320 + -D LGFX_ROTATION=0 + -D SPI_FREQUENCY=40000000 + -D SPI_READ_FREQUENCY=16000000 + -D BRIGHTNESS_DEFAULT=130 + ; ---- FT6336G capacitive touch (FT5x06-compatible, I2C address 0x38) ---- + -D HAS_TOUCHSCREEN=1 + -D LGFX_TOUCH=FT5x06 + -D LGFX_TOUCH_I2C_FREQ=400000 + -D LGFX_TOUCH_I2C_PORT=0 + -D LGFX_TOUCH_I2C_ADDR=0x38 + -D LGFX_TOUCH_I2C_SDA=4 + -D LGFX_TOUCH_I2C_SCL=3 + -D LGFX_TOUCH_RST=45 + -D LGFX_TOUCH_INT=-1 + -D LGFX_TOUCH_X_MIN=0 + -D LGFX_TOUCH_X_MAX=239 + -D LGFX_TOUCH_Y_MIN=0 + -D LGFX_TOUCH_Y_MAX=319 + -D LGFX_TOUCH_ROTATION=0 + +lib_deps = + ${heltec_v4_base.lib_deps} + ${device-ui_base.lib_deps} + # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX + lovyan03/LovyanGFX@1.2.26 diff --git a/variants/esp32s3/heltec_v4_r2/variant.h b/variants/esp32s3/heltec_v4_r2/variant.h new file mode 100644 index 00000000000..20efc231485 --- /dev/null +++ b/variants/esp32s3/heltec_v4_r2/variant.h @@ -0,0 +1,86 @@ +// Heltec WiFi LoRa 32 V4-R2 — ESP32-S3 + SX1262 +// External 2.8" ILI9341V SPI display (240x320) + FT6336G I2C capacitive touch +// No on-board LCD screen + +#define VEXT_ENABLE 36 // active low, powers the LoRa antenna boost +#define VEXT_ON_VALUE LOW +#define BUTTON_PIN 0 + +#define ADC_CTRL 37 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 1 // ADC1_CH0 — battery voltage via divider; pull ADC_CTRL high to read +#define ADC_CHANNEL ADC_CHANNEL_0 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 +#define ADC_MULTIPLIER 4.9 * 1.045 + +#define USE_SX1262 + +#define LORA_DIO0 -1 // not connected on SX1262 +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // not connected on PCB + +#define LORA_SCK 9 +#define LORA_MISO 11 +#define LORA_MOSI 10 +#define LORA_CS 8 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY LORA_DIO2 +#define SX126X_RESET LORA_RESET + +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +// Enable Traffic Management Module +#ifndef HAS_TRAFFIC_MANAGEMENT +#define HAS_TRAFFIC_MANAGEMENT 1 +#endif + +// ---- GC1109 RF FRONT END CONFIGURATION (V4.2) ---- +// CTX -> SX1262 DIO2 (automatic via SX126X_DIO2_AS_RF_SWITCH) +// CSD -> GPIO2, CPS -> GPIO46, VCC -> GPIO7 +#define LORA_PA_POWER 7 // VFEM_Ctrl +#define LORA_GC1109_PA_EN 2 // CSD — chip enable (HIGH=on) +#define LORA_GC1109_PA_TX_EN 46 // CPS — PA mode (HIGH=full PA) + +// ---- KCT8103L RF FRONT END CONFIGURATION (V4.3) ---- +// CPS -> SX1262 DIO2 (automatic via SX126X_DIO2_AS_RF_SWITCH) +// CSD -> GPIO2, CTX -> GPIO5, VCC -> GPIO7 +#define LORA_KCT8103L_PA_CSD 2 // CSD — chip enable (HIGH=on) +#define LORA_KCT8103L_PA_CTX 5 // CTX — RX bypass (HIGH) / RX LNA (LOW) + +/* + * GPS pins — L76K GNSS module + */ +#define GPS_L76K +#define PIN_GPS_RESET (42) +#define GPS_RESET_MODE LOW +#define PIN_GPS_EN (34) +#define GPS_EN_ACTIVE LOW +#define PERIPHERAL_WARMUP_MS 1000 +#define PIN_GPS_STANDBY (40) +#define PIN_GPS_PPS (41) +#define GPS_TX_PIN (38) // GNSS → CPU +#define GPS_RX_PIN (39) // CPU → GNSS +#define GPS_THREAD_INTERVAL 50 + +/* + * External display connections (set via LGFX_PIN_* in platformio.ini) + * + * Display SPI (SPI3_HOST): + * SCK → GPIO47 LCD_RS/DC → GPIO21 + * MOSI → GPIO33 CS → GPIO34 + * MISO → GPIO26 RST → GPIO48 + * BL → GPIO35 + * + * Touch I2C (Wire, port 0 — GPIO3/4): + * SDA → GPIO4 RST → GPIO45 + * SCL → GPIO3 INT → not connected (polling) + */ + +// I2C bus used for touch and peripheral scan +#define I2C_SDA 4 +#define I2C_SCL 3 From 60aff98cd2ef1dc61ed562d5573bc2b6ce9ccd6d Mon Sep 17 00:00:00 2001 From: MuddyPaws2 Date: Sat, 25 Jul 2026 12:38:45 -0500 Subject: [PATCH 2/7] heltec-v4-r2: fix base, PSRAM/SPI pins, display rotation, hw model mapping - Extend esp32s3_base instead of heltec_v4_base to avoid heltec_v4/variant.h shadowing the R2 header and causing TFTDisplay.cpp to compile incorrectly - LGFX_PIN_MISO=-1: GPIO26 is PSRAM CS on ESP32-S3, must not be used for SPI - LGFX_ROTATION=2, VIEW_320x240, DISPLAY_SIZE=320x240: working display orientation - Add HELTEC_V4_R2 to architecture.h hw model mapping (-> HELTEC_V4, model 110) - Add HELTEC_V4_R2 to NodeDB BT default-off list for MUI boards Note: lv_conf.h in meshtastic/device-ui also needs updating to guard LV_MEM_POOL_ALLOC with CONFIG_SPIRAM_USE_MALLOC detection (see separate PR). --- src/mesh/NodeDB.cpp | 3 ++- src/platform/esp32/architecture.h | 2 ++ variants/esp32s3/heltec_v4_r2/platformio.ini | 26 ++++++++++++++------ variants/esp32s3/heltec_v4_r2/variant.h | 4 +-- 4 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/mesh/NodeDB.cpp b/src/mesh/NodeDB.cpp index f46bbfcce48..e8803484eaf 100644 --- a/src/mesh/NodeDB.cpp +++ b/src/mesh/NodeDB.cpp @@ -1006,7 +1006,8 @@ void NodeDB::installDefaultConfig(bool preserveKey = false) strncpy(config.network.ntp_server, "meshtastic.pool.ntp.org", 32); #if (defined(T_DECK) || defined(T_WATCH_S3) || defined(UNPHONE) || defined(PICOMPUTER_S3) || defined(SENSECAP_INDICATOR) || \ - defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(RAK_WISMESH_TAP_V2)) && \ + defined(ELECROW_PANEL) || defined(HELTEC_V4_TFT) || defined(HELTEC_V4_R8_TFT) || defined(HELTEC_V4_R2) || \ + defined(RAK_WISMESH_TAP_V2)) && \ HAS_TFT // switch BT off by default; use TFT programming mode or hotkey to enable config.bluetooth.enabled = false; diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index e58a9b52008..4b7b4125487 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -200,6 +200,8 @@ #define HW_VENDOR meshtastic_HardwareModel_TBEAM_1_WATT #elif defined(T_LORA_PAGER) #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER +#elif defined(HELTEC_V4_R2) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(HELTEC_V4) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(M5STACK_UNITC6L) diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini index f7395d7a24b..0ad4e83fd0b 100644 --- a/variants/esp32s3/heltec_v4_r2/platformio.ini +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -9,12 +9,23 @@ ; Touch I2C wiring (Wire, port 0): ; GPIO4 → CTP_SDA GPIO3 → CTP_SCL ; GPIO45 → CTP_RST not connected → CTP_INT (polling) +; +; NOTE: this base intentionally extends esp32s3_base (not heltec_v4_base) so +; that only variants/esp32s3/heltec_v4_r2/variant.h is on the include path. +; Inheriting heltec_v4_base would prepend -I variants/esp32s3/heltec_v4 first, +; causing the V4's variant.h (which contains #if HAS_TFT → USE_TFTDISPLAY) to +; shadow the R2 header and incorrectly compile TFTDisplay.cpp. [heltec_v4_r2_base] -extends = heltec_v4_base +extends = esp32s3_base +board = heltec_v4 +board_check = true +board_build.partitions = default_16MB.csv build_flags = - ${heltec_v4_base.build_flags} + ${esp32s3_base.build_flags} + -D HELTEC_V4 -D HELTEC_V4_R2 + -D HAS_LORA_FEM=1 -I variants/esp32s3/heltec_v4_r2 [env:heltec-v4-r2] @@ -69,18 +80,19 @@ build_flags = -D LGFX_CFG_HOST=SPI3_HOST -D LGFX_PIN_SCK=47 -D LGFX_PIN_MOSI=33 - -D LGFX_PIN_MISO=26 + -D LGFX_PIN_MISO=-1 -D LGFX_PIN_DC=21 -D LGFX_PIN_CS=34 -D LGFX_PIN_RST=48 -D LGFX_PIN_BL=35 -D LGFX_PANEL=ILI9341 + -D LGFX_INVERT_COLOR=false -D LGFX_SCREEN_WIDTH=240 -D LGFX_SCREEN_HEIGHT=320 - -D VIEW_240x320 + -D VIEW_320x240 -D DISPLAY_SET_RESOLUTION - -D DISPLAY_SIZE=240x320 - -D LGFX_ROTATION=0 + -D DISPLAY_SIZE=320x240 + -D LGFX_ROTATION=2 -D SPI_FREQUENCY=40000000 -D SPI_READ_FREQUENCY=16000000 -D BRIGHTNESS_DEFAULT=130 @@ -101,7 +113,7 @@ build_flags = -D LGFX_TOUCH_ROTATION=0 lib_deps = - ${heltec_v4_base.lib_deps} + ${esp32s3_base.lib_deps} ${device-ui_base.lib_deps} # renovate: datasource=custom.pio depName=LovyanGFX packageName=lovyan03/library/LovyanGFX lovyan03/LovyanGFX@1.2.26 diff --git a/variants/esp32s3/heltec_v4_r2/variant.h b/variants/esp32s3/heltec_v4_r2/variant.h index 20efc231485..ecec3ef7c47 100644 --- a/variants/esp32s3/heltec_v4_r2/variant.h +++ b/variants/esp32s3/heltec_v4_r2/variant.h @@ -73,8 +73,8 @@ * Display SPI (SPI3_HOST): * SCK → GPIO47 LCD_RS/DC → GPIO21 * MOSI → GPIO33 CS → GPIO34 - * MISO → GPIO26 RST → GPIO48 - * BL → GPIO35 + * MISO → NC (-1) RST → GPIO48 [GPIO26 is PSRAM CS — do not use] + * BL → GPIO35 * * Touch I2C (Wire, port 0 — GPIO3/4): * SDA → GPIO4 RST → GPIO45 From a637aac6373ad1bc9b6cf9489166ee3e8c7707f8 Mon Sep 17 00:00:00 2001 From: MuddyPaws2 Date: Tue, 28 Jul 2026 09:09:50 -0500 Subject: [PATCH 3/7] Update LGFX rotation setting and add SPI interrupt flag in platformio configuration --- variants/esp32s3/heltec_v4_r2/platformio.ini | 23 +++++++++++++++----- variants/esp32s3/heltec_v4_r2/variant.h | 16 +++++++------- 2 files changed, 26 insertions(+), 13 deletions(-) diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini index 0ad4e83fd0b..0e4c3d72959 100644 --- a/variants/esp32s3/heltec_v4_r2/platformio.ini +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -3,13 +3,15 @@ ; No on-board LCD screen ; ; Display SPI wiring (SPI3_HOST): -; GPIO47 → SCK GPIO33 → MOSI GPIO26 → MISO -; GPIO34 → LCD_CS GPIO21 → LCD_RS GPIO48 → LCD_RST GPIO35 → LED/BL +; GPIO47 → SCK GPIO33 → MOSI GPIO5 → MISO +; GPIO34 → LCD_CS GPIO21 → LCD_RS GPIO48 → LCD_RST GPIO2 → LED/BL ; ; Touch I2C wiring (Wire, port 0): ; GPIO4 → CTP_SDA GPIO3 → CTP_SCL ; GPIO45 → CTP_RST not connected → CTP_INT (polling) ; +; GPIO6 → SD_CS (SD card shares SPI bus with display) +; ; NOTE: this base intentionally extends esp32s3_base (not heltec_v4_base) so ; that only variants/esp32s3/heltec_v4_r2/variant.h is on the include path. ; Inheriting heltec_v4_base would prepend -I variants/esp32s3/heltec_v4 first, @@ -80,11 +82,11 @@ build_flags = -D LGFX_CFG_HOST=SPI3_HOST -D LGFX_PIN_SCK=47 -D LGFX_PIN_MOSI=33 - -D LGFX_PIN_MISO=-1 + -D LGFX_PIN_MISO=5 -D LGFX_PIN_DC=21 -D LGFX_PIN_CS=34 -D LGFX_PIN_RST=48 - -D LGFX_PIN_BL=35 + -D LGFX_PIN_BL=7 -D LGFX_PANEL=ILI9341 -D LGFX_INVERT_COLOR=false -D LGFX_SCREEN_WIDTH=240 @@ -92,7 +94,7 @@ build_flags = -D VIEW_320x240 -D DISPLAY_SET_RESOLUTION -D DISPLAY_SIZE=320x240 - -D LGFX_ROTATION=2 + -D LGFX_ROTATION=0 -D SPI_FREQUENCY=40000000 -D SPI_READ_FREQUENCY=16000000 -D BRIGHTNESS_DEFAULT=130 @@ -111,6 +113,17 @@ build_flags = -D LGFX_TOUCH_Y_MIN=0 -D LGFX_TOUCH_Y_MAX=319 -D LGFX_TOUCH_ROTATION=0 + ; ---- SD card (GPIO6 → SD_CS, shares SPI3_HOST bus with display) ---- + -D HAS_SDCARD + -D SDCARD_USE_SPI3 + -D SDCARD_USER_SPI_BEGIN + -D SPI_MOSI=LGFX_PIN_MOSI + -D SPI_SCK=LGFX_PIN_SCK + -D SPI_MISO=LGFX_PIN_MISO + -D SPI_CS=6 + -D SDCARD_CS=SPI_CS + -D SD_SPI_FREQUENCY=SPI_FREQUENCY + lib_deps = ${esp32s3_base.lib_deps} diff --git a/variants/esp32s3/heltec_v4_r2/variant.h b/variants/esp32s3/heltec_v4_r2/variant.h index ecec3ef7c47..88eaf6d0477 100644 --- a/variants/esp32s3/heltec_v4_r2/variant.h +++ b/variants/esp32s3/heltec_v4_r2/variant.h @@ -16,7 +16,7 @@ #define USE_SX1262 #define LORA_DIO0 -1 // not connected on SX1262 -#define LORA_RESET 12 +#define LORA_RESET 12 // SX1262 RESET #define LORA_DIO1 14 // SX1262 IRQ #define LORA_DIO2 13 // SX1262 BUSY #define LORA_DIO3 // not connected on PCB @@ -56,15 +56,15 @@ * GPS pins — L76K GNSS module */ #define GPS_L76K -#define PIN_GPS_RESET (42) +#define PIN_GPS_RESET (42) // GNSS_RESET #define GPS_RESET_MODE LOW -#define PIN_GPS_EN (34) +#define PIN_GPS_EN (34) // **LCD_CS** #define GPS_EN_ACTIVE LOW #define PERIPHERAL_WARMUP_MS 1000 -#define PIN_GPS_STANDBY (40) -#define PIN_GPS_PPS (41) -#define GPS_TX_PIN (38) // GNSS → CPU -#define GPS_RX_PIN (39) // CPU → GNSS +#define PIN_GPS_STANDBY (40) // GNSS_WAKEUP +#define PIN_GPS_PPS (41) // GNSS_PPS +#define GPS_TX_PIN (38) // GNSS → CPU (GNSS_RX) +#define GPS_RX_PIN (39) // CPU → GNSS (GNSS_TX) #define GPS_THREAD_INTERVAL 50 /* @@ -74,7 +74,7 @@ * SCK → GPIO47 LCD_RS/DC → GPIO21 * MOSI → GPIO33 CS → GPIO34 * MISO → NC (-1) RST → GPIO48 [GPIO26 is PSRAM CS — do not use] - * BL → GPIO35 + * BL → GPIO35 * * Touch I2C (Wire, port 0 — GPIO3/4): * SDA → GPIO4 RST → GPIO45 From 9d119f67bc6bbd490bb1edc8f05f28c44e3cb8ab Mon Sep 17 00:00:00 2001 From: MuddyPaws2 <136486863+MuddyPaws2@users.noreply.github.com> Date: Tue, 28 Jul 2026 12:31:13 -0500 Subject: [PATCH 4/7] fix: update pin assignments for I2C and SPI interfaces in Heltec V4-R2 variant --- variants/esp32s3/heltec_v4_r2/pins_arduino.h | 4 ++-- variants/esp32s3/heltec_v4_r2/platformio.ini | 24 ++++++++++---------- variants/esp32s3/heltec_v4_r2/variant.h | 12 +++++----- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/variants/esp32s3/heltec_v4_r2/pins_arduino.h b/variants/esp32s3/heltec_v4_r2/pins_arduino.h index 5f439a45ab3..d75d10546a8 100644 --- a/variants/esp32s3/heltec_v4_r2/pins_arduino.h +++ b/variants/esp32s3/heltec_v4_r2/pins_arduino.h @@ -9,8 +9,8 @@ static const uint8_t TX = 43; static const uint8_t RX = 44; -static const uint8_t SDA = 4; -static const uint8_t SCL = 3; +static const uint8_t SDA = 17; +static const uint8_t SCL = 18; static const uint8_t SS = 8; static const uint8_t MOSI = 10; diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini index 0e4c3d72959..fade4c32276 100644 --- a/variants/esp32s3/heltec_v4_r2/platformio.ini +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -3,12 +3,12 @@ ; No on-board LCD screen ; ; Display SPI wiring (SPI3_HOST): -; GPIO47 → SCK GPIO33 → MOSI GPIO5 → MISO -; GPIO34 → LCD_CS GPIO21 → LCD_RS GPIO48 → LCD_RST GPIO2 → LED/BL +; GPIO47 → SCK GPIO33 → MOSI GPIO4 → MISO +; GPIO34 → LCD_CS GPIO21 → LCD_RS GPIO48 → LCD_RST GPIO3 → LED/BL ; ; Touch I2C wiring (Wire, port 0): -; GPIO4 → CTP_SDA GPIO3 → CTP_SCL -; GPIO45 → CTP_RST not connected → CTP_INT (polling) +; GPIO17 → CTP_SDA GPIO18 → CTP_SCL +; GPIO45 → CTP_RST GPIO15 → CTP_INT (polling) ; ; GPIO6 → SD_CS (SD card shares SPI bus with display) ; @@ -82,11 +82,11 @@ build_flags = -D LGFX_CFG_HOST=SPI3_HOST -D LGFX_PIN_SCK=47 -D LGFX_PIN_MOSI=33 - -D LGFX_PIN_MISO=5 + -D LGFX_PIN_MISO=4 -D LGFX_PIN_DC=21 - -D LGFX_PIN_CS=34 + -D LGFX_PIN_CS=45 -D LGFX_PIN_RST=48 - -D LGFX_PIN_BL=7 + -D LGFX_PIN_BL=3 -D LGFX_PANEL=ILI9341 -D LGFX_INVERT_COLOR=false -D LGFX_SCREEN_WIDTH=240 @@ -104,10 +104,10 @@ build_flags = -D LGFX_TOUCH_I2C_FREQ=400000 -D LGFX_TOUCH_I2C_PORT=0 -D LGFX_TOUCH_I2C_ADDR=0x38 - -D LGFX_TOUCH_I2C_SDA=4 - -D LGFX_TOUCH_I2C_SCL=3 - -D LGFX_TOUCH_RST=45 - -D LGFX_TOUCH_INT=-1 + -D LGFX_TOUCH_I2C_SDA=17 + -D LGFX_TOUCH_I2C_SCL=18 + -D LGFX_TOUCH_RST=16 + -D LGFX_TOUCH_INT=15 -D LGFX_TOUCH_X_MIN=0 -D LGFX_TOUCH_X_MAX=239 -D LGFX_TOUCH_Y_MIN=0 @@ -120,7 +120,7 @@ build_flags = -D SPI_MOSI=LGFX_PIN_MOSI -D SPI_SCK=LGFX_PIN_SCK -D SPI_MISO=LGFX_PIN_MISO - -D SPI_CS=6 + -D SPI_CS=5 -D SDCARD_CS=SPI_CS -D SD_SPI_FREQUENCY=SPI_FREQUENCY diff --git a/variants/esp32s3/heltec_v4_r2/variant.h b/variants/esp32s3/heltec_v4_r2/variant.h index 88eaf6d0477..09550a33e38 100644 --- a/variants/esp32s3/heltec_v4_r2/variant.h +++ b/variants/esp32s3/heltec_v4_r2/variant.h @@ -58,7 +58,7 @@ #define GPS_L76K #define PIN_GPS_RESET (42) // GNSS_RESET #define GPS_RESET_MODE LOW -#define PIN_GPS_EN (34) // **LCD_CS** +#define PIN_GPS_EN (34) // **LISTED AS SPIIO5,FSPICS0, SUBSPICS0 IN DOCUMENTATION** #define GPS_EN_ACTIVE LOW #define PERIPHERAL_WARMUP_MS 1000 #define PIN_GPS_STANDBY (40) // GNSS_WAKEUP @@ -72,9 +72,9 @@ * * Display SPI (SPI3_HOST): * SCK → GPIO47 LCD_RS/DC → GPIO21 - * MOSI → GPIO33 CS → GPIO34 - * MISO → NC (-1) RST → GPIO48 [GPIO26 is PSRAM CS — do not use] - * BL → GPIO35 + * MOSI → GPIO33 CS → GPIO45 + * MISO → GPIO04 RST → GPIO48 [GPIO26 is PSRAM CS — do not use] + * BL → GPIO3 * * Touch I2C (Wire, port 0 — GPIO3/4): * SDA → GPIO4 RST → GPIO45 @@ -82,5 +82,5 @@ */ // I2C bus used for touch and peripheral scan -#define I2C_SDA 4 -#define I2C_SCL 3 +#define I2C_SDA 17 +#define I2C_SCL 18 From 6e4f38c82fcdf8795b55a20da4cd0155d79a16bd Mon Sep 17 00:00:00 2001 From: MuddyPaws2 <136486863+MuddyPaws2@users.noreply.github.com> Date: Tue, 28 Jul 2026 17:23:26 -0500 Subject: [PATCH 5/7] fix: update SD card CS pin and correct I2C touch pin assignments for Heltec V4-R2 --- .vscode/settings.json | 3 ++- variants/esp32s3/heltec_v4_r2/platformio.ini | 2 +- variants/esp32s3/heltec_v4_r2/variant.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 81deca8f999..057e1516e09 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,5 +10,6 @@ }, "[powershell]": { "editor.defaultFormatter": "ms-vscode.powershell" - } + }, + "idf.currentSetup": "C:\\esp\\v6.0.1\\esp-idf" } diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini index fade4c32276..8a4b02c28e9 100644 --- a/variants/esp32s3/heltec_v4_r2/platformio.ini +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -113,7 +113,7 @@ build_flags = -D LGFX_TOUCH_Y_MIN=0 -D LGFX_TOUCH_Y_MAX=319 -D LGFX_TOUCH_ROTATION=0 - ; ---- SD card (GPIO6 → SD_CS, shares SPI3_HOST bus with display) ---- + ; ---- SD card (GPIO5 → SD_CS, shares SPI3_HOST bus with display) ---- -D HAS_SDCARD -D SDCARD_USE_SPI3 -D SDCARD_USER_SPI_BEGIN diff --git a/variants/esp32s3/heltec_v4_r2/variant.h b/variants/esp32s3/heltec_v4_r2/variant.h index 09550a33e38..a3aeb98bc79 100644 --- a/variants/esp32s3/heltec_v4_r2/variant.h +++ b/variants/esp32s3/heltec_v4_r2/variant.h @@ -78,7 +78,7 @@ * * Touch I2C (Wire, port 0 — GPIO3/4): * SDA → GPIO4 RST → GPIO45 - * SCL → GPIO3 INT → not connected (polling) + * SCL → GPIO18 INT → GPIO15 */ // I2C bus used for touch and peripheral scan From a1cd5e2ef7f37e821e04d0afac4e227d813bd5a4 Mon Sep 17 00:00:00 2001 From: MuddyPaws2 Date: Tue, 28 Jul 2026 18:09:51 -0500 Subject: [PATCH 6/7] fix: update SD card GPIO assignment and SPI interface in platformio configuration --- variants/esp32s3/heltec_v4_r2/platformio.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini index fade4c32276..73f4ce64956 100644 --- a/variants/esp32s3/heltec_v4_r2/platformio.ini +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -113,9 +113,9 @@ build_flags = -D LGFX_TOUCH_Y_MIN=0 -D LGFX_TOUCH_Y_MAX=319 -D LGFX_TOUCH_ROTATION=0 - ; ---- SD card (GPIO6 → SD_CS, shares SPI3_HOST bus with display) ---- + ; ---- SD card (GPIO5 → SD_CS, shares SPI3_HOST/HSPI bus with display) ---- -D HAS_SDCARD - -D SDCARD_USE_SPI3 + -D SDCARD_USE_SPI1 -D SDCARD_USER_SPI_BEGIN -D SPI_MOSI=LGFX_PIN_MOSI -D SPI_SCK=LGFX_PIN_SCK From 4327838900b0fc2cb3d624aa10a0386f53672142 Mon Sep 17 00:00:00 2001 From: MuddyPaws2 Date: Wed, 29 Jul 2026 03:45:06 -0500 Subject: [PATCH 7/7] fix: improve ESP32 ISR handling and streamline PacketAPI logic --- src/mesh/RadioInterface.cpp | 10 ++++++ src/mesh/api/PacketAPI.cpp | 36 ++++---------------- src/mesh/api/PacketAPI.h | 2 -- variants/esp32s3/heltec_v4_r2/platformio.ini | 22 ++++++------ 4 files changed, 29 insertions(+), 41 deletions(-) diff --git a/src/mesh/RadioInterface.cpp b/src/mesh/RadioInterface.cpp index 043bd59eff7..a7eb2be82c9 100644 --- a/src/mesh/RadioInterface.cpp +++ b/src/mesh/RadioInterface.cpp @@ -20,6 +20,9 @@ #include "meshUtils.h" // for pow_of_2 #include "sleep.h" #include +#ifdef ARCH_ESP32 +#include "driver/gpio.h" +#endif #include #include #include @@ -374,6 +377,13 @@ std::unique_ptr initLoRa() { std::unique_ptr rIf = nullptr; +#ifdef ARCH_ESP32 + // RadioLib calls detachInterrupt() on the IRQ pin before attaching a new one. On ESP32 this + // invokes gpio_isr_handler_remove() which requires gpio_install_isr_service() to have been + // called first. Install it here so the call is a no-op if it arrives again later. + gpio_install_isr_service(0); +#endif + #if ARCH_PORTDUINO SPISettings loraSpiSettings(portduino_config.spiSpeed, MSBFIRST, SPI_MODE0); #else diff --git a/src/mesh/api/PacketAPI.cpp b/src/mesh/api/PacketAPI.cpp index 3f145bfe3f1..a7d6939f5a8 100644 --- a/src/mesh/api/PacketAPI.cpp +++ b/src/mesh/api/PacketAPI.cpp @@ -17,26 +17,19 @@ PacketAPI *PacketAPI::create(PacketServer *_server) } PacketAPI::PacketAPI(PacketServer *_server) - : concurrency::OSThread("PacketAPI"), isConnected(false), programmingMode(false), server(_server) + : concurrency::OSThread("PacketAPI"), isConnected(false), server(_server) { api_type = TYPE_PACKET; } int32_t PacketAPI::runOnce() { - bool success = false; -#ifndef ARCH_PORTDUINO - if (config.bluetooth.enabled) { - if (!programmingMode) { - // in programmingMode we don't send any packets to the client except this one notify - programmingMode = true; - success = notifyProgrammingMode(); - } - } else -#endif - { - success = sendPacket(); - } + // Always run the full config sync regardless of bluetooth state. The device-ui (MUI) must + // receive deviceuiConfig first so its state advances past eBootScreenDone before the + // bluetooth config arrives; only then does updateBluetoothConfig() skip enterProgrammingMode(). + // Sending only the bluetooth config via notifyProgrammingMode() when BT is enabled caused the + // TFT screen to be permanently stuck on the ">> Programming mode <<" screen. + bool success = sendPacket(); success |= receivePacket(); return success ? 10 : 50; } @@ -107,21 +100,6 @@ bool PacketAPI::sendPacket(void) return false; } -bool PacketAPI::notifyProgrammingMode(void) -{ - // tell the client we are in programming mode by sending only the bluetooth config state - LOG_INFO("force client into programmingMode"); - memset(&fromRadioScratch, 0, sizeof(fromRadioScratch)); - fromRadioScratch.id = nodeDB->getNodeNum(); - fromRadioScratch.which_payload_variant = meshtastic_FromRadio_config_tag; - fromRadioScratch.config.which_payload_variant = meshtastic_Config_bluetooth_tag; - fromRadioScratch.config.payload_variant.bluetooth = config.bluetooth; - return server->sendPacket(DataPacket(0, fromRadioScratch)); -} - -/** - * return true if we got (once!) contact from our client and the server send queue is not full - */ bool PacketAPI::checkIsConnected() { isConnected |= server->hasData(); diff --git a/src/mesh/api/PacketAPI.h b/src/mesh/api/PacketAPI.h index aececf85e0d..cfcafd04405 100644 --- a/src/mesh/api/PacketAPI.h +++ b/src/mesh/api/PacketAPI.h @@ -27,10 +27,8 @@ class PacketAPI : public PhoneAPI, public concurrency::OSThread private: bool receivePacket(void); bool sendPacket(void); - bool notifyProgrammingMode(void); bool isConnected; - bool programmingMode; PacketServer *server; uint8_t txBuf[MAX_TO_FROM_RADIO_SIZE] = {0}; // dummy buf to obey PhoneAPI }; diff --git a/variants/esp32s3/heltec_v4_r2/platformio.ini b/variants/esp32s3/heltec_v4_r2/platformio.ini index 73f4ce64956..b1f34cdf36d 100644 --- a/variants/esp32s3/heltec_v4_r2/platformio.ini +++ b/variants/esp32s3/heltec_v4_r2/platformio.ini @@ -113,16 +113,18 @@ build_flags = -D LGFX_TOUCH_Y_MIN=0 -D LGFX_TOUCH_Y_MAX=319 -D LGFX_TOUCH_ROTATION=0 - ; ---- SD card (GPIO5 → SD_CS, shares SPI3_HOST/HSPI bus with display) ---- - -D HAS_SDCARD - -D SDCARD_USE_SPI1 - -D SDCARD_USER_SPI_BEGIN - -D SPI_MOSI=LGFX_PIN_MOSI - -D SPI_SCK=LGFX_PIN_SCK - -D SPI_MISO=LGFX_PIN_MISO - -D SPI_CS=5 - -D SDCARD_CS=SPI_CS - -D SD_SPI_FREQUENCY=SPI_FREQUENCY + ; ---- SD card ---- + ; SD card support is disabled: GPIO5 shares the SPI3_HOST bus with the ILI9341 + ; display (LovyanGFX uses spi_bus_initialize() directly). Enabling SDCARD_USE_SPI1 + ; causes setupSDCard() to call SPI_HSPI.begin() before LovyanGFX initialises the + ; bus, producing addApbChangeCallback duplicate errors that corrupt the display SPI + ; and prevent the boot screen from advancing to the main UI. + ; -D HAS_SDCARD + ; -D SDCARD_USE_SPI1 + ; -D SDCARD_USER_SPI_BEGIN + ; -D SPI_CS=5 + ; -D SDCARD_CS=SPI_CS + ; -D SD_SPI_FREQUENCY=SPI_FREQUENCY lib_deps =