diff --git a/src/configuration.h b/src/configuration.h index fd4a5b196..d5aacdbd2 100644 --- a/src/configuration.h +++ b/src/configuration.h @@ -152,6 +152,7 @@ along with this program. If not, see . #define MLX90614_ADDR_DEF 0x5A #define CGRADSENS_ADDR 0x66 #define LTR390UV_ADDR 0x53 +#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 // same adress as TCA8418 // ----------------------------------------------------------------------------- // ACCELEROMETER diff --git a/src/detect/ScanI2C.cpp b/src/detect/ScanI2C.cpp index 4caa0f730..0ce6e1127 100644 --- a/src/detect/ScanI2C.cpp +++ b/src/detect/ScanI2C.cpp @@ -31,8 +31,8 @@ ScanI2C::FoundDevice ScanI2C::firstRTC() const ScanI2C::FoundDevice ScanI2C::firstKeyboard() const { - ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB}; - return firstOfOrNONE(5, types); + ScanI2C::DeviceType types[] = {CARDKB, TDECKKB, BBQ10KB, RAK14004, MPR121KB, TCA8418KB}; + return firstOfOrNONE(6, types); } ScanI2C::FoundDevice ScanI2C::firstAccelerometer() const diff --git a/src/detect/ScanI2C.h b/src/detect/ScanI2C.h index 5b6bbe629..ca9bf4d82 100644 --- a/src/detect/ScanI2C.h +++ b/src/detect/ScanI2C.h @@ -18,7 +18,7 @@ class ScanI2C TDECKKB, BBQ10KB, RAK14004, - PMU_AXP192_AXP2101, + PMU_AXP192_AXP2101, // has the same adress as the TCA8418KB BME_680, BME_280, BMP_280, @@ -69,6 +69,7 @@ class ScanI2C DFROBOT_RAIN, DPS310, LTR390UV, + TCA8418KB, } DeviceType; // typedef uint8_t DeviceAddress; diff --git a/src/detect/ScanI2CTwoWire.cpp b/src/detect/ScanI2CTwoWire.cpp index 8b779277d..e4aa702c8 100644 --- a/src/detect/ScanI2CTwoWire.cpp +++ b/src/detect/ScanI2CTwoWire.cpp @@ -10,11 +10,6 @@ #include "meshUtils.h" // vformat #endif -// AXP192 and AXP2101 have the same device address, we just need to identify it in Power.cpp -#ifndef XPOWERS_AXP192_AXP2101_ADDRESS -#define XPOWERS_AXP192_AXP2101_ADDRESS 0x34 -#endif - bool in_array(uint8_t *array, int size, uint8_t lookfor) { int i; @@ -218,9 +213,13 @@ void ScanI2CTwoWire::scanPort(I2CPort port, uint8_t *address, uint8_t asize) #ifdef HAS_NCP5623 SCAN_SIMPLE_CASE(NCP5623_ADDR, NCP5623, "NCP5623", (uint8_t)addr.address); #endif -#ifdef HAS_PMU +#if defined(HAS_AXP192) || defined(HAS_AXP2101) SCAN_SIMPLE_CASE(XPOWERS_AXP192_AXP2101_ADDRESS, PMU_AXP192_AXP2101, "AXP192/AXP2101", (uint8_t)addr.address) #endif +#if !defined(HAS_AXP192) && !defined(HAS_AXP2101) + SCAN_SIMPLE_CASE(XPOWERS_AXP192_AXP2101_ADDRESS, TCA8418KB, "TCA8418", (uint8_t)addr.address) +#endif + case BME_ADDR: case BME_ADDR_ALTERNATE: registerValue = getRegisterValue(ScanI2CTwoWire::RegisterLocation(addr, 0xD0), 1); // GET_ID diff --git a/src/input/TCA8418Keyboard.cpp b/src/input/TCA8418Keyboard.cpp new file mode 100644 index 000000000..21cd7b2d5 --- /dev/null +++ b/src/input/TCA8418Keyboard.cpp @@ -0,0 +1,561 @@ +// Based on the MPR121 Keyboard and Adafruit TCA8418 library + +#include "TCA8418Keyboard.h" +#include "configuration.h" + +#include + +// REGISTERS +// #define _TCA8418_REG_RESERVED 0x00 +#define _TCA8418_REG_CFG 0x01 // Configuration register +#define _TCA8418_REG_INT_STAT 0x02 // Interrupt status +#define _TCA8418_REG_KEY_LCK_EC 0x03 // Key lock and event counter +#define _TCA8418_REG_KEY_EVENT_A 0x04 // Key event register A +#define _TCA8418_REG_KEY_EVENT_B 0x05 // Key event register B +#define _TCA8418_REG_KEY_EVENT_C 0x06 // Key event register C +#define _TCA8418_REG_KEY_EVENT_D 0x07 // Key event register D +#define _TCA8418_REG_KEY_EVENT_E 0x08 // Key event register E +#define _TCA8418_REG_KEY_EVENT_F 0x09 // Key event register F +#define _TCA8418_REG_KEY_EVENT_G 0x0A // Key event register G +#define _TCA8418_REG_KEY_EVENT_H 0x0B // Key event register H +#define _TCA8418_REG_KEY_EVENT_I 0x0C // Key event register I +#define _TCA8418_REG_KEY_EVENT_J 0x0D // Key event register J +#define _TCA8418_REG_KP_LCK_TIMER 0x0E // Keypad lock1 to lock2 timer +#define _TCA8418_REG_UNLOCK_1 0x0F // Unlock register 1 +#define _TCA8418_REG_UNLOCK_2 0x10 // Unlock register 2 +#define _TCA8418_REG_GPIO_INT_STAT_1 0x11 // GPIO interrupt status 1 +#define _TCA8418_REG_GPIO_INT_STAT_2 0x12 // GPIO interrupt status 2 +#define _TCA8418_REG_GPIO_INT_STAT_3 0x13 // GPIO interrupt status 3 +#define _TCA8418_REG_GPIO_DAT_STAT_1 0x14 // GPIO data status 1 +#define _TCA8418_REG_GPIO_DAT_STAT_2 0x15 // GPIO data status 2 +#define _TCA8418_REG_GPIO_DAT_STAT_3 0x16 // GPIO data status 3 +#define _TCA8418_REG_GPIO_DAT_OUT_1 0x17 // GPIO data out 1 +#define _TCA8418_REG_GPIO_DAT_OUT_2 0x18 // GPIO data out 2 +#define _TCA8418_REG_GPIO_DAT_OUT_3 0x19 // GPIO data out 3 +#define _TCA8418_REG_GPIO_INT_EN_1 0x1A // GPIO interrupt enable 1 +#define _TCA8418_REG_GPIO_INT_EN_2 0x1B // GPIO interrupt enable 2 +#define _TCA8418_REG_GPIO_INT_EN_3 0x1C // GPIO interrupt enable 3 +#define _TCA8418_REG_KP_GPIO_1 0x1D // Keypad/GPIO select 1 +#define _TCA8418_REG_KP_GPIO_2 0x1E // Keypad/GPIO select 2 +#define _TCA8418_REG_KP_GPIO_3 0x1F // Keypad/GPIO select 3 +#define _TCA8418_REG_GPI_EM_1 0x20 // GPI event mode 1 +#define _TCA8418_REG_GPI_EM_2 0x21 // GPI event mode 2 +#define _TCA8418_REG_GPI_EM_3 0x22 // GPI event mode 3 +#define _TCA8418_REG_GPIO_DIR_1 0x23 // GPIO data direction 1 +#define _TCA8418_REG_GPIO_DIR_2 0x24 // GPIO data direction 2 +#define _TCA8418_REG_GPIO_DIR_3 0x25 // GPIO data direction 3 +#define _TCA8418_REG_GPIO_INT_LVL_1 0x26 // GPIO edge/level detect 1 +#define _TCA8418_REG_GPIO_INT_LVL_2 0x27 // GPIO edge/level detect 2 +#define _TCA8418_REG_GPIO_INT_LVL_3 0x28 // GPIO edge/level detect 3 +#define _TCA8418_REG_DEBOUNCE_DIS_1 0x29 // Debounce disable 1 +#define _TCA8418_REG_DEBOUNCE_DIS_2 0x2A // Debounce disable 2 +#define _TCA8418_REG_DEBOUNCE_DIS_3 0x2B // Debounce disable 3 +#define _TCA8418_REG_GPIO_PULL_1 0x2C // GPIO pull-up disable 1 +#define _TCA8418_REG_GPIO_PULL_2 0x2D // GPIO pull-up disable 2 +#define _TCA8418_REG_GPIO_PULL_3 0x2E // GPIO pull-up disable 3 +// #define _TCA8418_REG_RESERVED 0x2F + +// FIELDS CONFIG REGISTER 1 +#define _TCA8418_REG_CFG_AI 0x80 // Auto-increment for read/write +#define _TCA8418_REG_CFG_GPI_E_CGF 0x40 // Event mode config +#define _TCA8418_REG_CFG_OVR_FLOW_M 0x20 // Overflow mode enable +#define _TCA8418_REG_CFG_INT_CFG 0x10 // Interrupt config +#define _TCA8418_REG_CFG_OVR_FLOW_IEN 0x08 // Overflow interrupt enable +#define _TCA8418_REG_CFG_K_LCK_IEN 0x04 // Keypad lock interrupt enable +#define _TCA8418_REG_CFG_GPI_IEN 0x02 // GPI interrupt enable +#define _TCA8418_REG_CFG_KE_IEN 0x01 // Key events interrupt enable + +// FIELDS INT_STAT REGISTER 2 +#define _TCA8418_REG_STAT_CAD_INT 0x10 // Ctrl-alt-del seq status +#define _TCA8418_REG_STAT_OVR_FLOW_INT 0x08 // Overflow interrupt status +#define _TCA8418_REG_STAT_K_LCK_INT 0x04 // Key lock interrupt status +#define _TCA8418_REG_STAT_GPI_INT 0x02 // GPI interrupt status +#define _TCA8418_REG_STAT_K_INT 0x01 // Key events interrupt status + +// FIELDS KEY_LCK_EC REGISTER 3 +#define _TCA8418_REG_LCK_EC_K_LCK_EN 0x40 // Key lock enable +#define _TCA8418_REG_LCK_EC_LCK_2 0x20 // Keypad lock status 2 +#define _TCA8418_REG_LCK_EC_LCK_1 0x10 // Keypad lock status 1 +#define _TCA8418_REG_LCK_EC_KLEC_3 0x08 // Key event count bit 3 +#define _TCA8418_REG_LCK_EC_KLEC_2 0x04 // Key event count bit 2 +#define _TCA8418_REG_LCK_EC_KLEC_1 0x02 // Key event count bit 1 +#define _TCA8418_REG_LCK_EC_KLEC_0 0x01 // Key event count bit 0 + +// Pin IDs for matrix rows/columns +enum { + _TCA8418_ROW0, // Pin ID for row 0 + _TCA8418_ROW1, // Pin ID for row 1 + _TCA8418_ROW2, // Pin ID for row 2 + _TCA8418_ROW3, // Pin ID for row 3 + _TCA8418_ROW4, // Pin ID for row 4 + _TCA8418_ROW5, // Pin ID for row 5 + _TCA8418_ROW6, // Pin ID for row 6 + _TCA8418_ROW7, // Pin ID for row 7 + _TCA8418_COL0, // Pin ID for column 0 + _TCA8418_COL1, // Pin ID for column 1 + _TCA8418_COL2, // Pin ID for column 2 + _TCA8418_COL3, // Pin ID for column 3 + _TCA8418_COL4, // Pin ID for column 4 + _TCA8418_COL5, // Pin ID for column 5 + _TCA8418_COL6, // Pin ID for column 6 + _TCA8418_COL7, // Pin ID for column 7 + _TCA8418_COL8, // Pin ID for column 8 + _TCA8418_COL9 // Pin ID for column 9 +}; + +#define _TCA8418_COLS 3 +#define _TCA8418_ROWS 4 +#define _TCA8418_NUM_KEYS 12 + +uint8_t TCA8418TapMod[_TCA8418_NUM_KEYS] = {13, 7, 7, 7, 7, 7, + 9, 7, 9, 2, 2, 2}; // Num chars per key, Modulus for rotating through characters + +unsigned char TCA8418TapMap[_TCA8418_NUM_KEYS][13] = { + {'1', '.', ',', '?', '!', ':', ';', '-', '_', '\\', '/', '(', ')'}, // 1 + {'2', 'a', 'b', 'c', 'A', 'B', 'C'}, // 2 + {'3', 'd', 'e', 'f', 'D', 'E', 'F'}, // 3 + {'4', 'g', 'h', 'i', 'G', 'H', 'I'}, // 4 + {'5', 'j', 'k', 'l', 'J', 'K', 'L'}, // 5 + {'6', 'm', 'n', 'o', 'M', 'N', 'O'}, // 6 + {'7', 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S'}, // 7 + {'8', 't', 'u', 'v', 'T', 'U', 'V'}, // 8 + {'9', 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z'}, // 9 + {'*', '+'}, // * + {'0', ' '}, // 0 + {'#', '@'}, // # +}; + +unsigned char TCA8418LongPressMap[_TCA8418_NUM_KEYS] = { + _TCA8418_ESC, // 1 + _TCA8418_UP, // 2 + _TCA8418_NONE, // 3 + _TCA8418_LEFT, // 4 + _TCA8418_NONE, // 5 + _TCA8418_RIGHT, // 6 + _TCA8418_NONE, // 7 + _TCA8418_DOWN, // 8 + _TCA8418_NONE, // 9 + _TCA8418_BSP, // * + _TCA8418_NONE, // 0 + _TCA8418_NONE, // # +}; + +#define _TCA8418_LONG_PRESS_THRESHOLD 2000 +#define _TCA8418_MULTI_TAP_THRESHOLD 750 + +TCA8418Keyboard::TCA8418Keyboard() : m_wire(nullptr), m_addr(0), readCallback(nullptr), writeCallback(nullptr) +{ + state = Init; + last_key = -1; + next_key = -1; + should_backspace = false; + last_tap = 0L; + char_idx = 0; + tap_interval = 0; + backlight_on = true; + queue = ""; +} + +void TCA8418Keyboard::begin(uint8_t addr, TwoWire *wire) +{ + m_addr = addr; + m_wire = wire; + + m_wire->begin(); + + reset(); +} + +void TCA8418Keyboard::begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr) +{ + m_addr = addr; + m_wire = nullptr; + writeCallback = w; + readCallback = r; + reset(); +} + +void TCA8418Keyboard::reset() +{ + LOG_DEBUG("TCA8418 Reset"); + // GPIO + // set default all GIO pins to INPUT + writeRegister(_TCA8418_REG_GPIO_DIR_1, 0x00); + writeRegister(_TCA8418_REG_GPIO_DIR_2, 0x00); + // Set COL9 as GPIO output + writeRegister(_TCA8418_REG_GPIO_DIR_3, 0x02); + // Switch off keyboard backlight (COL9 = LOW) + writeRegister(_TCA8418_REG_GPIO_DAT_OUT_3, 0x00); + + // add all pins to key events + writeRegister(_TCA8418_REG_GPI_EM_1, 0xFF); + writeRegister(_TCA8418_REG_GPI_EM_2, 0xFF); + writeRegister(_TCA8418_REG_GPI_EM_3, 0xFF); + + // set all pins to FALLING interrupts + writeRegister(_TCA8418_REG_GPIO_INT_LVL_1, 0x00); + writeRegister(_TCA8418_REG_GPIO_INT_LVL_2, 0x00); + writeRegister(_TCA8418_REG_GPIO_INT_LVL_3, 0x00); + + // add all pins to interrupts + writeRegister(_TCA8418_REG_GPIO_INT_EN_1, 0xFF); + writeRegister(_TCA8418_REG_GPIO_INT_EN_2, 0xFF); + writeRegister(_TCA8418_REG_GPIO_INT_EN_3, 0xFF); + + // Set keyboard matrix size + matrix(_TCA8418_ROWS, _TCA8418_COLS); + enableDebounce(); + flush(); + state = Idle; +} + +bool TCA8418Keyboard::matrix(uint8_t rows, uint8_t columns) +{ + if ((rows > 8) || (columns > 10)) + return false; + + // Skip zero size matrix + if ((rows != 0) && (columns != 0)) { + // Setup the keypad matrix. + uint8_t mask = 0x00; + for (int r = 0; r < rows; r++) { + mask <<= 1; + mask |= 1; + } + writeRegister(_TCA8418_REG_KP_GPIO_1, mask); + + mask = 0x00; + for (int c = 0; c < columns && c < 8; c++) { + mask <<= 1; + mask |= 1; + } + writeRegister(_TCA8418_REG_KP_GPIO_2, mask); + + if (columns > 8) { + if (columns == 9) + mask = 0x01; + else + mask = 0x03; + writeRegister(_TCA8418_REG_KP_GPIO_3, mask); + } + } + + return true; +} + +uint8_t TCA8418Keyboard::keyCount() const +{ + uint8_t eventCount = readRegister(_TCA8418_REG_KEY_LCK_EC); + eventCount &= 0x0F; // lower 4 bits only + return eventCount; +} + +bool TCA8418Keyboard::hasEvent() +{ + return queue.length() > 0; +} + +void TCA8418Keyboard::queueEvent(char next) +{ + if (next == _TCA8418_NONE) { + return; + } + queue.concat(next); +} + +char TCA8418Keyboard::dequeueEvent() +{ + if (queue.length() < 1) { + return _TCA8418_NONE; + } + char next = queue.charAt(0); + queue.remove(0, 1); + return next; +} + +void TCA8418Keyboard::trigger() +{ + if (keyCount() == 0) { + return; + } + if (state != Init) { + // Read the key register + uint8_t k = readRegister(_TCA8418_REG_KEY_EVENT_A); + uint8_t key = k & 0x7F; + if (k & 0x80) { + if (state == Idle) + pressed(key); + return; + } else { + if (state == Held) { + released(); + } + state = Idle; + return; + } + } else { + reset(); + } +} + +void TCA8418Keyboard::pressed(uint8_t key) +{ + if (state == Init || state == Busy) { + return; + } + 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 + } + + // Compute key index based on dynamic row/column + next_key = row * _TCA8418_COLS + col; + + // LOG_DEBUG("TCA8418: Key %u -> Next Key %u", key, next_key); + + state = Held; + uint32_t now = millis(); + tap_interval = now - last_tap; + if (tap_interval < 0) { + // Long running, millis has overflowed. + last_tap = 0; + state = Busy; + return; + } + + // Check if the key is the same as the last one or if the time interval has passed + if (next_key != last_key || tap_interval > _TCA8418_MULTI_TAP_THRESHOLD) { + char_idx = 0; // Reset char index if new key or long press + should_backspace = false; // dont backspace on new key + } else { + char_idx += 1; // Cycle through characters if same key pressed + should_backspace = true; // allow backspace on same key + } + + // Store the current key as the last key + last_key = next_key; + last_tap = now; +} + +void TCA8418Keyboard::released() +{ + if (state != Held) { + return; + } + + if (last_key < 0 || last_key > _TCA8418_NUM_KEYS) { // reset to idle if last_key out of bounds + last_key = -1; + state = Idle; + return; + } + uint32_t now = millis(); + int32_t held_interval = now - last_tap; + last_tap = now; + if (tap_interval < _TCA8418_MULTI_TAP_THRESHOLD && should_backspace) { + queueEvent(_TCA8418_BSP); + } + if (held_interval > _TCA8418_LONG_PRESS_THRESHOLD) { + queueEvent(TCA8418LongPressMap[last_key]); + // LOG_DEBUG("Long Press Key: %i Map: %i", last_key, TCA8418LongPressMap[last_key]); + } else { + queueEvent(TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]); + // LOG_DEBUG("Key Press: %i Index:%i if %i Map: %c", last_key, char_idx, TCA8418TapMod[last_key], + // TCA8418TapMap[last_key][(char_idx % TCA8418TapMod[last_key])]); + } +} + +uint8_t TCA8418Keyboard::flush() +{ + // Flush key events + uint8_t count = 0; + while (readRegister(_TCA8418_REG_KEY_EVENT_A) != 0) + count++; + // Flush gpio events + readRegister(_TCA8418_REG_GPIO_INT_STAT_1); + readRegister(_TCA8418_REG_GPIO_INT_STAT_2); + readRegister(_TCA8418_REG_GPIO_INT_STAT_3); + // Clear INT_STAT register + writeRegister(_TCA8418_REG_INT_STAT, 3); + return count; +} + +uint8_t TCA8418Keyboard::digitalRead(uint8_t pinnum) const +{ + if (pinnum > _TCA8418_COL9) + return 0xFF; + + uint8_t reg = _TCA8418_REG_GPIO_DAT_STAT_1 + pinnum / 8; + uint8_t mask = (1 << (pinnum % 8)); + + // Level 0 = low other = high + uint8_t value = readRegister(reg); + if (value & mask) + return HIGH; + return LOW; +} + +bool TCA8418Keyboard::digitalWrite(uint8_t pinnum, uint8_t level) +{ + if (pinnum > _TCA8418_COL9) + return false; + + uint8_t reg = _TCA8418_REG_GPIO_DAT_OUT_1 + pinnum / 8; + uint8_t mask = (1 << (pinnum % 8)); + + // Level 0 = low other = high + uint8_t value = readRegister(reg); + if (level == LOW) + value &= ~mask; + else + value |= mask; + writeRegister(reg, value); + return true; +} + +bool TCA8418Keyboard::pinMode(uint8_t pinnum, uint8_t mode) +{ + if (pinnum > _TCA8418_COL9) + return false; + + uint8_t idx = pinnum / 8; + uint8_t reg = _TCA8418_REG_GPIO_DIR_1 + idx; + uint8_t mask = (1 << (pinnum % 8)); + + // Mode 0 = input 1 = output + uint8_t value = readRegister(reg); + if (mode == OUTPUT) + value |= mask; + else + value &= ~mask; + writeRegister(reg, value); + + // Pullup 0 = enabled 1 = disabled + reg = _TCA8418_REG_GPIO_PULL_1 + idx; + value = readRegister(reg); + if (mode == INPUT_PULLUP) + value &= ~mask; + else + value |= mask; + writeRegister(reg, value); + + return true; +} + +bool TCA8418Keyboard::pinIRQMode(uint8_t pinnum, uint8_t mode) +{ + if (pinnum > _TCA8418_COL9) + return false; + if ((mode != RISING) && (mode != FALLING)) + return false; + + // Mode 0 = falling 1 = rising + uint8_t idx = pinnum / 8; + uint8_t reg = _TCA8418_REG_GPIO_INT_LVL_1 + idx; + uint8_t mask = (1 << (pinnum % 8)); + + uint8_t value = readRegister(reg); + if (mode == RISING) + value |= mask; + else + value &= ~mask; + writeRegister(reg, value); + + // Enable interrupt + reg = _TCA8418_REG_GPIO_INT_EN_1 + idx; + value = readRegister(reg); + value |= mask; + writeRegister(reg, value); + + return true; +} + +void TCA8418Keyboard::enableInterrupts() +{ + uint8_t value = readRegister(_TCA8418_REG_CFG); + value |= (_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN); + writeRegister(_TCA8418_REG_CFG, value); +}; + +void TCA8418Keyboard::disableInterrupts() +{ + uint8_t value = readRegister(_TCA8418_REG_CFG); + value &= ~(_TCA8418_REG_CFG_GPI_IEN | _TCA8418_REG_CFG_KE_IEN); + writeRegister(_TCA8418_REG_CFG, value); +}; + +void TCA8418Keyboard::enableMatrixOverflow() +{ + uint8_t value = readRegister(_TCA8418_REG_CFG); + value |= _TCA8418_REG_CFG_OVR_FLOW_M; + writeRegister(_TCA8418_REG_CFG, value); +}; + +void TCA8418Keyboard::disableMatrixOverflow() +{ + uint8_t value = readRegister(_TCA8418_REG_CFG); + value &= ~_TCA8418_REG_CFG_OVR_FLOW_M; + writeRegister(_TCA8418_REG_CFG, value); +}; + +void TCA8418Keyboard::enableDebounce() +{ + writeRegister(_TCA8418_REG_DEBOUNCE_DIS_1, 0x00); + writeRegister(_TCA8418_REG_DEBOUNCE_DIS_2, 0x00); + writeRegister(_TCA8418_REG_DEBOUNCE_DIS_3, 0x00); +} + +void TCA8418Keyboard::disableDebounce() +{ + writeRegister(_TCA8418_REG_DEBOUNCE_DIS_1, 0xFF); + writeRegister(_TCA8418_REG_DEBOUNCE_DIS_2, 0xFF); + writeRegister(_TCA8418_REG_DEBOUNCE_DIS_3, 0xFF); +} + +void TCA8418Keyboard::setBacklight(bool on) +{ + if (on) { + digitalWrite(_TCA8418_COL9, HIGH); + } else { + digitalWrite(_TCA8418_COL9, LOW); + } +} + +uint8_t TCA8418Keyboard::readRegister(uint8_t reg) const +{ + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(reg); + m_wire->endTransmission(); + + m_wire->requestFrom(m_addr, (uint8_t)1); + if (m_wire->available() < 1) + return 0; + + return m_wire->read(); + } + if (readCallback) { + uint8_t data; + readCallback(m_addr, reg, &data, 1); + return data; + } + return 0; +} + +void TCA8418Keyboard::writeRegister(uint8_t reg, uint8_t value) +{ + uint8_t data[2]; + data[0] = reg; + data[1] = value; + + if (m_wire) { + m_wire->beginTransmission(m_addr); + m_wire->write(data, sizeof(uint8_t) * 2); + m_wire->endTransmission(); + } + if (writeCallback) { + writeCallback(m_addr, data[0], &(data[1]), 1); + } +} \ No newline at end of file diff --git a/src/input/TCA8418Keyboard.h b/src/input/TCA8418Keyboard.h new file mode 100644 index 000000000..c7f3c1f28 --- /dev/null +++ b/src/input/TCA8418Keyboard.h @@ -0,0 +1,83 @@ +// Based on the MPR121 Keyboard and Adafruit TCA8418 library +#include "configuration.h" +#include + +#define _TCA8418_NONE 0x00 +#define _TCA8418_REBOOT 0x90 +#define _TCA8418_LEFT 0xb4 +#define _TCA8418_UP 0xb5 +#define _TCA8418_DOWN 0xb6 +#define _TCA8418_RIGHT 0xb7 +#define _TCA8418_ESC 0x1b +#define _TCA8418_BSP 0x08 +#define _TCA8418_SELECT 0x0d + +class TCA8418Keyboard +{ + public: + typedef uint8_t (*i2c_com_fptr_t)(uint8_t dev_addr, uint8_t reg_addr, uint8_t *data, uint8_t len); + + enum KeyState { Init = 0, Idle, Held, Busy }; + + KeyState state; + int8_t last_key; + int8_t next_key; + bool should_backspace; + uint32_t last_tap; + uint8_t char_idx; + int32_t tap_interval; + bool backlight_on; + + String queue; + + TCA8418Keyboard(); + + void begin(uint8_t addr = XPOWERS_AXP192_AXP2101_ADDRESS, TwoWire *wire = &Wire); + void begin(i2c_com_fptr_t r, i2c_com_fptr_t w, uint8_t addr = XPOWERS_AXP192_AXP2101_ADDRESS); + + void reset(void); + // Configure the size of the keypad. + // All other rows and columns are set as inputs. + bool matrix(uint8_t rows, uint8_t columns); + + // Flush all events in the FIFO buffer + GPIO events. + uint8_t flush(void); + + // Key events available in the internal FIFO buffer. + uint8_t keyCount(void) const; + + void trigger(void); + void pressed(uint8_t key); + void released(void); + bool hasEvent(void); + char dequeueEvent(void); + void queueEvent(char); + + uint8_t digitalRead(uint8_t pinnum) const; + bool digitalWrite(uint8_t pinnum, uint8_t level); + bool pinMode(uint8_t pinnum, uint8_t mode); + bool pinIRQMode(uint8_t pinnum, uint8_t mode); // MODE FALLING or RISING + + // enable / disable interrupts for matrix and GPI pins + void enableInterrupts(); + void disableInterrupts(); + + // ignore key events when FIFO buffer is full or not. + void enableMatrixOverflow(); + void disableMatrixOverflow(); + + // debounce keys. + void enableDebounce(); + void disableDebounce(); + + void setBacklight(bool on); + + uint8_t readRegister(uint8_t reg) const; + void writeRegister(uint8_t reg, uint8_t value); + + private: + TwoWire *m_wire; + uint8_t m_addr; + i2c_com_fptr_t readCallback; + i2c_com_fptr_t writeCallback; +}; diff --git a/src/input/cardKbI2cImpl.cpp b/src/input/cardKbI2cImpl.cpp index eb9b07d6e..21ecf381a 100644 --- a/src/input/cardKbI2cImpl.cpp +++ b/src/input/cardKbI2cImpl.cpp @@ -12,8 +12,8 @@ void CardKbI2cImpl::init() #if !MESHTASTIC_EXCLUDE_I2C && !defined(ARCH_PORTDUINO) && !defined(I2C_NO_RESCAN) if (cardkb_found.address == 0x00) { LOG_DEBUG("Rescan for I2C keyboard"); - uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR}; - uint8_t i2caddr_asize = 4; + uint8_t i2caddr_scan[] = {CARDKB_ADDR, TDECK_KB_ADDR, BBQ10_KB_ADDR, MPR121_KB_ADDR, XPOWERS_AXP192_AXP2101_ADDRESS}; + uint8_t i2caddr_asize = 5; auto i2cScanner = std::unique_ptr(new ScanI2CTwoWire()); #if WIRE_INTERFACES_COUNT == 2 @@ -43,6 +43,10 @@ void CardKbI2cImpl::init() // assign an arbitrary value to distinguish from other models kb_model = 0x37; break; + case ScanI2C::DeviceType::TCA8418KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x84; + break; default: // use this as default since it's also just zero LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type); diff --git a/src/input/kbI2cBase.cpp b/src/input/kbI2cBase.cpp index 9b1a27745..b0775a1bb 100644 --- a/src/input/kbI2cBase.cpp +++ b/src/input/kbI2cBase.cpp @@ -43,6 +43,9 @@ int32_t KbI2cBase::runOnce() if (cardkb_found.address == MPR121_KB_ADDR) { MPRkeyboard.begin(MPR121_KB_ADDR, &Wire1); } + if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) { + TCAkeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire1); + } break; #endif case ScanI2C::WIRE: @@ -55,6 +58,9 @@ int32_t KbI2cBase::runOnce() if (cardkb_found.address == MPR121_KB_ADDR) { MPRkeyboard.begin(MPR121_KB_ADDR, &Wire); } + if (cardkb_found.address == XPOWERS_AXP192_AXP2101_ADDRESS) { + TCAKeyboard.begin(XPOWERS_AXP192_AXP2101_ADDRESS, &Wire); + } break; case ScanI2C::NO_I2C: default: @@ -226,6 +232,68 @@ int32_t KbI2cBase::runOnce() } break; } + case 0x84: { // Adafruit TCA8418 + TCAKeyboard.trigger(); + InputEvent e; + while (TCAKeyboard.hasEvent()) { + char nextEvent = TCAKeyboard.dequeueEvent(); + e.inputEvent = ANYKEY; + e.kbchar = 0x00; + e.source = this->_originName; + switch (nextEvent) { + case _TCA8418_NONE: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.kbchar = 0x00; + break; + case _TCA8418_REBOOT: + e.inputEvent = ANYKEY; + e.kbchar = INPUT_BROKER_MSG_REBOOT; + break; + case _TCA8418_LEFT: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_LEFT; + e.kbchar = 0x00; + break; + case _TCA8418_UP: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_UP; + e.kbchar = 0x00; + break; + case _TCA8418_DOWN: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_DOWN; + e.kbchar = 0x00; + break; + case _TCA8418_RIGHT: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_RIGHT; + e.kbchar = 0x00; + break; + case _TCA8418_BSP: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_BACK; + e.kbchar = 0x08; + break; + case _TCA8418_SELECT: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_SELECT; + e.kbchar = 0x0d; + break; + case _TCA8418_ESC: + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_CANCEL; + e.kbchar = 0x1b; + break; + default: + if (nextEvent > 127) { + e.inputEvent = meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE; + e.kbchar = 0x00; + break; + } + e.inputEvent = ANYKEY; + e.kbchar = nextEvent; + break; + } + if (e.inputEvent != meshtastic_ModuleConfig_CannedMessageConfig_InputEventChar_NONE) { + LOG_DEBUG("TCA8418 Notifying: %i Char: %c", e.inputEvent, e.kbchar); + this->notifyObservers(&e); + } + } + break; + } case 0x02: { // RAK14004 uint8_t rDataBuf[8] = {0}; diff --git a/src/input/kbI2cBase.h b/src/input/kbI2cBase.h index dc2414fc0..8193433fe 100644 --- a/src/input/kbI2cBase.h +++ b/src/input/kbI2cBase.h @@ -3,6 +3,7 @@ #include "BBQ10Keyboard.h" #include "InputBroker.h" #include "MPR121Keyboard.h" +#include "TCA8418Keyboard.h" #include "Wire.h" #include "concurrency/OSThread.h" @@ -21,5 +22,6 @@ class KbI2cBase : public Observable, public concurrency::OST BBQ10Keyboard Q10keyboard; MPR121Keyboard MPRkeyboard; + TCA8418Keyboard TCAKeyboard; bool is_sym = false; }; \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 59cd6d8e9..08260a26c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -587,6 +587,10 @@ void setup() // assign an arbitrary value to distinguish from other models kb_model = 0x37; break; + case ScanI2C::DeviceType::TCA8418KB: + // assign an arbitrary value to distinguish from other models + kb_model = 0x84; + break; default: // use this as default since it's also just zero LOG_WARN("kb_info.type is unknown(0x%02x), setting kb_model=0x00", kb_info.type);