From db0ecd3c586461ab33ce230f7fc6f3ed4784bf08 Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Tue, 21 Apr 2026 14:07:06 +1000 Subject: [PATCH] initial screen based t-echo lite with card kb support build --- examples/companion_radio/DataStore.cpp | 6 +- examples/companion_radio/MyMesh.h | 4 +- examples/companion_radio/main.cpp | 90 ++++++- .../companion_radio/ui-new/CardKBKeyboard.h | 15 +- examples/companion_radio/ui-new/UITask.cpp | 24 +- patch_nrf52_bsp.py | 66 +++++ src/helpers/nrf52/SerialBLEInterface.cpp | 2 +- src/helpers/ui/GxEPDDisplay.cpp | 17 +- variants/lilygo_techo_lite/CPUPowerManager.h | 15 ++ variants/lilygo_techo_lite/FS.h | 19 ++ variants/lilygo_techo_lite/SD.h | 43 ++++ variants/lilygo_techo_lite/TechoBoard.cpp | 54 ++++ variants/lilygo_techo_lite/TechoBoard.h | 43 ++++ variants/lilygo_techo_lite/platformio.ini | 238 ++++++++++++++++++ variants/lilygo_techo_lite/target.cpp | 55 ++++ variants/lilygo_techo_lite/target.h | 32 +++ variants/lilygo_techo_lite/variant.cpp | 39 +++ variants/lilygo_techo_lite/variant.h | 159 ++++++++++++ 18 files changed, 902 insertions(+), 19 deletions(-) create mode 100644 patch_nrf52_bsp.py create mode 100644 variants/lilygo_techo_lite/CPUPowerManager.h create mode 100644 variants/lilygo_techo_lite/FS.h create mode 100644 variants/lilygo_techo_lite/SD.h create mode 100644 variants/lilygo_techo_lite/TechoBoard.cpp create mode 100644 variants/lilygo_techo_lite/TechoBoard.h create mode 100644 variants/lilygo_techo_lite/platformio.ini create mode 100644 variants/lilygo_techo_lite/target.cpp create mode 100644 variants/lilygo_techo_lite/target.h create mode 100644 variants/lilygo_techo_lite/variant.cpp create mode 100644 variants/lilygo_techo_lite/variant.h diff --git a/examples/companion_radio/DataStore.cpp b/examples/companion_radio/DataStore.cpp index 0fb5825e..93943df7 100644 --- a/examples/companion_radio/DataStore.cpp +++ b/examples/companion_radio/DataStore.cpp @@ -9,7 +9,8 @@ DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _fsExtra(nullptr), _clock(&clock), #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - identity_store(fs, "") + identity_store(fs, ""), + _saveFile(fs) #elif defined(RP2040_PLATFORM) identity_store(fs, "/identity") #else @@ -21,7 +22,8 @@ DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _fsExtra #if defined(EXTRAFS) || defined(QSPIFLASH) DataStore::DataStore(FILESYSTEM& fs, FILESYSTEM& fsExtra, mesh::RTCClock& clock) : _fs(&fs), _fsExtra(&fsExtra), _clock(&clock), #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) - identity_store(fs, "") + identity_store(fs, ""), + _saveFile(fs) #elif defined(RP2040_PLATFORM) identity_store(fs, "/identity") #else diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index d294471e..a77acfe1 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -8,11 +8,11 @@ #define FIRMWARE_VER_CODE 11 #ifndef FIRMWARE_BUILD_DATE -#define FIRMWARE_BUILD_DATE "19 April 2026" +#define FIRMWARE_BUILD_DATE "21 April 2026" #endif #ifndef FIRMWARE_VERSION -#define FIRMWARE_VERSION "Meck v1.7" +#define FIRMWARE_VERSION "Meck v1.7.1" #endif #if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM) diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 9649d4bc..9cb23c1b 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -1,8 +1,8 @@ #include // needed for PlatformIO -#ifdef BLE_PIN_CODE +#if defined(ESP32) && defined(BLE_PIN_CODE) #include // for esp_bt_controller_mem_release (web reader WiFi) #endif -#ifdef MECK_OTA_UPDATE +#if defined(ESP32) && defined(MECK_OTA_UPDATE) #include #endif #include @@ -890,11 +890,37 @@ } #endif +// --- T-Echo Lite: CardKB keyboard, GxEPD2 e-ink, no touch --- +#if defined(LILYGO_TECHO_LITE) + #include "ContactsScreen.h" + #include "ChannelScreen.h" + #include "ChannelPickerScreen.h" + #include "SettingsScreen.h" + #include "RepeaterAdminScreen.h" + #include "DiscoveryScreen.h" + #include "LastHeardScreen.h" + #include "PathEditorScreen.h" + + #ifdef MECK_CARDKB + #include "CardKBKeyboard.h" + static CardKBKeyboard cardkb; + static unsigned long lastCardKBProbe = 0; + #define CARDKB_PROBE_INTERVAL_MS 5000 + #endif +#endif + // Board-agnostic: CPU frequency scaling and AGC reset CPUPowerManager cpuPower; #define AGC_RESET_INTERVAL_MS 500 static unsigned long lastAGCReset = 0; +// nRF52 RAM diagnostic +extern "C" char *sbrk(int incr); +static int dbg_free_ram() { + char top; + return &top - reinterpret_cast(sbrk(0)); +} + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -2089,9 +2115,11 @@ void setup() { #endif // Initialize CardKB external keyboard (if connected via QWIIC) - #if defined(LilyGo_T5S3_EPaper_Pro) && defined(MECK_CARDKB) + #if defined(MECK_CARDKB) if (cardkb.begin()) { + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.setCardKBDetected(true); + #endif Serial.println("setup() - CardKB detected at 0x5F"); } else { Serial.println("setup() - CardKB not detected (will re-probe)"); @@ -2317,8 +2345,10 @@ void setup() { the_mesh.setVoiceEnvelopeHandler(voiceEnvelopeCallback); #endif - Serial.printf("setup() complete — free heap: %d, largest block: %d\n", +#ifdef ESP32 + Serial.printf("setup() complete - free heap: %d, largest block: %d\n", ESP.getFreeHeap(), ESP.getMaxAllocHeap()); +#endif MESH_DEBUG_PRINTLN("=== setup() - COMPLETE ==="); } @@ -2851,6 +2881,17 @@ void loop() { #endif #endif rtc_clock.tick(); + + // --- T-Echo Lite runtime diagnostic (remove after debugging) --- + { + static unsigned long lastDbg = 0; + if (millis() - lastDbg > 2000) { + Serial.printf("loop alive - free RAM: %d, screen: %s\n", + dbg_free_ram(), + ui_task.isOnHomeScreen() ? "home" : "other"); + lastDbg = millis(); + } + } // Periodic AGC reset - re-assert boosted RX gain to prevent sensitivity drift #ifdef MECK_OTA_UPDATE if (!otaRadioPaused) @@ -3130,12 +3171,12 @@ void loop() { #endif // MECK_TOUCH_ENABLED // --------------------------------------------------------------------------- - // CardKB external keyboard polling (T5S3 only, via QWIIC) + // CardKB external keyboard polling (via QWIIC) // When VKB is active: typed characters feed into the VKB text buffer. // When VKB is not active: navigation keys route through injectKey(). // ESC key maps to 'q' (back) when no VKB is active. // --------------------------------------------------------------------------- -#if defined(LilyGo_T5S3_EPaper_Pro) && defined(MECK_CARDKB) +#if defined(MECK_CARDKB) { // Hot-plug detection: re-probe periodically if (millis() - lastCardKBProbe >= CARDKB_PROBE_INTERVAL_MS) { @@ -3143,7 +3184,9 @@ void loop() { bool wasDetected = cardkb.isDetected(); bool nowDetected = cardkb.probe(); if (nowDetected != wasDetected) { + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.setCardKBDetected(nowDetected); + #endif Serial.printf("[CardKB] %s\n", nowDetected ? "Connected" : "Disconnected"); } } @@ -3151,15 +3194,22 @@ void loop() { // Poll for keypress char ckb = cardkb.readKey(); if (ckb != 0) { - // Block input while locked (same as touch) + // Block input while locked (T5S3 only — T-Echo Lite has no lock screen yet) + #if defined(LilyGo_T5S3_EPaper_Pro) if (!ui_task.isLocked()) { + #else + { + #endif cpuPower.setBoost(); ui_task.keepAlive(); + #if defined(LilyGo_T5S3_EPaper_Pro) if (ui_task.isVKBActive()) { // VKB is open — feed character into VKB text buffer ui_task.feedCardKBChar(ckb); - } else if (ui_task.isOnHomeScreen()) { + } else + #endif + if (ui_task.isOnHomeScreen()) { // Home screen: ESC does nothing special, letter shortcuts open tiles if (ckb == 0x1B) { // ESC on home — no-op (already home) @@ -3167,8 +3217,10 @@ void loop() { switch (ckb) { case 'm': ui_task.gotoChannelPickerScreen(); break; case 'c': ui_task.gotoContactsScreen(); break; +#if !defined(LILYGO_TECHO_LITE) case 'e': ui_task.gotoTextReader(); break; case 'n': ui_task.gotoNotesScreen(); break; +#endif case 's': ui_task.gotoSettingsScreen(); break; case 'f': ui_task.gotoDiscoveryScreen(); break; case 'h': ui_task.gotoLastHeardScreen(); break; @@ -3187,6 +3239,7 @@ void loop() { // Notes editing/renaming: route ALL keys directly (no VKB). // This gives: Enter=newline, arrows=cursor, printable=insert, ESC=save&exit +#if !defined(LILYGO_TECHO_LITE) if (ui_task.isOnNotesScreen()) { NotesScreen* notesScr = (NotesScreen*)ui_task.getNotesScreen(); if (notesScr && (notesScr->isEditing() || notesScr->isRenaming())) { @@ -3214,6 +3267,7 @@ void loop() { ui_task.forceRefresh(); } } +#endif if (!handled) { // ESC → back (same as 'q' on T-Deck Pro) for all non-notes screens @@ -3253,7 +3307,11 @@ void loop() { if (the_mesh.getContactByIdx(j, ci) && strcmp(ci.name, dmName) == 0) { char label[40]; snprintf(label, sizeof(label), "DM: %s", dmName); + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.showVirtualKeyboard(VKB_DM, label, "", 137, j); + #else + ui_task.injectKey('\r'); // T-Echo Lite: compose via native handler + #endif ui_task.clearDMUnread(j); break; } @@ -3266,7 +3324,11 @@ void loop() { if (the_mesh.getChannel(chIdx, ch)) { char label[40]; snprintf(label, sizeof(label), "To: %s", ch.name); + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.showVirtualKeyboard(VKB_CHANNEL_MSG, label, "", 137, chIdx); + #else + ui_task.injectKey('\r'); // T-Echo Lite: compose via native handler + #endif } } } else if (ui_task.isOnContactsScreen()) { @@ -3290,7 +3352,11 @@ void loop() { cs->getSelectedContactName(dname, sizeof(dname)); char label[40]; snprintf(label, sizeof(label), "DM: %s", dname); + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.showVirtualKeyboard(VKB_DM, label, "", 137, idx); + #else + ui_task.injectKey('\r'); // T-Echo Lite: compose via native handler + #endif } } else if (idx >= 0 && ctype == ADV_TYPE_REPEATER) { ui_task.gotoRepeaterAdmin(idx); @@ -3310,9 +3376,17 @@ void loop() { if (admin) { RepeaterAdminScreen::AdminState astate = admin->getState(); if (astate == RepeaterAdminScreen::STATE_PASSWORD_ENTRY) { + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.showVirtualKeyboard(VKB_ADMIN_PASSWORD, "Admin Password", "", 32); + #else + ui_task.injectKey('\r'); + #endif } else { + #if defined(LilyGo_T5S3_EPaper_Pro) ui_task.showVirtualKeyboard(VKB_ADMIN_CLI, "Admin Command", "", 137); + #else + ui_task.injectKey('\r'); + #endif } } } else if (ui_task.isOnPathEditor()) { diff --git a/examples/companion_radio/ui-new/CardKBKeyboard.h b/examples/companion_radio/ui-new/CardKBKeyboard.h index feb3307c..c20f304b 100644 --- a/examples/companion_radio/ui-new/CardKBKeyboard.h +++ b/examples/companion_radio/ui-new/CardKBKeyboard.h @@ -5,19 +5,22 @@ // Polls 0x5F on the shared I2C bus via QWIIC connector. // Maps CardKB special key codes to Meck key constants. // +// Platform support: +// - ESP32/ESP32-S3 (T5S3, T-Deck Pro): Wire.begin(SDA, SCL) +// - nRF52840 (T-Echo Lite): Wire.begin() uses variant.h pins +// // Usage: // CardKBKeyboard cardkb; // if (cardkb.begin()) { /* detected */ } // char key = cardkb.readKey(); // returns 0 if no key // ============================================================================= -#if defined(LilyGo_T5S3_EPaper_Pro) && defined(MECK_CARDKB) +#if defined(MECK_CARDKB) #ifndef CARDKB_KEYBOARD_H #define CARDKB_KEYBOARD_H #include #include -#include "variant.h" // For I2C_SDA, I2C_SCL (bus recovery) // I2C address (defined in variant.h, fallback here) #ifndef CARDKB_I2C_ADDR @@ -75,7 +78,13 @@ public: _errorCount++; if (_errorCount >= 3) { // I2C bus may be stuck — re-init to recover +#if defined(ESP32) + // ESP32: Wire.begin() accepts explicit SDA/SCL pins Wire.begin(I2C_SDA, I2C_SCL); +#else + // nRF52: Wire.begin() uses PIN_WIRE_SDA/SCL from variant.h + Wire.begin(); +#endif Wire.setClock(100000); _pollInterval = 500; // Back off for 500ms _errorCount = 0; @@ -119,4 +128,4 @@ private: }; #endif // CARDKB_KEYBOARD_H -#endif // LilyGo_T5S3_EPaper_Pro && MECK_CARDKB \ No newline at end of file +#endif // MECK_CARDKB \ No newline at end of file diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 350e19a7..c196c99d 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1,7 +1,9 @@ #include "UITask.h" #include #include "../MyMesh.h" +#if !defined(LILYGO_TECHO_LITE) #include "NotesScreen.h" +#endif #include "RepeaterAdminScreen.h" #include "PathEditorScreen.h" #include "DiscoveryScreen.h" @@ -56,7 +58,9 @@ #include "ChannelScreen.h" #include "ChannelPickerScreen.h" #include "ContactsScreen.h" +#if !defined(LILYGO_TECHO_LITE) #include "TextReaderScreen.h" +#endif #include "SettingsScreen.h" #ifdef MECK_AUDIO_VARIANT #include "AudiobookPlayerScreen.h" @@ -1300,8 +1304,13 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no ((ChannelPickerScreen*)channel_picker_screen)->setChannelScreen((ChannelScreen*)channel_screen); contacts_screen = new ContactsScreen(this, &rtc_clock); ((ContactsScreen*)contacts_screen)->setDMUnreadPtr(_dmUnread); +#if !defined(LILYGO_TECHO_LITE) text_reader = new TextReaderScreen(this, node_prefs); notes_screen = new NotesScreen(this, node_prefs); +#else + text_reader = nullptr; // T-Echo Lite: excluded to save RAM (256KB nRF52) + notes_screen = nullptr; +#endif settings_screen = new SettingsScreen(this, &rtc_clock, node_prefs); repeater_admin = nullptr; // Lazy-initialized on first use to preserve heap for audio path_editor = nullptr; // Lazy-initialized on first use from contacts screen @@ -1678,6 +1687,7 @@ void UITask::loop() { c = checkDisplayOn(KEY_NEXT); } else { // Navigate back: reader reading→file list, file list→home, others→home +#if !defined(LILYGO_TECHO_LITE) if (isOnTextReader()) { TextReaderScreen* reader = (TextReaderScreen*)text_reader; if (reader && reader->isReading()) { @@ -1695,7 +1705,9 @@ void UITask::loop() { gotoHomeScreen(); } c = 0; - } else if (isOnChannelPickerScreen()) { + } else +#endif + if (isOnChannelPickerScreen()) { gotoHomeScreen(); // picker → home c = 0; } else if (isOnChannelScreen()) { @@ -2336,12 +2348,14 @@ void UITask::onVKBSubmit() { break; } case VKB_NOTES: { +#if !defined(LILYGO_TECHO_LITE) NotesScreen* notes = (NotesScreen*)getNotesScreen(); if (notes && strlen(text) > 0) { for (int i = 0; text[i]; i++) { notes->handleInput(text[i]); } } +#endif if (_screenBeforeVKB) setCurrScreen(_screenBeforeVKB); break; } @@ -2403,6 +2417,7 @@ void UITask::onVKBSubmit() { } #endif case VKB_TEXT_PAGE: { +#if !defined(LILYGO_TECHO_LITE) if (strlen(text) > 0) { int pageNum = atoi(text); TextReaderScreen* reader = (TextReaderScreen*)getTextReaderScreen(); @@ -2410,6 +2425,7 @@ void UITask::onVKBSubmit() { reader->gotoPage(pageNum); } } +#endif if (_screenBeforeVKB) setCurrScreen(_screenBeforeVKB); break; } @@ -2622,6 +2638,8 @@ void UITask::gotoContactsScreen() { } void UITask::gotoTextReader() { + if (!text_reader) return; // Not available on this platform +#if !defined(LILYGO_TECHO_LITE) TextReaderScreen* reader = (TextReaderScreen*)text_reader; if (_display != NULL) { reader->enter(*_display); @@ -2632,9 +2650,12 @@ void UITask::gotoTextReader() { } _auto_off = millis() + AUTO_OFF_MILLIS; _next_refresh = 100; +#endif } void UITask::gotoNotesScreen() { + if (!notes_screen) return; // Not available on this platform +#if !defined(LILYGO_TECHO_LITE) NotesScreen* notes = (NotesScreen*)notes_screen; if (_display != NULL) { notes->enter(*_display); @@ -2649,6 +2670,7 @@ void UITask::gotoNotesScreen() { } _auto_off = millis() + AUTO_OFF_MILLIS; _next_refresh = 100; +#endif } void UITask::gotoSettingsScreen() { diff --git a/patch_nrf52_bsp.py b/patch_nrf52_bsp.py new file mode 100644 index 00000000..8d2f3d8e --- /dev/null +++ b/patch_nrf52_bsp.py @@ -0,0 +1,66 @@ +""" +patch_nrf52_bsp.py — Pre-build BSP patches for nRF52 Meck builds + +Patches the Adafruit nRF52 BSP's LittleFS File class to add a default +constructor. BSP 1.7.0 removed the default File() constructor, but the +Meck screen headers (NotesScreen, TextReaderScreen, EpubZipReader) have +File member variables that need default construction. + +Runs automatically before each build via extra_scripts in platformio.ini. +Idempotent — safe to run repeatedly. +""" +Import("env") +import os + +framework_dir = env.PioPlatform().get_package_dir("framework-arduinoadafruitnrf52") +lfs_src = os.path.join(framework_dir, "libraries", "Adafruit_LittleFS", "src") + +# ------------------------------------------------------------------------- +# 1. Patch header: add File() default constructor declaration +# ------------------------------------------------------------------------- +header_path = os.path.join(lfs_src, "Adafruit_LittleFS_File.h") + +with open(header_path, "r") as f: + h = f.read() + +if "File ();" not in h and "File();" not in h: + h = h.replace( + "File (Adafruit_LittleFS &fs);", + "File (Adafruit_LittleFS &fs);\n File (); // Meck nRF52 compat" + ) + with open(header_path, "w") as f: + f.write(h) + print("LittleFS File patch: Added default constructor declaration") +else: + print("LittleFS File patch: OK - header already patched") + +# ------------------------------------------------------------------------- +# 2. Patch source: add File() default constructor implementation +# Uses C++11 delegating constructor → File(InternalFS) +# ------------------------------------------------------------------------- +source_path = os.path.join(lfs_src, "Adafruit_LittleFS_File.cpp") + +with open(source_path, "r") as f: + s = f.read() + +if "File::File()" not in s: + # Locate InternalFileSystem header (Adafruit BSP has a known typo: "Sytem") + ifs_include = None + for dirname in ["InternalFileSytem", "InternalFileSystem"]: + candidate = os.path.join(framework_dir, "libraries", dirname, "src") + if os.path.isdir(candidate): + rel = os.path.relpath(candidate, lfs_src).replace("\\", "/") + ifs_include = rel + "/InternalFileSystem.h" + break + + if ifs_include: + s += "\n// Meck nRF52 compat: default File() constructor\n" + s += '#include "' + ifs_include + '"\n' + s += "File::File() : File(InternalFS) {}\n" + with open(source_path, "w") as f: + f.write(s) + print("LittleFS File patch: Added default constructor implementation") + else: + print("LittleFS File patch: WARNING - could not find InternalFileSystem") +else: + print("LittleFS File patch: OK - source already patched") diff --git a/src/helpers/nrf52/SerialBLEInterface.cpp b/src/helpers/nrf52/SerialBLEInterface.cpp index 5648707e..ac7d5d9c 100644 --- a/src/helpers/nrf52/SerialBLEInterface.cpp +++ b/src/helpers/nrf52/SerialBLEInterface.cpp @@ -130,7 +130,7 @@ void SerialBLEInterface::begin(const char* prefix, char* name, uint32_t pin_code snprintf(charpin, sizeof(charpin), "%lu", (unsigned long)pin_code); // If we want to control BLE LED ourselves, uncomment this: - // Bluefruit.autoConnLed(false); + Bluefruit.autoConnLed(false); Bluefruit.configPrphBandwidth(BANDWIDTH_MAX); Bluefruit.begin(); diff --git a/src/helpers/ui/GxEPDDisplay.cpp b/src/helpers/ui/GxEPDDisplay.cpp index be62ee68..3169ed35 100644 --- a/src/helpers/ui/GxEPDDisplay.cpp +++ b/src/helpers/ui/GxEPDDisplay.cpp @@ -25,6 +25,10 @@ bool GxEPDDisplay::begin() { // Tell GxEPD2 to use our SPI instance // Using slower speed (4MHz) for reliable e-ink communication display.epd2.selectSPI(displaySpi, SPISettings(4000000, MSBFIRST, SPI_MODE0)); +#elif defined(LILYGO_TECHO) + // T-Echo Lite: display on SPI1 (pins 19/20), LoRa on SPI (pins 13/15/17) + SPI1.begin(); + display.epd2.selectSPI(SPI1, SPISettings(4000000, MSBFIRST, SPI_MODE0)); #endif // Initialize with: @@ -35,10 +39,15 @@ bool GxEPDDisplay::begin() { display.init(115200, true, 2, false); display.setRotation(DISPLAY_ROTATION); setTextSize(1); // Default to size 1 +#ifdef EINK_FULL_REFRESH_ONLY + display.setFullWindow(); + display.fillScreen(GxEPD_WHITE); + display.display(false); // Full refresh (SSD1681 doesn't support partial) +#else display.setPartialWindow(0, 0, display.width(), display.height()); - display.fillScreen(GxEPD_WHITE); display.display(true); +#endif #if DISP_BACKLIGHT digitalWrite(DISP_BACKLIGHT, LOW); @@ -238,7 +247,11 @@ uint16_t GxEPDDisplay::getTextWidth(const char* str) { void GxEPDDisplay::endFrame() { uint32_t crc = display_crc.finalize(); if (crc != last_display_crc_value) { - display.display(true); // Partial refresh +#ifdef EINK_FULL_REFRESH_ONLY + display.display(false); // Full refresh (SSD1681 doesn't support partial) +#else + display.display(true); // Partial refresh +#endif last_display_crc_value = crc; } } \ No newline at end of file diff --git a/variants/lilygo_techo_lite/CPUPowerManager.h b/variants/lilygo_techo_lite/CPUPowerManager.h new file mode 100644 index 00000000..37d2bb90 --- /dev/null +++ b/variants/lilygo_techo_lite/CPUPowerManager.h @@ -0,0 +1,15 @@ +#pragma once +// CPUPowerManager.h — nRF52 no-op stub +// nRF52840 runs at fixed 64 MHz; no frequency scaling available. +// All methods are empty so main.cpp compiles without #ifdef guards. + +class CPUPowerManager { +public: + void begin() {} + void loop() {} + void setBoost() {} + void setIdle() {} + void setLowPower() {} + void clearLowPower() {} + int getFrequencyMHz() { return 64; } +}; diff --git a/variants/lilygo_techo_lite/FS.h b/variants/lilygo_techo_lite/FS.h new file mode 100644 index 00000000..e4ce3242 --- /dev/null +++ b/variants/lilygo_techo_lite/FS.h @@ -0,0 +1,19 @@ +#pragma once +// FS.h — nRF52 compatibility stub +// ESP32 Arduino core provides this as the base filesystem abstraction. +// On nRF52, File and filesystem types come from Adafruit_LittleFS. +// This stub exists solely to satisfy #include in shared headers. + +#include +#include // struct tm, gmtime — implicit on ESP32, needs explicit on nRF52 + +// ESP32 FS.h defines these mode strings; some shared code references them +#ifndef FILE_READ +#define FILE_READ "r" +#endif +#ifndef FILE_WRITE +#define FILE_WRITE "w" +#endif +#ifndef FILE_APPEND +#define FILE_APPEND "a" +#endif \ No newline at end of file diff --git a/variants/lilygo_techo_lite/SD.h b/variants/lilygo_techo_lite/SD.h new file mode 100644 index 00000000..3b9e2773 --- /dev/null +++ b/variants/lilygo_techo_lite/SD.h @@ -0,0 +1,43 @@ +#pragma once +// SD.h — nRF52 compatibility stub for Meck +// Maps Arduino SD API to Adafruit InternalFS (LittleFS on QSPI flash). +// T-Echo Lite has no SD card slot; file operations use internal flash. + +#include +#include +#include // struct tm, gmtime — implicit on ESP32, explicit on nRF52 + +// ESP32 SD uses string file modes; define them here for compile compatibility +#ifndef FILE_READ +#define FILE_READ "r" +#endif +#ifndef FILE_WRITE +#define FILE_WRITE "w" +#endif + +class SDClass { +public: + // InternalFS is already initialised by main — begin() is a no-op + bool begin(uint8_t cs = 0) { return true; } + + // Accept any extra args (cs, SPI, freq) without complaint + template + bool begin(Args...) { return true; } + + bool exists(const char* path) { return InternalFS.exists(path); } + bool remove(const char* path) { return InternalFS.remove(path); } + bool mkdir(const char* path) { return InternalFS.mkdir(path); } + + // String mode overload — matches ESP32 SD API (FILE_READ="r", FILE_WRITE="w", "r+") + Adafruit_LittleFS_Namespace::File open(const char* path, const char* mode = "r") { + uint8_t m = FILE_O_READ; + if (mode) { + if (mode[0] == 'w') m = FILE_O_WRITE; + else if (mode[0] == 'r' && mode[1] == '+') m = FILE_O_WRITE; + } + return InternalFS.open(path, m); + } +}; + +// Static instance per translation unit — no state (just forwards to InternalFS singleton) +static SDClass SD; \ No newline at end of file diff --git a/variants/lilygo_techo_lite/TechoBoard.cpp b/variants/lilygo_techo_lite/TechoBoard.cpp new file mode 100644 index 00000000..a11d31b2 --- /dev/null +++ b/variants/lilygo_techo_lite/TechoBoard.cpp @@ -0,0 +1,54 @@ +#include +#include + +#include "TechoBoard.h" + +#ifdef LILYGO_TECHO + +void TechoBoard::begin() { + NRF52Board::begin(); + + // Configure battery measurement control BEFORE Wire.begin() + // to ensure P0.02 is not claimed by another peripheral + pinMode(PIN_VBAT_MEAS_EN, OUTPUT); + digitalWrite(PIN_VBAT_MEAS_EN, LOW); + pinMode(PIN_VBAT_READ, INPUT); + + Wire.begin(); + + pinMode(SX126X_POWER_EN, OUTPUT); + digitalWrite(SX126X_POWER_EN, HIGH); + delay(10); +} + +uint16_t TechoBoard::getBattMilliVolts() { + // Use LilyGo's exact ADC configuration + analogReference(AR_INTERNAL_3_0); + analogReadResolution(12); + + // Enable battery voltage divider (MOSFET gate on P0.31) + pinMode(PIN_VBAT_MEAS_EN, OUTPUT); + digitalWrite(PIN_VBAT_MEAS_EN, HIGH); + + // Reclaim P0.02 for analog input (in case another peripheral touched it) + pinMode(PIN_VBAT_READ, INPUT); + delay(10); // let divider + ADC settle + + // Read and average (matching LilyGo's approach) + uint32_t sum = 0; + for (int i = 0; i < 8; i++) { + sum += analogRead(PIN_VBAT_READ); + delayMicroseconds(100); + } + uint16_t adc = sum / 8; + + // Disable divider to save power + digitalWrite(PIN_VBAT_MEAS_EN, LOW); + + // LilyGo's exact formula: adc * (3000.0 / 4096.0) * 2.0 + // = adc * 0.73242188 * 2.0 = adc * 1.46484375 + uint16_t millivolts = (uint16_t)((float)adc * (3000.0f / 4096.0f) * 2.0f); + + return millivolts; +} +#endif \ No newline at end of file diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h new file mode 100644 index 00000000..1bf00936 --- /dev/null +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +// ============================================================ +// T-Echo Lite battery pins — hardcoded from LilyGo t_echo_lite_config.h +// NOT using any defines from variant.h for battery measurement +// ============================================================ +#define PIN_VBAT_READ _PINNUM(0, 2) // BATTERY_ADC_DATA +#define PIN_VBAT_MEAS_EN _PINNUM(0, 31) // BATTERY_MEASUREMENT_CONTROL + +class TechoBoard : public NRF52BoardDCDC { +public: + TechoBoard() {} + void begin(); + uint16_t getBattMilliVolts() override; + + const char* getManufacturerName() const override { + return "LilyGo T-Echo Lite"; + } + + void powerOff() override { + digitalWrite(PIN_VBAT_MEAS_EN, LOW); + #ifdef LED_RED + digitalWrite(LED_RED, LOW); + #endif + #ifdef LED_GREEN + digitalWrite(LED_GREEN, LOW); + #endif + #ifdef LED_BLUE + digitalWrite(LED_BLUE, LOW); + #endif + #ifdef DISP_BACKLIGHT + digitalWrite(DISP_BACKLIGHT, LOW); + #endif + #ifdef PIN_PWR_EN + digitalWrite(PIN_PWR_EN, LOW); + #endif + sd_power_system_off(); + } +}; \ No newline at end of file diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini new file mode 100644 index 00000000..ef4d9a1c --- /dev/null +++ b/variants/lilygo_techo_lite/platformio.ini @@ -0,0 +1,238 @@ +; ============================================================================= +; LilyGo T-Echo Lite — Meck variant configuration +; +; nRF52840 + SX1262 + GxEPD2 1.22" e-ink (176×192, GDEM0122T61/SSD1681) +; + CardKB via QWIIC (0x5F) + optional L76K GPS +; +; Display: GxEPD2_122_T61 — full refresh only (~2s), no fast/partial refresh. +; UI must minimise unnecessary redraws. +; Scale factors: 1.5×/2.0× give ~117×96 virtual coordinate space. +; +; Platform: nRF52 (Adafruit nRF52 Arduino) +; Board JSON: boards/t-echo.json (nRF52840 PCA10056 compatible) +; ============================================================================= + +; --- Base configuration for all T-Echo Lite Meck builds (with display) --- +[lilygo_techo_lite_meck] +extends = nrf52_base +board = t-echo +board_build.ldscript = boards/nrf52840_s140_v6.ld +extra_scripts = pre:patch_nrf52_bsp.py +build_flags = ${nrf52_base.build_flags} + -I variants/lilygo_techo_lite + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -D LILYGO_TECHO + -D LILYGO_TECHO_LITE + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_POWER_EN=30 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + ; nRF52 compatibility — no PSRAM, no SD card, fallback GPS baud for CLI code paths + -D ps_calloc=calloc + -D ps_malloc=malloc + -D GPS_BAUDRATE=9600 + -D SDCARD_CS=-1 + -D ROW_AUTO_LOCK=255 + ; Display — GxEPD2 1.22" e-ink (176×192, SSD1681) + -D DISPLAY_CLASS=GxEPDDisplay + -D EINK_DISPLAY_MODEL=GxEPD2_122_T61 + -D EINK_SCALE_X=1.5f + -D EINK_SCALE_Y=2.0f + -D EINK_X_OFFSET=0 + -D EINK_Y_OFFSET=10 + -D DISPLAY_ROTATION=4 + -D EINK_FULL_REFRESH_ONLY=1 + -D AUTO_OFF_MILLIS=0 +build_src_filter = ${nrf52_base.build_src_filter} + + + + + + + + + + + +<../variants/lilygo_techo_lite> +lib_deps = + ${nrf52_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit BME280 Library @ ^2.3.0 + https://github.com/SoulOfNoob/GxEPD2.git + bakercp/CRC32 @ ^2.0.0 +debug_tool = jlink +upload_protocol = nrfutil + +; ============================================================================= +; Build Environments +; ============================================================================= + +; --- BLE Companion Radio (no GPS) --- +; Pairs with MeshCore companion app over Bluetooth. +; CardKB provides on-device text input for standalone messaging. +; No GPS — time synced via BLE companion or serial CLI. +[env:meck_techo_lite_ble] +extends = lilygo_techo_lite_meck +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${lilygo_techo_lite_meck.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=500 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + ; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=64 + -D MECK_CARDKB + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + ; -D MESH_PACKET_LOGGING=1 + ; -D MESH_DEBUG=1 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +build_src_filter = ${lilygo_techo_lite_meck.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${lilygo_techo_lite_meck.lib_deps} + densaugeo/base64 @ ~1.4.0 + +; --- BLE Companion Radio (with GPS) --- +; Same as above + L76K GPS for location and time sync. +; Requires external GPS module connected to UART1 (TX=P0.29, RX=P1.10). +[env:meck_techo_lite_gps_ble] +extends = lilygo_techo_lite_meck +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${lilygo_techo_lite_meck.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D ENV_INCLUDE_GPS=1 + -D GPS_BAUD_RATE=9600 + -D PIN_GPS_EN=GPS_EN + -D MAX_CONTACTS=500 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=123456 + -D OFFLINE_QUEUE_SIZE=64 + -D MECK_CARDKB + -D UI_RECENT_LIST_SIZE=9 + -D UI_SENSORS_PAGE=1 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +build_src_filter = ${lilygo_techo_lite_meck.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${lilygo_techo_lite_meck.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + densaugeo/base64 @ ~1.4.0 + +; --- Repeater --- +; Standalone LoRa repeater node. E-ink shows status. +; CardKB not useful here but included by base for consistency. +[env:meck_techo_lite_repeater] +extends = lilygo_techo_lite_meck +build_src_filter = ${lilygo_techo_lite_meck.build_src_filter} + +<../examples/simple_repeater> +build_flags = + ${lilygo_techo_lite_meck.build_flags} + -D ADVERT_NAME='"Meck T-Echo Lite Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +; --- Room Server --- +; BBS-style message board node. +[env:meck_techo_lite_room_server] +extends = lilygo_techo_lite_meck +build_src_filter = ${lilygo_techo_lite_meck.build_src_filter} + +<../examples/simple_room_server> +build_flags = + ${lilygo_techo_lite_meck.build_flags} + -D ADVERT_NAME='"Meck T-Echo Lite Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 + +; ============================================================================= +; Headless (no display) variants +; ============================================================================= + +; --- Headless base (no display, no GxEPD2) --- +[lilygo_techo_lite_meck_core] +extends = nrf52_base +board = t-echo +board_build.ldscript = boards/nrf52840_s140_v6.ld +build_flags = ${nrf52_base.build_flags} + -I variants/lilygo_techo_lite + -I src/helpers/nrf52 + -I lib/nrf52/s140_nrf52_6.1.1_API/include + -I lib/nrf52/s140_nrf52_6.1.1_API/include/nrf52 + -D LILYGO_TECHO + -D LILYGO_TECHO_LITE + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_POWER_EN=30 + -D SX126X_CURRENT_LIMIT=140 + -D SX126X_RX_BOOSTED_GAIN=1 + ; nRF52 compatibility + -D ps_calloc=calloc + -D ps_malloc=malloc + -D GPS_BAUDRATE=9600 + -D SDCARD_CS=-1 + -D ROW_AUTO_LOCK=255 + -D DISABLE_DIAGNOSTIC_OUTPUT + -D AUTO_OFF_MILLIS=0 +build_src_filter = ${nrf52_base.build_src_filter} + + + + + + + + + +<../variants/lilygo_techo_lite> +lib_deps = + ${nrf52_base.lib_deps} + stevemarple/MicroNMEA @ ^2.0.6 + adafruit/Adafruit BME280 Library @ ^2.3.0 + bakercp/CRC32 @ ^2.0.0 +debug_tool = jlink +upload_protocol = nrfutil + +; --- Headless Repeater (no display — lowest power, outdoor deployment) --- +[env:meck_techo_lite_core_repeater] +extends = lilygo_techo_lite_meck_core +build_src_filter = ${lilygo_techo_lite_meck_core.build_src_filter} + +<../examples/simple_repeater> +build_flags = + ${lilygo_techo_lite_meck_core.build_flags} + -D ADVERT_NAME='"Meck T-Echo Lite Core Repeater"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D MAX_NEIGHBOURS=50 + +; --- Headless BLE Companion (no display — phone-only UI) --- +[env:meck_techo_lite_core_ble] +extends = lilygo_techo_lite_meck_core +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +build_flags = + ${lilygo_techo_lite_meck_core.build_flags} + -D MAX_CONTACTS=500 + -D MAX_GROUP_CHANNELS=8 + -D BLE_PIN_CODE=234567 + -D OFFLINE_QUEUE_SIZE=64 + -D AUTO_SHUTDOWN_MILLIVOLTS=3300 +build_src_filter = ${lilygo_techo_lite_meck_core.build_src_filter} + + + +<../examples/companion_radio/*.cpp> +lib_deps = + ${lilygo_techo_lite_meck_core.lib_deps} + densaugeo/base64 @ ~1.4.0 \ No newline at end of file diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp new file mode 100644 index 00000000..5d18ba38 --- /dev/null +++ b/variants/lilygo_techo_lite/target.cpp @@ -0,0 +1,55 @@ +#include +#include "target.h" +#include +#include + +TechoBoard board; + +RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI); + +WRAPPER_CLASS radio_driver(radio, board); + +VolatileRTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); + +#ifdef ENV_INCLUDE_GPS +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); +#else +EnvironmentSensorManager sensors = EnvironmentSensorManager(); +#endif + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +bool radio_init() { + rtc_clock.begin(Wire); + + return radio.std_init(&SPI); +} + +uint32_t radio_get_rng_seed() { + return radio.random(0x7FFFFFFF); +} + +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr) { + radio.setFrequency(freq); + radio.setSpreadingFactor(sf); + radio.setBandwidth(bw); + radio.setCodingRate(cr); +} + +void radio_set_tx_power(int8_t dbm) { + radio.setOutputPower(dbm); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} + +void radio_reset_agc() { + radio.setRxBoostedGainMode(true); +} \ No newline at end of file diff --git a/variants/lilygo_techo_lite/target.h b/variants/lilygo_techo_lite/target.h new file mode 100644 index 00000000..02c83805 --- /dev/null +++ b/variants/lilygo_techo_lite/target.h @@ -0,0 +1,32 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif + +extern TechoBoard 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(); +uint32_t radio_get_rng_seed(); +void radio_set_params(float freq, float bw, uint8_t sf, uint8_t cr); +void radio_set_tx_power(int8_t dbm); +mesh::LocalIdentity radio_new_identity(); +void radio_reset_agc(); \ No newline at end of file diff --git a/variants/lilygo_techo_lite/variant.cpp b/variants/lilygo_techo_lite/variant.cpp new file mode 100644 index 00000000..3cd82d70 --- /dev/null +++ b/variants/lilygo_techo_lite/variant.cpp @@ -0,0 +1,39 @@ +#include "variant.h" +#include "wiring_constants.h" +#include "wiring_digital.h" + +const int MISO = PIN_SPI1_MISO; +const int MOSI = PIN_SPI1_MOSI; +const int SCK = PIN_SPI1_SCK; + +const uint32_t g_ADigitalPinMap[] = { + 0xff, 0xff, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, + 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, + 40, 41, 42, 43, 44, 45, 46, 47 +}; + +void initVariant() { + pinMode(PIN_PWR_EN, OUTPUT); + digitalWrite(PIN_PWR_EN, HIGH); + + pinMode(PIN_BUTTON1, INPUT_PULLUP); + pinMode(PIN_BUTTON2, INPUT_PULLUP); + + pinMode(LED_RED, OUTPUT); + pinMode(LED_GREEN, OUTPUT); + pinMode(LED_BLUE, OUTPUT); + digitalWrite(LED_BLUE, HIGH); + digitalWrite(LED_GREEN, HIGH); + digitalWrite(LED_RED, HIGH); + + // pinMode(PIN_TXCO, OUTPUT); + // digitalWrite(PIN_TXCO, HIGH); + + pinMode(DISP_POWER, OUTPUT); + digitalWrite(DISP_POWER, LOW); + + // shutdown gps + pinMode(GPS_EN, OUTPUT); + digitalWrite(GPS_EN, LOW); +} diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h new file mode 100644 index 00000000..07202165 --- /dev/null +++ b/variants/lilygo_techo_lite/variant.h @@ -0,0 +1,159 @@ +/* + * variant.h + * Copyright (C) 2023 Seeed K.K. + * MIT License + */ + +#pragma once + +#define _PINNUM(port, pin) ((port) * 32 + (pin)) + +#include "WVariant.h" + +//////////////////////////////////////////////////////////////////////////////// +// Low frequency clock source + +#define USE_LFXO // 32.768 kHz crystal oscillator +#define VARIANT_MCK (64000000ul) + +#define WIRE_INTERFACES_COUNT (1) + +//////////////////////////////////////////////////////////////////////////////// +// Power + +#define PIN_PWR_EN _PINNUM(0, 30) // RT9080_EN + +#define BATTERY_PIN _PINNUM(0, 2) +#define ADC_MULTIPLIER (2.0F) + +#define ADC_RESOLUTION (14) +#define BATTERY_SENSE_RES (12) + +#define AREF_VOLTAGE (3.0) + +//////////////////////////////////////////////////////////////////////////////// +// Number of pins + +#define PINS_COUNT (48) +#define NUM_DIGITAL_PINS (48) +#define NUM_ANALOG_INPUTS (1) +#define NUM_ANALOG_OUTPUTS (0) + +//////////////////////////////////////////////////////////////////////////////// +// UART pin definition + +#define PIN_SERIAL1_RX PIN_GPS_TX +#define PIN_SERIAL1_TX PIN_GPS_RX +//////////////////////////////////////////////////////////////////////////////// +// I2C pin definition + +#define PIN_WIRE_SDA _PINNUM(1, 4) // (SDA) - per LilyGo IIC_1_SDA +#define PIN_WIRE_SCL _PINNUM(1, 2) // (SCL) - per LilyGo IIC_1_SCL + +//////////////////////////////////////////////////////////////////////////////// +// SPI pin definition + +#define SPI_INTERFACES_COUNT (2) + +#define PIN_SPI_MISO _PINNUM(0, 17) // (MISO) +#define PIN_SPI_MOSI _PINNUM(0, 15) // (MOSI) +#define PIN_SPI_SCK _PINNUM(0, 13) // (SCK) +#define PIN_SPI_NSS (-1) + +//////////////////////////////////////////////////////////////////////////////// +// QSPI FLASH + +#define PIN_QSPI_SCK _PINNUM(0, 4) +#define PIN_QSPI_CS _PINNUM(0, 12) +#define PIN_QSPI_IO0 _PINNUM(0, 6) +#define PIN_QSPI_IO1 _PINNUM(0, 8) +#define PIN_QSPI_IO2 _PINNUM(1, 9) +#define PIN_QSPI_IO3 _PINNUM(0, 26) + +#define EXTERNAL_FLASH_DEVICES ZD25WQ32CEIGR +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// Builtin LEDs + +#define LED_RED _PINNUM(1, 14) // LED_3 +#define LED_BLUE _PINNUM(1, 5) // LED_2 +#define LED_GREEN _PINNUM(1, 7) // LED_1 + +//#define PIN_STATUS_LED LED_BLUE +#define LED_BUILTIN (-1) +#define LED_PIN LED_BUILTIN +#define LED_STATE_ON LOW + +//////////////////////////////////////////////////////////////////////////////// +// Builtin buttons + +#define PIN_BUTTON1 _PINNUM(0, 24) // BOOT +#define BUTTON_PIN PIN_BUTTON1 +#define PIN_USER_BTN BUTTON_PIN + +#define PIN_BUTTON2 _PINNUM(0, 18) +#define BUTTON_PIN2 PIN_BUTTON2 + +#define EXTERNAL_FLASH_DEVICES MX25R1635F +#define EXTERNAL_FLASH_USE_QSPI + +//////////////////////////////////////////////////////////////////////////////// +// Lora + +#define USE_SX1262 +#define LORA_CS _PINNUM(0, 11) +#define SX126X_POWER_EN _PINNUM(0, 30) +#define SX126X_DIO1 _PINNUM(1, 8) +#define SX126X_BUSY _PINNUM(0, 14) +#define SX126X_RESET _PINNUM(0, 7) +#define SX126X_RF_VC1 _PINNUM(0, 27) +#define SX126X_RF_VC2 _PINNUM(0, 33) + +#define P_LORA_DIO_1 SX126X_DIO1 +#define P_LORA_NSS LORA_CS +#define P_LORA_RESET SX126X_RESET +#define P_LORA_BUSY SX126X_BUSY +#define P_LORA_SCLK PIN_SPI_SCK +#define P_LORA_MISO PIN_SPI_MISO +#define P_LORA_MOSI PIN_SPI_MOSI + +//////////////////////////////////////////////////////////////////////////////// +// SPI1 + +#define PIN_SPI1_MISO (-1) // Not used for Display +#define PIN_SPI1_MOSI _PINNUM(0, 20) +#define PIN_SPI1_SCK _PINNUM(0, 19) + +// GxEPD2 needs that for a panel that is not even used ! +extern const int MISO; +extern const int MOSI; +extern const int SCK; + +//////////////////////////////////////////////////////////////////////////////// +// Display + +// #define DISP_MISO (-1) // Not used for Display +#define DISP_MOSI _PINNUM(0, 20) +#define DISP_SCLK _PINNUM(0, 19) +#define DISP_CS _PINNUM(0, 22) +#define DISP_DC _PINNUM(0, 21) +#define DISP_RST _PINNUM(0, 28) +#define DISP_BUSY _PINNUM(0, 3) +#define DISP_POWER _PINNUM(1, 12) +// #define DISP_BACKLIGHT (-1) // Display has no backlight + +#define PIN_DISPLAY_CS DISP_CS +#define PIN_DISPLAY_DC DISP_DC +#define PIN_DISPLAY_RST DISP_RST +#define PIN_DISPLAY_BUSY DISP_BUSY + +//////////////////////////////////////////////////////////////////////////////// +// GPS — per LilyGo t_echo_lite_config.h +// PIN_GPS_TX/RX named from GPS module's perspective + +#define PIN_GPS_TX _PINNUM(0, 29) // GPS UART TX → MCU RX +#define PIN_GPS_RX _PINNUM(1, 10) // GPS UART RX ← MCU TX +#define GPS_EN _PINNUM(1, 11) // GPS RT9080 power enable +#define PIN_GPS_STANDBY _PINNUM(1, 13) // GPS wake-up +#define PIN_GPS_PPS _PINNUM(1, 15) // GPS 1PPS \ No newline at end of file