From 05e57357d31f35f93183395e2e148dbe9a47fd73 Mon Sep 17 00:00:00 2001 From: Jakub <106778416+MarekZegare4@users.noreply.github.com> Date: Sat, 8 Aug 2026 01:06:24 +0200 Subject: [PATCH] feat(boards): add M5Stack Cardputer ADV and LilyGO T-Echo Lite KeyShield New M5Stack Cardputer ADV variant (ESP32-S3, ST7789 TFT, built-in TCA8418 QWERTY keyboard, PI4IOE5V6408 LoRa-cap IO-expander autodetect), and a KeyShield accessory variant for the existing LilyGO T-Echo Lite (external TCA8418 T9 keypad + AW21009 backlight driver). Both keyboards share one ENV_USE_TCA8418 polling block in UITask.cpp::loop(), coexisting with the unrelated CardKB support (different chip/address/flag). Fixes carried in from the contributed T-Echo Lite code: swapped GPS RX/TX pins, TX-LED hooks, TCXO voltage, missing GxEPD2_122_T61 panel include. Fixed during integration: I2C bus was probed for an RTC before Wire.begin() configured its pins on Cardputer ADV (silent RTC autodetect failure). Added dedicated *_solo_dual release envs for both boards (auto-picked up by the solo-firmware release workflow). Gave the T-Echo Lite KeyShield solo build -Os/-Ofast-unflag like every other nRF52 solo build (was missing, cut flash usage from 90.7% to 61.4%). Ported the shared misc-fixed 6x9 font (full Latin/Greek/Cyrillic, opt-in via OLED_MISC_FIXED_FONT) to ST7789Display for the Cardputer's on-screen keyboard. ST7789Spi isn't Adafruit_GFX-based like the other single-font drivers, and this panel's logical->physical scale is non-integer, so glyphs are re-packed to XBM and blitted through the existing drawXbm(), which already does correct fractional-scale boundary math, rather than duplicating that logic. Co-Authored-By: Claude Opus 5 --- examples/companion_radio/ui-new/UITask.cpp | 23 ++ lib/GxEPD2-patch/src/GxEPD2_BW.h | 3 + src/helpers/ui/ST7789Display.cpp | 93 +++++++ src/helpers/ui/ST7789Display.h | 31 ++- .../lilygo_techo_lite/KeyshieldBacklight.cpp | 66 +++++ variants/lilygo_techo_lite/TEchoKeyShield.cpp | 240 ++++++++++++++++++ variants/lilygo_techo_lite/TechoBoard.h | 10 + variants/lilygo_techo_lite/platformio.ini | 46 +++- variants/lilygo_techo_lite/variant.h | 7 +- .../CardputerADVBoard.cpp | 31 +++ .../m5stack_cardputer_adv/CardputerADVBoard.h | 58 +++++ .../CardputerKeyboard.cpp | 166 ++++++++++++ variants/m5stack_cardputer_adv/platformio.ini | 201 +++++++++++++++ variants/m5stack_cardputer_adv/target.cpp | 63 +++++ variants/m5stack_cardputer_adv/target.h | 28 ++ 15 files changed, 1061 insertions(+), 5 deletions(-) create mode 100644 variants/lilygo_techo_lite/KeyshieldBacklight.cpp create mode 100644 variants/lilygo_techo_lite/TEchoKeyShield.cpp create mode 100644 variants/m5stack_cardputer_adv/CardputerADVBoard.cpp create mode 100644 variants/m5stack_cardputer_adv/CardputerADVBoard.h create mode 100644 variants/m5stack_cardputer_adv/CardputerKeyboard.cpp create mode 100644 variants/m5stack_cardputer_adv/platformio.ini create mode 100644 variants/m5stack_cardputer_adv/target.cpp create mode 100644 variants/m5stack_cardputer_adv/target.h diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 15af6032..30799876 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -2332,6 +2332,29 @@ void UITask::loop() { } #endif pollCardKB(); +#ifdef ENV_USE_TCA8418 + { + extern char tca8418_keypad_read(); // provided by the active variant + char k = tca8418_keypad_read(); + if (k) { + switch (k) { + case KEY_UP: enqueueKey(checkDisplayOn(KEY_UP)); break; + case KEY_ENTER: enqueueKey(checkDisplayOn(KEY_ENTER)); break; + case KEY_DOWN: enqueueKey(checkDisplayOn(KEY_DOWN)); break; + case KEY_CANCEL:enqueueKey(checkDisplayOn(KEY_CANCEL));break; + case KEY_HOME: + #ifdef LILYGO_TECHO_LITE_KEYSHIELD + extern void techo_keyshield_backlight_toggle(); + techo_keyshield_backlight_toggle(); + #endif + break; + default: + enqueueKey(checkDisplayOn(k)); + break; + } + } + } +#endif #if defined(BACKLIGHT_BTN) if ((int32_t)(millis() - next_backlight_btn_check) >= 0) { bool touch_state = digitalRead(PIN_BUTTON2); diff --git a/lib/GxEPD2-patch/src/GxEPD2_BW.h b/lib/GxEPD2-patch/src/GxEPD2_BW.h index af9c9936..e8d64c07 100644 --- a/lib/GxEPD2-patch/src/GxEPD2_BW.h +++ b/lib/GxEPD2-patch/src/GxEPD2_BW.h @@ -223,6 +223,9 @@ #if __has_include("it8951/GxEPD2_it103_1872x1404.h") #include "it8951/GxEPD2_it103_1872x1404.h" #endif +#if __has_include("epd/GxEPD2_122_T61.h") +#include "epd/GxEPD2_122_T61.h" +#endif template class GxEPD2_BW : public GxEPD2_GFX_BASE_CLASS diff --git a/src/helpers/ui/ST7789Display.cpp b/src/helpers/ui/ST7789Display.cpp index f7d20b8a..bed4848a 100644 --- a/src/helpers/ui/ST7789Display.cpp +++ b/src/helpers/ui/ST7789Display.cpp @@ -2,6 +2,11 @@ #include "ST7789Display.h" +#ifdef OLED_MISC_FIXED_FONT + #include "MiscFixedFont.h" + #include "LemonIcons.h" +#endif + #ifndef X_OFFSET #define X_OFFSET 0 // No offset needed for landscape #endif @@ -13,11 +18,49 @@ #ifdef HELTEC_VISION_MASTER_T190 #define SCALE_X 2.5f // 320 / 128 #define SCALE_Y 2.65625f // 170 / 64 +#elif defined(CARDPUTER_ADV) + #define SCALE_X 1.875f // 240 / 128 + #define SCALE_Y 2.109375f // 135 / 64 #else #define SCALE_X 1.875f // 240 / 128 #define SCALE_Y 2.109375f // 135 / 64 #endif +#ifdef OLED_MISC_FIXED_FONT +// MiscFixedFont.h/LemonIcons.h store each glyph's rows bit-packed +// *continuously* (Adafruit_GFX's native GFXfont convention -- no per-row byte +// padding), but drawXbm() below expects classic XBM packing (each row padded +// to a whole byte). Re-pack into a small stack buffer so the glyph can be +// blitted through drawXbm() -- which already implements the correct +// logical->physical scaling for this panel's non-integer SCALE_X/Y (via +// per-pixel boundary differences), instead of duplicating that math here. +static bool repackGlyphToXbm(const uint8_t* bitmapProgmem, uint16_t byteOffset, uint8_t w, uint8_t h, uint8_t* out, uint8_t outCap) { + uint8_t widthInBytes = (w + 7) / 8; + uint16_t needed = (uint16_t)widthInBytes * h; + if (needed == 0 || needed > outCap) return false; + memset(out, 0, needed); + uint16_t srcBit = 0; + for (uint8_t row = 0; row < h; row++) { + for (uint8_t col = 0; col < w; col++) { + uint16_t byteIdx = byteOffset + (srcBit >> 3); + uint8_t bitMask = 0x80 >> (srcBit & 7); + if (pgm_read_byte(bitmapProgmem + byteIdx) & bitMask) { + out[row * widthInBytes + (col >> 3)] |= (0x80 >> (col & 7)); + } + srcBit++; + } + } + return true; +} + +uint8_t ST7789Display::glyphXAdvance(uint32_t cp) { + for (uint8_t i = 0; i < lemonIconCount; i++) + if (pgm_read_dword(&lemonIconCPs[i]) == cp) return pgm_read_byte(&lemonIconGlyphs[i].xAdvance); + if (cp < MiscFixed.first || cp > MiscFixed.last) return 6; + return pgm_read_byte(&MiscFixedGlyphs[cp - MiscFixed.first].xAdvance); +} +#endif + bool ST7789Display::begin() { if(!_isOn) { pinMode(PIN_TFT_VDD_CTL, OUTPUT); @@ -131,12 +174,55 @@ void ST7789Display::setColor(Color c) { } void ST7789Display::setCursor(int x, int y) { +#ifdef OLED_MISC_FIXED_FONT + _lx = x; + _ly = y; +#endif _x = x*SCALE_X + X_OFFSET; _y = y*SCALE_Y + Y_OFFSET; } void ST7789Display::print(const char* str) { +#ifdef OLED_MISC_FIXED_FONT + int lx = _lx, ly = _ly; + uint8_t glyphBuf[16]; + const uint8_t* p = (const uint8_t*)str; + while (*p) { + uint32_t cp = DisplayDriver::decodeCodepoint(p); + if (cp == '\n') { ly += MiscFixed.yAdvance; lx = _lx; continue; } + + bool drawn = false; + for (uint8_t i = 0; i < lemonIconCount; i++) { + if (pgm_read_dword(&lemonIconCPs[i]) != cp) continue; + const GFXglyph* g = &lemonIconGlyphs[i]; + uint8_t gw = pgm_read_byte(&g->width), gh = pgm_read_byte(&g->height); + int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset); + uint16_t bo = pgm_read_word(&g->bitmapOffset); + if (repackGlyphToXbm(lemonIconBitmaps, bo, gw, gh, glyphBuf, sizeof(glyphBuf))) + drawXbm(lx + xo, ly + 6 + yo, glyphBuf, gw, gh); + lx += pgm_read_byte(&g->xAdvance); + drawn = true; + break; + } + if (drawn) continue; + + if (cp < MiscFixed.first || cp > MiscFixed.last) { + if (cp >= 0x20) fillRect(lx + 1, ly, 4, 6); + lx += 6; + continue; + } + + const GFXglyph* g = &MiscFixedGlyphs[cp - MiscFixed.first]; + uint8_t gw = pgm_read_byte(&g->width), gh = pgm_read_byte(&g->height); + int8_t xo = (int8_t)pgm_read_byte(&g->xOffset), yo = (int8_t)pgm_read_byte(&g->yOffset); + uint16_t bo = pgm_read_word(&g->bitmapOffset); + if (repackGlyphToXbm(MiscFixedBitmaps, bo, gw, gh, glyphBuf, sizeof(glyphBuf))) + drawXbm(lx + xo, ly + 7 + yo, glyphBuf, gw, gh); + lx += pgm_read_byte(&g->xAdvance); + } +#else display.drawString(_x, _y, str); +#endif } void ST7789Display::printWordWrap(const char* str, int max_width) { @@ -188,7 +274,14 @@ void ST7789Display::drawXbm(int x, int y, const uint8_t* bits, int w, int h) { } uint16_t ST7789Display::getTextWidth(const char* str) { +#ifdef OLED_MISC_FIXED_FONT + uint16_t w = 0; + const uint8_t* p = (const uint8_t*)str; + while (*p) w += glyphXAdvance(DisplayDriver::decodeCodepoint(p)); + return w; +#else return display.getStringWidth(str) / SCALE_X; +#endif } void ST7789Display::endFrame() { diff --git a/src/helpers/ui/ST7789Display.h b/src/helpers/ui/ST7789Display.h index cb56ff8a..7c9bd84d 100644 --- a/src/helpers/ui/ST7789Display.h +++ b/src/helpers/ui/ST7789Display.h @@ -10,17 +10,46 @@ class ST7789Display : public DisplayDriver { ST7789Spi display; bool _isOn; uint16_t _color; - int _x=0, _y=0; + int _x=0, _y=0; // physical (SCALE_X/Y-scaled) cursor, used by the ArialMT path +#ifdef OLED_MISC_FIXED_FONT + int _lx=0, _ly=0; // logical (pre-scale) cursor, used by the misc-fixed path +#endif bool i2c_probe(TwoWire& wire, uint8_t addr); +#ifdef OLED_MISC_FIXED_FONT + // Thin wrapper over the shared misc-fixed glyph metrics, kept out of the + // header so the font tables land in one translation unit only. + uint8_t glyphXAdvance(uint32_t cp); +#endif public: #ifdef HELTEC_VISION_MASTER_T190 ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 320, 170,PIN_TFT_SDA,-1,PIN_TFT_SCL) {_isOn = false;} +#elif defined(CARDPUTER_ADV) + ST7789Display() : DisplayDriver(128, 64), display(&SPI, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135, PIN_TFT_SDA, -1, PIN_TFT_SCL) {_isOn = false;} #else ST7789Display() : DisplayDriver(128, 64), display(&SPI1, PIN_TFT_RST, PIN_TFT_DC, PIN_TFT_CS, GEOMETRY_RAWMODE, 240, 135) {_isOn = false;} #endif bool begin(); +#ifdef OLED_MISC_FIXED_FONT + // Same misc-fixed 6x9 font the SH1106 and e-ink drivers use: full + // Latin/Greek/Cyrillic instead of the built-in ArialMT font's Latin-only + // coverage, so the keyboard's alphabets render as themselves. Opt-in per + // variant (the font costs ~14 KB of flash) — see the boards that set + // OLED_MISC_FIXED_FONT in their platformio.ini. + int getLineHeight() const override { return 9; } // 6x9 box height, logical units + // The font's own glyph table is ink-tight, unlike ArialMT (below). + int textWidthTrailingGap() const override { return 0; } + bool isSingleFont() const override { return true; } + void setSingleFont(bool) override { } // single-font: ignore toggles + void translateUTF8ToBlocks(char* dest, const char* src, size_t dest_size) override { + // No transliteration needed — print() renders UTF-8 directly. + strncpy(dest, src, dest_size - 1); + dest[dest_size - 1] = '\0'; + } + uint16_t getCodepointWidth(uint32_t cp) override { return glyphXAdvance(cp); } +#endif + bool isOn() override { return _isOn; } void turnOn() override; void turnOff() override; diff --git a/variants/lilygo_techo_lite/KeyshieldBacklight.cpp b/variants/lilygo_techo_lite/KeyshieldBacklight.cpp new file mode 100644 index 00000000..4c9ac560 --- /dev/null +++ b/variants/lilygo_techo_lite/KeyshieldBacklight.cpp @@ -0,0 +1,66 @@ +// TechoKeyBacklight.cpp +// Self-contained AW21009(QNR) keyboard-backlight driver for the T-Echo Lite +// KeyShield. Provides a single free function consumed by the UITask Home-key hook: +// +// void techo_keyshield_backlight_toggle(); // on <-> off +// +// Raw-Wire register driver. The register sequence mirrors LilyGo's +// cpp_bus_driver Aw21009 implementation (reset / global-control / global-current +// / per-channel scaling / latch, then 12-bit per-channel brightness), so no +// dependency on that library is pulled in. + +#ifdef LILYGO_TECHO_LITE_KEYSHIELD + +#include +#include + +// ---- AW21009 (KeyShield backlight) ----------------------------------------- +// I2C 0x20 on the shared Wire bus. Wire.begin() (TechoBoard::begin) and the +// shield 3V3 rail are already up before the UI loop runs, so this lazy-inits on +// first toggle. +#define AW21009_ADDR 0x20 +#define AW21009_REG_GCR 0x20 // global control (auto-save / PWM / chip enable) +#define AW21009_REG_BRIGHT 0x21 // brightness start; per ch: LSB=0x21+ch*2, MSB+1 +#define AW21009_REG_UPDATE 0x45 // write-to-latch register +#define AW21009_REG_SCALE 0x46 // per-channel current scaling start +#define AW21009_REG_GCCR 0x58 // global current control +#define AW21009_REG_RESET 0x70 // software reset + +#define AW21009_CHANNELS 6 +#define AW21009_ON_LEVEL 4095 // 0..4095; lower this to dim the backlight + +static void aw_write(uint8_t reg, uint8_t val) { + Wire.beginTransmission(AW21009_ADDR); + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +static void aw_init() { + aw_write(AW21009_REG_RESET, 0x00); // software reset + delay(10); + aw_write(AW21009_REG_GCR, 0x87); // auto power save + 12-bit PWM + chip enable + aw_write(AW21009_REG_GCCR, 0xFF); // global current = max + for (uint8_t i = 0; i < AW21009_CHANNELS; i++) + aw_write(AW21009_REG_SCALE + i, 0xFF); // per-channel current = max + aw_write(AW21009_REG_UPDATE, 0x00); // latch +} + +static void aw_set_brightness(uint16_t value) { + if (value > 4095) value = 4095; + for (uint8_t i = 0; i < AW21009_CHANNELS; i++) { + aw_write(AW21009_REG_BRIGHT + i * 2, (uint8_t)(value & 0xFF)); // LSB + aw_write(AW21009_REG_BRIGHT + i * 2 + 1, (uint8_t)((value >> 8) & 0x0F)); // MSB (12-bit) + } + aw_write(AW21009_REG_UPDATE, 0x00); // latch +} + +void techo_keyshield_backlight_toggle() { + static bool inited = false; + static bool on = false; + if (!inited) { aw_init(); inited = true; } + on = !on; + aw_set_brightness(on ? AW21009_ON_LEVEL : 0); +} + +#endif \ No newline at end of file diff --git a/variants/lilygo_techo_lite/TEchoKeyShield.cpp b/variants/lilygo_techo_lite/TEchoKeyShield.cpp new file mode 100644 index 00000000..789e57da --- /dev/null +++ b/variants/lilygo_techo_lite/TEchoKeyShield.cpp @@ -0,0 +1,240 @@ +#ifdef LILYGO_TECHO_LITE_KEYSHIELD + +#include +#include +#include // KEY_PREV / KEY_NEXT / KEY_ENTER / KEY_CANCEL + +// ---- TCA8418 (KeyShield keypad) -------------------------------------------- +#define TCA8418_ADDR 0x34 +#define TCA8418_REG_CFG 0x01 +#define TCA8418_REG_INT_STAT 0x02 +#define TCA8418_REG_KEY_LCK_EC 0x03 +#define TCA8418_REG_KEY_EVENT_A 0x04 +#define TCA8418_REG_KP_GPIO1 0x1D +#define TCA8418_REG_KP_GPIO2 0x1E +#define TCA8418_REG_KP_GPIO3 0x1F +#define TCA8418_REG_GPI_EM1 0x20 +#define TCA8418_REG_GPI_EM2 0x21 +#define TCA8418_REG_GPI_EM3 0x22 +#define TCA8418_REG_DEBOUNCE 0x29 + +#define MULTI_TAP_THRESHOLD 300 // multitap threshold csúszó ablak ideje +#define LONG_PRESS_THRESHOLD 600 // longpress threshold idő + +// ---- Multi-tap / long-press állapotgép ------------------------------------- + +// true : folyamatos mód – minden koppintáskor azonnal küld backspace-t + új karaktert +// (gyors, sima kijelzőknek jó, "élőben" látszik a T9 ciklus) +// false : halasztott mód – csak akkor küldi a végleges karaktert, ha lejárt a +// MULTI_TAP_THRESHOLD (e-ink-hez ajánlott) +static bool sendBackSpace = false; + +static void tca_write(uint8_t reg, uint8_t val) { + Wire.beginTransmission(TCA8418_ADDR); + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +static uint8_t tca_read(uint8_t reg) { + Wire.beginTransmission(TCA8418_ADDR); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)TCA8418_ADDR, (uint8_t)1); + return Wire.available() ? Wire.read() : 0; +} + +// Num chars per key (T9 ciklus hossz) +static uint8_t KeyTapMod[5*4] = { + 1, 5, 1, 5, + 1, 9, 7, 9, + 1, 7, 7, 7, + 1, 1, 7, 7, + 1, 1, 1, 1 +}; + +static const unsigned char keymap[5*4][9] = { + {' '}, + {'.', ',', '?', '!', '*'}, + {'0'}, + {':', ';', '-', '+', '#'}, + {'\b'}, + {'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S', '7'}, + {'t', 'u', 'v', 'T', 'U', 'V', '8'}, + {'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z', '9'}, + {KEY_DOWN}, + {'g', 'h', 'i', 'G', 'H', 'I', '4'}, + {'j', 'k', 'l', 'J', 'K', 'L', '5'}, + {'m', 'n', 'o', 'M', 'N', 'O', '6'}, + {KEY_ENTER}, + {'1'}, + {'a', 'b', 'c', 'A', 'B', 'C', '2'}, + {'d', 'e', 'f', 'D', 'E', 'F', '3'}, + {KEY_UP}, + {KEY_CANCEL}, + {KEY_HOME}, + {KEY_CONTEXT_MENU} +}; + +// long press +static const unsigned char keymapLongPress[5*4] = { + 0, 0, 0, 0, + 0, 0, 0, 0, + KEY_LEFT, 0, 0, 0, + KEY_KB_ENTER, 0, 0, 0, + KEY_RIGHT, KEY_CANCEL, KEY_HOME, KEY_CONTEXT_MENU +}; + +void tca8418_keypad_set_backspace_mode(bool continuous) { + sendBackSpace = continuous; +} + +// kimeneti kör-FIFO, mert egy koppintás akár 2 karaktert is eredményezhet +// (backspace + új kar) folyamatos módban +#define KEY_OUT_QUEUE_LEN 4 +static char s_outQueue[KEY_OUT_QUEUE_LEN]; +static uint8_t s_outHead = 0, s_outTail = 0; + +static void queue_push(char c) { + uint8_t next = (s_outTail + 1) % KEY_OUT_QUEUE_LEN; + if (next != s_outHead) { s_outQueue[s_outTail] = c; s_outTail = next; } +} + +static bool queue_pop(char &c) { + if (s_outHead == s_outTail) return false; + c = s_outQueue[s_outHead]; + s_outHead = (s_outHead + 1) % KEY_OUT_QUEUE_LEN; + return true; +} + +// aktuálisan lenyomva tartott (fizikai) billentyű, a long-press méréshez +static uint8_t s_downKeyIndex = 0xFF; +static unsigned long s_downTime = 0; + +// függőben lévő (még nem lezárt) multi-tap ciklus +static uint8_t s_pendingIndex = 0xFF; // 0xFF = nincs függő ciklus +static uint8_t s_pendingTap = 0; // aktuális tap-index (0-alapú) +static unsigned long s_pendingTapTime = 0; + +static void keypad_init() { + tca_write(TCA8418_REG_CFG, 0x00); + for (int i = 0; i < 16; i++) { + if ((tca_read(TCA8418_REG_KEY_LCK_EC) & 0x0F) == 0) break; + tca_read(TCA8418_REG_KEY_EVENT_A); + } + tca_write(TCA8418_REG_INT_STAT, 0x1F); + tca_write(TCA8418_REG_GPI_EM1, 0x00); + tca_write(TCA8418_REG_GPI_EM2, 0x00); + tca_write(TCA8418_REG_GPI_EM3, 0x00); + tca_write(TCA8418_REG_KP_GPIO1, 0x1F); // ROW0-ROW4 in keypad matrix 0b00011111 + tca_write(TCA8418_REG_KP_GPIO2, 0x0F); // COL0-COL3 in keypad matrix 0b00001111 + tca_write(TCA8418_REG_KP_GPIO3, 0x00); // COL8, COL9 + tca_write(TCA8418_REG_DEBOUNCE, 0x03); + tca_write(TCA8418_REG_INT_STAT, 0x1F); + tca_write(TCA8418_REG_CFG, 0x11); + delay(5); + for (int i = 0; i < 16; i++) { + if ((tca_read(TCA8418_REG_KEY_LCK_EC) & 0x0F) == 0) break; + tca_read(TCA8418_REG_KEY_EVENT_A); + } + tca_write(TCA8418_REG_INT_STAT, 0x1F); +} + +// lezár egy függő multi-tap ciklust: halasztott módban ilyenkor kell +// kiküldeni a "végleges" kart, mert eddig semmi nem ment ki érte +static void commit_pending_if_needed() { + if (s_pendingIndex == 0xFF) return; + if (!sendBackSpace) { + uint8_t idx = s_pendingTap % KeyTapMod[s_pendingIndex]; + queue_push(keymap[s_pendingIndex][idx]); + } + s_pendingIndex = 0xFF; +} + +char tca8418_keypad_read() { + static bool inited = false; + if (!inited) { keypad_init(); inited = true; } + + unsigned long now = millis(); + + // 1) lejárt-e a függő multi-tap ciklus? (nem kap I2C eseményt, ezért + // minden híváskor ellenőrizni kell) + if (s_pendingIndex != 0xFF && (now - s_pendingTapTime) >= MULTI_TAP_THRESHOLD) { + commit_pending_if_needed(); + } + + // 2) van-e még kiküldendő karakter a pufferben? + char qc; + if (queue_pop(qc)) return qc; + + // 3) hardver FIFO + if ((tca_read(TCA8418_REG_KEY_LCK_EC) & 0x0F) == 0) return 0; + + uint8_t ev = tca_read(TCA8418_REG_KEY_EVENT_A); + tca_write(TCA8418_REG_INT_STAT, 0x1F); + + bool pressed = (ev & 0x80) != 0; + uint8_t code = ev & 0x7F; + uint8_t row = (code - 1) / 10; + uint8_t col = (code - 1) % 10; + uint8_t key_index = row * 4 + col; + if (key_index >= 5*4) return 0; + + if (pressed) { + // csak eltároljuk, a döntés (rövid/hosszú) a release-nél történik + s_downKeyIndex = key_index; + s_downTime = now; + return 0; + } + + // --- release --- + if (s_downKeyIndex != key_index) return 0; // eltévedt/pár nélküli release + unsigned long duration = now - s_downTime; + s_downKeyIndex = 0xFF; + + bool longEligible = keymapLongPress[key_index] != 0; + bool isLong = longEligible && duration >= LONG_PRESS_THRESHOLD; + + if (isLong) { + if (sendBackSpace && s_pendingIndex == key_index) { + queue_push('\b'); // az előzőleg megjelenített preview karakter törlése + } else { + commit_pending_if_needed(); // más gombra vonatkozó függő ciklus lezárása + } + s_pendingIndex = 0xFF; + queue_push(keymapLongPress[key_index]); + } + else if (KeyTapMod[key_index] <= 1) { + // nem ciklikus gomb -> mindig azonnali, önálló leütés + commit_pending_if_needed(); + s_pendingIndex = 0xFF; + queue_push(keymap[key_index][0]); + } + else { + // Serial.printf("gap: %lu ms\n", now - s_pendingTapTime); // gap idő debug + bool isContinuation = (s_pendingIndex == key_index) && + ((now - s_pendingTapTime) < MULTI_TAP_THRESHOLD); + + if (!isContinuation) { + commit_pending_if_needed(); // esetleges más gombos függő ciklus lezárása + s_pendingIndex = key_index; + s_pendingTap = 0; + } else { + s_pendingTap = (s_pendingTap + 1) % KeyTapMod[key_index]; + } + s_pendingTapTime = now; + + if (sendBackSpace) { + if (isContinuation) queue_push('\b'); + queue_push(keymap[key_index][s_pendingTap]); + } + // halasztott módban itt nem küldünk semmit, a threshold lejártakor + // (1-es lépés) vagy a következő eltérő gombnál (commit_pending_if_needed) + // fog kimenni a végleges karakter + } + + if (queue_pop(qc)) return qc; + return 0; +} + +#endif \ No newline at end of file diff --git a/variants/lilygo_techo_lite/TechoBoard.h b/variants/lilygo_techo_lite/TechoBoard.h index 7a43fd83..ef44dcb7 100644 --- a/variants/lilygo_techo_lite/TechoBoard.h +++ b/variants/lilygo_techo_lite/TechoBoard.h @@ -21,6 +21,16 @@ public: return "LilyGo T-Echo Lite"; } + #if defined(P_LORA_TX_LED) + void onBeforeTransmit() override { + digitalWrite(P_LORA_TX_LED, LOW); // turn TX LED on + } + + void onAfterTransmit() override { + digitalWrite(P_LORA_TX_LED, HIGH); // turn TX LED off + } + #endif + void powerOff() override { digitalWrite(PIN_VBAT_MEAS_EN, LOW); #ifdef LED_RED diff --git a/variants/lilygo_techo_lite/platformio.ini b/variants/lilygo_techo_lite/platformio.ini index 4b5edf69..f0a40df6 100644 --- a/variants/lilygo_techo_lite/platformio.ini +++ b/variants/lilygo_techo_lite/platformio.ini @@ -12,7 +12,7 @@ build_flags = ${nrf52_base.build_flags} -D WRAPPER_CLASS=CustomSX1262Wrapper -D LORA_TX_POWER=22 -D SX126X_POWER_EN=30 - -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_DIO3_TCXO_VOLTAGE=3.0 ;https://github.com/Xinyuan-LilyGO/T-Echo-Lite/issues/14 -D SX126X_CURRENT_LIMIT=140 -D SX126X_RX_BOOSTED_GAIN=1 -D P_LORA_TX_LED=LED_GREEN @@ -28,10 +28,11 @@ build_flags = ${nrf52_base.build_flags} -D EINK_Y_OFFSET=10 -D DISPLAY_ROTATION=4 -D AUTO_OFF_MILLIS=0 + -D DUAL_SERIAL=1 build_src_filter = ${nrf52_base.build_src_filter} + + - + + + + + +<../variants/lilygo_techo_lite> @@ -44,6 +45,45 @@ lib_deps = debug_tool = jlink upload_protocol = nrfutil +[env:LilyGo_T-Echo-Lite_keyshield_companion_solo_dual] +extends = LilyGo_T-Echo-Lite +board_build.ldscript = boards/nrf52840_s140_v6_extrafs.ld +board_upload.maximum_size = 712704 +; Optimise for size, not speed: the nRF52 Arduino core defaults to -Ofast +; (≈ -O3 + -ffast-math), which bloats flash by ~200 KB on this feature-rich +; solo build. -Os keeps every feature, fits comfortably, and is actually safer +; for float (no fast-math reassociation). Speed is irrelevant for the UI/mesh. +build_unflags = -Ofast +build_flags = + ${LilyGo_T-Echo-Lite.build_flags} + -I src/helpers/ui + -I examples/companion_radio/ui-new + -D LILYGO_TECHO_LITE_KEYSHIELD + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + ; -D QSPIFLASH=1 + -D BLE_PIN_CODE=123456 + ; -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 + -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 + -D ENV_USE_TCA8418=1 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.16"' + -D ENABLE_SCREENSHOT + -Os +build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${LilyGo_T-Echo-Lite.lib_deps} + densaugeo/base64 @ ~1.4.0 + ;https://github.com/lewisxhe/SensorLib.git + [env:LilyGo_T-Echo-Lite_repeater] extends = LilyGo_T-Echo-Lite build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} @@ -90,6 +130,8 @@ build_flags = ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 -D AUTO_SHUTDOWN_MILLIVOLTS=3300 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.16"' build_src_filter = ${LilyGo_T-Echo-Lite.build_src_filter} + +<../examples/companion_radio/*.cpp> diff --git a/variants/lilygo_techo_lite/variant.h b/variants/lilygo_techo_lite/variant.h index 702ab334..46623772 100644 --- a/variants/lilygo_techo_lite/variant.h +++ b/variants/lilygo_techo_lite/variant.h @@ -105,6 +105,7 @@ #define LORA_CS _PINNUM(0, 11) #define SX126X_POWER_EN _PINNUM(0, 30) #define SX126X_DIO1 _PINNUM(1, 8) +#define SX1262_DIO2 _PINNUM(0, 5) #define SX126X_BUSY _PINNUM(0, 14) #define SX126X_RESET _PINNUM(0, 7) #define SX126X_RXEN _PINNUM(1, 1) @@ -152,8 +153,10 @@ extern const int SCK; // 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 PIN_GPS_TX _PINNUM(0, 29) // GPS UART TX → MCU RX +//#define PIN_GPS_RX _PINNUM(1, 10) // GPS UART RX ← MCU TX +#define PIN_GPS_RX _PINNUM(0, 29) // GPS UART TX → MCU RX +#define PIN_GPS_TX _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 diff --git a/variants/m5stack_cardputer_adv/CardputerADVBoard.cpp b/variants/m5stack_cardputer_adv/CardputerADVBoard.cpp new file mode 100644 index 00000000..48703a4c --- /dev/null +++ b/variants/m5stack_cardputer_adv/CardputerADVBoard.cpp @@ -0,0 +1,31 @@ +#include +#include "CardputerADVBoard.h" + +uint32_t deviceOnline = 0x00; + +void CardputerADVBoard::begin() { + + ESP32Board::begin(); + + // Configure user button + pinMode(PIN_USER_BTN, INPUT); + + // Configure LoRa Pins + pinMode(P_LORA_MISO, INPUT_PULLUP); + // pinMode(P_LORA_DIO_1, INPUT_PULLUP); + + #ifdef P_LORA_TX_LED + digitalWrite(P_LORA_TX_LED, HIGH); // inverted pin for SX1276 - HIGH for off + #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; // received a LoRa packet (while in deep sleep) + } + + rtc_gpio_hold_dis((gpio_num_t)P_LORA_NSS); + rtc_gpio_deinit((gpio_num_t)P_LORA_DIO_1); + } +} \ No newline at end of file diff --git a/variants/m5stack_cardputer_adv/CardputerADVBoard.h b/variants/m5stack_cardputer_adv/CardputerADVBoard.h new file mode 100644 index 00000000..28493b50 --- /dev/null +++ b/variants/m5stack_cardputer_adv/CardputerADVBoard.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include "helpers/ESP32Board.h" +#include + +#define PIN_VBAT_READ 10 +#define BATTERY_SAMPLES 8 +#define ADC_MULTIPLIER (2.0f * 3.3f * 1000) + +class CardputerADVBoard : public ESP32Board { +public: + void begin(); + + void enterDeepSleep(uint32_t secs, int pin_wake_btn) { + esp_sleep_pd_config(ESP_PD_DOMAIN_RTC_PERIPH, ESP_PD_OPTION_ON); + + // Make sure the DIO1 and NSS GPIOs are hold on required levels during deep sleep + 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); + + if (pin_wake_btn < 0) { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet + } else { + esp_sleep_enable_ext1_wakeup( (1L << P_LORA_DIO_1) | (1L << pin_wake_btn), ESP_EXT1_WAKEUP_ANY_HIGH); // wake up on: recv LoRa packet OR wake btn + } + + if (secs > 0) { + esp_sleep_enable_timer_wakeup(secs * 1000000); + } + + // Finally set ESP32 into sleep + esp_deep_sleep_start(); // CPU halts here and never returns! + } + + uint16_t getBattMilliVolts() { + #if defined(PIN_VBAT_READ) && defined(ADC_MULTIPLIER) + analogReadResolution(12); + + uint32_t raw = 0; + for (int i = 0; i < BATTERY_SAMPLES; i++) { + raw += analogRead(PIN_VBAT_READ); + } + + raw = raw / BATTERY_SAMPLES; + return (ADC_MULTIPLIER * raw) / 4096; + #else + return 0; + #endif + } + + const char* getManufacturerName() const{ + return "M5Stack Cardputer ADV"; + } +}; \ No newline at end of file diff --git a/variants/m5stack_cardputer_adv/CardputerKeyboard.cpp b/variants/m5stack_cardputer_adv/CardputerKeyboard.cpp new file mode 100644 index 00000000..147049f1 --- /dev/null +++ b/variants/m5stack_cardputer_adv/CardputerKeyboard.cpp @@ -0,0 +1,166 @@ +#ifdef CARDPUTER_ADV + +#include +#include +#include // KEY_PREV / KEY_NEXT / KEY_ENTER / KEY_CANCEL + +// --- TCA8418 registers --- +#define TCA8418_ADDR 0x34 +#define TCA8418_REG_CFG 0x01 +#define TCA8418_REG_INT_STAT 0x02 +#define TCA8418_REG_KEY_LCK_EC 0x03 +#define TCA8418_REG_KEY_EVENT_A 0x04 +#define TCA8418_REG_KP_GPIO1 0x1D +#define TCA8418_REG_KP_GPIO2 0x1E +#define TCA8418_REG_KP_GPIO3 0x1F +#define TCA8418_REG_GPI_EM1 0x20 +#define TCA8418_REG_GPI_EM2 0x21 +#define TCA8418_REG_GPI_EM3 0x22 +#define TCA8418_REG_DEBOUNCE 0x29 + +// --- modifier key event-kódok (nyers TCA8418 "code", key_index előtti érték) --- +#define TCA_CODE_TAB 2 // egyelőre nem használt +#define TCA_CODE_FN 3 +#define TCA_CODE_CTRL 4 // egyelőre nincs funkció +#define TCA_CODE_SHIFT 7 +#define TCA_CODE_OPT 8 // egyelőre nincs funkció +#define TCA_CODE_ALT 14 // egyelőre nincs funkció + +// --- modifier állapot flag-ek --- +static bool mod_fn = false; +static bool mod_ctrl = false; +static bool mod_shift = false; +static bool mod_opt = false; +static bool mod_alt = false; + +static void tca_write(uint8_t reg, uint8_t val) { + Wire.beginTransmission(TCA8418_ADDR); + Wire.write(reg); + Wire.write(val); + Wire.endTransmission(); +} + +static uint8_t tca_read(uint8_t reg) { + Wire.beginTransmission(TCA8418_ADDR); + Wire.write(reg); + Wire.endTransmission(); + Wire.requestFrom((uint8_t)TCA8418_ADDR, (uint8_t)1); + return Wire.available() ? Wire.read() : 0; +} + +// Map a TCA8418 key-event number to a UI value. +// normal +static const unsigned char keymap[7*8] = { + '\'', KEY_CONTEXT_MENU, 0, 0, + '1', 'q', 0, 0, + '2', 'w', 'a', 0, + '3', 'e', 's', 'z', + '4', 'r', 'd', 'x', + '5', 't', 'f', 'c', + '6', 'y', 'g', 'v', + '7', 'u', 'h', 'b', + '8', 'i', 'j', 'n', + '9', 'o', 'k', 'm', + '0', 'p', 'l', ',', + '_', '[', ';', '.', + '=', ']', '\'', '/', + '\b', '\\', KEY_ENTER, ' ' +}; +// Fn pressed +static const unsigned char keymapFN[7*8] = { + KEY_CANCEL, KEY_CONTEXT_MENU, 0, 0, + '1', 'q', 0, 0, + '2', 'w', 'a', 0, + '3', 'e', 's', 'z', + '4', 'r', 'd', 'x', + '5', 't', 'f', 'c', + '6', 'y', 'g', 'v', + '7', 'u', 'h', 'b', + '8', 'i', 'j', 'n', + '9', 'o', 'k', 'm', + '0', 'p', 'l', KEY_LEFT, + '_', '[', KEY_UP, KEY_DOWN, + '=', ']', '\'', KEY_RIGHT, + '\b', '\\', KEY_KB_ENTER, ' ' +}; +// Shift pressed +static const unsigned char keymapSH[7*8] = { + '~', 0, 0, 0, + '!', 'Q', 0, 0, + '@', 'W', 'A', 0, + '#', 'E', 'S', 'Z', + '$', 'R', 'D', 'X', + '%', 'T', 'F', 'C', + '^', 'Y', 'G', 'V', + '&', 'U', 'H', 'B', + '*', 'I', 'J', 'N', + '{', 'O', 'K', 'M', + '}', 'P', 'L', '<', + '-', '(', ':', '>', + '+', ')', '"', '?', + '\b', '|', KEY_SELECT, ' ' +}; + + +static void keypad_init() { + tca_write(TCA8418_REG_CFG, 0x00); // scanner off + for (int i = 0; i < 16; i++) { + if ((tca_read(TCA8418_REG_KEY_LCK_EC) & 0x0F) == 0) break; + tca_read(TCA8418_REG_KEY_EVENT_A); + } + tca_write(TCA8418_REG_INT_STAT, 0x1F); // clear all int flags + tca_write(TCA8418_REG_GPI_EM1, 0x00); + tca_write(TCA8418_REG_GPI_EM2, 0x00); + tca_write(TCA8418_REG_GPI_EM3, 0x00); + tca_write(TCA8418_REG_KP_GPIO1, 0x7F); // ROW0-ROW6 in keypad matrix 0b01111111 + tca_write(TCA8418_REG_KP_GPIO2, 0xFF); // COL0-COL7 in keypad matrix 0b11111111 + tca_write(TCA8418_REG_KP_GPIO3, 0x00); // COL8, COL9 + tca_write(TCA8418_REG_DEBOUNCE, 0x03); // + tca_write(TCA8418_REG_INT_STAT, 0x1F); + tca_write(TCA8418_REG_CFG, 0x11); // KE_IEN + INT_CFG, scanner on + delay(5); + for (int i = 0; i < 16; i++) { + if ((tca_read(TCA8418_REG_KEY_LCK_EC) & 0x0F) == 0) break; + tca_read(TCA8418_REG_KEY_EVENT_A); + } + tca_write(TCA8418_REG_INT_STAT, 0x1F); +} + +char tca8418_keypad_read() { + static bool inited = false; + if (!inited) { keypad_init(); inited = true; } + + if ((tca_read(TCA8418_REG_KEY_LCK_EC) & 0x0F) == 0) return 0; // FIFO empty + + uint8_t ev = tca_read(TCA8418_REG_KEY_EVENT_A); + tca_write(TCA8418_REG_INT_STAT, 0x1F); // clear int + + bool pressed = (ev & 0x80) != 0; + uint8_t code = ev & 0x7F; + + // --- modifier kezelés: press ÉS release is számít, sosem küld karaktert --- + switch (code) { + case TCA_CODE_FN: mod_fn = pressed; return 0; + case TCA_CODE_CTRL: mod_ctrl = pressed; return 0; + case TCA_CODE_SHIFT: mod_shift = pressed; return 0; + case TCA_CODE_OPT: mod_opt = pressed; return 0; + case TCA_CODE_ALT: mod_alt = pressed; return 0; + default: break; + } + + if (!pressed) return 0; // sima gomb release -> ignore + + uint8_t row = (code - 1) / 10; + uint8_t col = (code - 1) % 10; + uint8_t key_index = row * 8 + col; + if (key_index >= 7*8) return 0; // védelem, ha valamiért kilógna + + // --- prioritás: Fn > Shift > normal (ctrl/opt/alt egyelőre nincs saját map) --- + const unsigned char* active_map = keymap; + if (mod_fn) active_map = keymapFN; + else if (mod_shift) active_map = keymapSH; + + return active_map[key_index]; +} + +#endif \ No newline at end of file diff --git a/variants/m5stack_cardputer_adv/platformio.ini b/variants/m5stack_cardputer_adv/platformio.ini new file mode 100644 index 00000000..9ed142ad --- /dev/null +++ b/variants/m5stack_cardputer_adv/platformio.ini @@ -0,0 +1,201 @@ +[M5Stack_Cardputer_ADV] +extends = esp32_base +board = m5stack-stamps3 +build_flags = + ${esp32_base.build_flags} + -I variants/m5stack_cardputer_adv + -D CARDPUTER_ADV + -D ARDUINO_USB_CDC_ON_BOOT=1 + ;---LoRa + -D P_LORA_DIO_1=4 ;IRQ + -D P_LORA_NSS=5 + -D P_LORA_RESET=3 + -D P_LORA_BUSY=6 + -D P_LORA_SCLK=40 + -D P_LORA_MISO=39 + -D P_LORA_MOSI=14 + -D SX126X_DIO2_AS_RF_SWITCH=true + -D SX126X_DIO3_TCXO_VOLTAGE=1.8 + -D SX126X_CURRENT_LIMIT=140 + -D USE_SX1262 + -D RADIO_CLASS=CustomSX1262 + -D WRAPPER_CLASS=CustomSX1262Wrapper + -D LORA_TX_POWER=22 + -D SX126X_RX_BOOSTED_GAIN=1 + ;--BATTERY + -D PIN_VBAT_READ=10 + -D PIN_USER_BTN=0 + ;---I2C + -D PIN_BOARD_SDA=8 ;14pin header & TCA8418 + -D PIN_BOARD_SCL=9 ;14pin header & TCA8418 + -D PIN_BOARD_SDA1=2 ;grove + -D PIN_BOARD_SCL1=1 ;grove + ;---GPS + -D ENV_INCLUDE_GPS=1 + -D PIN_GPS_RX=13 + -D PIN_GPS_TX=15 + -D GPS_BAUD_RATE=115200 + ;---LCD + -D PIN_TFT_SCL=36 + -D PIN_TFT_SDA=35 + -D PIN_TFT_RST=33 + -D PIN_TFT_VDD_CTL=38 + -D PIN_TFT_LEDA_CTL=38 + -D PIN_TFT_LEDA_CTL_ACTIVE=HIGH + -D PIN_TFT_CS=37 + -D PIN_TFT_DC=34 ;RS + -D ST7789 + -D DISPLAY_CLASS=ST7789Display + ;---TCA8418 + -D ENV_USE_TCA8418=1 +build_src_filter = ${esp32_base.build_src_filter} + +<../variants/m5stack_cardputer_adv> + + + + + + + + +lib_deps = + ${esp32_base.lib_deps} + ${sensor_base.lib_deps} + adafruit/Adafruit ST7735 and ST7789 Library @ ^1.11.0 + +[env:M5Stack_Cardputer_ADV_companion_radio_ble] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D BLE_DEBUG_LOGGING=1 + -D OFFLINE_QUEUE_SIZE=256 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:M5Stack_Cardputer_ADV_companion_radio_usb] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 +; NOTE: DO NOT ENABLE --> -D MESH_PACKET_LOGGING=1 +; NOTE: DO NOT ENABLE --> -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:M5Stack_Cardputer_ADV_companion_solo_dual] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -I examples/companion_radio/ui-new + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=40 + -D BLE_PIN_CODE=123456 + -D DUAL_SERIAL=1 + -D OFFLINE_QUEUE_SIZE=256 + -D FIRMWARE_SOLO_BUILD=1 + -D MESHCORE_VERSION='"1.16"' + -D UI_SENSORS_PAGE=1 + -D ENABLE_SCREENSHOT + ; misc-fixed 6x9 font (full Latin/Greek/Cyrillic), same as the Wio L1/Heltec + ; solo builds. Solo only: it costs ~14 KB of flash, and this board's own + ; QWERTY keyboard is exactly the case that benefits from typing those + ; alphabets and having them render as themselves instead of blocks. + -D OLED_MISC_FIXED_FONT=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + + + + + +<../examples/companion_radio/*.cpp> + +<../examples/companion_radio/ui-new/*.cpp> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:M5Stack_Cardputer_ADV_repeater] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -D ADVERT_NAME='"Cardputer_adv 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 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + +<../examples/simple_repeater> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + ${esp32_ota.lib_deps} + +[env:M5Stack_Cardputer_ADV_repeater_bridge_espnow] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.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 +; -D BRIDGE_DEBUG=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + + + +<../examples/simple_repeater> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + ${esp32_ota.lib_deps} + +[env:M5Stack_Cardputer_ADV_terminal_chat] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -D MAX_CONTACTS=350 + -D MAX_GROUP_CHANNELS=1 +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + +<../examples/simple_secure_chat/main.cpp> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + densaugeo/base64 @ ~1.4.0 + +[env:M5Stack_Cardputer_ADV_room_server] +extends = M5Stack_Cardputer_ADV +build_flags = + ${M5Stack_Cardputer_ADV.build_flags} + -D ADVERT_NAME='"Cardputer_adv Room"' + -D ADVERT_LAT=0.0 + -D ADVERT_LON=0.0 + -D ADMIN_PASSWORD='"password"' + -D ROOM_PASSWORD='"hello"' +; -D MESH_PACKET_LOGGING=1 +; -D MESH_DEBUG=1 +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + +<../examples/simple_room_server> +lib_deps = + ${M5Stack_Cardputer_ADV.lib_deps} + ${esp32_ota.lib_deps} + +[env:M5Stack_Cardputer_ADV_kiss_modem] +extends = M5Stack_Cardputer_ADV +build_src_filter = ${M5Stack_Cardputer_ADV.build_src_filter} + +<../examples/kiss_modem/> diff --git a/variants/m5stack_cardputer_adv/target.cpp b/variants/m5stack_cardputer_adv/target.cpp new file mode 100644 index 00000000..886cb5a1 --- /dev/null +++ b/variants/m5stack_cardputer_adv/target.cpp @@ -0,0 +1,63 @@ +#include +#include "target.h" + +CardputerADVBoard board; + +static SPIClass spi; +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); + +ESP32RTCClock fallback_clock; +AutoDiscoverRTCClock rtc_clock(fallback_clock); +MicroNMEALocationProvider gps(Serial1, &rtc_clock); +EnvironmentSensorManager sensors(gps); + +#ifdef DISPLAY_CLASS + DISPLAY_CLASS display; + MomentaryButton user_btn(PIN_USER_BTN, 1000, true); +#endif + +static void init_lora_cap_ioe() { + const uint8_t PI4IOE_ADDR = 0x43; // PI4IOE5V6408, ADDR pin -> GND + + // Egyszerű "probe": ha nincs ACK, nincs ott az IO expander (régi cap) + Wire.beginTransmission(PI4IOE_ADDR); + if (Wire.endTransmission() != 0) { + return; // régi Cap LoRa868, nincs teendő + } + + // Új Cap LoRa-1262: P0 -> output, High-Z letiltva, majd HIGH + Wire.beginTransmission(PI4IOE_ADDR); + Wire.write(0x03); // I/O Direction register + Wire.write(0x01); // bit0 = 1 -> P0 kimenet, többi marad bemenet + Wire.endTransmission(); + + Wire.beginTransmission(PI4IOE_ADDR); + Wire.write(0x07); // Output High-Impedance register + Wire.write(0x00); // bit0 = 0 -> P0 kijön a High-Z állapotból + Wire.endTransmission(); + + Wire.beginTransmission(PI4IOE_ADDR); + Wire.write(0x05); // Output Port register + Wire.write(0x01); // bit0 = 1 -> P0 HIGH (LoRa "SW" pin engedélyezve) + Wire.endTransmission(); +} + +bool radio_init() { + fallback_clock.begin(); + + Wire.begin(PIN_BOARD_SDA, PIN_BOARD_SCL); // 14pin header & internal + Wire1.begin(PIN_BOARD_SDA1, PIN_BOARD_SCL1); // grove + + rtc_clock.begin(Wire); // needs Wire already begun on the right pins to auto-detect an RTC chip + + init_lora_cap_ioe(); + + return radio.std_init(&spi); +} + +mesh::LocalIdentity radio_new_identity() { + RadioNoiseListener rng(radio); + return mesh::LocalIdentity(&rng); // create new random identity +} diff --git a/variants/m5stack_cardputer_adv/target.h b/variants/m5stack_cardputer_adv/target.h new file mode 100644 index 00000000..e468953e --- /dev/null +++ b/variants/m5stack_cardputer_adv/target.h @@ -0,0 +1,28 @@ +#pragma once + +#define RADIOLIB_STATIC_ONLY 1 +#include +#include +#include +#include +#include +#include +#ifdef DISPLAY_CLASS + #include + #include +#endif +#include "helpers/sensors/EnvironmentSensorManager.h" +#include "helpers/sensors/MicroNMEALocationProvider.h" + +extern CardputerADVBoard 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();