mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-09-27 17:57:56 +00:00
Three battery fixes plus the record in M9_PORT.md: - The touchSleep battery saver was unreachable on the M9: the settings switch and its callback were compiled only for the T-Deck and V4-R8, and the pref defaults OFF with no other setter. Both #ifs now include HAS_THINKNODE_M9 (the M9 is in batteryIsCharging's #else branch, so the "USB powered" gate can never wrongly block there). - WadaNmeaLocationProvider parked GPS_RESET asserted while the GPS was stopped, per the core's convention. On the M9 that polarity is inverted through R46 into NPN Q16, so "asserted" sourced base current the entire time the GPS was OFF. Park the line LOW instead — the zero-current level for both circuit styles; every start path still pulses reset() explicitly, so the clean-reset guarantee holds. - A mid-session GPS enable ran the whole session on the stock 256 B Serial1 RX ring (the 4 KB TTFF ring is boot-gated on the persisted pref, and setRxBufferSize is a no-op on a running UART) — and with the saver newly reachable, one 50 ms park overflows that ring at the M9's 115200 baud. gpsEnsureBigRxRing() cycles the UART to 4 KB from both "gps on" funnels (toggleGPS, CMD_SET_CUSTOM_VAR) right after a successful start, so a refused enable never spends the DRAM. Also corrects both stale "keyboard S2 on the switched rail" comments (schematic truth: always-on 3V3 via R3 — it decides the deep-sleep drain budget). All 8 envs compile; on-device VERIFY list in M9_PORT.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MYDmpc53KpdmTLKzUNsX2s
120 lines
4.7 KiB
C++
120 lines
4.7 KiB
C++
// ThinkNode M9 keyboard driver — see M9Keyboard.h.
|
|
#if defined(HAS_M9_KEYBOARD) && defined(ESP32)
|
|
|
|
#include "M9Keyboard.h"
|
|
#include <Arduino.h>
|
|
#include <Wire.h>
|
|
|
|
#ifndef PIN_KB_ADDR
|
|
#define PIN_KB_ADDR 0x6c
|
|
#endif
|
|
|
|
// Controller register map — from Elecrow's keyboard-controller firmware
|
|
// (ThinkNode-M9-KB-platformio, src/main.cpp). The keyboard is a separate
|
|
// ESP32-S2 running a matrix scanner as an I2C SLAVE with addressed registers:
|
|
// reads MUST be preceded by a 1-byte register-address write, otherwise the
|
|
// slave answers with whatever register was addressed last (0x00 = HW version
|
|
// after its reset — a constant 0x03, which is exactly why the bare-read
|
|
// protocol produced "no keys do anything").
|
|
#define M9_KB_REG_HW_VER 0x00 // constant 0x03 on this controller FW
|
|
#define M9_KB_REG_KEY 0x01 // pending key byte, 0x00 = none (single slot)
|
|
#define M9_KB_REG_BACKLIGHT 0x02 // PWM duty 0..255 (controller lights on keypress)
|
|
#define M9_KB_REG_FW_VER 0xFE // constant 0x10 on this controller FW
|
|
|
|
static volatile uint8_t s_ring[16];
|
|
static volatile uint8_t s_head = 0;
|
|
static volatile uint8_t s_tail = 0;
|
|
static bool s_inited = false;
|
|
static TwoWire* s_bus = nullptr; // set once the controller answers
|
|
static uint32_t s_last_probe_ms = 0;
|
|
|
|
static bool kbReadReg(TwoWire& w, uint8_t reg, uint8_t* out) {
|
|
w.beginTransmission((uint8_t)PIN_KB_ADDR);
|
|
w.write(reg);
|
|
if (w.endTransmission() != 0) return false; // NACK / bus error
|
|
if (w.requestFrom((int)PIN_KB_ADDR, 1) != 1) return false;
|
|
*out = (uint8_t)w.read();
|
|
return true;
|
|
}
|
|
|
|
static bool kbProbe(TwoWire& w, const char* name) {
|
|
uint8_t hw = 0, fw = 0;
|
|
if (!kbReadReg(w, M9_KB_REG_HW_VER, &hw)) return false;
|
|
kbReadReg(w, M9_KB_REG_FW_VER, &fw); // best-effort, for the log only
|
|
Serial.printf("M9 keyboard: controller on %s (hw=0x%02X fw=0x%02X)\n", name, hw, fw);
|
|
return true;
|
|
}
|
|
|
|
// The schematic reading puts the keyboard on its own bus (host 20/21); the
|
|
// shared sensor bus (Wire, 7/6) is probed as a fallback so a wrong pin
|
|
// reading shows up in the boot log instead of as a dead keyboard.
|
|
static void kbTryFindBus() {
|
|
if (kbProbe(Wire1, "Wire1 (20/21)")) s_bus = &Wire1;
|
|
else if (kbProbe(Wire, "Wire (7/6)")) s_bus = &Wire;
|
|
}
|
|
|
|
void m9KeyboardBegin() {
|
|
Wire1.begin(PIN_KB_SDA, PIN_KB_SCL);
|
|
// The controller is its own MCU (ESP32-S2) on the ALWAYS-ON 3V3 rail via R3
|
|
// (schematic-verified, see the keyboard-bus note in platformio.ini — an
|
|
// earlier revision of this comment said "switched peripheral rail", which
|
|
// matters for the deep-sleep drain budget: the S2 keeps drawing through a
|
|
// deep sleep, and only the power slider actually cuts it). It may still be
|
|
// booting here — poll re-probes once a second until it answers.
|
|
kbTryFindBus();
|
|
if (!s_bus) Serial.println("M9 keyboard: no controller yet (will keep probing)");
|
|
s_inited = true;
|
|
}
|
|
|
|
void m9KeyboardPoll() {
|
|
if (!s_inited) return;
|
|
// Rate-limit: the UI loop free-runs, and unthrottled this was a blocking
|
|
// ~0.5 ms write+read on the 100 kHz bus EVERY iteration (hundreds/s) — pure
|
|
// waste on the UI thread. The controller latches one key, so the poll only
|
|
// has to outpace keystrokes: 15 ms (~66/s) is ample for burst typing and
|
|
// removes ~97% of the bus traffic. Wraparound-safe; first call passes.
|
|
{
|
|
static uint32_t s_next_poll_ms = 0;
|
|
const uint32_t now_ms = millis();
|
|
if ((int32_t)(now_ms - s_next_poll_ms) < 0) return;
|
|
s_next_poll_ms = now_ms + 15;
|
|
}
|
|
if (!s_bus) {
|
|
uint32_t now = millis();
|
|
if (now - s_last_probe_ms < 1000) return;
|
|
s_last_probe_ms = now;
|
|
kbTryFindBus();
|
|
if (!s_bus) return;
|
|
}
|
|
// Read the latched key; when one IS pending, drain a couple more reads in
|
|
// the same poll (bounded) — the controller holds a single slot, so a second
|
|
// key struck inside the 15 ms window would otherwise be lost.
|
|
for (int i = 0; i < 3; i++) {
|
|
uint8_t key = 0;
|
|
if (!kbReadReg(*s_bus, M9_KB_REG_KEY, &key)) return;
|
|
if (key == 0x00 || key == 0xFF) return; // no key / undefined-register reply
|
|
const uint8_t nh = (uint8_t)((s_head + 1) & 15);
|
|
if (nh != s_tail) { // drop if the ring is full
|
|
s_ring[s_head] = key;
|
|
s_head = nh;
|
|
}
|
|
}
|
|
}
|
|
|
|
int m9KeyboardReadKey() {
|
|
if (s_tail == s_head) return 0; // empty
|
|
const uint8_t key = s_ring[s_tail];
|
|
s_tail = (uint8_t)((s_tail + 1) & 15);
|
|
return key;
|
|
}
|
|
|
|
bool m9KeyboardSetBacklight(uint8_t duty) {
|
|
if (!s_inited || !s_bus) return false; // controller not found yet (see the probe note above)
|
|
s_bus->beginTransmission((uint8_t)PIN_KB_ADDR);
|
|
s_bus->write(M9_KB_REG_BACKLIGHT);
|
|
s_bus->write(duty);
|
|
return s_bus->endTransmission() == 0; // NACK/bus error -> the duty was NOT taken
|
|
}
|
|
|
|
#endif
|