From 98a625c1a1027e72da0bcb1ae2805fd59db4444f Mon Sep 17 00:00:00 2001 From: Jonathan Bennett Date: Tue, 29 Jul 2025 13:40:57 -0500 Subject: [PATCH 1/2] Initial support for the ThinkNode M5 --- src/graphics/EInkDisplay2.cpp | 15 +++ src/graphics/EInkDisplay2.h | 2 +- src/main.cpp | 15 +++ src/modules/SerialModule.cpp | 11 ++- src/platform/esp32/architecture.h | 2 + .../ELECROW-ThinkNode-M5/pins_arduino.h | 28 ++++++ .../ELECROW-ThinkNode-M5/platformio.ini | 22 +++++ .../esp32s3/ELECROW-ThinkNode-M5/variant.h | 99 +++++++++++++++++++ 8 files changed, 189 insertions(+), 5 deletions(-) create mode 100644 variants/esp32s3/ELECROW-ThinkNode-M5/pins_arduino.h create mode 100644 variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini create mode 100644 variants/esp32s3/ELECROW-ThinkNode-M5/variant.h diff --git a/src/graphics/EInkDisplay2.cpp b/src/graphics/EInkDisplay2.cpp index 3bd20feec..a627a42cc 100644 --- a/src/graphics/EInkDisplay2.cpp +++ b/src/graphics/EInkDisplay2.cpp @@ -151,6 +151,21 @@ bool EInkDisplay::connect() #else adafruitDisplay->setRotation(3); #endif + adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); + } +#elif defined(ELECROW_ThinkNode_M5) + { + // Start HSPI + hspi = new SPIClass(HSPI); + hspi->begin(PIN_EINK_SCLK, -1, PIN_EINK_MOSI, PIN_EINK_CS); // SCLK, MISO, MOSI, SS + + auto lowLevel = new EINK_DISPLAY_MODEL(PIN_EINK_CS, PIN_EINK_DC, PIN_EINK_RES, PIN_EINK_BUSY, *hspi); + + adafruitDisplay = new GxEPD2_BW(*lowLevel); + adafruitDisplay->init(); + + adafruitDisplay->setRotation(4); + adafruitDisplay->setPartialWindow(0, 0, displayWidth, displayHeight); } #elif defined(MESHLINK) diff --git a/src/graphics/EInkDisplay2.h b/src/graphics/EInkDisplay2.h index 284337627..b840ce9ba 100644 --- a/src/graphics/EInkDisplay2.h +++ b/src/graphics/EInkDisplay2.h @@ -80,7 +80,7 @@ class EInkDisplay : public OLEDDisplay // If display uses HSPI #if defined(HELTEC_WIRELESS_PAPER) || defined(HELTEC_WIRELESS_PAPER_V1_0) || defined(HELTEC_VISION_MASTER_E213) || \ defined(HELTEC_VISION_MASTER_E290) || defined(TLORA_T3S3_EPAPER) || defined(CROWPANEL_ESP32S3_5_EPAPER) || \ - defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) + defined(CROWPANEL_ESP32S3_4_EPAPER) || defined(CROWPANEL_ESP32S3_2_EPAPER) || defined(ELECROW_ThinkNode_M5) SPIClass *hspi = NULL; #endif diff --git a/src/main.cpp b/src/main.cpp index 1868d98c7..0aae57adc 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -38,6 +38,12 @@ #include #include +#ifdef ELECROW_ThinkNode_M5 +#include +#include +PCA9557 io(0x18, &Wire); +#endif + #ifdef ARCH_ESP32 #include "freertosinc.h" #if !MESHTASTIC_EXCLUDE_WEBSERVER @@ -296,6 +302,15 @@ void setup() digitalWrite(PIN_POWER_EN, HIGH); #endif +#if defined(ELECROW_ThinkNode_M5) + Wire.begin(48, 47); + io.pinMode(PCA_PIN_EINK_EN, OUTPUT); + io.pinMode(PCA_PIN_POWER_EN, OUTPUT); + io.digitalWrite(PCA_PIN_EINK_EN, HIGH); + io.digitalWrite(PCA_PIN_POWER_EN, HIGH); + // io.pinMode(C2_PIN, OUTPUT); +#endif + #ifdef LED_POWER pinMode(LED_POWER, OUTPUT); digitalWrite(LED_POWER, LED_STATE_ON); diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index f3921ef19..6b013cf19 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -60,7 +60,8 @@ SerialModule *serialModule; SerialModuleRadio *serialModuleRadio; -#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) +#if defined(TTGO_T_ECHO) || defined(CANARYONE) || defined(MESHLINK) || defined(ELECROW_ThinkNode_M1) || \ + defined(ELECROW_ThinkNode_M5) SerialModule::SerialModule() : StreamAPI(&Serial), concurrency::OSThread("Serial") {} static Print *serialPrint = &Serial; #elif defined(CONFIG_IDF_TARGET_ESP32C6) @@ -158,7 +159,8 @@ int32_t SerialModule::runOnce() Serial.begin(baud); Serial.setTimeout(moduleConfig.serial.timeout > 0 ? moduleConfig.serial.timeout : TIMEOUT); } -#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) +#elif !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \ + !defined(ELECROW_ThinkNode_M5) if (moduleConfig.serial.rxd && moduleConfig.serial.txd) { #ifdef ARCH_RP2040 Serial2.setFIFOSize(RX_BUFFER); @@ -214,7 +216,8 @@ int32_t SerialModule::runOnce() } } -#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) +#if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(MESHLINK) && !defined(ELECROW_ThinkNode_M1) && \ + !defined(ELECROW_ThinkNode_M5) else if ((moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_WS85)) { processWXSerial(); @@ -474,7 +477,7 @@ ParsedLine parseLine(const char *line) void SerialModule::processWXSerial() { #if !defined(TTGO_T_ECHO) && !defined(CANARYONE) && !defined(CONFIG_IDF_TARGET_ESP32C6) && !defined(MESHLINK) && \ - !defined(ELECROW_ThinkNode_M1) + !defined(ELECROW_ThinkNode_M1) && !defined(ELECROW_ThinkNode_M5) static unsigned int lastAveraged = 0; static unsigned int averageIntervalMillis = 300000; // 5 minutes hard coded. static double dir_sum_sin = 0; diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 3168d9121..522e862ac 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -146,6 +146,8 @@ #define HW_VENDOR meshtastic_HardwareModel_EBYTE_ESP32_S3 #elif defined(ELECROW_ThinkNode_M2) #define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M2 +#elif defined(ELECROW_ThinkNode_M5) +#define HW_VENDOR meshtastic_HardwareModel_THINKNODE_M5 #elif defined(ESP32_S3_PICO) #define HW_VENDOR meshtastic_HardwareModel_ESP32_S3_PICO #elif defined(SENSELORA_S3) diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/pins_arduino.h b/variants/esp32s3/ELECROW-ThinkNode-M5/pins_arduino.h new file mode 100644 index 000000000..46415d30f --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/pins_arduino.h @@ -0,0 +1,28 @@ +// Need this file for ESP32-S3 +// No need to modify this file, changes to pins imported from variant.h +// Most is similar to https://github.com/espressif/arduino-esp32/blob/master/variants/esp32s3/pins_arduino.h + +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Serial +static const uint8_t TX = UART_TX; +static const uint8_t RX = UART_RX; + +// Default SPI will be mapped to Radio +static const uint8_t SS = LORA_CS; +static const uint8_t SCK = LORA_SCK; +static const uint8_t MOSI = LORA_MOSI; +static const uint8_t MISO = LORA_MISO; + +// The default Wire will be mapped to PMU and RTC +static const uint8_t SCL = I2C_SCL; +static const uint8_t SDA = I2C_SDA; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini new file mode 100644 index 000000000..328fab43a --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -0,0 +1,22 @@ +[env:thinknode_m5] +extends = esp32s3_base +board = ESP32-S3-WROOM-1-N4 +build_flags = + ${esp32s3_base.build_flags} + -D ELECROW_ThinkNode_M5 + -I variants/esp32s3/ELECROW-ThinkNode-M5 + -DEINK_DISPLAY_MODEL=GxEPD2_154_D67 + -DEINK_WIDTH=200 + -DEINK_HEIGHT=200 + -DUSE_EINK + -DUSE_EINK_DYNAMICDISPLAY ; Enable Dynamic EInk + -DEINK_LIMIT_FASTREFRESH=10 ; How many consecutive fast-refreshes are permitted //20 + -DEINK_LIMIT_RATE_BACKGROUND_SEC=10 ; Minimum interval between BACKGROUND updates //30 + -DEINK_LIMIT_RATE_RESPONSIVE_SEC=1 ; Minimum interval between RESPONSIVE updates +; -DEINK_LIMIT_GHOSTING_PX=2000 ; (Optional) How much image ghosting is tolerated + -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. + +lib_deps = ${esp32s3_base.lib_deps} + https://github.com/meshtastic/GxEPD2.git + lewisxhe/PCF8563_Library@^1.0.1 + maxpromer/PCA9557-arduino @ ^1.0.0 \ No newline at end of file diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h new file mode 100644 index 000000000..af92849cf --- /dev/null +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/variant.h @@ -0,0 +1,99 @@ +// Status +// #define LED_PIN_POWER 1 +// #define BIAS_T_ENABLE LED_PIN_POWER +// #define BIAS_T_VALUE HIGH + +#define ELECROW_ThinkNode_M5 + +#define M5_buzzer 5 + +#define UART_TX 43 +#define UART_RX 44 +// LED +#define POWER_LED 3 // red +#define LED_POWER 2 +// #define USER_LED 1 //green + +// USB_CHECK +#define USB_CHECK 12 +#define ADC_V 4 + +/* + * Buttons + */ +#define PIN_BUTTON2 14 +#define PIN_BUTTON1 21 + +/*Wire Interfaces*/ + +#define I2C_SCL 1 +#define I2C_SDA 2 +// #define I2C_SCL 47 +// #define I2C_SDA 48 +/* + * GPS pins + */ +#define GPS_SWITH 10 +// #define HAS_GPS 1 +#define GPS_L76K +#define PIN_GPS_REINIT 13 // An output to reset L76K GPS. As per datasheet, low for > 100ms will reset the L76K + +#define PIN_GPS_STANDBY 11 // An output to wake GPS, low means allow sleep, high means force wake +// Seems to be missing on this new board +// #define PIN_GPS_PPS (32 + 4) // Pulse per second input from the GPS +#define GPS_TX_PIN 20 // This is for bits going TOWARDS the CPU +#define GPS_RX_PIN 19 // This is for bits going TOWARDS the GPS + +#define GPS_THREAD_INTERVAL 50 + +#define PIN_SERIAL1_RX GPS_TX_PIN +#define PIN_SERIAL1_TX GPS_RX_PIN + +// PCF8563 RTC Module +#define PCF8563_RTC 0x51 + +#define SX126X_CS 17 +#define LORA_SCK 16 +#define LORA_MOSI 15 +#define LORA_MISO 7 +#define SX126X_RESET 6 +#define SX126X_BUSY 5 +#define SX126X_DIO1 4 +#define SX126X_DIO2_AS_RF_SWITCH +// #define SX126X_DIO3 9 +#define SX126X_DIO3_TCXO_VOLTAGE 3.3 +#define SX126X_POWER_EN 46 +#define SX126X_MAX_POWER 22 // SX126xInterface.cpp defaults to 22 if not defined, but here we define it for good practice +#define USE_SX1262 +#define LORA_CS SX126X_CS // FIXME: for some reason both are used in /src +#define LORA_DIO1 SX126X_DIO1 + +#define USE_EINK +#define PIN_EINK_EN -1 // Note: this is really just backlight power +#define PCA_PIN_EINK_EN 5 +#define PIN_EINK_CS 39 +#define PIN_EINK_BUSY 42 +#define PIN_EINK_DC 40 +#define PIN_EINK_RES 41 +#define PIN_EINK_SCLK 38 +#define PIN_EINK_MOSI 45 // also called SDI + +// Controls power for all peripherals (eink + GPS + LoRa + Sensor) +#define PIN_POWER_EN -1 +#define PCA_PIN_POWER_EN 4 + +// #define PIN_SPI1_MISO -1 +// #define PIN_SPI1_MOSI PIN_EINK_MOSI +// #define PIN_SPI1_SCK PIN_EINK_SCLK +/* + * SPI Interfaces + */ +// #define SPI_INTERFACES_COUNT 1s + +// For LORA, spi 2 +#define PIN_SPI_MISO 7 +#define PIN_SPI_MOSI 15 +#define PIN_SPI_SCK 16 + +#define BUTTON_PIN PIN_BUTTON1 +#define BUTTON_PIN_ALT PIN_BUTTON2 From 7b6badb115bb5c6fa22ed1f80306ecbc660d2296 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Thu, 31 Jul 2025 19:11:08 -0500 Subject: [PATCH 2/2] Update variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini Co-authored-by: Austin --- variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini index 328fab43a..a2cbaf3d4 100644 --- a/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini +++ b/variants/esp32s3/ELECROW-ThinkNode-M5/platformio.ini @@ -17,6 +17,6 @@ build_flags = -DEINK_BACKGROUND_USES_FAST ; (Optional) Use FAST refresh for both BACKGROUND and RESPONSIVE, until a limit is reached. lib_deps = ${esp32s3_base.lib_deps} - https://github.com/meshtastic/GxEPD2.git + https://github.com/meshtastic/GxEPD2/archive/1655054ba298e0e29fc2044741940f927f9c2a43.zip lewisxhe/PCF8563_Library@^1.0.1 maxpromer/PCA9557-arduino @ ^1.0.0 \ No newline at end of file