From 2010871e4ba1167ca0a16770245af99e837f25f3 Mon Sep 17 00:00:00 2001 From: Markus <974709+Links2004@users.noreply.github.com> Date: Sun, 21 Sep 2025 13:22:29 +0200 Subject: [PATCH 01/10] Fix Rotary Encoder Button (#8001) this fixes the Rotary Encoder Button, currenlty its not working at all. Currently the action `ROTARY_ACTION_PRESSED` is only triggerd with a IRQ on RISING, which results in nothing since the function detects the "not longer" pressed button --> no action. the `ROTARY_ACTION_PRESSED` implementation needs to be called on both edges (on press and release of the button) changing the interupt setting to `CHANGE` fixes the problem. --- src/input/RotaryEncoderInterruptBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 980057ce4..c315f23d9 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -27,7 +27,7 @@ void RotaryEncoderInterruptBase::init( if (!isRAK || pinPress != 0) { pinMode(pinPress, INPUT_PULLUP); - attachInterrupt(pinPress, onIntPress, RISING); + attachInterrupt(pinPress, onIntPress, CHANGE); } if (!isRAK || this->_pinA != 0) { pinMode(this->_pinA, INPUT_PULLUP); From c42513d7c8a22106da4d1ab83ff8ab296d9bfb38 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 21 Sep 2025 06:25:32 -0500 Subject: [PATCH 02/10] Update RadioLib to v7.3.0 (#8065) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platformio.ini b/platformio.ini index 941e33beb..5da8c2e67 100644 --- a/platformio.ini +++ b/platformio.ini @@ -114,7 +114,7 @@ lib_deps = [radiolib_base] lib_deps = # renovate: datasource=custom.pio depName=RadioLib packageName=jgromes/library/RadioLib - jgromes/RadioLib@7.2.1 + jgromes/RadioLib@7.3.0 [device-ui_base] lib_deps = From 5701755608394c5f65c16967950e2e1b22a4f498 Mon Sep 17 00:00:00 2001 From: Tom <116762865+NomDeTom@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:27:39 +0100 Subject: [PATCH 03/10] Add another seeed_xiao_nrf52840_kit build environment for I2C pinout (#8036) * Update platformio.ini * Remove some more extraneous lines --- variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini index 623eace71..4c68b40e8 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini @@ -16,3 +16,11 @@ lib_deps = debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink + +; Seeed Xiao BLE but with GPS undefined, and therefore i2c active +[env:seeed_xiao_nrf52840_kit_i2c] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} + -DSEEED_XIAO_NRF52840_KIT +build_unflags = -DGPS_L76K From 27b07cd1c52b2c166b547f876a4cf62041d25694 Mon Sep 17 00:00:00 2001 From: Markus <974709+Links2004@users.noreply.github.com> Date: Sun, 21 Sep 2025 13:22:29 +0200 Subject: [PATCH 04/10] Fix Rotary Encoder Button (#8001) this fixes the Rotary Encoder Button, currenlty its not working at all. Currently the action `ROTARY_ACTION_PRESSED` is only triggerd with a IRQ on RISING, which results in nothing since the function detects the "not longer" pressed button --> no action. the `ROTARY_ACTION_PRESSED` implementation needs to be called on both edges (on press and release of the button) changing the interupt setting to `CHANGE` fixes the problem. --- src/input/RotaryEncoderInterruptBase.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/input/RotaryEncoderInterruptBase.cpp b/src/input/RotaryEncoderInterruptBase.cpp index 598353f98..f31da8c21 100644 --- a/src/input/RotaryEncoderInterruptBase.cpp +++ b/src/input/RotaryEncoderInterruptBase.cpp @@ -25,7 +25,7 @@ void RotaryEncoderInterruptBase::init( if (!isRAK || pinPress != 0) { pinMode(pinPress, INPUT_PULLUP); - attachInterrupt(pinPress, onIntPress, RISING); + attachInterrupt(pinPress, onIntPress, CHANGE); } if (!isRAK || this->_pinA != 0) { pinMode(this->_pinA, INPUT_PULLUP); From d1fd102952e3580d563be56f6b48d6862d398b9f Mon Sep 17 00:00:00 2001 From: Tom <116762865+NomDeTom@users.noreply.github.com> Date: Sun, 21 Sep 2025 12:27:39 +0100 Subject: [PATCH 05/10] Add another seeed_xiao_nrf52840_kit build environment for I2C pinout (#8036) * Update platformio.ini * Remove some more extraneous lines --- variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini index 623eace71..4c68b40e8 100644 --- a/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini +++ b/variants/nrf52840/seeed_xiao_nrf52840_kit/platformio.ini @@ -16,3 +16,11 @@ lib_deps = debug_tool = jlink ; If not set we will default to uploading over serial (first it forces bootloader entry by talking 1200bps to cdcacm) ;upload_protocol = jlink + +; Seeed Xiao BLE but with GPS undefined, and therefore i2c active +[env:seeed_xiao_nrf52840_kit_i2c] +extends = env:seeed_xiao_nrf52840_kit +board_level = extra +build_flags = ${env:seeed_xiao_nrf52840_kit.build_flags} + -DSEEED_XIAO_NRF52840_KIT +build_unflags = -DGPS_L76K From 11eb4a5b9011caabc76826cb85f28d947429d9c4 Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Sun, 21 Sep 2025 20:03:44 +0800 Subject: [PATCH 06/10] Add heltec_v4 board. (#7845) * add heltec_v4 board. * Update variants/esp32s3/heltec_v4/platformio.ini Co-authored-by: Austin * Limit the maximum output power. * Trunk fixes Fixes formatting to match meshtastic trunk linter. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Meadors Co-authored-by: Austin Co-authored-by: Tom Fifield Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boards/heltec_v4.json | 43 ++++++++++++++ src/mesh/SX126xInterface.cpp | 36 +++++++++++- src/mesh/SX126xInterface.h | 4 ++ src/platform/esp32/architecture.h | 2 + src/sleep.cpp | 6 ++ variants/esp32s3/heltec_v4/pins_arduino.h | 70 +++++++++++++++++++++++ variants/esp32s3/heltec_v4/platformio.ini | 10 ++++ variants/esp32s3/heltec_v4/variant.h | 58 +++++++++++++++++++ 8 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 boards/heltec_v4.json create mode 100644 variants/esp32s3/heltec_v4/pins_arduino.h create mode 100644 variants/esp32s3/heltec_v4/platformio.ini create mode 100644 variants/esp32s3/heltec_v4/variant.h diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json new file mode 100644 index 000000000..8eac3a9b2 --- /dev/null +++ b/boards/heltec_v4.json @@ -0,0 +1,43 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_qspi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "qspi", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_v4" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 2097152, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/", + "vendor": "heltec" +} diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 49dc562d4..3fc2562b3 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -52,6 +52,16 @@ template bool SX126xInterface::init() pinMode(SX126X_POWER_EN, OUTPUT); #endif +#ifdef HELTEC_V4 + pinMode(LORA_PA_POWER, OUTPUT); + digitalWrite(LORA_PA_POWER, HIGH); + + pinMode(LORA_PA_EN, OUTPUT); + digitalWrite(LORA_PA_EN, LOW); + pinMode(LORA_PA_TX_EN, OUTPUT); + digitalWrite(LORA_PA_TX_EN, LOW); +#endif + #if ARCH_PORTDUINO tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000; if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) { @@ -63,7 +73,7 @@ template bool SX126xInterface::init() LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage"); else LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage); - + setTransmitEnable(false); // FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? @@ -259,6 +269,7 @@ template void SX126xInterface::addReceiveMetadata(meshtastic_Mes */ template void SX126xInterface::configHardwareForSend() { + setTransmitEnable(true); RadioLibInterface::configHardwareForSend(); } @@ -271,6 +282,7 @@ template void SX126xInterface::startReceive() sleep(); #else + setTransmitEnable(false); setStandby(); // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. @@ -298,7 +310,7 @@ template bool SX126xInterface::isChannelActive() .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}}; int16_t result; - + setTransmitEnable(false); setStandby(); result = lora.scanChannel(cfg); if (result == RADIOLIB_LORA_DETECTED) @@ -337,6 +349,26 @@ template bool SX126xInterface::sleep() digitalWrite(SX126X_POWER_EN, LOW); #endif +#ifdef HELTEC_V4 + /* + * 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); +#endif return true; } + +/** Some boards require GPIO control of tx vs rx paths */ +template void SX126xInterface::setTransmitEnable(bool txon) +{ +#ifdef HELTEC_V4 + digitalWrite(LORA_PA_POWER, HIGH); + digitalWrite(LORA_PA_EN, HIGH); + digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); +#endif +} + #endif \ No newline at end of file diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index 47b07c284..dc7024daa 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -71,5 +71,9 @@ template class SX126xInterface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; + + private: + /** Some boards require GPIO control of tx vs rx paths */ + void setTransmitEnable(bool txon); }; #endif \ No newline at end of file diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index a87391739..6b658c2a3 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -197,6 +197,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #elif defined(T_LORA_PAGER) #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER +#elif defined(HELTEC_V4) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(M5STACK_UNITC6L) #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 83597e349..d6eb865e5 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -545,6 +545,12 @@ void enableLoraInterrupt() gpio_pullup_en((gpio_num_t)LORA_CS); #endif +#ifdef HELTEC_V4 + gpio_pullup_en((gpio_num_t)LORA_PA_POWER); + gpio_pullup_en((gpio_num_t)LORA_PA_EN); + gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); +#endif + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1); gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h new file mode 100644 index 000000000..45561b4b5 --- /dev/null +++ b/variants/esp32s3/heltec_v4/pins_arduino.h @@ -0,0 +1,70 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 3; +static const uint8_t SCL = 4; + +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; +static const uint8_t LED = 35; +static const uint8_t RST_OLED = 21; +static const uint8_t SCL_OLED = 18; +static const uint8_t SDA_OLED = 17; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini new file mode 100644 index 000000000..0177342a4 --- /dev/null +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -0,0 +1,10 @@ +[env:heltec-v4] +extends = esp32s3_base +board = heltec_v4 +board_check = true +build_flags = + ${esp32s3_base.build_flags} + -D HELTEC_V4 + -I variants/esp32s3/heltec_v4 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -D SX126X_MAX_POWER=11 diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h new file mode 100644 index 000000000..2b6b7af3d --- /dev/null +++ b/variants/esp32s3/heltec_v4/variant.h @@ -0,0 +1,58 @@ +#define LED_PIN 35 + +#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver) + +#define RESET_OLED 21 +#define I2C_SDA 17 // I2C pins for this board +#define I2C_SCL 18 + +#define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + +#define ADC_CTRL 37 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 * 1.045 + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled + +#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 + +#define LORA_PA_POWER 7 // power en +#define LORA_PA_EN 2 +#define LORA_PA_TX_EN 46 // enable tx + +/* + * GPS pins + */ +#define GPS_L76K +#define PIN_GPS_RESET (42) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +#define GPS_RESET_MODE LOW +#define PIN_GPS_EN (34) +#define GPS_EN_ACTIVE LOW +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define PIN_GPS_STANDBY (40) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (41) +// Seems to be missing on this new board +#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS +#define GPS_THREAD_INTERVAL 50 \ No newline at end of file From cea9e1238bf48b766e38984f2aa326e2ce821073 Mon Sep 17 00:00:00 2001 From: Quency-D <55523105+Quency-D@users.noreply.github.com> Date: Sun, 21 Sep 2025 20:03:44 +0800 Subject: [PATCH 07/10] Add heltec_v4 board. (#7845) * add heltec_v4 board. * Update variants/esp32s3/heltec_v4/platformio.ini Co-authored-by: Austin * Limit the maximum output power. * Trunk fixes Fixes formatting to match meshtastic trunk linter. * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Ben Meadors Co-authored-by: Austin Co-authored-by: Tom Fifield Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- boards/heltec_v4.json | 43 ++++++++++++++ src/mesh/SX126xInterface.cpp | 36 +++++++++++- src/mesh/SX126xInterface.h | 4 ++ src/platform/esp32/architecture.h | 2 + src/sleep.cpp | 6 ++ variants/esp32s3/heltec_v4/pins_arduino.h | 70 +++++++++++++++++++++++ variants/esp32s3/heltec_v4/platformio.ini | 10 ++++ variants/esp32s3/heltec_v4/variant.h | 58 +++++++++++++++++++ 8 files changed, 227 insertions(+), 2 deletions(-) create mode 100644 boards/heltec_v4.json create mode 100644 variants/esp32s3/heltec_v4/pins_arduino.h create mode 100644 variants/esp32s3/heltec_v4/platformio.ini create mode 100644 variants/esp32s3/heltec_v4/variant.h diff --git a/boards/heltec_v4.json b/boards/heltec_v4.json new file mode 100644 index 000000000..8eac3a9b2 --- /dev/null +++ b/boards/heltec_v4.json @@ -0,0 +1,43 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_qspi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=0", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "qspi", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_v4" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "heltec_wifi_lora_32 v4 (16 MB FLASH, 2 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 2097152, + "maximum_size": 16777216, + "use_1200bps_touch": true, + "wait_for_upload_port": true, + "require_upload_port": true, + "speed": 921600 + }, + "url": "https://heltec.org/", + "vendor": "heltec" +} diff --git a/src/mesh/SX126xInterface.cpp b/src/mesh/SX126xInterface.cpp index 49dc562d4..3fc2562b3 100644 --- a/src/mesh/SX126xInterface.cpp +++ b/src/mesh/SX126xInterface.cpp @@ -52,6 +52,16 @@ template bool SX126xInterface::init() pinMode(SX126X_POWER_EN, OUTPUT); #endif +#ifdef HELTEC_V4 + pinMode(LORA_PA_POWER, OUTPUT); + digitalWrite(LORA_PA_POWER, HIGH); + + pinMode(LORA_PA_EN, OUTPUT); + digitalWrite(LORA_PA_EN, LOW); + pinMode(LORA_PA_TX_EN, OUTPUT); + digitalWrite(LORA_PA_TX_EN, LOW); +#endif + #if ARCH_PORTDUINO tcxoVoltage = (float)portduino_config.dio3_tcxo_voltage / 1000; if (portduino_config.lora_sx126x_ant_sw_pin.pin != RADIOLIB_NC) { @@ -63,7 +73,7 @@ template bool SX126xInterface::init() LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE not defined, not using DIO3 as TCXO reference voltage"); else LOG_DEBUG("SX126X_DIO3_TCXO_VOLTAGE defined, using DIO3 as TCXO reference voltage at %f V", tcxoVoltage); - + setTransmitEnable(false); // FIXME: May want to set depending on a definition, currently all SX126x variant files use the DC-DC regulator option bool useRegulatorLDO = false; // Seems to depend on the connection to pin 9/DCC_SW - if an inductor DCDC? @@ -259,6 +269,7 @@ template void SX126xInterface::addReceiveMetadata(meshtastic_Mes */ template void SX126xInterface::configHardwareForSend() { + setTransmitEnable(true); RadioLibInterface::configHardwareForSend(); } @@ -271,6 +282,7 @@ template void SX126xInterface::startReceive() sleep(); #else + setTransmitEnable(false); setStandby(); // We use a 16 bit preamble so this should save some power by letting radio sit in standby mostly. @@ -298,7 +310,7 @@ template bool SX126xInterface::isChannelActive() .irqFlags = RADIOLIB_IRQ_CAD_DEFAULT_FLAGS, .irqMask = RADIOLIB_IRQ_CAD_DEFAULT_MASK}}; int16_t result; - + setTransmitEnable(false); setStandby(); result = lora.scanChannel(cfg); if (result == RADIOLIB_LORA_DETECTED) @@ -337,6 +349,26 @@ template bool SX126xInterface::sleep() digitalWrite(SX126X_POWER_EN, LOW); #endif +#ifdef HELTEC_V4 + /* + * 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); +#endif return true; } + +/** Some boards require GPIO control of tx vs rx paths */ +template void SX126xInterface::setTransmitEnable(bool txon) +{ +#ifdef HELTEC_V4 + digitalWrite(LORA_PA_POWER, HIGH); + digitalWrite(LORA_PA_EN, HIGH); + digitalWrite(LORA_PA_TX_EN, txon ? 1 : 0); +#endif +} + #endif \ No newline at end of file diff --git a/src/mesh/SX126xInterface.h b/src/mesh/SX126xInterface.h index 47b07c284..dc7024daa 100644 --- a/src/mesh/SX126xInterface.h +++ b/src/mesh/SX126xInterface.h @@ -71,5 +71,9 @@ template class SX126xInterface : public RadioLibInterface virtual void addReceiveMetadata(meshtastic_MeshPacket *mp) override; virtual void setStandby() override; + + private: + /** Some boards require GPIO control of tx vs rx paths */ + void setTransmitEnable(bool txon); }; #endif \ No newline at end of file diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 22ce6487f..c58b44718 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -194,6 +194,8 @@ #define HW_VENDOR meshtastic_HardwareModel_T_DECK_PRO #elif defined(T_LORA_PAGER) #define HW_VENDOR meshtastic_HardwareModel_T_LORA_PAGER +#elif defined(HELTEC_V4) +#define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(M5STACK_UNITC6L) #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L #endif diff --git a/src/sleep.cpp b/src/sleep.cpp index 83597e349..d6eb865e5 100644 --- a/src/sleep.cpp +++ b/src/sleep.cpp @@ -545,6 +545,12 @@ void enableLoraInterrupt() gpio_pullup_en((gpio_num_t)LORA_CS); #endif +#ifdef HELTEC_V4 + gpio_pullup_en((gpio_num_t)LORA_PA_POWER); + gpio_pullup_en((gpio_num_t)LORA_PA_EN); + gpio_pulldown_en((gpio_num_t)LORA_PA_TX_EN); +#endif + LOG_INFO("setup LORA_DIO1 (GPIO%02d) with wakeup by gpio interrupt", LORA_DIO1); gpio_wakeup_enable((gpio_num_t)LORA_DIO1, GPIO_INTR_HIGH_LEVEL); diff --git a/variants/esp32s3/heltec_v4/pins_arduino.h b/variants/esp32s3/heltec_v4/pins_arduino.h new file mode 100644 index 000000000..45561b4b5 --- /dev/null +++ b/variants/esp32s3/heltec_v4/pins_arduino.h @@ -0,0 +1,70 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +static const uint8_t LED_BUILTIN = 35; +#define BUILTIN_LED LED_BUILTIN // backward compatibility +#define LED_BUILTIN LED_BUILTIN // allow testing #ifdef LED_BUILTIN + +static const uint8_t TX = 43; +static const uint8_t RX = 44; + +static const uint8_t SDA = 3; +static const uint8_t SCL = 4; + +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; +static const uint8_t LED = 35; +static const uint8_t RST_OLED = 21; +static const uint8_t SCL_OLED = 18; +static const uint8_t SDA_OLED = 17; + +static const uint8_t RST_LoRa = 12; +static const uint8_t BUSY_LoRa = 13; +static const uint8_t DIO0 = 14; + +#endif /* Pins_Arduino_h */ \ No newline at end of file diff --git a/variants/esp32s3/heltec_v4/platformio.ini b/variants/esp32s3/heltec_v4/platformio.ini new file mode 100644 index 000000000..0177342a4 --- /dev/null +++ b/variants/esp32s3/heltec_v4/platformio.ini @@ -0,0 +1,10 @@ +[env:heltec-v4] +extends = esp32s3_base +board = heltec_v4 +board_check = true +build_flags = + ${esp32s3_base.build_flags} + -D HELTEC_V4 + -I variants/esp32s3/heltec_v4 + -D GPS_POWER_TOGGLE ; comment this line to disable triple press function on the user button to turn off gps entirely. + -D SX126X_MAX_POWER=11 diff --git a/variants/esp32s3/heltec_v4/variant.h b/variants/esp32s3/heltec_v4/variant.h new file mode 100644 index 000000000..2b6b7af3d --- /dev/null +++ b/variants/esp32s3/heltec_v4/variant.h @@ -0,0 +1,58 @@ +#define LED_PIN 35 + +#define USE_SSD1306 // Heltec_v4 has an SSD1315 display (compatible with SSD1306 driver) + +#define RESET_OLED 21 +#define I2C_SDA 17 // I2C pins for this board +#define I2C_SCL 18 + +#define VEXT_ENABLE 36 // active low, powers the oled display and the lora antenna boost +#define BUTTON_PIN 0 + +#define ADC_CTRL 37 +#define ADC_CTRL_ENABLED HIGH +#define BATTERY_PIN 1 // A battery voltage measurement pin, voltage divider connected here to measure battery voltage +#define ADC_CHANNEL ADC1_GPIO1_CHANNEL +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 // lower dB for high resistance voltage divider +#define ADC_MULTIPLIER 4.9 * 1.045 + +#define USE_SX1262 + +#define LORA_DIO0 -1 // a No connect on the SX1262 module +#define LORA_RESET 12 +#define LORA_DIO1 14 // SX1262 IRQ +#define LORA_DIO2 13 // SX1262 BUSY +#define LORA_DIO3 // Not connected on PCB, but internally on the TTGO SX1262, if DIO3 is high the TCXO is enabled + +#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 + +#define LORA_PA_POWER 7 // power en +#define LORA_PA_EN 2 +#define LORA_PA_TX_EN 46 // enable tx + +/* + * GPS pins + */ +#define GPS_L76K +#define PIN_GPS_RESET (42) // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K +#define GPS_RESET_MODE LOW +#define PIN_GPS_EN (34) +#define GPS_EN_ACTIVE LOW +#define PERIPHERAL_WARMUP_MS 1000 // Make sure I2C QuickLink has stable power before continuing +#define PIN_GPS_STANDBY (40) // An output to wake GPS, low means allow sleep, high means force wake +#define PIN_GPS_PPS (41) +// Seems to be missing on this new board +#define GPS_TX_PIN (38) // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN (39) // This is for bits going TOWARDS the GPS +#define GPS_THREAD_INTERVAL 50 \ No newline at end of file From b3df32c6c5016eae4298fce8b47a543f1201ef77 Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 21 Sep 2025 14:04:17 -0500 Subject: [PATCH 08/10] Fix build errors (#8067) --- src/graphics/images.h | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/graphics/images.h b/src/graphics/images.h index 7b80fc5ab..b5010b116 100644 --- a/src/graphics/images.h +++ b/src/graphics/images.h @@ -287,9 +287,7 @@ const uint8_t digital_icon_clock[] PROGMEM = {0b00111100, 0b01000010, 0b10000101 #define analog_icon_clock_height 8 const uint8_t analog_icon_clock[] PROGMEM = {0b11111111, 0b01000010, 0b00100100, 0b00011000, 0b00100100, 0b01000010, 0b01000010, 0b11111111}; -#ifdef M5STACK_UNITC6L -#include "img/icon_small.xbm" -#else + #define chirpy_width 38 #define chirpy_height 50 const uint8_t chirpy[] = { @@ -362,6 +360,9 @@ const uint8_t chirpy_hirez[] = { #define chirpy_small_image_height 8 const uint8_t chirpy_small[] = {0x7f, 0x41, 0x55, 0x55, 0x55, 0x55, 0x41, 0x7f}; +#ifdef M5STACK_UNITC6L +#include "img/icon_small.xbm" +#else #include "img/icon.xbm" #endif static_assert(sizeof(icon_bits) >= 0, "Silence unused variable warning"); \ No newline at end of file From 3d51287ba77693e5b0140d81abfe4cd218e0c2ae Mon Sep 17 00:00:00 2001 From: Jason P Date: Sun, 21 Sep 2025 17:54:54 -0500 Subject: [PATCH 09/10] Introduce Radio Preset elections through BaseUI (#8071) --- src/graphics/draw/MenuHandler.cpp | 62 ++++++++++++++++++++++++++++--- src/graphics/draw/MenuHandler.h | 2 + 2 files changed, 59 insertions(+), 5 deletions(-) diff --git a/src/graphics/draw/MenuHandler.cpp b/src/graphics/draw/MenuHandler.cpp index 9be0e0b02..7c4f4e05f 100644 --- a/src/graphics/draw/MenuHandler.cpp +++ b/src/graphics/draw/MenuHandler.cpp @@ -31,19 +31,21 @@ uint8_t test_count = 0; void menuHandler::loraMenu() { - static const char *optionsArray[] = {"Back", "Region Picker", "Device Role"}; - enum optionsNumbers { Back = 0, lora_picker = 1, device_role_picker = 2 }; + static const char *optionsArray[] = {"Back", "Device Role", "Radio Preset", "LoRa Region"}; + enum optionsNumbers { Back = 0, device_role_picker = 1, radio_preset_picker = 2, lora_picker = 3 }; BannerOverlayOptions bannerOptions; bannerOptions.message = "LoRa Actions"; bannerOptions.optionsArrayPtr = optionsArray; - bannerOptions.optionsCount = 3; + bannerOptions.optionsCount = 4; bannerOptions.bannerCallback = [](int selected) -> void { if (selected == Back) { // No action - } else if (selected == lora_picker) { - menuHandler::menuQueue = menuHandler::lora_picker; } else if (selected == device_role_picker) { menuHandler::menuQueue = menuHandler::device_role_picker; + } else if (selected == radio_preset_picker) { + menuHandler::menuQueue = menuHandler::radio_preset_picker; + } else if (selected == lora_picker) { + menuHandler::menuQueue = menuHandler::lora_picker; } }; screen->showOverlayBanner(bannerOptions); @@ -180,6 +182,53 @@ void menuHandler::DeviceRolePicker() screen->showOverlayBanner(bannerOptions); } +void menuHandler::RadioPresetPicker() +{ + static const char *optionsArray[] = {"Back", "LongSlow", "LongModerate", "LongFast", "MediumSlow", + "MediumFast", "ShortSlow", "ShortFast", "ShortTurbo"}; + enum optionsNumbers { + Back = 0, + radiopreset_LongSlow = 1, + radiopreset_LongModerate = 2, + radiopreset_LongFast = 3, + radiopreset_MediumSlow = 4, + radiopreset_MediumFast = 5, + radiopreset_ShortSlow = 6, + radiopreset_ShortFast = 7, + radiopreset_ShortTurbo = 8 + }; + BannerOverlayOptions bannerOptions; + bannerOptions.message = "Radio Preset"; + bannerOptions.optionsArrayPtr = optionsArray; + bannerOptions.optionsCount = 9; + bannerOptions.bannerCallback = [](int selected) -> void { + if (selected == Back) { + menuHandler::menuQueue = menuHandler::lora_Menu; + screen->runNow(); + return; + } else if (selected == radiopreset_LongSlow) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_SLOW; + } else if (selected == radiopreset_LongModerate) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_MODERATE; + } else if (selected == radiopreset_LongFast) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_LONG_FAST; + } else if (selected == radiopreset_MediumSlow) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_SLOW; + } else if (selected == radiopreset_MediumFast) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_MEDIUM_FAST; + } else if (selected == radiopreset_ShortSlow) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_SLOW; + } else if (selected == radiopreset_ShortFast) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_FAST; + } else if (selected == radiopreset_ShortTurbo) { + config.lora.modem_preset = meshtastic_Config_LoRaConfig_ModemPreset_SHORT_TURBO; + } + service->reloadConfig(SEGMENT_CONFIG); + rebootAtMsec = (millis() + DEFAULT_REBOOT_SECONDS * 1000); + }; + screen->showOverlayBanner(bannerOptions); +} + void menuHandler::TwelveHourPicker() { static const char *optionsArray[] = {"Back", "12-hour", "24-hour"}; @@ -1478,6 +1527,9 @@ void menuHandler::handleMenuSwitch(OLEDDisplay *display) case device_role_picker: DeviceRolePicker(); break; + case radio_preset_picker: + RadioPresetPicker(); + break; case no_timeout_lora_picker: LoraRegionPicker(0); break; diff --git a/src/graphics/draw/MenuHandler.h b/src/graphics/draw/MenuHandler.h index 56477258f..47dbb2543 100644 --- a/src/graphics/draw/MenuHandler.h +++ b/src/graphics/draw/MenuHandler.h @@ -12,6 +12,7 @@ class menuHandler lora_Menu, lora_picker, device_role_picker, + radio_preset_picker, no_timeout_lora_picker, TZ_picker, twelve_hour_picker, @@ -50,6 +51,7 @@ class menuHandler static void LoraRegionPicker(uint32_t duration = 30000); static void loraMenu(); static void DeviceRolePicker(); + static void RadioPresetPicker(); static void handleMenuSwitch(OLEDDisplay *display); static void showConfirmationBanner(const char *message, std::function onConfirm); static void clockMenu(); From 388c821028cffea0b78db84d07ec6a4ee8b1fe40 Mon Sep 17 00:00:00 2001 From: Tom Fifield Date: Mon, 22 Sep 2025 10:23:42 +1000 Subject: [PATCH 10/10] Allow label enforcement job to run on self-hosted runners (#7909) Previously, this check would only run on github-provided runners. --- .github/workflows/pr_enforce_labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pr_enforce_labels.yml b/.github/workflows/pr_enforce_labels.yml index 5fca90961..543e23558 100644 --- a/.github/workflows/pr_enforce_labels.yml +++ b/.github/workflows/pr_enforce_labels.yml @@ -10,7 +10,7 @@ permissions: jobs: check-label: - runs-on: ubuntu-24.04 + runs-on: ubuntu-latest steps: - name: Check for PR labels uses: actions/github-script@v8