mirror of
https://github.com/MarekZegare4/MeshCore-Solo.git
synced 2026-09-14 15:16:40 +00:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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<typename GxEPD2_Type, const uint16_t page_height>
|
||||
class GxEPD2_BW : public GxEPD2_GFX_BASE_CLASS
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 <Arduino.h>
|
||||
#include <Wire.h>
|
||||
|
||||
// ---- 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
|
||||
@@ -0,0 +1,240 @@
|
||||
#ifdef LILYGO_TECHO_LITE_KEYSHIELD
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <helpers/ui/UIScreen.h> // 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
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
+<helpers/*.cpp>
|
||||
+<TechoBoard.cpp>
|
||||
+<helpers/sensors/EnvironmentSensorManager.cpp>
|
||||
+<helpers/sensors>
|
||||
+<helpers/ui/GxEPDDisplay.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../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}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../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}
|
||||
+<helpers/nrf52/SerialBLEInterface.cpp>
|
||||
+<../examples/companion_radio/*.cpp>
|
||||
|
||||
@@ -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
|
||||
@@ -0,0 +1,31 @@
|
||||
#include <Arduino.h>
|
||||
#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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include <Wire.h>
|
||||
#include <Arduino.h>
|
||||
#include "helpers/ESP32Board.h"
|
||||
#include <driver/rtc_io.h>
|
||||
|
||||
#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";
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,166 @@
|
||||
#ifdef CARDPUTER_ADV
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Wire.h>
|
||||
#include <helpers/ui/UIScreen.h> // 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
|
||||
@@ -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>
|
||||
+<helpers/sensors/*.cpp>
|
||||
+<helpers/ui/ST7789Display.cpp>
|
||||
+<helpers/ui/OLEDDisplay.cpp>
|
||||
+<helpers/ui/OLEDDisplayFonts.cpp>
|
||||
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}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../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}
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../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}
|
||||
+<helpers/esp32/*.cpp>
|
||||
+<helpers/ui/MomentaryButton.cpp>
|
||||
+<../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}
|
||||
+<helpers/bridges/ESPNowBridge.cpp>
|
||||
+<../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/>
|
||||
@@ -0,0 +1,63 @@
|
||||
#include <Arduino.h>
|
||||
#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
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#define RADIOLIB_STATIC_ONLY 1
|
||||
#include <RadioLib.h>
|
||||
#include <helpers/radiolib/RadioLibWrappers.h>
|
||||
#include <helpers/radiolib/CustomSX1262Wrapper.h>
|
||||
#include <CardputerADVBoard.h>
|
||||
#include <helpers/AutoDiscoverRTCClock.h>
|
||||
#include <helpers/SensorManager.h>
|
||||
#ifdef DISPLAY_CLASS
|
||||
#include <helpers/ui/ST7789Display.h>
|
||||
#include <helpers/ui/MomentaryButton.h>
|
||||
#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();
|
||||
Reference in New Issue
Block a user