* attaky_mesh_series: add board variant with FT6636 touch and landscape UI * attaky_mesh_series: P3 battery (MAX17048) + GPS + shared-I2C mutex - AttakySharedI2C: one Wire.begin() + a mutex serializing the shared board I2C bus (SDA=2/SCL=1) across the FT6636 touch poll (core 0), the MAX17048 fuel gauge (core 1), and the future AW9523 keyboard. Arduino's TwoWire buffers are not concurrency-safe. - board.begin() owns the authoritative bus init; the touch driver now takes the lock around every transaction instead of its own Wire.begin(). - getBattMilliVolts(): real MAX17048 VCELL read (78.125 uV/LSB -> mV = raw*5/64), 2 s cache + lock, replacing the 4000 mV stub. Upstream maps mV -> % itself. Hardware-verified ~4058 mV. - GPS: rename the dead GPS_BAUD flag to GPS_BAUD_RATE (the core reads that; 9600 only worked via the #else default). Pins verified on hardware: ESP RX = IO17 receives ATGM336H NMEA; MeshCore's module-centric PIN_GPS_TX=17 maps correctly. * attaky_mesh_series: Control_Keyboard_(Bottom) dual-AW9523 keyboard Add the Control_Keyboard_(Bottom)_1.0 module as an ADDITIVE hardware keyboard on the touch board. Two AW9523 expanders (0x5A left / 0x5B right) each scan a 5x5 matrix over the shared board I2C bus, serialized through the AttakySharedI2C mutex alongside the FT6636 touch poll and the MAX17048 fuel gauge. The driver emits final bytes (printable ASCII, backspace, enter, space) with momentary Shift resolved to uppercase in-driver. UITask injects them into the focused textarea exactly like the web-mirror keystroke path, so CAP_KEYBOARD stays 0 and the on-screen keyboard is not suppressed (touch typing does not regress). Focus-gated: the matrices are only scanned while an editable field is bound, so a key has zero side effect when nothing is focused. Hardware-verified, both halves: q/w/e/r/t (0x5A) + y (0x5B), Shift uppercase, backspace, space, enter; on-screen keyboard intact. * attaky_mesh_series: use reliable 115200 upload speed * attaky_mesh_series: use core default GPS baud * attaky_mesh_series: restore GPS_BAUD setting * attaky_mesh_series: rename PlatformIO environment
159 lines
4.5 KiB
C++
159 lines
4.5 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
#include "AttakyMeshSeriesKeyboard.h"
|
|
|
|
#if defined(HAS_ATTAKY_MESH_KEYBOARD) && defined(ESP32)
|
|
|
|
#include <Arduino.h>
|
|
#include <Wire.h>
|
|
#include "AttakySharedI2C.h"
|
|
|
|
static const uint8_t AW_REG_IN_P0 = 0x00;
|
|
static const uint8_t AW_REG_OUT_P1 = 0x03;
|
|
static const uint8_t AW_REG_CFG_P0 = 0x04;
|
|
static const uint8_t AW_REG_CFG_P1 = 0x05;
|
|
static const uint8_t AW_REG_OUT_P0 = 0x02;
|
|
static const uint8_t AW_REG_MODE_P0 = 0x12;
|
|
static const uint8_t AW_REG_MODE_P1 = 0x13;
|
|
static const uint8_t AW_REG_SWRST = 0x7F;
|
|
|
|
static const uint8_t KB_ADDR[2] = { 0x5A, 0x5B };
|
|
|
|
static const uint8_t P1_IDLE = 0xE0;
|
|
static const uint8_t ROW_DRIVE[5] = { 0xFE, 0xFD, 0xFB, 0xF7, 0xEF };
|
|
|
|
#define K_NOP 0x00
|
|
#define K_SFT 0x01
|
|
#define K_BSP 0x08
|
|
#define K_TAB 0x09
|
|
#define K_ENT 0x0D
|
|
#define K_SPC 0x20
|
|
|
|
static const uint8_t KEYMAP_L[5][5] = {
|
|
{ '1', '2', '3', '4', '5' },
|
|
{ 'q', 'w', 'e', 'r', 't' },
|
|
{ 'a', 's', 'd', 'f', 'g' },
|
|
{ K_SFT, 'z', 'x', 'c', 'v' },
|
|
{ K_NOP, K_TAB, K_NOP, ',', K_SPC },
|
|
};
|
|
static const uint8_t KEYMAP_R[5][5] = {
|
|
{ '6', '7', '8', '9', '0' },
|
|
{ 'y', 'u', 'i', 'o', 'p' },
|
|
{ 'h', 'j', 'k', 'l', K_BSP },
|
|
{ 'b', 'n', 'm', K_NOP, K_ENT },
|
|
{ K_NOP, '.', K_NOP, '#', K_NOP },
|
|
};
|
|
static const uint8_t SHIFT_HALF = 0, SHIFT_ROW = 3, SHIFT_COL = 0;
|
|
|
|
static bool s_inited = false;
|
|
static bool s_present[2] = { false, false };
|
|
static uint8_t s_prev[2][5] = { { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF },
|
|
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } };
|
|
|
|
static volatile uint8_t s_ring[16];
|
|
static volatile uint8_t s_head = 0, s_tail = 0;
|
|
|
|
static void ringPush(uint8_t b) {
|
|
uint8_t nh = (uint8_t)((s_head + 1) & 15);
|
|
if (nh != s_tail) { s_ring[s_head] = b; s_head = nh; }
|
|
}
|
|
static void ringClear() { s_head = s_tail = 0; }
|
|
|
|
static bool awWrite(uint8_t addr, uint8_t reg, uint8_t val) {
|
|
Wire.beginTransmission(addr);
|
|
Wire.write(reg);
|
|
Wire.write(val);
|
|
return Wire.endTransmission() == 0;
|
|
}
|
|
static uint8_t awRead(uint8_t addr, uint8_t reg) {
|
|
Wire.beginTransmission(addr);
|
|
Wire.write(reg);
|
|
if (Wire.endTransmission(false) != 0) return 0xFF;
|
|
if (Wire.requestFrom((int)addr, 1) != 1) return 0xFF;
|
|
return (uint8_t)Wire.read();
|
|
}
|
|
|
|
static bool initHalf(uint8_t addr) {
|
|
bool ok = true;
|
|
ok &= awWrite(addr, AW_REG_SWRST, 0x00);
|
|
delay(2);
|
|
ok &= awWrite(addr, AW_REG_MODE_P0, 0xFF);
|
|
ok &= awWrite(addr, AW_REG_MODE_P1, 0xFF);
|
|
ok &= awWrite(addr, AW_REG_CFG_P0, 0xFF);
|
|
ok &= awWrite(addr, AW_REG_CFG_P1, 0x00);
|
|
ok &= awWrite(addr, AW_REG_OUT_P0, 0x00);
|
|
ok &= awWrite(addr, AW_REG_OUT_P1, P1_IDLE);
|
|
return ok;
|
|
}
|
|
|
|
static void ensureInit() {
|
|
if (s_inited) return;
|
|
s_inited = true;
|
|
for (int h = 0; h < 2; h++) {
|
|
if (!attakyI2cLock(30)) continue;
|
|
s_present[h] = initHalf(KB_ADDR[h]);
|
|
attakyI2cUnlock();
|
|
}
|
|
}
|
|
|
|
static void scanHalf(uint8_t addr, uint8_t cols[5]) {
|
|
for (uint8_t row = 0; row < 5; row++) {
|
|
awWrite(addr, AW_REG_OUT_P1, (uint8_t)(ROW_DRIVE[row] | 0xE0));
|
|
delayMicroseconds(50);
|
|
cols[row] = (uint8_t)(awRead(addr, AW_REG_IN_P0) & 0x1F);
|
|
}
|
|
awWrite(addr, AW_REG_OUT_P1, P1_IDLE);
|
|
}
|
|
|
|
void attakyKeyboardPoll(bool active) {
|
|
ensureInit();
|
|
|
|
if (!active) {
|
|
for (int h = 0; h < 2; h++)
|
|
for (int r = 0; r < 5; r++) s_prev[h][r] = 0xFF;
|
|
ringClear();
|
|
return;
|
|
}
|
|
|
|
uint8_t cur[2][5] = { { 0x1F,0x1F,0x1F,0x1F,0x1F }, { 0x1F,0x1F,0x1F,0x1F,0x1F } };
|
|
for (int h = 0; h < 2; h++) {
|
|
if (!s_present[h]) continue;
|
|
if (!attakyI2cLock(20)) continue;
|
|
scanHalf(KB_ADDR[h], cur[h]);
|
|
attakyI2cUnlock();
|
|
}
|
|
|
|
const bool shift_down =
|
|
s_present[SHIFT_HALF] && !(cur[SHIFT_HALF][SHIFT_ROW] & (1u << SHIFT_COL));
|
|
|
|
for (int h = 0; h < 2; h++) {
|
|
if (!s_present[h]) continue;
|
|
for (int row = 0; row < 5; row++) {
|
|
uint8_t now_bits = cur[h][row];
|
|
uint8_t prev_bits = s_prev[h][row];
|
|
for (int col = 0; col < 5; col++) {
|
|
const uint8_t mask = (uint8_t)(1u << col);
|
|
const bool down_now = !(now_bits & mask);
|
|
const bool down_prev = !(prev_bits & mask);
|
|
if (down_now && !down_prev) {
|
|
uint8_t k = (h == 0) ? KEYMAP_L[row][col] : KEYMAP_R[row][col];
|
|
if (k == K_NOP || k == K_SFT) continue;
|
|
if (shift_down && k >= 'a' && k <= 'z') k = (uint8_t)(k - 0x20);
|
|
ringPush(k);
|
|
}
|
|
}
|
|
s_prev[h][row] = now_bits;
|
|
}
|
|
}
|
|
}
|
|
|
|
int attakyKeyboardReadKey() {
|
|
if (s_tail == s_head) return 0;
|
|
uint8_t b = s_ring[s_tail];
|
|
s_tail = (uint8_t)((s_tail + 1) & 15);
|
|
return b;
|
|
}
|
|
|
|
bool attakyKeyboardPresent() { return s_present[0] || s_present[1]; }
|
|
|
|
#endif
|