diff --git a/src/input/CardputerAdvKeyboard.cpp b/src/input/CardputerAdvKeyboard.cpp new file mode 100644 index 000000000..b15ace391 --- /dev/null +++ b/src/input/CardputerAdvKeyboard.cpp @@ -0,0 +1,208 @@ +#if defined(M5STACK_CARDPUTER_ADV) + +#include "CardputerAdvKeyboard.h" +#include "main.h" + +#define _TCA8418_COLS 8 +#define _TCA8418_ROWS 7 +#define _TCA8418_NUM_KEYS 56 + +#define _TCA8418_MULTI_TAP_THRESHOLD 1500 + +using Key = TCA8418KeyboardBase::TCA8418Key; + +constexpr uint8_t modifierShiftKey = 7 - 1; // keynum -1 +constexpr uint8_t modifierRightShift = 0b0001; + +constexpr uint8_t modifierFnKey = 3 - 1; +constexpr uint8_t modifierFn = 0b0010; + +constexpr uint8_t modifierCtrlKey = 4 - 1; + +constexpr uint8_t modifierOptKey = 8 - 1; + +constexpr uint8_t modifierAltKey = 12 - 1; + +// Num chars per key, Modulus for rotating through characters +// https://m5stack-doc.oss-cn-shenzhen.aliyuncs.com/1178/Sch_M5CardputerAdv_v1.0_2025_06_20_17_19_58_page_02.png +static uint8_t CardputerAdvTapMod[_TCA8418_NUM_KEYS] = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3}; + +static unsigned char CardputerAdvTapMap[_TCA8418_NUM_KEYS][3] = {{'`', '~', Key::ESC}, + {Key::TAB, 0x00, 0x00}, + {0x00, 0x00, 0x00}, // Fn + {0x00, 0x00, 0x00}, // ctrl + {'1', '!', 0x00}, + {'q', 'Q', Key::REBOOT}, + {0x00, 0x00, 0x00}, // shift + {0x00, 0x00, 0x00}, // opt + {'2', '@', 0x00}, + {'w', 'W', 0x00}, + {'a', 'A', 0x00}, + {0x00, 0x00, 0x00}, // alt + {'3', '#', 0x00}, + {'e', 'E', 0x00}, + {'s', 'S', 0x00}, + {'z', 'Z', 0x00}, + {'4', '$', 0x00}, + {'r', 'R', 0x00}, + {'d', 'D', 0x00}, + {'x', 'X', 0x00}, + {'5', '%', 0x00}, + {'t', 'T', 0x00}, + {'f', 'F', 0x00}, + {'c', 'C', 0x00}, + {'6', '^', 0x00}, + {'y', 'Y', 0x00}, + {'g', 'G', Key::GPS_TOGGLE}, + {'v', 'V', 0x00}, + {'7', '&', 0x00}, + {'u', 'U', 0x00}, + {'h', 'H', 0x00}, + {'b', 'B', Key::BT_TOGGLE}, + {'8', '*', 0x00}, + {'i', 'I', 0x00}, + {'j', 'J', 0x00}, + {'n', 'N', 0x00}, + {'9', '(', 0x00}, + {'o', 'O', 0x00}, + {'k', 'K', 0x00}, + {'m', 'M', Key::MUTE_TOGGLE}, + {'0', ')', 0x00}, + {'p', 'P', Key::SEND_PING}, + {'l', 'L', 0x00}, + {',', '<', Key::LEFT}, + {'_', '-', 0x00}, + {'[', '{', 0x00}, + {';', ':', Key::UP}, + {'.', '>', Key::DOWN}, + {'=', '+', 0x00}, + {']', '}', 0x00}, + {'\'', '"', 0x00}, + {'/', '?', Key::RIGHT}, + {Key::BSP, 0x00, 0x00}, + {'\\', '|', 0x00}, + {Key::SELECT, 0x00, 0x00}, // Enter + {' ', ' ', ' '}}; // Space + +CardputerAdvKeyboard::CardputerAdvKeyboard() + : TCA8418KeyboardBase(_TCA8418_ROWS, _TCA8418_COLS), modifierFlag(0), last_modifier_time(0), last_key(-1), next_key(-1), + last_tap(0L), char_idx(0), tap_interval(0) +{ + reset(); +} + +void CardputerAdvKeyboard::reset(void) +{ + TCA8418KeyboardBase::reset(); +} + +// handle multi-key presses (shift and alt) +void CardputerAdvKeyboard::trigger() +{ + uint8_t count = keyCount(); + if (count == 0) + return; + for (uint8_t i = 0; i < count; ++i) { + uint8_t k = readRegister(TCA8418_REG_KEY_EVENT_A + i); + uint8_t key = k & 0x7F; + if (k & 0x80) { + pressed(key); + } else { + released(); + state = Idle; + } + } +} + +void CardputerAdvKeyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + + if (modifierFlag && (millis() - last_modifier_time > _TCA8418_MULTI_TAP_THRESHOLD)) { + modifierFlag = 0; + } + + uint8_t next_key = 0; + int row = (key - 1) / 10; + int col = (key - 1) % 10; + + if (row >= _TCA8418_ROWS || col >= _TCA8418_COLS) { + return; // Invalid key + } + + next_key = row * _TCA8418_COLS + col; + state = Held; + + uint32_t now = millis(); + tap_interval = now - last_tap; + + updateModifierFlag(next_key); + if (isModifierKey(next_key)) { + last_modifier_time = now; + } + + if (tap_interval < 0) { + last_tap = 0; + state = Busy; + return; + } + + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; + } else { + char_idx += 1; + } + + last_key = next_key; + last_tap = now; +} + +void CardputerAdvKeyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key >= _TCA8418_NUM_KEYS) { + last_key = -1; + state = Idle; + return; + } + + uint32_t now = millis(); + last_tap = now; + + if (CardputerAdvTapMap[last_key][modifierFlag % CardputerAdvTapMod[last_key]] == Key::BL_TOGGLE) { + return; + } + + queueEvent(CardputerAdvTapMap[last_key][modifierFlag % CardputerAdvTapMod[last_key]]); + if (isModifierKey(last_key) == false) + modifierFlag = 0; +} + +void CardputerAdvKeyboard::updateModifierFlag(uint8_t key) +{ + if (key == modifierShiftKey) { + modifierFlag ^= modifierRightShift; + } else if (key == modifierFnKey) { + modifierFlag ^= modifierFn; + } else if (key == modifierCtrlKey) { + // modifierFlag ^= modifierCtrl; + } else if (key == modifierOptKey) { + // modifierFlag ^= modifierOpt; + } else if (key == modifierAltKey) { + // modifierFlag ^= modifierAlt; + } +} + +bool CardputerAdvKeyboard::isModifierKey(uint8_t key) +{ + return (key == modifierShiftKey || key == modifierFnKey); +} + +#endif \ No newline at end of file diff --git a/src/input/CardputerAdvKeyboard.h b/src/input/CardputerAdvKeyboard.h new file mode 100644 index 000000000..79992edcf --- /dev/null +++ b/src/input/CardputerAdvKeyboard.h @@ -0,0 +1,26 @@ +#include "TCA8418KeyboardBase.h" + +class CardputerAdvKeyboard : public TCA8418KeyboardBase +{ + public: + CardputerAdvKeyboard(); + void reset(void); + void trigger(void) override; + virtual ~CardputerAdvKeyboard() {} + + protected: + void pressed(uint8_t key) override; + void released(void) override; + + void updateModifierFlag(uint8_t key); + bool isModifierKey(uint8_t key); + + private: + uint8_t modifierFlag; // Flag to indicate if a modifier key is pressed + uint32_t last_modifier_time; // Timestamp of the last modifier key press + int8_t last_key; + int8_t next_key; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; +}; diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 0ed2df116..8718a7394 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -7,6 +7,8 @@ #include "TDeckProKeyboard.h" #elif defined(T_LORA_PAGER) #include "TLoraPagerKeyboard.h" +#elif defined(M5STACK_CARDPUTER_ADV) +#include "CardputerAdvKeyboard.h" #else #include "TCA8418Keyboard.h" #endif @@ -20,6 +22,8 @@ KbI2cBase::KbI2cBase(const char *name) TCAKeyboard(*(new TDeckProKeyboard())) #elif defined(T_LORA_PAGER) TCAKeyboard(*(new TLoraPagerKeyboard())) +#elif defined(M5STACK_CARDPUTER_ADV) + TCAKeyboard(*(new CardputerAdvKeyboard())) #else TCAKeyboard(*(new TCA8418Keyboard())) #endif diff --git a/src/platform/esp32/architecture.h b/src/platform/esp32/architecture.h index 53b23124d..7b52f6cc1 100644 --- a/src/platform/esp32/architecture.h +++ b/src/platform/esp32/architecture.h @@ -201,6 +201,8 @@ #define HW_VENDOR meshtastic_HardwareModel_HELTEC_V4 #elif defined(M5STACK_UNITC6L) #define HW_VENDOR meshtastic_HardwareModel_M5STACK_C6L +#elif defined(M5STACK_CARDPUTER_ADV) +#define HW_VENDOR meshtastic_HardwareModel_M5STACK_CARDPUTER_ADV #elif defined(HELTEC_WIRELESS_TRACKER_V2) #define HW_VENDOR meshtastic_HardwareModel_HELTEC_WIRELESS_TRACKER_V2 #endif diff --git a/variants/esp32s3/m5stack_cardputer_adv/pins_arduino.h b/variants/esp32s3/m5stack_cardputer_adv/pins_arduino.h new file mode 100644 index 000000000..30f173496 --- /dev/null +++ b/variants/esp32s3/m5stack_cardputer_adv/pins_arduino.h @@ -0,0 +1,33 @@ +#ifndef Pins_Arduino_h +#define Pins_Arduino_h + +#include "soc/soc_caps.h" +#include + +#define USB_VID 0x303a +#define USB_PID 0x1001 + +// Some boards have too low voltage on this pin (board design bug) +// Use different pin with 3V and connect with 48 +// and change this setup for the chosen pin (for example 38) +// static const uint8_t LED_BUILTIN = SOC_GPIO_PIN_COUNT + 48; +// #define BUILTIN_LED LED_BUILTIN // backward compatibility +// #define LED_BUILTIN LED_BUILTIN +// #define RGB_BUILTIN LED_BUILTIN +// #define RGB_BRIGHTNESS 64 + +static const uint8_t TXD2 = 13; +static const uint8_t RXD2 = 15; + +static const uint8_t SDA = 8; +static const uint8_t SCL = 9; + +// external & microSD +static const uint8_t SS = 5; // external SS +static const uint8_t MOSI = 14; +static const uint8_t MISO = 39; +static const uint8_t SCK = 40; + +static const uint8_t ADC = 10; + +#endif /* Pins_Arduino_h */ diff --git a/variants/esp32s3/m5stack_cardputer_adv/platformio.ini b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini new file mode 100644 index 000000000..186d7dedd --- /dev/null +++ b/variants/esp32s3/m5stack_cardputer_adv/platformio.ini @@ -0,0 +1,23 @@ +; M5stack Cardputer ADV +[env:m5stack-cardputer-adv] +extends = esp32s3_base +board = m5stack-stamps3 +board_check = true +board_build.partitions = default_8MB.csv +upload_protocol = esptool +build_flags = + ${esp32_base.build_flags} + ;-D PRIVATE_HW + -D M5STACK_CARDPUTER_ADV + -D ARDUINO_USB_CDC_ON_BOOT=1 + -D GPS_POWER_TOGGLE + ;-D HAS_SDCARD + ;-D SDCARD_USE_SPI1 + -I variants/esp32s3/m5stack_cardputer_adv +lib_deps = ${esp32_base.lib_deps} + adafruit/Adafruit NeoPixel @ ^1.12.0 + https://github.com/meshtastic/st7789/archive/bd33ea58ddfe4a5e4a66d53300ccbd38d66ac21f.zip + ;lovyan03/LovyanGFX@1.2.7 + earlephilhower/ESP8266Audio@1.9.9 + earlephilhower/ESP8266SAM@1.0.1 + https://github.com/pschatzmann/arduino-audio-driver/archive/refs/tags/v0.1.3.zip \ No newline at end of file diff --git a/variants/esp32s3/m5stack_cardputer_adv/variant.h b/variants/esp32s3/m5stack_cardputer_adv/variant.h new file mode 100644 index 000000000..45e7b8623 --- /dev/null +++ b/variants/esp32s3/m5stack_cardputer_adv/variant.h @@ -0,0 +1,126 @@ +// I2C +#define I2C_SDA SDA +#define I2C_SCL SCL + +// Custom port I2C1 or UART +#define G1 1 +#define G2 2 + +// Neopixel LED, PWR_EN Pin same as TFT Backlight GPIO38 +#define HAS_NEOPIXEL // Enable the use of neopixels +#define NEOPIXEL_COUNT 1 // How many neopixels are connected +#define NEOPIXEL_DATA 21 // gpio pin used to send data to the neopixels +#define NEOPIXEL_TYPE (NEO_GRB + NEO_KHZ800) // type of neopixels in use + +// Button +#define BUTTON_PIN 0 + +// Battery +#define BATTERY_PIN 10 +#define ADC_CHANNEL ADC1_GPIO10_CHANNEL + +// IR LED +#define IR_LED 44 // Not used, information only + +// TCA8418 keyboard +#define TCA8418_INT 11 +// #define KB_BL_PIN -1 // No keyboard backlight +#define I2C_NO_RESCAN +#define KB_INT TCA8418_INT +#define CANNED_MESSAGE_MODULE_ENABLE 1 + +// GPS +#define HAS_GPS 1 +#define GPS_RX_PIN RXD2 +#define GPS_TX_PIN TXD2 +#define GPS_BAUDRATE 115200 + +// BMI270 +#define USE_BMI270 // INFO | ??:??:?? 0 BMX160 found at address 0x69 + +// SD CARD +#define SPI_MOSI MOSI +#define SPI_SCK SCK +#define SPI_MISO MISO +#define SPI_CS 12 +#define SDCARD_CS SPI_CS +#define SD_SPI_FREQUENCY 75000000U + +// audio codec ES8311 +#define HAS_I2S +#define DAC_I2S_BCK 41 +#define DAC_I2S_WS 43 +#define DAC_I2S_DOUT 46 +#define DAC_I2S_DIN 42 +#define DAC_I2S_MCLK -1 //??? + +// lovyan +#if 0 +// ST7789 TFT LCD +#define ST7789_CS 37 +#define ST7789_RS 34 // DC +#define ST7789_SDA 35 // MOSI +#define ST7789_SCK 36 +#define ST7789_RESET 33 +#define ST7789_MISO -1 +#define ST7789_BUSY -1 +#define ST7789_BL 38 +#define ST7789_SPI_HOST SPI2_HOST +#define TFT_BL 38 +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 135 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define TFT_OFFSET_ROTATION 2 +#define SCREEN_ROTATE +#define SCREEN_TRANSITION_FRAMERATE 5 // fps +#endif + +// github.com/meshtastic/st7789 +#if 1 +// Display (TFT) +#define USE_ST7789 +#define ST7789_NSS 37 +#define ST7789_RS 34 // DC +#define ST7789_SDA 35 // MOSI +#define ST7789_SCK 36 +#define ST7789_RESET 33 +#define ST7789_MISO -1 +#define ST7789_BUSY -1 +#define VTFT_CTRL 38 +#define VTFT_LEDA 38 +#define TFT_BACKLIGHT_ON HIGH +#define ST7789_SPI_HOST SPI2_HOST +#define SPI_FREQUENCY 40000000 +#define SPI_READ_FREQUENCY 16000000 +#define TFT_HEIGHT 135 +#define TFT_WIDTH 240 +#define TFT_OFFSET_X 0 +#define TFT_OFFSET_Y 0 +#define BRIGHTNESS_DEFAULT 100 // Medium Low Brightnes +#endif + +// LoRa +#define USE_SX1262 // Currently only SX1262 CAP is available +#define USE_RF95 // Test + +#define LORA_SCK SCK +#define LORA_MISO MISO +#define LORA_MOSI MOSI +#define LORA_CS SS // NSS + +// SX127X/RFM95 +#define LORA_DIO0 4 // RF95 IRQ, SX1262 not connected +#define LORA_DIO1 6 // RF95 BUSY +#define LORA_RESET 3 + +// SX126X +#define SX126X_CS LORA_CS +#define SX126X_DIO1 4 // IRQ +#define SX126X_BUSY 6 // BUSY, LORA_DIO2 +#define SX126X_RESET LORA_RESET +#define SX126X_DIO2_AS_RF_SWITCH +#define TCXO_OPTIONAL +#define SX126X_DIO3_TCXO_VOLTAGE 1.8 // Check the correct value!