From c183050935f3ca5e589c7759e14262be9cf24f25 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Fri, 15 May 2026 16:53:18 +0800 Subject: [PATCH 01/14] Add heltec -v4-r8 board --- boards/heltec_v4_r8.json | 43 +++ src/helpers/ui/ST7789LCDDisplay.cpp | 8 +- src/helpers/ui/ST7789LCDDisplay.h | 4 +- variants/heltec_v4_r8/HeltecV4R8Board.cpp | 82 ++++++ variants/heltec_v4_r8/HeltecV4R8Board.h | 39 +++ variants/heltec_v4_r8/LoRaFEMControl.cpp | 52 ++++ variants/heltec_v4_r8/LoRaFEMControl.h | 24 ++ variants/heltec_v4_r8/pins_arduino.h | 56 ++++ variants/heltec_v4_r8/platformio.ini | 342 ++++++++++++++++++++++ variants/heltec_v4_r8/target.cpp | 45 +++ variants/heltec_v4_r8/target.h | 31 ++ 11 files changed, 722 insertions(+), 4 deletions(-) create mode 100644 boards/heltec_v4_r8.json create mode 100644 variants/heltec_v4_r8/HeltecV4R8Board.cpp create mode 100644 variants/heltec_v4_r8/HeltecV4R8Board.h create mode 100644 variants/heltec_v4_r8/LoRaFEMControl.cpp create mode 100644 variants/heltec_v4_r8/LoRaFEMControl.h create mode 100644 variants/heltec_v4_r8/pins_arduino.h create mode 100644 variants/heltec_v4_r8/platformio.ini create mode 100644 variants/heltec_v4_r8/target.cpp create mode 100644 variants/heltec_v4_r8/target.h diff --git a/boards/heltec_v4_r8.json b/boards/heltec_v4_r8.json new file mode 100644 index 00000000..6dd97c84 --- /dev/null +++ b/boards/heltec_v4_r8.json @@ -0,0 +1,43 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_v4_r8" + }, + "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 r8 (16 MB FLASH, 8 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "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/helpers/ui/ST7789LCDDisplay.cpp b/src/helpers/ui/ST7789LCDDisplay.cpp index dc75e963..7a02668b 100644 --- a/src/helpers/ui/ST7789LCDDisplay.cpp +++ b/src/helpers/ui/ST7789LCDDisplay.cpp @@ -1,5 +1,9 @@ #include "ST7789LCDDisplay.h" +#ifndef PIN_TFT_MISO + #define PIN_TFT_MISO -1 +#endif + #ifndef DISPLAY_ROTATION #define DISPLAY_ROTATION 3 #endif @@ -29,8 +33,8 @@ bool ST7789LCDDisplay::begin() { } // Im not sure if this is just a t-deck problem or not, if your display is slow try this. - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) - displaySPI.begin(PIN_TFT_SCL, -1, PIN_TFT_SDA, PIN_TFT_CS); + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT) + displaySPI.begin(PIN_TFT_SCL, PIN_TFT_MISO, PIN_TFT_SDA, PIN_TFT_CS); #endif display.init(DISPLAY_WIDTH, DISPLAY_HEIGHT); diff --git a/src/helpers/ui/ST7789LCDDisplay.h b/src/helpers/ui/ST7789LCDDisplay.h index 5b960ca1..03a6d3f1 100644 --- a/src/helpers/ui/ST7789LCDDisplay.h +++ b/src/helpers/ui/ST7789LCDDisplay.h @@ -8,7 +8,7 @@ #include class ST7789LCDDisplay : public DisplayDriver { - #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) + #if defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT) SPIClass displaySPI; #endif Adafruit_ST7789 display; @@ -25,7 +25,7 @@ public: { _isOn = false; } -#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) +#elif defined(LILYGO_TDECK) || defined(HELTEC_LORA_V4_TFT) || defined(HELTEC_V4_R8_TFT) ST7789LCDDisplay(RefCountedDigitalPin* peripher_power=NULL) : DisplayDriver(128, 64), displaySPI(HSPI), display(&displaySPI, PIN_TFT_CS, PIN_TFT_DC, PIN_TFT_RST), diff --git a/variants/heltec_v4_r8/HeltecV4R8Board.cpp b/variants/heltec_v4_r8/HeltecV4R8Board.cpp new file mode 100644 index 00000000..1d481fcc --- /dev/null +++ b/variants/heltec_v4_r8/HeltecV4R8Board.cpp @@ -0,0 +1,82 @@ +#include "HeltecV4R8Board.h" + +void HeltecV4R8Board::begin() { + ESP32Board::begin(); + + periph_power.begin(); + periph_power.claim(); // R8 VEXT also feeds the LoRa antenna boost rail. + + loRaFEMControl.init(); + +#ifdef PIN_TOUCH_RST + pinMode(PIN_TOUCH_RST, OUTPUT); + digitalWrite(PIN_TOUCH_RST, HIGH); +#endif + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + 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 HeltecV4R8Board::onBeforeTransmit(void) { + digitalWrite(P_LORA_TX_LED, HIGH); + loRaFEMControl.setTxModeEnable(); +} + +void HeltecV4R8Board::onAfterTransmit(void) { + digitalWrite(P_LORA_TX_LED, LOW); + loRaFEMControl.setRxModeEnable(); +} + +void HeltecV4R8Board::enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + rtc_gpio_set_direction((gpio_num_t)P_LORA_DIO_1, RTC_GPIO_MODE_INPUT_ONLY); + rtc_gpio_pulldown_en((gpio_num_t)P_LORA_DIO_1); + + rtc_gpio_hold_en((gpio_num_t)P_LORA_NSS); + loRaFEMControl.setRxModeEnableWhenMCUSleep(); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); + } else { + esp_sleep_enable_ext1_wakeup((1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + esp_deep_sleep_start(); +} + +void HeltecV4R8Board::powerOff() { + enterDeepSleep(0); +} + +uint16_t HeltecV4R8Board::getBattMilliVolts() { + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogRead(PIN_VBAT_READ); + } + raw = raw / 8; + + return (adc_mult * (3.3f / 4096.0f) * raw) * 1000; +} + +const char* HeltecV4R8Board::getManufacturerName() const { +#ifdef HELTEC_V4_R8_TFT + return "Heltec V4 R8 TFT"; +#else + return "Heltec V4 R8 OLED"; +#endif +} diff --git a/variants/heltec_v4_r8/HeltecV4R8Board.h b/variants/heltec_v4_r8/HeltecV4R8Board.h new file mode 100644 index 00000000..20811abb --- /dev/null +++ b/variants/heltec_v4_r8/HeltecV4R8Board.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include +#include "LoRaFEMControl.h" + +#ifndef ADC_MULTIPLIER + #define ADC_MULTIPLIER (4.9f * 1.035f) +#endif + +class HeltecV4R8Board : public ESP32Board { +protected: + float adc_mult = ADC_MULTIPLIER; + +public: + RefCountedDigitalPin periph_power; + LoRaFEMControl loRaFEMControl; + + HeltecV4R8Board() : periph_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void onBeforeTransmit(void) override; + void onAfterTransmit(void) override; + void enterDeepSleep(uint32_t secs, int pin_wake_btn = -1); + void powerOff() override; + uint16_t getBattMilliVolts() override; + bool setAdcMultiplier(float multiplier) override { + if (multiplier == 0.0f) { + adc_mult = ADC_MULTIPLIER; + } else { + adc_mult = multiplier; + } + return true; + } + float getAdcMultiplier() const override { return adc_mult; } + const char* getManufacturerName() const override; +}; diff --git a/variants/heltec_v4_r8/LoRaFEMControl.cpp b/variants/heltec_v4_r8/LoRaFEMControl.cpp new file mode 100644 index 00000000..bb530de3 --- /dev/null +++ b/variants/heltec_v4_r8/LoRaFEMControl.cpp @@ -0,0 +1,52 @@ +#include "LoRaFEMControl.h" + +#include +#include +#include + +void LoRaFEMControl::init(void) { + pinMode(P_LORA_PA_POWER, OUTPUT); + digitalWrite(P_LORA_PA_POWER, HIGH); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_PA_POWER); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason != ESP_RST_DEEPSLEEP) { + delay(1); + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_KCT8103L_PA_CSD); + rtc_gpio_hold_dis((gpio_num_t)P_LORA_KCT8103L_PA_CTX); + + pinMode(P_LORA_KCT8103L_PA_CSD, OUTPUT); + digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH); + pinMode(P_LORA_KCT8103L_PA_CTX, OUTPUT); + digitalWrite(P_LORA_KCT8103L_PA_CTX, lna_enabled ? LOW : HIGH); +} + +void LoRaFEMControl::setSleepModeEnable(void) { + digitalWrite(P_LORA_KCT8103L_PA_CSD, LOW); +} + +void LoRaFEMControl::setTxModeEnable(void) { + digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH); + digitalWrite(P_LORA_KCT8103L_PA_CTX, HIGH); +} + +void LoRaFEMControl::setRxModeEnable(void) { + digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH); + digitalWrite(P_LORA_KCT8103L_PA_CTX, lna_enabled ? LOW : HIGH); +} + +void LoRaFEMControl::setRxModeEnableWhenMCUSleep(void) { + digitalWrite(P_LORA_PA_POWER, HIGH); + rtc_gpio_hold_en((gpio_num_t)P_LORA_PA_POWER); + + digitalWrite(P_LORA_KCT8103L_PA_CSD, HIGH); + rtc_gpio_hold_en((gpio_num_t)P_LORA_KCT8103L_PA_CSD); + digitalWrite(P_LORA_KCT8103L_PA_CTX, lna_enabled ? LOW : HIGH); + rtc_gpio_hold_en((gpio_num_t)P_LORA_KCT8103L_PA_CTX); +} + +void LoRaFEMControl::setLNAEnable(bool enabled) { + lna_enabled = enabled; +} diff --git a/variants/heltec_v4_r8/LoRaFEMControl.h b/variants/heltec_v4_r8/LoRaFEMControl.h new file mode 100644 index 00000000..961cfd07 --- /dev/null +++ b/variants/heltec_v4_r8/LoRaFEMControl.h @@ -0,0 +1,24 @@ +#pragma once + +typedef enum { + KCT8103L_PA, + OTHER_FEM_TYPES +} LoRaFEMType; + +class LoRaFEMControl { +public: + LoRaFEMControl() { } + virtual ~LoRaFEMControl() { } + void init(void); + void setSleepModeEnable(void); + void setTxModeEnable(void); + void setRxModeEnable(void); + void setRxModeEnableWhenMCUSleep(void); + void setLNAEnable(bool enabled); + bool isLnaCanControl(void) { return true; } + void setLnaCanControl(bool can_control) { } + LoRaFEMType getFEMType(void) const { return KCT8103L_PA; } + +private: + bool lna_enabled = false; +}; diff --git a/variants/heltec_v4_r8/pins_arduino.h b/variants/heltec_v4_r8/pins_arduino.h new file mode 100644 index 00000000..9e412aac --- /dev/null +++ b/variants/heltec_v4_r8/pins_arduino.h @@ -0,0 +1,56 @@ +#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 = 17; +static const uint8_t SCL = 18; + +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; + +#endif diff --git a/variants/heltec_v4_r8/platformio.ini b/variants/heltec_v4_r8/platformio.ini new file mode 100644 index 00000000..4057d6f1 --- /dev/null +++ b/variants/heltec_v4_r8/platformio.ini @@ -0,0 +1,342 @@ +[Heltec_v4_r8] +extends = esp32_base +board = heltec_v4_r8 +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/heltec_v4_r8 + -D HELTEC_V4_R8 + -D USE_SX1262 + -D ESP32_CPU_FREQ=80 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=8 + -D P_LORA_RESET=12 + -D P_LORA_BUSY=13 + -D P_LORA_SCLK=9 + -D P_LORA_MISO=11 + -D P_LORA_MOSI=10 + -D P_LORA_PA_POWER=7 + -D P_LORA_KCT8103L_PA_CSD=2 + -D P_LORA_KCT8103L_PA_CTX=5 + -D P_LORA_TX_LED=46 + -D PIN_USER_BTN=0 + -D PIN_VEXT_EN=40 + -D PIN_VEXT_EN_ACTIVE=LOW + -D ADC_MULTIPLIER=5.0715f + -D PIN_VBAT_READ=1 + -D LORA_TX_POWER=10 + -D MAX_LORA_TX_POWER=22 + -D SX126X_REGISTER_PATCH=1 + -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 PIN_GPS_RX=38 + -D PIN_GPS_TX=39 + -D PIN_GPS_EN=42 + -D PIN_GPS_EN_ACTIVE=LOW + -D ENV_INCLUDE_GPS=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_v4_r8> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[heltec_v4_r8_oled] +extends = Heltec_v4_r8 +build_flags = + ${Heltec_v4_r8.build_flags} + -D HELTEC_V4_R8_OLED + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D PIN_OLED_RESET=21 +build_src_filter = ${Heltec_v4_r8.build_src_filter} +lib_deps = ${Heltec_v4_r8.lib_deps} + +[heltec_v4_r8_tft] +extends = Heltec_v4_r8 +build_flags = + ${Heltec_v4_r8.build_flags} + -D HELTEC_V4_R8_TFT + -D PIN_BOARD_SDA=17 + -D PIN_BOARD_SCL=18 + -D DISPLAY_SCALE_X=2.5 + -D DISPLAY_SCALE_Y=3.75 + -D PIN_TFT_RST=-1 + -D PIN_TFT_VDD_CTL=-1 + -D PIN_TFT_LEDA_CTL=44 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=47 + -D PIN_TFT_DC=48 + -D PIN_TFT_SCL=16 + -D PIN_TFT_SDA=15 + -D PIN_TFT_MISO=45 + -D PIN_BUZZER=4 + -D PIN_TOUCH_RST=21 +build_src_filter = ${Heltec_v4_r8.build_src_filter} + + +lib_deps = + ${Heltec_v4_r8.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_v4_r8_repeater] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec R8 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_v4_r8_room_server] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -D DISPLAY_CLASS=SSD1306Display + -D ADVERT_NAME='"Heltec R8 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_r8_terminal_chat] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_companion_radio_usb] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_companion_radio_ble] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=SSD1306Display + -D BLE_PIN_CODE=123456 + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_companion_radio_wifi] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=SSD1306Display + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_sensor] +extends = heltec_v4_r8_oled +build_flags = + ${heltec_v4_r8_oled.build_flags} + -D ADVERT_NAME='"Heltec R8 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D DISPLAY_CLASS=SSD1306Display +build_src_filter = ${heltec_v4_r8_oled.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_r8_oled.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_r8_tft_repeater] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec R8 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_v4_r8_tft_room_server] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -D DISPLAY_CLASS=ST7789LCDDisplay + -D ADVERT_NAME='"Heltec R8 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + + + +<../examples/simple_room_server> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_r8_tft_terminal_chat] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_tft_companion_radio_usb] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D DISPLAY_CLASS=ST7789LCDDisplay +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_tft_companion_radio_ble] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=ST7789LCDDisplay + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_tft_companion_radio_wifi] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D OFFLINE_QUEUE_SIZE=256 + -D DISPLAY_CLASS=ST7789LCDDisplay + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_v4_r8_tft_sensor] +extends = heltec_v4_r8_tft +build_flags = + ${heltec_v4_r8_tft.build_flags} + -D ADVERT_NAME='"Heltec R8 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D DISPLAY_CLASS=ST7789LCDDisplay +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + + + +<../examples/simple_sensor> +lib_deps = + ${heltec_v4_r8_tft.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_v4_r8_kiss_modem] +extends = Heltec_v4_r8 +build_src_filter = ${Heltec_v4_r8.build_src_filter} + +<../examples/kiss_modem/> + +[env:heltec_v4_r8_tft_kiss_modem] +extends = heltec_v4_r8_tft +build_src_filter = ${heltec_v4_r8_tft.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_v4_r8/target.cpp b/variants/heltec_v4_r8/target.cpp new file mode 100644 index 00000000..0b38531e --- /dev/null +++ b/variants/heltec_v4_r8/target.cpp @@ -0,0 +1,45 @@ +#include +#include "target.h" + +HeltecV4R8Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi; + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display(&board.periph_power); + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} diff --git a/variants/heltec_v4_r8/target.h b/variants/heltec_v4_r8/target.h new file mode 100644 index 00000000..2d1d7a5b --- /dev/null +++ b/variants/heltec_v4_r8/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #ifdef HELTEC_V4_R8_OLED + #include + #elif defined(HELTEC_V4_R8_TFT) + #include + #endif + #include +#endif + +extern HeltecV4R8Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +mesh::LocalIdentity radio_new_identity(); From 1ac5f359ca572141ca0fa297a9540656a959d842 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Sat, 16 May 2026 10:59:02 +0800 Subject: [PATCH 02/14] Added touch reset --- variants/heltec_v4_r8/HeltecV4R8Board.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/variants/heltec_v4_r8/HeltecV4R8Board.cpp b/variants/heltec_v4_r8/HeltecV4R8Board.cpp index 1d481fcc..f9a771df 100644 --- a/variants/heltec_v4_r8/HeltecV4R8Board.cpp +++ b/variants/heltec_v4_r8/HeltecV4R8Board.cpp @@ -11,6 +11,10 @@ void HeltecV4R8Board::begin() { #ifdef PIN_TOUCH_RST pinMode(PIN_TOUCH_RST, OUTPUT); digitalWrite(PIN_TOUCH_RST, HIGH); + delay(10); + digitalWrite(PIN_TOUCH_RST, LOW); + delay(100); + digitalWrite(PIN_TOUCH_RST, HIGH); #endif esp_reset_reason_t reason = esp_reset_reason(); From e5a3839d64192c984e2fa5c76450c77bd06dc36e Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 27 May 2026 15:12:59 +0800 Subject: [PATCH 03/14] Optimize ADC readout --- variants/heltec_v4_r8/HeltecV4R8Board.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/variants/heltec_v4_r8/HeltecV4R8Board.cpp b/variants/heltec_v4_r8/HeltecV4R8Board.cpp index f9a771df..1fb123b2 100644 --- a/variants/heltec_v4_r8/HeltecV4R8Board.cpp +++ b/variants/heltec_v4_r8/HeltecV4R8Board.cpp @@ -70,11 +70,11 @@ uint16_t HeltecV4R8Board::getBattMilliVolts() { uint32_t raw = 0; for (int i = 0; i < 8; i++) { - raw += analogRead(PIN_VBAT_READ); + raw += analogReadMilliVolts(PIN_VBAT_READ); } raw = raw / 8; - return (adc_mult * (3.3f / 4096.0f) * raw) * 1000; + return (adc_mult * raw); } const char* HeltecV4R8Board::getManufacturerName() const { From 2efb3af69b9207b23d57b630bd0b1380c4f4405c Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 2 Jul 2026 21:55:19 -0400 Subject: [PATCH 04/14] repeater: poweroff on long press --- .vscode/extensions.json | 9 ---- examples/simple_repeater/UITask.cpp | 79 +++++++++++++++++++---------- examples/simple_repeater/UITask.h | 5 +- examples/simple_repeater/main.cpp | 5 +- 4 files changed, 60 insertions(+), 38 deletions(-) delete mode 100644 .vscode/extensions.json diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 8057bc70..00000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "recommendations": [ - "pioarduino.pioarduino-ide", - "platformio.platformio-ide" - ], - "unwantedRecommendations": [ - "ms-vscode.cpptools-extension-pack" - ] -} diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index 05d863fc..17c708e3 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -1,4 +1,5 @@ #include "UITask.h" +#include "target.h" #include #include @@ -9,6 +10,8 @@ #define AUTO_OFF_MILLIS 20000 // 20 seconds #define BOOT_SCREEN_MILLIS 4000 // 4 seconds +#define POWEROFF_DELAY 3000 + // 'meshcore', 128x13px static const uint8_t meshcore_logo [] PROGMEM = { 0x3c, 0x01, 0xe3, 0xff, 0xc7, 0xff, 0x8f, 0x03, 0x87, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, @@ -29,9 +32,14 @@ static const uint8_t meshcore_logo [] PROGMEM = { void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version) { _prevBtnState = HIGH; _auto_off = millis() + AUTO_OFF_MILLIS; + _started_at = millis(); _node_prefs = node_prefs; _display->turnOn(); +#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS) + user_btn.begin(); +#endif + // strip off dash and commit hash by changing dash to null terminator // e.g: v1.2.3-abcdef -> v1.2.3 char *version = strdup(firmware_version); @@ -47,7 +55,7 @@ void UITask::begin(NodePrefs* node_prefs, const char* build_date, const char* fi void UITask::renderCurrScreen() { char tmp[80]; - if (millis() < BOOT_SCREEN_MILLIS) { // boot screen + if (millis() < _started_at + BOOT_SCREEN_MILLIS) { // boot screen // meshcore logo _display->setColor(DisplayDriver::BLUE); int logoWidth = 128; @@ -57,24 +65,34 @@ void UITask::renderCurrScreen() { const char* website = "https://meshcore.io"; _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); - uint16_t websiteWidth = _display->getTextWidth(website); - _display->setCursor((_display->width() - websiteWidth) / 2, 22); - _display->print(website); + _display->drawTextCentered(_display->width() / 2, 22, website); // version info _display->setColor(DisplayDriver::LIGHT); _display->setTextSize(1); - uint16_t versionWidth = _display->getTextWidth(_version_info); - _display->setCursor((_display->width() - versionWidth) / 2, 35); - _display->print(_version_info); + _display->drawTextCentered(_display->width() / 2, 35, _version_info); // node type const char* node_type = "< Repeater >"; - uint16_t typeWidth = _display->getTextWidth(node_type); - _display->setCursor((_display->width() - typeWidth) / 2, 48); - _display->print(node_type); - } else { // home screen - // node name + _display->drawTextCentered(_display->width() / 2, 48, node_type); + } else if (_powering_off_at > 0) { + // meshcore logo + _display->setColor(DisplayDriver::BLUE); + int logoWidth = 128; + _display->drawXbm((_display->width() - logoWidth) / 2, 3, meshcore_logo, logoWidth, 13); + + // meshcore website + const char* website = "https://meshcore.io"; + _display->setColor(DisplayDriver::LIGHT); + _display->setTextSize(1); + _display->drawTextCentered(_display->width()/ 2, 22, website); + + // Powering off + const char* poweroff_string = "Turning OFF"; + uint16_t poffWidth = _display->getTextWidth(poweroff_string); + _display->setCursor((_display->width() - poffWidth) / 2, 48); + _display->drawTextCentered(_display->width()/2, 48, poweroff_string); + } else { _display->setCursor(0, 0); _display->setTextSize(1); _display->setColor(DisplayDriver::GREEN); @@ -94,21 +112,19 @@ void UITask::renderCurrScreen() { } void UITask::loop() { -#ifdef PIN_USER_BTN - if (millis() >= _next_read) { - int btnState = digitalRead(PIN_USER_BTN); - if (btnState != _prevBtnState) { - if (btnState == USER_BTN_PRESSED) { // pressed? - if (_display->isOn()) { - // TODO: any action ? - } else { - _display->turnOn(); - } - _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer - } - _prevBtnState = btnState; +#if defined(PIN_USER_BTN) && defined(DISPLAY_CLASS) + int ev = user_btn.check(); + if (ev == BUTTON_EVENT_CLICK) { + if (_display->isOn()) { + // TODO: any action ? + } else { + _display->turnOn(); } - _next_read = millis() + 200; // 5 reads per second + _auto_off = millis() + AUTO_OFF_MILLIS; // extend auto-off timer + } else if (ev == BUTTON_EVENT_LONG_PRESS) { + _display->turnOn(); + Serial.println("Powering Off"); + _powering_off_at = millis() + POWEROFF_DELAY; } #endif @@ -124,4 +140,15 @@ void UITask::loop() { _display->turnOff(); } } + + if (_powering_off_at > 0) { // power off timer armed +#ifdef LED_PIN + digitalWrite(LED_PIN, LED_STATE_ON); // switch on the led until poweroff +#endif + if (millis() > _powering_off_at) { + _display->turnOff(); + radio_driver.powerOff(); + _board->powerOff(); // should not return + } + } } diff --git a/examples/simple_repeater/UITask.h b/examples/simple_repeater/UITask.h index a27259f1..d8e3ce1d 100644 --- a/examples/simple_repeater/UITask.h +++ b/examples/simple_repeater/UITask.h @@ -4,15 +4,18 @@ #include class UITask { + mesh::MainBoard* _board; DisplayDriver* _display; unsigned long _next_read, _next_refresh, _auto_off; int _prevBtnState; NodePrefs* _node_prefs; char _version_info[32]; + unsigned long _powering_off_at = 0; + unsigned long _started_at = 0; void renderCurrScreen(); public: - UITask(DisplayDriver& display) : _display(&display) { _next_read = _next_refresh = 0; } + UITask(mesh::MainBoard& board, DisplayDriver& display) : _board(&board), _display(&display) { _next_read = _next_refresh = 0; } void begin(NodePrefs* node_prefs, const char* build_date, const char* firmware_version); void loop(); diff --git a/examples/simple_repeater/main.cpp b/examples/simple_repeater/main.cpp index 2ce056f5..c13cd99d 100644 --- a/examples/simple_repeater/main.cpp +++ b/examples/simple_repeater/main.cpp @@ -5,7 +5,7 @@ #ifdef DISPLAY_CLASS #include "UITask.h" - static UITask ui_task(display); + static UITask ui_task(board, display); #endif StdRNG fast_rng; @@ -130,7 +130,8 @@ void loop() { command[0] = 0; // reset command buffer } -#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) +#if defined(PIN_USER_BTN) && defined(_SEEED_SENSECAP_SOLAR_H_) && !defined(DISPLAY_CLASS) + // // Hold the user button to power off the SenseCAP Solar repeater. int btnState = digitalRead(PIN_USER_BTN); if (btnState == LOW) { From 369016acc952eda75ebbd53fe74380cb617dea67 Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 7 Jul 2026 10:20:03 -0400 Subject: [PATCH 05/14] introduce shutdownPeripherals in NRF52Board to prepare for shutdown --- src/helpers/NRF52Board.cpp | 6 +++++- src/helpers/NRF52Board.h | 1 + variants/lilygo_techo/TechoBoard.h | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/helpers/NRF52Board.cpp b/src/helpers/NRF52Board.cpp index beee3212..088ddc23 100644 --- a/src/helpers/NRF52Board.cpp +++ b/src/helpers/NRF52Board.cpp @@ -298,7 +298,7 @@ float NRF52Board::getMCUTemperature() { return temp * 0.25f; // Convert to *C } -void NRF52Board::powerOff() { +void NRF52Board::shutdownPeripherals() { // Power off the display if any #ifdef DISPLAY_CLASS display.turnOff(); @@ -318,6 +318,10 @@ void NRF52Board::powerOff() { // Flush serial buffers Serial.flush(); delay(100); +} + +void NRF52Board::powerOff() { + shutdownPeripherals(); // Enter SYSTEMOFF uint8_t sd_enabled = 0; diff --git a/src/helpers/NRF52Board.h b/src/helpers/NRF52Board.h index cbf4cd49..dba15f97 100644 --- a/src/helpers/NRF52Board.h +++ b/src/helpers/NRF52Board.h @@ -50,6 +50,7 @@ public: virtual uint8_t getStartupReason() const override { return startup_reason; } virtual float getMCUTemperature() override; virtual void reboot() override { NVIC_SystemReset(); } + virtual void shutdownPeripherals(); virtual void powerOff() override; virtual bool getBootloaderVersion(char* version, size_t max_len) override; virtual bool startOTAUpdate(const char *id, char reply[]) override; diff --git a/variants/lilygo_techo/TechoBoard.h b/variants/lilygo_techo/TechoBoard.h index e957d2e5..867fc24c 100644 --- a/variants/lilygo_techo/TechoBoard.h +++ b/variants/lilygo_techo/TechoBoard.h @@ -23,8 +23,8 @@ public: return "LilyGo T-Echo"; } - void powerOff() override { - NRF52Board::powerOff(); + void shutdownPeripherals() override { + NRF52Board::shutdownPeripherals(); #ifdef LED_RED digitalWrite(LED_RED, HIGH); #endif From 17d68e328f0eb5d016fe3c6cf4ada6ba64f7aee2 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 8 Jul 2026 16:20:12 +0800 Subject: [PATCH 06/14] Add heltec rc32 board --- boards/heltec-rc32.json | 43 ++ src/helpers/ui/NV3001BDisplay.cpp | 553 +++++++++++++++++++++++ src/helpers/ui/NV3001BDisplay.h | 68 +++ variants/heltec_rc32/HeltecRC32Board.cpp | 66 +++ variants/heltec_rc32/HeltecRC32Board.h | 32 ++ variants/heltec_rc32/pins_arduino.h | 60 +++ variants/heltec_rc32/platformio.ini | 346 ++++++++++++++ variants/heltec_rc32/target.cpp | 45 ++ variants/heltec_rc32/target.h | 31 ++ variants/heltec_rc32/variant.h | 51 +++ 10 files changed, 1295 insertions(+) create mode 100644 boards/heltec-rc32.json create mode 100644 src/helpers/ui/NV3001BDisplay.cpp create mode 100644 src/helpers/ui/NV3001BDisplay.h create mode 100644 variants/heltec_rc32/HeltecRC32Board.cpp create mode 100644 variants/heltec_rc32/HeltecRC32Board.h create mode 100644 variants/heltec_rc32/pins_arduino.h create mode 100644 variants/heltec_rc32/platformio.ini create mode 100644 variants/heltec_rc32/target.cpp create mode 100644 variants/heltec_rc32/target.h create mode 100644 variants/heltec_rc32/variant.h diff --git a/boards/heltec-rc32.json b/boards/heltec-rc32.json new file mode 100644 index 00000000..b9bafa26 --- /dev/null +++ b/boards/heltec-rc32.json @@ -0,0 +1,43 @@ +{ + "build": { + "arduino": { + "ldscript": "esp32s3_out.ld", + "partitions": "default_16MB.csv", + "memory_type": "qio_opi" + }, + "core": "esp32", + "extra_flags": [ + "-DBOARD_HAS_PSRAM", + "-DARDUINO_USB_CDC_ON_BOOT=1", + "-DARDUINO_USB_MODE=1", + "-DARDUINO_RUNNING_CORE=1", + "-DARDUINO_EVENT_RUNNING_CORE=1" + ], + "f_cpu": "240000000L", + "f_flash": "80000000L", + "flash_mode": "qio", + "psram_type": "opi", + "hwids": [["0x303A", "0x1001"]], + "mcu": "esp32s3", + "variant": "heltec_rc32" + }, + "connectivity": ["wifi", "bluetooth", "lora"], + "debug": { + "default_tool": "esp-builtin", + "onboard_tools": ["esp-builtin"], + "openocd_target": "esp32s3.cfg" + }, + "frameworks": ["arduino", "espidf"], + "name": "Heltec RC32 (16 MB FLASH, 8 MB PSRAM)", + "upload": { + "flash_size": "16MB", + "maximum_ram_size": 327680, + "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/helpers/ui/NV3001BDisplay.cpp b/src/helpers/ui/NV3001BDisplay.cpp new file mode 100644 index 00000000..03825cc0 --- /dev/null +++ b/src/helpers/ui/NV3001BDisplay.cpp @@ -0,0 +1,553 @@ +#include "NV3001BDisplay.h" +#include +#include + +#ifndef SPI_FREQUENCY + #define SPI_FREQUENCY 8000000 +#endif + +#ifndef PIN_TFT_SCL + #error "PIN_TFT_SCL must be defined" +#endif + +#ifndef PIN_TFT_SDA + #error "PIN_TFT_SDA must be defined" +#endif + +#ifndef PIN_TFT_CS + #error "PIN_TFT_CS must be defined" +#endif + +#ifndef PIN_TFT_DC + #error "PIN_TFT_DC must be defined" +#endif + +#ifndef PIN_TFT_MISO + #define PIN_TFT_MISO -1 +#endif + +#ifndef PIN_TFT_RST + #define PIN_TFT_RST -1 +#endif + +#ifndef PIN_TFT_EN + #define PIN_TFT_EN -1 +#endif + +#ifndef PIN_TFT_BL + #define PIN_TFT_BL -1 +#endif + +#ifndef PIN_TFT_EN_ACTIVE + #define PIN_TFT_EN_ACTIVE LOW +#endif + +#ifndef PIN_TFT_BL_ACTIVE + #define PIN_TFT_BL_ACTIVE HIGH +#endif + +#ifndef DISPLAY_ROTATION + #define DISPLAY_ROTATION 0 +#endif + +#ifndef NV3001B_SCREEN_WIDTH + #define NV3001B_SCREEN_WIDTH 220 +#endif + +#ifndef NV3001B_SCREEN_HEIGHT + #define NV3001B_SCREEN_HEIGHT 128 +#endif + +#ifndef DISPLAY_SCALE_X + #define DISPLAY_SCALE_X ((float)NV3001B_SCREEN_WIDTH / NV3001B_LOGICAL_WIDTH) +#endif + +#ifndef DISPLAY_SCALE_Y + #define DISPLAY_SCALE_Y ((float)NV3001B_SCREEN_HEIGHT / NV3001B_LOGICAL_HEIGHT) +#endif + +#define NV3001B_SWRESET 0x01 +#define NV3001B_SLPOUT 0x11 +#define NV3001B_DISPON 0x29 +#define NV3001B_CASET 0x2A +#define NV3001B_RASET 0x2B +#define NV3001B_RAMWR 0x2C +#define NV3001B_MADCTL 0x36 +#define NV3001B_COLMOD 0x3A + +#define NV3001B_MADCTL_MY 0x80 +#define NV3001B_MADCTL_MX 0x40 +#define NV3001B_MADCTL_MV 0x20 +#define NV3001B_MADCTL_RGB 0x00 + +#ifndef NV3001B_TEXT_SIZE1_SCALE_X + #define NV3001B_TEXT_SIZE1_SCALE_X 1 +#endif + +#ifndef NV3001B_TEXT_SIZE1_SCALE_Y + #define NV3001B_TEXT_SIZE1_SCALE_Y 2 +#endif + +#ifndef NV3001B_TEXT_SIZE2_SCALE_X + #define NV3001B_TEXT_SIZE2_SCALE_X 2 +#endif + +#ifndef NV3001B_TEXT_SIZE2_SCALE_Y + #define NV3001B_TEXT_SIZE2_SCALE_Y 3 +#endif + +static uint16_t mapColor(DisplayDriver::Color c) { + switch (c) { + case DisplayDriver::DARK: return 0x0000; + case DisplayDriver::LIGHT: return 0xffff; + case DisplayDriver::RED: return 0xf800; + case DisplayDriver::GREEN: return 0x07e0; + case DisplayDriver::BLUE: return 0x001f; + case DisplayDriver::YELLOW: return 0xffe0; + case DisplayDriver::ORANGE: return 0xfd20; + default: return 0xffff; + } +} + +static int scaleX(int x) { + return (int)(x * DISPLAY_SCALE_X); +} + +static int scaleY(int y) { + return (int)(y * DISPLAY_SCALE_Y); +} + +static int scaleWidth(int x, int w) { + if (w <= 0) return 0; + int scaled = scaleX(x + w) - scaleX(x); + return scaled > 0 ? scaled : 1; +} + +static int scaleHeight(int y, int h) { + if (h <= 0) return 0; + int scaled = scaleY(y + h) - scaleY(y); + return scaled > 0 ? scaled : 1; +} + +static uint8_t nv3001bMADCTL(uint8_t rotation) { + uint8_t madctl; + switch (rotation & 3) { + case 0: + madctl = NV3001B_MADCTL_MY | NV3001B_MADCTL_MV | NV3001B_MADCTL_RGB; + break; + case 1: + madctl = NV3001B_MADCTL_MY | NV3001B_MADCTL_MX | NV3001B_MADCTL_RGB; + break; + case 2: + madctl = NV3001B_MADCTL_RGB; + break; + default: + madctl = NV3001B_MADCTL_MX | NV3001B_MADCTL_MV | NV3001B_MADCTL_RGB; + break; + } + return madctl; +} + +static const uint8_t font5x7[] PROGMEM = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x00, 0x00, 0x00, 0x07, 0x00, 0x07, 0x00, 0x14, + 0x7f, 0x14, 0x7f, 0x14, 0x24, 0x2a, 0x7f, 0x2a, 0x12, 0x23, 0x13, 0x08, 0x64, 0x62, 0x36, 0x49, + 0x55, 0x22, 0x50, 0x00, 0x05, 0x03, 0x00, 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00, 0x00, 0x41, 0x22, + 0x1c, 0x00, 0x14, 0x08, 0x3e, 0x08, 0x14, 0x08, 0x08, 0x3e, 0x08, 0x08, 0x00, 0x50, 0x30, 0x00, + 0x00, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00, 0x60, 0x60, 0x00, 0x00, 0x20, 0x10, 0x08, 0x04, 0x02, + 0x3e, 0x51, 0x49, 0x45, 0x3e, 0x00, 0x42, 0x7f, 0x40, 0x00, 0x42, 0x61, 0x51, 0x49, 0x46, 0x21, + 0x41, 0x45, 0x4b, 0x31, 0x18, 0x14, 0x12, 0x7f, 0x10, 0x27, 0x45, 0x45, 0x45, 0x39, 0x3c, 0x4a, + 0x49, 0x49, 0x30, 0x01, 0x71, 0x09, 0x05, 0x03, 0x36, 0x49, 0x49, 0x49, 0x36, 0x06, 0x49, 0x49, + 0x29, 0x1e, 0x00, 0x36, 0x36, 0x00, 0x00, 0x00, 0x56, 0x36, 0x00, 0x00, 0x08, 0x14, 0x22, 0x41, + 0x00, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00, 0x41, 0x22, 0x14, 0x08, 0x02, 0x01, 0x51, 0x09, 0x06, + 0x32, 0x49, 0x79, 0x41, 0x3e, 0x7e, 0x11, 0x11, 0x11, 0x7e, 0x7f, 0x49, 0x49, 0x49, 0x36, 0x3e, + 0x41, 0x41, 0x41, 0x22, 0x7f, 0x41, 0x41, 0x22, 0x1c, 0x7f, 0x49, 0x49, 0x49, 0x41, 0x7f, 0x09, + 0x09, 0x09, 0x01, 0x3e, 0x41, 0x49, 0x49, 0x7a, 0x7f, 0x08, 0x08, 0x08, 0x7f, 0x00, 0x41, 0x7f, + 0x41, 0x00, 0x20, 0x40, 0x41, 0x3f, 0x01, 0x7f, 0x08, 0x14, 0x22, 0x41, 0x7f, 0x40, 0x40, 0x40, + 0x40, 0x7f, 0x02, 0x0c, 0x02, 0x7f, 0x7f, 0x04, 0x08, 0x10, 0x7f, 0x3e, 0x41, 0x41, 0x41, 0x3e, + 0x7f, 0x09, 0x09, 0x09, 0x06, 0x3e, 0x41, 0x51, 0x21, 0x5e, 0x7f, 0x09, 0x19, 0x29, 0x46, 0x46, + 0x49, 0x49, 0x49, 0x31, 0x01, 0x01, 0x7f, 0x01, 0x01, 0x3f, 0x40, 0x40, 0x40, 0x3f, 0x1f, 0x20, + 0x40, 0x20, 0x1f, 0x3f, 0x40, 0x38, 0x40, 0x3f, 0x63, 0x14, 0x08, 0x14, 0x63, 0x07, 0x08, 0x70, + 0x08, 0x07, 0x61, 0x51, 0x49, 0x45, 0x43, 0x00, 0x7f, 0x41, 0x41, 0x00, 0x02, 0x04, 0x08, 0x10, + 0x20, 0x00, 0x41, 0x41, 0x7f, 0x00, 0x04, 0x02, 0x01, 0x02, 0x04, 0x40, 0x40, 0x40, 0x40, 0x40, + 0x00, 0x01, 0x02, 0x04, 0x00, 0x20, 0x54, 0x54, 0x54, 0x78, 0x7f, 0x48, 0x44, 0x44, 0x38, 0x38, + 0x44, 0x44, 0x44, 0x20, 0x38, 0x44, 0x44, 0x48, 0x7f, 0x38, 0x54, 0x54, 0x54, 0x18, 0x08, 0x7e, + 0x09, 0x01, 0x02, 0x0c, 0x52, 0x52, 0x52, 0x3e, 0x7f, 0x08, 0x04, 0x04, 0x78, 0x00, 0x44, 0x7d, + 0x40, 0x00, 0x20, 0x40, 0x44, 0x3d, 0x00, 0x7f, 0x10, 0x28, 0x44, 0x00, 0x00, 0x41, 0x7f, 0x40, + 0x00, 0x7c, 0x04, 0x18, 0x04, 0x78, 0x7c, 0x08, 0x04, 0x04, 0x78, 0x38, 0x44, 0x44, 0x44, 0x38, + 0x7c, 0x14, 0x14, 0x14, 0x08, 0x08, 0x14, 0x14, 0x18, 0x7c, 0x7c, 0x08, 0x04, 0x04, 0x08, 0x48, + 0x54, 0x54, 0x54, 0x20, 0x04, 0x3f, 0x44, 0x40, 0x20, 0x3c, 0x40, 0x40, 0x20, 0x7c, 0x1c, 0x20, + 0x40, 0x20, 0x1c, 0x3c, 0x40, 0x30, 0x40, 0x3c, 0x44, 0x28, 0x10, 0x28, 0x44, 0x0c, 0x50, 0x50, + 0x50, 0x3c, 0x44, 0x64, 0x54, 0x4c, 0x44, 0x00, 0x08, 0x36, 0x41, 0x00, 0x00, 0x00, 0x7f, 0x00, + 0x00, 0x00, 0x41, 0x36, 0x08, 0x00, 0x08, 0x08, 0x2a, 0x1c, 0x08, 0x00, 0x06, 0x09, 0x09, 0x06 +}; + +static int textPixelScaleX(uint8_t size) { + return size <= 1 ? NV3001B_TEXT_SIZE1_SCALE_X : NV3001B_TEXT_SIZE2_SCALE_X; +} + +static int textPixelScaleY(uint8_t size) { + return size <= 1 ? NV3001B_TEXT_SIZE1_SCALE_Y : NV3001B_TEXT_SIZE2_SCALE_Y; +} + +static void setupOptionalOutput(int pin, int level) { + if (pin < 0) return; + + pinMode(pin, OUTPUT); + digitalWrite(pin, level); +} + +static void writeOptionalPin(int pin, int level) { + if (pin < 0) return; + + digitalWrite(pin, level); +} + +void NV3001BDisplay::writeCommand(uint8_t cmd) { + spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0)); + digitalWrite(PIN_TFT_DC, LOW); + digitalWrite(PIN_TFT_CS, LOW); + spi.transfer(cmd); + digitalWrite(PIN_TFT_CS, HIGH); + spi.endTransaction(); +} + +void NV3001BDisplay::writeBytes(const uint8_t* data, size_t len) { + if (!data || len == 0) return; + + spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0)); + digitalWrite(PIN_TFT_DC, HIGH); + digitalWrite(PIN_TFT_CS, LOW); + for (size_t i = 0; i < len; i++) { + spi.transfer(data[i]); + } + digitalWrite(PIN_TFT_CS, HIGH); + spi.endTransaction(); +} + +void NV3001BDisplay::writeCommandData(uint8_t cmd, const uint8_t* data, size_t len) { + writeCommand(cmd); + writeBytes(data, len); +} + +void NV3001BDisplay::setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h) { + uint16_t x2 = x + w - 1; + uint16_t y2 = y + h - 1; + uint8_t data[4]; + + data[0] = x >> 8; + data[1] = x & 0xff; + data[2] = x2 >> 8; + data[3] = x2 & 0xff; + writeCommandData(NV3001B_CASET, data, sizeof(data)); + + data[0] = y >> 8; + data[1] = y & 0xff; + data[2] = y2 >> 8; + data[3] = y2 & 0xff; + writeCommandData(NV3001B_RASET, data, sizeof(data)); + + writeCommand(NV3001B_RAMWR); +} + +void NV3001BDisplay::writeColor(uint16_t rgb, uint32_t count) { + uint8_t hi = rgb >> 8; + uint8_t lo = rgb & 0xff; + + spi.beginTransaction(SPISettings(SPI_FREQUENCY, MSBFIRST, SPI_MODE0)); + digitalWrite(PIN_TFT_DC, HIGH); + digitalWrite(PIN_TFT_CS, LOW); + while (count--) { + spi.transfer(hi); + spi.transfer(lo); + } + digitalWrite(PIN_TFT_CS, HIGH); + spi.endTransaction(); +} + +void NV3001BDisplay::initPanel() { +#define CMD0(C) do { writeCommand(C); } while (0) +#define CMD1(C, A) do { const uint8_t d[] = { A }; writeCommandData(C, d, sizeof(d)); } while (0) +#define CMD2(C, A, B) do { const uint8_t d[] = { A, B }; writeCommandData(C, d, sizeof(d)); } while (0) + + CMD0(NV3001B_SWRESET); + delay(120); + CMD1(0xFF, 0xA5); + CMD1(0x41, 0x00); + CMD1(0x50, 0x02); + CMD1(0x52, 0x6E); + CMD1(0x57, 0x02); + CMD1(0x46, 0x11); + CMD2(0x47, 0x00, 0x01); + CMD2(0x8F, 0x22, 0x03); + CMD1(0x9A, 0x78); + CMD1(0x9B, 0x78); + CMD1(0x9C, 0xA0); + CMD1(0x9D, 0x17); + CMD1(0x9E, 0xC1); + CMD1(0x83, 0x5A); + CMD1(0x84, 0xB6); + CMD1(0xFF, 0xA5); + CMD1(0x85, 0x5F); + CMD1(0x6E, 0x0F); + CMD1(0x7E, 0x0F); + CMD1(0x60, 0x00); + CMD1(0x70, 0x00); + CMD1(0x6D, 0x33); + CMD1(0x7D, 0x37); + CMD1(0x61, 0x09); + CMD1(0x71, 0x0A); + CMD1(0x6C, 0x2A); + CMD1(0x7C, 0x36); + CMD1(0x62, 0x11); + CMD1(0x72, 0x10); + CMD1(0x68, 0x4E); + CMD1(0x78, 0x4E); + CMD1(0x66, 0x36); + CMD1(0x76, 0x3C); + CMD1(0x1A, 0x1C); + CMD1(0x7B, 0x14); + CMD1(0x63, 0x0D); + CMD1(0x73, 0x0A); + CMD1(0x6A, 0x16); + CMD1(0x7A, 0x12); + CMD1(0x64, 0x0B); + CMD1(0x74, 0x0A); + CMD1(0x69, 0x08); + CMD1(0x79, 0x0A); + CMD1(0x65, 0x06); + CMD1(0x75, 0x07); + CMD1(0x67, 0x23); + CMD1(0x77, 0x44); + CMD1(0xE0, 0x00); + CMD1(0xE9, 0x30); + CMD1(0xEB, 0xB7); + CMD1(0xEC, 0x00); + CMD1(0xED, 0x11); + CMD1(0xF0, 0xB7); + CMD1(0x53, 0x04); + CMD1(0x54, 0x04); + CMD1(0x55, 0x40); + CMD1(0x56, 0x40); + CMD2(0xA0, 0x60, 0x01); + CMD1(0xA1, 0x84); + CMD1(0xA2, 0x85); + CMD2(0xAB, 0x00, 0x02); + CMD2(0xAC, 0x00, 0x06); + CMD2(0xAD, 0x00, 0x03); + CMD2(0xAE, 0x00, 0x07); + CMD1(0xC7, 0x01); + CMD1(0xB9, 0x82); + CMD1(0xBA, 0x83); + CMD1(0xBB, 0x00); + CMD1(0xBC, 0x81); + CMD1(0xBD, 0x02); + CMD1(0xBE, 0x01); + CMD1(0xBF, 0x04); + CMD1(0xC0, 0x03); + CMD1(0xC8, 0x55); + CMD1(0xC9, 0xC9); + CMD1(0xCA, 0xC8); + CMD1(0xCB, 0xCB); + CMD1(0xCC, 0xCA); + CMD1(0xCD, 0x55); + CMD1(0xCE, 0xCE); + CMD1(0xCF, 0xCD); + CMD1(0xD0, 0xD0); + CMD1(0xD1, 0xCF); + CMD1(0xF2, 0x46); + CMD1(0xA8, 0x04); + CMD1(0xA9, 0xB0); + CMD1(0xAA, 0xA3); + CMD1(0xB6, 0x00); + CMD1(0xB7, 0xB0); + CMD1(0xB8, 0xA3); + CMD1(0xC4, 0x03); + CMD1(0xC5, 0xB0); + CMD1(0xC6, 0xA3); + CMD1(0x80, 0x10); + CMD1(0xFF, 0x00); + CMD1(0x35, 0x00); + CMD0(NV3001B_SLPOUT); + delay(120); + CMD1(NV3001B_COLMOD, 0x05); + CMD1(NV3001B_MADCTL, nv3001bMADCTL(DISPLAY_ROTATION)); + CMD0(NV3001B_DISPON); + delay(10); + +#undef CMD0 +#undef CMD1 +#undef CMD2 +} + +void NV3001BDisplay::fillPhysicalRect(int x, int y, int w, int h) { + if (!is_on || w <= 0 || h <= 0) return; + + if (x < 0) { + w += x; + x = 0; + } + if (y < 0) { + h += y; + y = 0; + } + if (x + w > NV3001B_SCREEN_WIDTH) w = NV3001B_SCREEN_WIDTH - x; + if (y + h > NV3001B_SCREEN_HEIGHT) h = NV3001B_SCREEN_HEIGHT - y; + if (w <= 0 || h <= 0) return; + + setAddrWindow(x, y, w, h); + writeColor(color, (uint32_t)w * h); +} + +void NV3001BDisplay::drawChar(int x, int y, char ch) { + if (ch < 32 || ch > 127) ch = '?'; + + uint16_t index = (uint16_t)(ch - 32) * 5; + int scale_x = textPixelScaleX(text_size); + int scale_y = textPixelScaleY(text_size); + for (int col = 0; col < 5; col++) { + uint8_t line = pgm_read_byte(font5x7 + index + col); + for (int row = 0; row < 7; row++) { + if (line & (1 << row)) { + fillPhysicalRect(x + col * scale_x, y + row * scale_y, scale_x, scale_y); + } + } + } +} + +bool NV3001BDisplay::begin() { + if (is_on) return true; + + if (periph_power) periph_power->claim(); + + setupOptionalOutput(PIN_TFT_EN, PIN_TFT_EN_ACTIVE); + setupOptionalOutput(PIN_TFT_BL, !PIN_TFT_BL_ACTIVE); + pinMode(PIN_TFT_CS, OUTPUT); + pinMode(PIN_TFT_DC, OUTPUT); + digitalWrite(PIN_TFT_CS, HIGH); + digitalWrite(PIN_TFT_DC, HIGH); + delay(20); + + spi.begin(PIN_TFT_SCL, PIN_TFT_MISO, PIN_TFT_SDA, PIN_TFT_CS); + if (PIN_TFT_RST >= 0) { + pinMode(PIN_TFT_RST, OUTPUT); + digitalWrite(PIN_TFT_RST, HIGH); + delay(10); + digitalWrite(PIN_TFT_RST, LOW); + delay(20); + digitalWrite(PIN_TFT_RST, HIGH); + delay(120); + } + + initPanel(); + is_on = true; + color = 0x0000; + fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT); + color = 0xffff; + text_size = 1; + cursor_x = 0; + cursor_y = 0; + writeOptionalPin(PIN_TFT_BL, PIN_TFT_BL_ACTIVE); + return true; +} + +void NV3001BDisplay::turnOn() { + begin(); +} + +void NV3001BDisplay::turnOff() { + if (!is_on) return; + + writeOptionalPin(PIN_TFT_BL, !PIN_TFT_BL_ACTIVE); + writeOptionalPin(PIN_TFT_EN, !PIN_TFT_EN_ACTIVE); + is_on = false; + if (periph_power) periph_power->release(); +} + +void NV3001BDisplay::clear() { + uint16_t saved = color; + color = 0x0000; + fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT); + color = saved; +} + +void NV3001BDisplay::startFrame(Color bkg) { + color = mapColor(bkg); + fillPhysicalRect(0, 0, NV3001B_SCREEN_WIDTH, NV3001B_SCREEN_HEIGHT); + color = 0xffff; + text_size = 1; + cursor_x = 0; + cursor_y = 0; +} + +void NV3001BDisplay::setTextSize(int sz) { + text_size = sz < 1 ? 1 : sz; +} + +void NV3001BDisplay::setColor(Color c) { + color = mapColor(c); +} + +void NV3001BDisplay::setCursor(int x, int y) { + cursor_x = scaleX(x); + cursor_y = scaleY(y); +} + +void NV3001BDisplay::print(const char* str) { + if (!str || !is_on) return; + + int scale_x = textPixelScaleX(text_size); + int scale_y = textPixelScaleY(text_size); + while (*str) { + if (*str == '\n') { + cursor_x = 0; + cursor_y += 8 * scale_y; + } else if (*str == '\r') { + cursor_x = 0; + } else { + drawChar(cursor_x, cursor_y, *str); + cursor_x += 6 * scale_x; + } + str++; + } +} + +void NV3001BDisplay::fillRect(int x, int y, int w, int h) { + fillPhysicalRect(scaleX(x), scaleY(y), scaleWidth(x, w), scaleHeight(y, h)); +} + +void NV3001BDisplay::drawRect(int x, int y, int w, int h) { + int x1 = scaleX(x); + int y1 = scaleY(y); + int sw = scaleWidth(x, w); + int sh = scaleHeight(y, h); + + fillPhysicalRect(x1, y1, sw, 1); + fillPhysicalRect(x1, y1 + sh - 1, sw, 1); + fillPhysicalRect(x1, y1, 1, sh); + fillPhysicalRect(x1 + sw - 1, y1, 1, sh); +} + +void NV3001BDisplay::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { + if (!bits || !is_on) return; + + int byte_width = (w + 7) / 8; + for (int j = 0; j < h; j++) { + for (int i = 0; i < w; i++) { + uint8_t byte = pgm_read_byte(bits + j * byte_width + i / 8); + if (byte & (0x80 >> (i & 7))) { + fillPhysicalRect(scaleX(x + i), scaleY(y + j), scaleWidth(x + i, 1), scaleHeight(y + j, 1)); + } + } + } +} + +uint16_t NV3001BDisplay::getTextWidth(const char* str) { + if (!str) return 0; + + uint16_t len = 0; + while (str[len] && str[len] != '\n' && str[len] != '\r') len++; + return (uint16_t)((len * 6 * textPixelScaleX(text_size)) / DISPLAY_SCALE_X); +} + +void NV3001BDisplay::endFrame() { +} diff --git a/src/helpers/ui/NV3001BDisplay.h b/src/helpers/ui/NV3001BDisplay.h new file mode 100644 index 00000000..98cdaae8 --- /dev/null +++ b/src/helpers/ui/NV3001BDisplay.h @@ -0,0 +1,68 @@ +#pragma once + +#include "DisplayDriver.h" +#include +#include + +#ifndef NV3001B_LOGICAL_WIDTH + #define NV3001B_LOGICAL_WIDTH 128 +#endif + +#ifndef NV3001B_LOGICAL_HEIGHT + #define NV3001B_LOGICAL_HEIGHT 64 +#endif + +#ifndef NV3001B_PANEL_WIDTH + #define NV3001B_PANEL_WIDTH 128 +#endif + +#ifndef NV3001B_PANEL_HEIGHT + #define NV3001B_PANEL_HEIGHT 220 +#endif + +#ifndef NV3001B_SPI_HOST + #define NV3001B_SPI_HOST HSPI +#endif + +class NV3001BDisplay : public DisplayDriver { + SPIClass spi; + RefCountedDigitalPin* periph_power; + bool is_on = false; + uint16_t color = 0xffff; + uint8_t text_size = 1; + int cursor_x = 0; + int cursor_y = 0; + + void writeCommand(uint8_t cmd); + void writeBytes(const uint8_t* data, size_t len); + void writeCommandData(uint8_t cmd, const uint8_t* data, size_t len); + void setAddrWindow(uint16_t x, uint16_t y, uint16_t w, uint16_t h); + void writeColor(uint16_t rgb, uint32_t count); + void fillPhysicalRect(int x, int y, int w, int h); + void initPanel(); + void drawChar(int x, int y, char ch); + +public: + NV3001BDisplay(RefCountedDigitalPin* power = nullptr) : + DisplayDriver(NV3001B_LOGICAL_WIDTH, NV3001B_LOGICAL_HEIGHT), spi(NV3001B_SPI_HOST), periph_power(power) { } + + bool begin(); + static const char* driverName() { return "NV3001B"; } + static uint16_t physicalWidth() { return NV3001B_PANEL_WIDTH; } + static uint16_t physicalHeight() { return NV3001B_PANEL_HEIGHT; } + + bool isOn() override { return is_on; } + void turnOn() override; + void turnOff() override; + void clear() override; + void startFrame(Color bkg = DARK) override; + void setTextSize(int sz) override; + void setColor(Color c) override; + void setCursor(int x, int y) override; + void print(const char* str) override; + void fillRect(int x, int y, int w, int h) override; + void drawRect(int x, int y, int w, int h) override; + void drawXbm(int x, int y, const uint8_t* bits, int w, int h) override; + uint16_t getTextWidth(const char* str) override; + void endFrame() override; +}; diff --git a/variants/heltec_rc32/HeltecRC32Board.cpp b/variants/heltec_rc32/HeltecRC32Board.cpp new file mode 100644 index 00000000..37188e9e --- /dev/null +++ b/variants/heltec_rc32/HeltecRC32Board.cpp @@ -0,0 +1,66 @@ +#include "HeltecRC32Board.h" + +void HeltecRC32Board::begin() { + ESP32Board::begin(); + + pinMode(PIN_ADC_CTRL, OUTPUT); + digitalWrite(PIN_ADC_CTRL, !ADC_CTRL_ENABLED); + +#ifdef SENSOR_RST_PIN + pinMode(SENSOR_RST_PIN, OUTPUT); + digitalWrite(SENSOR_RST_PIN, HIGH); +#endif + +#ifdef LED_POWER + pinMode(LED_POWER, OUTPUT); + digitalWrite(LED_POWER, LOW); +#endif + + periph_power.begin(); + vext_power.begin(); + + esp_reset_reason_t reason = esp_reset_reason(); + if (reason == ESP_RST_DEEPSLEEP) { + long wakeup_source = esp_sleep_get_ext1_wakeup_status(); + if (wakeup_source & (1L << 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 HeltecRC32Board::powerOff() { + enterDeepSleep(0); +} + +void HeltecRC32Board::onBeforeTransmit() { + digitalWrite(P_LORA_TX_LED, HIGH); +} + +void HeltecRC32Board::onAfterTransmit() { + digitalWrite(P_LORA_TX_LED, LOW); +} + +uint16_t HeltecRC32Board::getBattMilliVolts() { + analogReadResolution(12); + digitalWrite(PIN_ADC_CTRL, ADC_CTRL_ENABLED); + delay(10); + uint32_t raw = 0; + for (int i = 0; i < 8; i++) { + raw += analogReadMilliVolts(PIN_VBAT_READ); + } + raw = raw / 8; + + return (adc_mult * raw); +} + +bool HeltecRC32Board::setAdcMultiplier(float multiplier) { + adc_mult = multiplier == 0.0f ? ADC_MULTIPLIER : multiplier; + return true; +} + +const char* HeltecRC32Board::getManufacturerName() const { + return "Heltec RC32"; +} diff --git a/variants/heltec_rc32/HeltecRC32Board.h b/variants/heltec_rc32/HeltecRC32Board.h new file mode 100644 index 00000000..256cb964 --- /dev/null +++ b/variants/heltec_rc32/HeltecRC32Board.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include +#include +#include + +#ifndef ADC_MULTIPLIER + #define ADC_MULTIPLIER 4.9f +#endif + +class HeltecRC32Board : public ESP32Board { +protected: + float adc_mult = ADC_MULTIPLIER; + +public: + RefCountedDigitalPin periph_power; + RefCountedDigitalPin vext_power; + + HeltecRC32Board() : + periph_power(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON), + vext_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { } + + void begin(); + void onBeforeTransmit() override; + void onAfterTransmit() override; + void powerOff() override; + uint16_t getBattMilliVolts() override; + bool setAdcMultiplier(float multiplier) override; + float getAdcMultiplier() const override { return adc_mult; } + const char* getManufacturerName() const override; +}; diff --git a/variants/heltec_rc32/pins_arduino.h b/variants/heltec_rc32/pins_arduino.h new file mode 100644 index 00000000..8ea3a0c1 --- /dev/null +++ b/variants/heltec_rc32/pins_arduino.h @@ -0,0 +1,60 @@ +#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 = 21; +static const uint8_t SCL = 18; + +static const uint8_t SS = 10; +static const uint8_t MOSI = 12; +static const uint8_t MISO = 13; +static const uint8_t SCK = 11; + +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 RST_LoRa = 9; +static const uint8_t BUSY_LoRa = 1; +static const uint8_t DIO1_LoRa = 14; + +#endif diff --git a/variants/heltec_rc32/platformio.ini b/variants/heltec_rc32/platformio.ini new file mode 100644 index 00000000..9284650b --- /dev/null +++ b/variants/heltec_rc32/platformio.ini @@ -0,0 +1,346 @@ +[Heltec_RC32] +extends = esp32_base +board = heltec-rc32 +board_build.partitions = default_16MB.csv +build_flags = + ${esp32_base.build_flags} + ${sensor_base.build_flags} + -I variants/heltec_rc32 + -I src/helpers/ui + -D HELTEC_RC32 + -D USE_SX1262 + -D ESP32_CPU_FREQ=160 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D P_LORA_DIO_1=14 + -D P_LORA_NSS=10 + -D P_LORA_RESET=9 + -D P_LORA_BUSY=1 + -D P_LORA_SCLK=11 + -D P_LORA_MISO=13 + -D P_LORA_MOSI=12 + -D LORA_TX_POWER=22 + -D PIN_USER_BTN=0 + -D PIN_BOARD_SDA=21 + -D PIN_BOARD_SCL=18 + -D PIN_VEXT_EN=3 + -D PIN_VEXT_EN_ACTIVE=HIGH + -D SENSOR_POWER_CTRL_PIN=46 + -D SENSOR_POWER_ON=HIGH + -D SENSOR_RST_PIN=2 + -D P_LORA_TX_LED=47 + -D PIN_BUZZER=48 + -D PIN_TFT_SCL=17 + -D PIN_TFT_SDA=38 + -D PIN_TFT_CS=39 + -D PIN_TFT_DC=16 + -D PIN_TFT_RST=4 + -D PIN_TFT_EN=6 + -D PIN_TFT_EN_ACTIVE=LOW + -D PIN_TFT_BL=5 + -D PIN_TFT_BL_ACTIVE=HIGH + -D SPI_FREQUENCY=8000000 + -D PIN_GPS_TX=44 + -D PIN_GPS_RX=43 + -D PIN_GPS_EN=45 + -D PIN_GPS_EN_ACTIVE=HIGH + -D PIN_GPS_RESET=40 + -D PIN_GPS_RESET_ACTIVE=LOW + -D PIN_GPS_PPS=41 + -D GPS_BAUD_RATE=9600 + -D ENV_INCLUDE_GPS=1 + -D PIN_ADC_CTRL=15 + -D PIN_VBAT_READ=7 + -D ADC_CTRL_ENABLED=HIGH + -D ADC_MULTIPLIER=4.9 + -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 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/heltec_rc32> + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + +[Heltec_RC32_with_display] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -D HELTEC_RC32_WITH_DISPLAY + -D DISPLAY_CLASS=NV3001BDisplay +build_src_filter = ${Heltec_RC32.build_src_filter} + + + + +lib_deps = + ${Heltec_RC32.lib_deps} + +[env:heltec_rc32_without_display_repeater] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -D ADVERT_NAME='"Heltec RC32 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${Heltec_RC32.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${Heltec_RC32.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_rc32_without_display_repeater_bridge_espnow] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${Heltec_RC32.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_RC32.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_rc32_without_display_room_server] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -D ADVERT_NAME='"Heltec RC32 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_RC32.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${Heltec_RC32.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_rc32_without_display_companion_radio_usb] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +build_src_filter = ${Heltec_RC32.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_RC32.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_rc32_without_display_companion_radio_ble] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_RC32.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_RC32.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_rc32_without_display_companion_radio_wifi] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -I examples/companion_radio/ui-new + -D DISPLAY_CLASS=NullDisplayDriver + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_RC32.build_src_filter} + + + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_RC32.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_rc32_without_display_sensor] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -D ADVERT_NAME='"Heltec RC32 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=21 + -D ENV_PIN_SCL=18 +build_src_filter = ${Heltec_RC32.build_src_filter} + +<../examples/simple_sensor> +lib_deps = + ${Heltec_RC32.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_rc32_repeater] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -D ADVERT_NAME='"Heltec RC32 Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + ${esp32_ota.lib_deps} + bakercp/CRC32 @ ^2.0.0 + +[env:heltec_rc32_repeater_bridge_espnow] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -D ADVERT_NAME='"ESPNow Bridge"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + -D WITH_ESPNOW_BRIDGE=1 +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_rc32_room_server] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -D ADVERT_NAME='"Heltec RC32 Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_rc32_terminal_chat] +extends = Heltec_RC32 +build_flags = + ${Heltec_RC32.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +build_src_filter = ${Heltec_RC32.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${Heltec_RC32.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:heltec_rc32_companion_radio_usb] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_rc32_companion_radio_ble] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D AUTO_SHUTDOWN_MILLIVOLTS=3400 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_rc32_companion_radio_wifi] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D WIFI_DEBUG_LOGGING=1 + -D WIFI_SSID='"myssid"' + -D WIFI_PWD='"mypwd"' + -D OFFLINE_QUEUE_SIZE=256 +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + densaugeo/base64 @ ~1.4.0 + end2endzone/NonBlockingRTTTL@^1.3.0 + +[env:heltec_rc32_sensor] +extends = Heltec_RC32_with_display +build_flags = + ${Heltec_RC32_with_display.build_flags} + -D ADVERT_NAME='"Heltec RC32 Sensor"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ENV_PIN_SDA=21 + -D ENV_PIN_SCL=18 +build_src_filter = ${Heltec_RC32_with_display.build_src_filter} + +<../examples/simple_sensor> +lib_deps = + ${Heltec_RC32_with_display.lib_deps} + ${esp32_ota.lib_deps} + +[env:heltec_rc32_kiss_modem] +extends = Heltec_RC32 +build_src_filter = ${Heltec_RC32.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/heltec_rc32/target.cpp b/variants/heltec_rc32/target.cpp new file mode 100644 index 00000000..6c88d2bb --- /dev/null +++ b/variants/heltec_rc32/target.cpp @@ -0,0 +1,45 @@ +#include +#include "target.h" + +HeltecRC32Board board; + +#if defined(P_LORA_SCLK) + static SPIClass spi(FSPI); + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, spi); +#else + RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY); +#endif + +WRAPPER_CLASS radio_driver(radio, board); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#if ENV_INCLUDE_GPS + #include + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, PIN_GPS_RESET, PIN_GPS_EN, &board.periph_power); + EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else + EnvironmentSensorManager sensors; +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + fallback_clock.begin(); + rtc_clock.begin(Wire); + +#if defined(P_LORA_SCLK) + return radio.std_init(&spi); +#else + return radio.std_init(); +#endif +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); +} diff --git a/variants/heltec_rc32/target.h b/variants/heltec_rc32/target.h new file mode 100644 index 00000000..ae692598 --- /dev/null +++ b/variants/heltec_rc32/target.h @@ -0,0 +1,31 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include + +#ifdef DISPLAY_CLASS +#include +#ifdef HELTEC_RC32_WITH_DISPLAY +#include +#else +#include +#endif +#endif + +extern HeltecRC32Board board; +extern WRAPPER_CLASS radio_driver; +extern AutoDiscoverRTCClock rtc_clock; +extern EnvironmentSensorManager sensors; + +#ifdef DISPLAY_CLASS + extern DISPLAY_CLASS display; + extern MomentaryButton user_btn; +#endif + +bool radio_init(); +mesh::LocalIdentity radio_new_identity(); diff --git a/variants/heltec_rc32/variant.h b/variants/heltec_rc32/variant.h new file mode 100644 index 00000000..d510ab9b --- /dev/null +++ b/variants/heltec_rc32/variant.h @@ -0,0 +1,51 @@ +#ifndef _VARIANT_HELTEC_RC32_ +#define _VARIANT_HELTEC_RC32_ + +#define BUTTON_PIN 0 + +#define HAS_GPS 1 +#undef GPS_RX_PIN +#undef GPS_TX_PIN +#define GPS_RX_PIN 44 +#define GPS_TX_PIN 43 +#define PIN_GPS_EN 45 +#define GPS_EN_ACTIVE HIGH +#define PIN_GPS_RESET 40 +#define GPS_RESET_MODE LOW +#define PIN_GPS_PPS 41 + +#define I2C_SCL 18 +#define I2C_SDA 21 +#define SENSOR_INT_PIN 42 +#define SENSOR_RST_PIN 2 +#define SENSOR_POWER_CTRL_PIN 46 +#define SENSOR_POWER_ON HIGH +#define PERIPHERAL_WARMUP_MS 100 + +#define VEXT_ENABLE 3 +#define VEXT_ON_VALUE HIGH + +#define USE_SX1262 +#define LORA_SCK 11 +#define LORA_MISO 13 +#define LORA_MOSI 12 +#define LORA_CS 10 +#define LORA_DIO0 RADIOLIB_NC +#define LORA_DIO1 14 +#define LORA_RESET 9 + +#define SX126X_CS LORA_CS +#define SX126X_DIO1 LORA_DIO1 +#define SX126X_BUSY 1 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 + +#define BATTERY_PIN 7 +#define ADC_CHANNEL ADC_CHANNEL_6 +#define ADC_CTRL 15 +#define ADC_CTRL_ENABLED HIGH +#define ADC_MULTIPLIER 4.9 +#define ADC_ATTENUATION ADC_ATTEN_DB_2_5 + +#endif From 05d4bd2f7f363f2d69a776ed758838328eea2fa3 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Wed, 15 Jul 2026 17:45:24 +0800 Subject: [PATCH 07/14] Add a rotary encoder --- examples/companion_radio/ui-new/UITask.cpp | 10 ++ src/helpers/ui/RotaryInput.h | 18 +++ variants/heltec_rc32/HeltecRC32Board.cpp | 12 +- variants/heltec_rc32/HeltecRC32Board.h | 11 +- .../heltec_rc32/HeltecRC32RotaryInput.cpp | 122 ++++++++++++++++++ variants/heltec_rc32/HeltecRC32RotaryInput.h | 26 ++++ variants/heltec_rc32/platformio.ini | 5 +- variants/heltec_rc32/target.cpp | 5 +- variants/heltec_rc32/target.h | 6 + variants/heltec_rc32/variant.h | 3 - 10 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 src/helpers/ui/RotaryInput.h create mode 100644 variants/heltec_rc32/HeltecRC32RotaryInput.cpp create mode 100644 variants/heltec_rc32/HeltecRC32RotaryInput.h diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 28591cc1..64e61bac 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -750,6 +750,16 @@ void UITask::loop() { c = handleTripleClick(KEY_SELECT); } #endif +#if defined(UI_HAS_ROTARY_INPUT) + if (c == 0) { + RotaryInputEvent ev = rotary_input.poll(); + if (ev == RotaryInputEvent::Next) { + c = checkDisplayOn(KEY_NEXT); + } else if (ev == RotaryInputEvent::Prev) { + c = checkDisplayOn(KEY_PREV); + } + } +#endif #if defined(PIN_USER_BTN_ANA) if (abs(millis() - _analogue_pin_read_millis) > 10) { int ev = analog_btn.check(); diff --git a/src/helpers/ui/RotaryInput.h b/src/helpers/ui/RotaryInput.h new file mode 100644 index 00000000..2dda695b --- /dev/null +++ b/src/helpers/ui/RotaryInput.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +enum class RotaryInputEvent : uint8_t { + None, + Next, + Prev, +}; + +class RotaryInput { +public: + virtual ~RotaryInput() = default; + + virtual bool begin() = 0; + virtual RotaryInputEvent poll() = 0; + virtual bool isReady() const = 0; +}; diff --git a/variants/heltec_rc32/HeltecRC32Board.cpp b/variants/heltec_rc32/HeltecRC32Board.cpp index 37188e9e..11be483e 100644 --- a/variants/heltec_rc32/HeltecRC32Board.cpp +++ b/variants/heltec_rc32/HeltecRC32Board.cpp @@ -1,4 +1,14 @@ #include "HeltecRC32Board.h" +#if defined(UI_HAS_ROTARY_INPUT) +#include "HeltecRC32RotaryInput.h" +#endif + +#if defined(UI_HAS_ROTARY_INPUT) +RotaryInput& HeltecRC32Board::rotaryInput() { + static HeltecRC32RotaryInput input(&periph_power); + return input; +} +#endif void HeltecRC32Board::begin() { ESP32Board::begin(); @@ -17,7 +27,7 @@ void HeltecRC32Board::begin() { #endif periph_power.begin(); - vext_power.begin(); + periph_power.claim(); esp_reset_reason_t reason = esp_reset_reason(); if (reason == ESP_RST_DEEPSLEEP) { diff --git a/variants/heltec_rc32/HeltecRC32Board.h b/variants/heltec_rc32/HeltecRC32Board.h index 256cb964..5f3a0adf 100644 --- a/variants/heltec_rc32/HeltecRC32Board.h +++ b/variants/heltec_rc32/HeltecRC32Board.h @@ -4,6 +4,9 @@ #include #include #include +#if defined(UI_HAS_ROTARY_INPUT) +#include +#endif #ifndef ADC_MULTIPLIER #define ADC_MULTIPLIER 4.9f @@ -15,13 +18,13 @@ protected: public: RefCountedDigitalPin periph_power; - RefCountedDigitalPin vext_power; - HeltecRC32Board() : - periph_power(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON), - vext_power(PIN_VEXT_EN, PIN_VEXT_EN_ACTIVE) { } + HeltecRC32Board() : periph_power(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON){} void begin(); +#if defined(UI_HAS_ROTARY_INPUT) + RotaryInput& rotaryInput(); +#endif void onBeforeTransmit() override; void onAfterTransmit() override; void powerOff() override; diff --git a/variants/heltec_rc32/HeltecRC32RotaryInput.cpp b/variants/heltec_rc32/HeltecRC32RotaryInput.cpp new file mode 100644 index 00000000..e8b282ff --- /dev/null +++ b/variants/heltec_rc32/HeltecRC32RotaryInput.cpp @@ -0,0 +1,122 @@ +#include "HeltecRC32RotaryInput.h" + +#include + +namespace { +constexpr uint8_t TCA6408_ADDR = 0x20; +constexpr uint8_t TCA6408_INPUT_REG = 0x00; +constexpr uint8_t TCA6408_POLARITY_REG = 0x02; +constexpr uint8_t TCA6408_CONFIG_REG = 0x03; +constexpr uint8_t TCA6408_ROTARY_A_MASK = 0x01; +constexpr uint8_t TCA6408_ROTARY_B_MASK = 0x02; +constexpr uint8_t TCA6408_ROTARY_MASK = TCA6408_ROTARY_A_MASK | TCA6408_ROTARY_B_MASK; +constexpr uint32_t TCA6408_DEBOUNCE_MS = 5; +} + +bool HeltecRC32RotaryInput::begin() { + initialized = true; + ready = false; + input_state = TCA6408_ROTARY_MASK; + active_low_phase = false; + + if (periph_power && !power_claimed) { + periph_power->claim(); + power_claimed = true; + delay(12); + } + + if (!writeRegister(TCA6408_POLARITY_REG, 0x00) || !writeRegister(TCA6408_CONFIG_REG, 0xFF)) { + return false; + } + + uint8_t state = 0; + if (!readInput(state)) { + return false; + } + + input_state = state & TCA6408_ROTARY_MASK; + ready = true; + return true; +} + +RotaryInputEvent HeltecRC32RotaryInput::poll() { + if (!initialized) { + begin(); + return RotaryInputEvent::None; + } + + if (!ready) { + return RotaryInputEvent::None; + } + + uint8_t new_state = 0; + if (!readInput(new_state)) { + return RotaryInputEvent::None; + } + + new_state &= TCA6408_ROTARY_MASK; + RotaryInputEvent event = handleTransition(new_state); + input_state = new_state; + return event; +} + +bool HeltecRC32RotaryInput::writeRegister(uint8_t reg, uint8_t value) { + Wire.beginTransmission(TCA6408_ADDR); + Wire.write(reg); + Wire.write(value); + return Wire.endTransmission() == 0; +} + +bool HeltecRC32RotaryInput::readInput(uint8_t& value) { + Wire.beginTransmission(TCA6408_ADDR); + Wire.write(TCA6408_INPUT_REG); + if (Wire.endTransmission(false) != 0) { + return false; + } + if (Wire.requestFrom(TCA6408_ADDR, static_cast(1)) != 1) { + return false; + } + + value = Wire.read(); + return true; +} + +RotaryInputEvent HeltecRC32RotaryInput::handleTransition(uint8_t newState) { + uint8_t changed = (input_state ^ newState) & TCA6408_ROTARY_MASK; + RotaryInputEvent event = RotaryInputEvent::None; + bool a_low = (newState & TCA6408_ROTARY_A_MASK) == 0; + bool b_low = (newState & TCA6408_ROTARY_B_MASK) == 0; + + if (!a_low && !b_low) { + active_low_phase = false; + } + + if (!active_low_phase && (changed & TCA6408_ROTARY_A_MASK) && a_low && !b_low) { + event = RotaryInputEvent::Prev; + active_low_phase = true; + } else if (!active_low_phase && (changed & TCA6408_ROTARY_B_MASK) && b_low && !a_low) { + event = RotaryInputEvent::Next; + active_low_phase = true; + } + + if (event == RotaryInputEvent::None && !active_low_phase && (changed & TCA6408_ROTARY_A_MASK)) { + bool a_rising = (newState & TCA6408_ROTARY_A_MASK) != 0; + if (a_rising && b_low) { + event = RotaryInputEvent::Prev; + } + } + + if (event == RotaryInputEvent::None && !active_low_phase && (changed & TCA6408_ROTARY_B_MASK)) { + bool b_rising = (newState & TCA6408_ROTARY_B_MASK) != 0; + if (b_rising && a_low) { + event = RotaryInputEvent::Next; + } + } + + if (event == RotaryInputEvent::None || (millis() - last_event_ms) < TCA6408_DEBOUNCE_MS) { + return RotaryInputEvent::None; + } + + last_event_ms = millis(); + return event; +} diff --git a/variants/heltec_rc32/HeltecRC32RotaryInput.h b/variants/heltec_rc32/HeltecRC32RotaryInput.h new file mode 100644 index 00000000..36557dca --- /dev/null +++ b/variants/heltec_rc32/HeltecRC32RotaryInput.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +class HeltecRC32RotaryInput : public RotaryInput { +public: + explicit HeltecRC32RotaryInput(RefCountedDigitalPin* periphPower = nullptr) : periph_power(periphPower) { } + + bool begin() override; + RotaryInputEvent poll() override; + bool isReady() const override { return ready; } + +private: + bool writeRegister(uint8_t reg, uint8_t value); + bool readInput(uint8_t& value); + RotaryInputEvent handleTransition(uint8_t newState); + + uint8_t input_state = 0x03; + uint32_t last_event_ms = 0; + bool ready = false; + bool initialized = false; + bool power_claimed = false; + bool active_low_phase = false; + RefCountedDigitalPin* periph_power = nullptr; +}; diff --git a/variants/heltec_rc32/platformio.ini b/variants/heltec_rc32/platformio.ini index 9284650b..545472d8 100644 --- a/variants/heltec_rc32/platformio.ini +++ b/variants/heltec_rc32/platformio.ini @@ -23,8 +23,6 @@ build_flags = -D PIN_USER_BTN=0 -D PIN_BOARD_SDA=21 -D PIN_BOARD_SCL=18 - -D PIN_VEXT_EN=3 - -D PIN_VEXT_EN_ACTIVE=HIGH -D SENSOR_POWER_CTRL_PIN=46 -D SENSOR_POWER_ON=HIGH -D SENSOR_RST_PIN=2 @@ -270,6 +268,7 @@ extends = Heltec_RC32_with_display build_flags = ${Heltec_RC32_with_display.build_flags} -I examples/companion_radio/ui-new + -D UI_HAS_ROTARY_INPUT -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 build_src_filter = ${Heltec_RC32_with_display.build_src_filter} @@ -287,6 +286,7 @@ extends = Heltec_RC32_with_display build_flags = ${Heltec_RC32_with_display.build_flags} -I examples/companion_radio/ui-new + -D UI_HAS_ROTARY_INPUT -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D BLE_PIN_CODE=123456 @@ -308,6 +308,7 @@ extends = Heltec_RC32_with_display build_flags = ${Heltec_RC32_with_display.build_flags} -I examples/companion_radio/ui-new + -D UI_HAS_ROTARY_INPUT -D MAX_CONTACTS=350 -D MAX_GROUP_CHANNELS=40 -D WIFI_DEBUG_LOGGING=1 diff --git a/variants/heltec_rc32/target.cpp b/variants/heltec_rc32/target.cpp index 6c88d2bb..386fc559 100644 --- a/variants/heltec_rc32/target.cpp +++ b/variants/heltec_rc32/target.cpp @@ -17,7 +17,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, PIN_GPS_RESET, PIN_GPS_EN, &board.periph_power); + MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, PIN_GPS_RESET, PIN_GPS_EN); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -26,6 +26,9 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#if defined(UI_HAS_ROTARY_INPUT) + RotaryInput& rotary_input = board.rotaryInput(); +#endif #endif bool radio_init() { diff --git a/variants/heltec_rc32/target.h b/variants/heltec_rc32/target.h index ae692598..04cb6d94 100644 --- a/variants/heltec_rc32/target.h +++ b/variants/heltec_rc32/target.h @@ -10,6 +10,9 @@ #ifdef DISPLAY_CLASS #include +#if defined(UI_HAS_ROTARY_INPUT) +#include +#endif #ifdef HELTEC_RC32_WITH_DISPLAY #include #else @@ -25,6 +28,9 @@ extern EnvironmentSensorManager sensors; #ifdef DISPLAY_CLASS extern DISPLAY_CLASS display; extern MomentaryButton user_btn; +#if defined(UI_HAS_ROTARY_INPUT) + extern RotaryInput& rotary_input; +#endif #endif bool radio_init(); diff --git a/variants/heltec_rc32/variant.h b/variants/heltec_rc32/variant.h index d510ab9b..0546fbc4 100644 --- a/variants/heltec_rc32/variant.h +++ b/variants/heltec_rc32/variant.h @@ -22,9 +22,6 @@ #define SENSOR_POWER_ON HIGH #define PERIPHERAL_WARMUP_MS 100 -#define VEXT_ENABLE 3 -#define VEXT_ON_VALUE HIGH - #define USE_SX1262 #define LORA_SCK 11 #define LORA_MISO 13 From 3469e924586151f2209dc5a2d5a353e8959e0081 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 16 Jul 2026 11:37:12 +0800 Subject: [PATCH 08/14] Enable patch reception --- variants/heltec_rc32/platformio.ini | 1 + 1 file changed, 1 insertion(+) diff --git a/variants/heltec_rc32/platformio.ini b/variants/heltec_rc32/platformio.ini index 545472d8..d535fb77 100644 --- a/variants/heltec_rc32/platformio.ini +++ b/variants/heltec_rc32/platformio.ini @@ -55,6 +55,7 @@ build_flags = -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 + -D SX126X_REGISTER_PATCH=1 ; Patch register 0x8B5 for improved RX build_src_filter = ${esp32_base.build_src_filter} +<../variants/heltec_rc32> + From a472cd7956cb5cb31f650cdb2f4f04d4451ea259 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 16 Jul 2026 15:14:17 +0800 Subject: [PATCH 09/14] Delete duplicate pin definitions --- variants/heltec_rc32/variant.h | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/variants/heltec_rc32/variant.h b/variants/heltec_rc32/variant.h index 0546fbc4..5f7b8d81 100644 --- a/variants/heltec_rc32/variant.h +++ b/variants/heltec_rc32/variant.h @@ -3,26 +3,11 @@ #define BUTTON_PIN 0 -#define HAS_GPS 1 -#undef GPS_RX_PIN -#undef GPS_TX_PIN -#define GPS_RX_PIN 44 -#define GPS_TX_PIN 43 -#define PIN_GPS_EN 45 -#define GPS_EN_ACTIVE HIGH -#define PIN_GPS_RESET 40 -#define GPS_RESET_MODE LOW -#define PIN_GPS_PPS 41 - #define I2C_SCL 18 #define I2C_SDA 21 #define SENSOR_INT_PIN 42 -#define SENSOR_RST_PIN 2 -#define SENSOR_POWER_CTRL_PIN 46 -#define SENSOR_POWER_ON HIGH #define PERIPHERAL_WARMUP_MS 100 -#define USE_SX1262 #define LORA_SCK 11 #define LORA_MISO 13 #define LORA_MOSI 12 @@ -35,14 +20,10 @@ #define SX126X_DIO1 LORA_DIO1 #define SX126X_BUSY 1 #define SX126X_RESET LORA_RESET -#define SX126X_DIO2_AS_RF_SWITCH -#define SX126X_DIO3_TCXO_VOLTAGE 1.8 #define BATTERY_PIN 7 #define ADC_CHANNEL ADC_CHANNEL_6 #define ADC_CTRL 15 -#define ADC_CTRL_ENABLED HIGH -#define ADC_MULTIPLIER 4.9 #define ADC_ATTENUATION ADC_ATTEN_DB_2_5 #endif From 61a8821d3c6f950738c3bd35bd13b0dfbefc6af0 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Thu, 16 Jul 2026 17:45:00 +0800 Subject: [PATCH 10/14] Optimize battery voltage reading --- variants/heltec_rc32/HeltecRC32Board.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/variants/heltec_rc32/HeltecRC32Board.cpp b/variants/heltec_rc32/HeltecRC32Board.cpp index 11be483e..0517f5ff 100644 --- a/variants/heltec_rc32/HeltecRC32Board.cpp +++ b/variants/heltec_rc32/HeltecRC32Board.cpp @@ -55,6 +55,7 @@ void HeltecRC32Board::onAfterTransmit() { uint16_t HeltecRC32Board::getBattMilliVolts() { analogReadResolution(12); + analogSetAttenuation(ADC_2_5db); digitalWrite(PIN_ADC_CTRL, ADC_CTRL_ENABLED); delay(10); uint32_t raw = 0; @@ -62,6 +63,7 @@ uint16_t HeltecRC32Board::getBattMilliVolts() { raw += analogReadMilliVolts(PIN_VBAT_READ); } raw = raw / 8; + digitalWrite(PIN_ADC_CTRL, !ADC_CTRL_ENABLED); return (adc_mult * raw); } From b26f53a263c41b1587065a5cfcb9049de3999184 Mon Sep 17 00:00:00 2001 From: Quency-D Date: Fri, 17 Jul 2026 15:35:07 +0800 Subject: [PATCH 11/14] Avoid waking display from rotary input --- examples/companion_radio/ui-new/UITask.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 64e61bac..5a23324c 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -751,12 +751,12 @@ void UITask::loop() { } #endif #if defined(UI_HAS_ROTARY_INPUT) - if (c == 0) { - RotaryInputEvent ev = rotary_input.poll(); - if (ev == RotaryInputEvent::Next) { - c = checkDisplayOn(KEY_NEXT); - } else if (ev == RotaryInputEvent::Prev) { - c = checkDisplayOn(KEY_PREV); + RotaryInputEvent rotaryEv = rotary_input.poll(); + if (c == 0 && _display != NULL && _display->isOn()) { + if (rotaryEv == RotaryInputEvent::Next) { + c = KEY_NEXT; + } else if (rotaryEv == RotaryInputEvent::Prev) { + c = KEY_PREV; } } #endif From fd7b35f457a580c1e43724135b68a0c1b6d801ba Mon Sep 17 00:00:00 2001 From: Quency-D Date: Fri, 17 Jul 2026 15:50:25 +0800 Subject: [PATCH 12/14] Move the rotation input to target.cpp --- variants/heltec_rc32/HeltecRC32Board.cpp | 10 ---------- variants/heltec_rc32/HeltecRC32Board.h | 6 ------ variants/heltec_rc32/target.cpp | 6 +++++- 3 files changed, 5 insertions(+), 17 deletions(-) diff --git a/variants/heltec_rc32/HeltecRC32Board.cpp b/variants/heltec_rc32/HeltecRC32Board.cpp index 0517f5ff..59920d23 100644 --- a/variants/heltec_rc32/HeltecRC32Board.cpp +++ b/variants/heltec_rc32/HeltecRC32Board.cpp @@ -1,14 +1,4 @@ #include "HeltecRC32Board.h" -#if defined(UI_HAS_ROTARY_INPUT) -#include "HeltecRC32RotaryInput.h" -#endif - -#if defined(UI_HAS_ROTARY_INPUT) -RotaryInput& HeltecRC32Board::rotaryInput() { - static HeltecRC32RotaryInput input(&periph_power); - return input; -} -#endif void HeltecRC32Board::begin() { ESP32Board::begin(); diff --git a/variants/heltec_rc32/HeltecRC32Board.h b/variants/heltec_rc32/HeltecRC32Board.h index 5f3a0adf..5b2093c8 100644 --- a/variants/heltec_rc32/HeltecRC32Board.h +++ b/variants/heltec_rc32/HeltecRC32Board.h @@ -4,9 +4,6 @@ #include #include #include -#if defined(UI_HAS_ROTARY_INPUT) -#include -#endif #ifndef ADC_MULTIPLIER #define ADC_MULTIPLIER 4.9f @@ -22,9 +19,6 @@ public: HeltecRC32Board() : periph_power(SENSOR_POWER_CTRL_PIN, SENSOR_POWER_ON){} void begin(); -#if defined(UI_HAS_ROTARY_INPUT) - RotaryInput& rotaryInput(); -#endif void onBeforeTransmit() override; void onAfterTransmit() override; void powerOff() override; diff --git a/variants/heltec_rc32/target.cpp b/variants/heltec_rc32/target.cpp index 386fc559..cc7e4deb 100644 --- a/variants/heltec_rc32/target.cpp +++ b/variants/heltec_rc32/target.cpp @@ -1,5 +1,8 @@ #include #include "target.h" +#if defined(UI_HAS_ROTARY_INPUT) +#include "HeltecRC32RotaryInput.h" +#endif HeltecRC32Board board; @@ -27,7 +30,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); DISPLAY_CLASS display; MomentaryButton user_btn(PIN_USER_BTN, 1000, true); #if defined(UI_HAS_ROTARY_INPUT) - RotaryInput& rotary_input = board.rotaryInput(); + static HeltecRC32RotaryInput rotaryInputImpl(&board.periph_power); + RotaryInput& rotary_input = rotaryInputImpl; #endif #endif From 84d1e24338138327394a447b7faad27934e4c314 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 17 Jul 2026 07:36:39 -0400 Subject: [PATCH 13/14] nrf52_variants: override shutdownPeripherals instead of powerOff for TechoLite, TInpulse and TechoBoard (protect the gate) --- variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h | 4 ++-- variants/lilygo_techo_card/TechoCardBoard.cpp | 4 ++-- variants/lilygo_techo_card/TechoCardBoard.h | 2 +- variants/lilygo_techo_lite/TechoBoard.h | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h b/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h index 5fafcad8..30bbc3a5 100644 --- a/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h +++ b/variants/lilygo_t_impulse_plus/TImpulsePlusBoard.h @@ -47,9 +47,9 @@ public: return "LilyGo T-Impulse-Plus"; } - void powerOff() override { + void shutdownPeripherals() override { // power off system - NRF52Board::powerOff(); + NRF52Board::shutdownPeripherals(); // turn off 3.3v digitalWrite(RT9080_EN, LOW); diff --git a/variants/lilygo_techo_card/TechoCardBoard.cpp b/variants/lilygo_techo_card/TechoCardBoard.cpp index 8143587d..8a3a54b1 100644 --- a/variants/lilygo_techo_card/TechoCardBoard.cpp +++ b/variants/lilygo_techo_card/TechoCardBoard.cpp @@ -87,11 +87,11 @@ void TechoCardBoard::turnOffLeds() { } } -void TechoCardBoard::powerOff() { +void TechoCardBoard::shutdownPeripherals() { nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_PULLUP, NRF_GPIO_PIN_SENSE_LOW); turnOffLeds(); digitalWrite(PIN_PWR_EN, LOW); - NRF52Board::powerOff(); + NRF52Board::shutdownPeripherals(); } #endif diff --git a/variants/lilygo_techo_card/TechoCardBoard.h b/variants/lilygo_techo_card/TechoCardBoard.h index 8a2913a6..a2ee3ea1 100644 --- a/variants/lilygo_techo_card/TechoCardBoard.h +++ b/variants/lilygo_techo_card/TechoCardBoard.h @@ -28,7 +28,7 @@ public: return "LilyGo T-Echo Card"; } - void powerOff() override; + void shutdownPeripherals() override; void toggleTorch(); void turnOffLeds(); diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index f4c16016..1e5651e7 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -21,8 +21,8 @@ public: return "LilyGo T-Echo Lite"; } - void powerOff() override { - NRF52Board::powerOff(); + void shutdownPeripherals() override { + NRF52Board::shutdownPeripherals(); digitalWrite(PIN_VBAT_MEAS_EN, LOW); #ifdef LED_RED From c644720ea7f969d45731e7cd018f1b57d9186a08 Mon Sep 17 00:00:00 2001 From: Florent Date: Fri, 17 Jul 2026 08:29:31 -0400 Subject: [PATCH 14/14] uitask: screen and radio poweroff moved to board --- examples/companion_radio/ui-new/UITask.cpp | 3 --- examples/companion_radio/ui-orig/UITask.cpp | 2 -- examples/companion_radio/ui-tiny/UITask.cpp | 2 -- examples/simple_repeater/UITask.cpp | 2 -- 4 files changed, 9 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 28591cc1..403ea463 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -697,9 +697,6 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - // still necessary until all boards are refactored to use poweroff - _display->turnOff(); - radio_driver.powerOff(); // Power off board including radio, display, GPS and components _board->powerOff(); } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 34a7342b..b48f6412 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -307,8 +307,6 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - _display->turnOff(); - radio_driver.powerOff(); // Power off board including radio, display, GPS and components _board->powerOff(); } diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index a6cbe9de..452c02d4 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -566,8 +566,6 @@ void UITask::shutdown(bool restart){ if (restart) { _board->reboot(); } else { - _display->turnOff(); - radio_driver.powerOff(); // Power off board including radio, display, GPS and components _board->powerOff(); } diff --git a/examples/simple_repeater/UITask.cpp b/examples/simple_repeater/UITask.cpp index 17c708e3..6751aad6 100644 --- a/examples/simple_repeater/UITask.cpp +++ b/examples/simple_repeater/UITask.cpp @@ -146,8 +146,6 @@ void UITask::loop() { digitalWrite(LED_PIN, LED_STATE_ON); // switch on the led until poweroff #endif if (millis() > _powering_off_at) { - _display->turnOff(); - radio_driver.powerOff(); _board->powerOff(); // should not return } }