The GPS enable pin (GPIO11) is active-LOW on the original ThinkNode-M9 but active-HIGH on the newer revision (keyboard controller at 0x6D, e.g. Elecrow's Meshtastic SKU LMM15409D2). Driven LOW on those units the GPS never gets a fix: on one unit, same spot, 0 satellites with EN active-LOW vs 15 with active-HIGH, and 34 after 20 minutes outdoors with the GPS toggled off and on again from the UI. Meshtastic's ELECROW-ThinkNode-M9 variant makes the same split, keyed on the same keyboard address (m9Version > 1 ? HIGH : LOW), and Elecrow engineers have confirmed the split. WadaNmeaLocationProvider: keep the enable level in a member that defaults to the compile-time GPS_EN_ACTIVE, add setEnActive() to override it at runtime (re-driving the pin so a running GPS stays on), and an optional setPostEnableHook() run at the end of begin(). Every other board is unaffected. thinknode_m9: m9KeyboardAddr() and a one-shot found-callback on the keyboard driver; target.cpp uses it, when the controller is at 0x6D, to call gps.setEnActive(true) and to register a GPIO10 wake pulse (50 ms, HIGH 5 ms, LOW, 50 ms) after enabling, as Elecrow engineers confirmed. Any other address keeps the existing default (LOW, no pulse), so the 0x6C path is unchanged; it is not tested here, no 0x6C unit was available. Depends on the keyboard 0x6D probe change. Signed-off-by: Pratik Patel <Pratik@MakerNova.io>
83 lines
4.1 KiB
C
83 lines
4.1 KiB
C
#pragma once
|
|
|
|
// ThinkNode M9 keyboard controller. Unlike the T-Deck — where the keyboard
|
|
// shares an I2C bus with the touch controller and must be polled from a
|
|
// dedicated core-0 task — the M9 keyboard lives on its OWN bus (Wire1,
|
|
// SDA=20 / SCL=21, addr 0x6c on V1.0 or 0x6d on V1.1), with a fallback
|
|
// probe on Wire (7/6). There is no touch controller present, so
|
|
// this can be polled directly from the UI thread; no cross-core ring buffer
|
|
// is strictly required, but one is kept anyway for interface parity with
|
|
// TDeckKeyboard and so a future move to a dedicated poll task is a no-op.
|
|
//
|
|
// PROTOCOL — from Elecrow's own keyboard-controller firmware
|
|
// (ThinkNode-M9-KB-platformio): the keyboard is a separate ESP32-S2 running a
|
|
// matrix scanner as an I2C slave with ADDRESSED REGISTERS. A key read is
|
|
// write-reg(0x01) then read 1 byte (0x00 = no key; the controller latches a
|
|
// single pending key). The earlier "one raw byte per read" protocol note was
|
|
// WRONG — a bare read returns the last-addressed register (0x00 = HW version,
|
|
// constant 0x03), which is why bring-up builds 1-7 saw no keys. The
|
|
// controller resolves shift/symbol/alt layers itself and emits final ASCII
|
|
// for printable keys; non-ASCII bytes are the d-pad / dedicated function
|
|
// keys (see M9_KEY_* below), passed through unchanged, so
|
|
// m9KeyboardReadKey() returns either a printable ASCII code or a sentinel.
|
|
// Backlight: write-reg(0x02) + duty byte; the controller auto-lights on
|
|
// keypress and times out after 10 s on its own.
|
|
#if defined(HAS_M9_KEYBOARD) && defined(ESP32)
|
|
|
|
#include <stdint.h>
|
|
|
|
// Raw byte values confirmed on hardware (Specter bring-up, notes/m9_keycodes.md).
|
|
#define M9_KEY_LEFT 0xB4
|
|
#define M9_KEY_UP 0xB5
|
|
#define M9_KEY_DOWN 0xB6
|
|
#define M9_KEY_RIGHT 0xB7
|
|
#define M9_KEY_ENTER 0x0D // shared with d-pad centre click — indistinguishable
|
|
#define M9_KEY_DEL 0x08
|
|
#define M9_KEY_MIC 0x88 // triangle/mic glyph key
|
|
#define M9_KEY_LEFT_MESSAGE 0x81
|
|
#define M9_KEY_HOME 0x82
|
|
#define M9_KEY_SUB_MESSAGE 0x83
|
|
#define M9_KEY_SUB_MAP 0x84
|
|
#define M9_KEY_MAP 0x85
|
|
#define M9_KEY_HW_BACK 0x86
|
|
#define M9_KEY_CTRL 0x90 // bound: opens the Control Center (single latched key — can never chord)
|
|
// From the controller firmware's key tables: 0x84 long-press emits 0x87
|
|
// (labeled "GPS toggle" — bound to toggleGPS()), Enter long-press emits 0xA3
|
|
// (bound: LV_EVENT_LONG_PRESSED on the focused widget / lock-screen unlock).
|
|
// Shift/Sym/Alt never reach the wire — the controller consumes them as layer
|
|
// modifiers. DELIBERATELY UNBOUND: MIC 0x88 (no audio-capture feature exists)
|
|
// and 0x89 (matrix col3/row4, physical key cap unidentified — binding an
|
|
// unknown cap invites accidental triggers).
|
|
#define M9_KEY_GPS_LONG 0x87
|
|
#define M9_KEY_ENTER_LONG 0xA3
|
|
|
|
/** Bring up Wire1 (SDA=PIN_KB_SDA, SCL=PIN_KB_SCL) and mark the keyboard
|
|
* available. Safe to call from the UI thread — this bus has no other
|
|
* device on it. */
|
|
void m9KeyboardBegin();
|
|
|
|
/** Read one key over I2C and push it into the ring. May be called from any
|
|
* single task (this board has no cross-core contention on Wire1). */
|
|
void m9KeyboardPoll();
|
|
|
|
/** Pop the next buffered raw byte (ASCII or M9_KEY_* sentinel), or 0 if none. */
|
|
int m9KeyboardReadKey();
|
|
|
|
/** Set the keyboard backlight PWM duty (0..255) on the controller. The
|
|
* controller lights up on keypress by itself and times out after 10 s;
|
|
* this just sets HOW bright. Returns false — writing nothing — until the
|
|
* controller has been found, or when the I2C write NACKs, so a caller that
|
|
* caches "last written" can retry instead of latching a duty the
|
|
* controller never took. */
|
|
bool m9KeyboardSetBacklight(uint8_t duty);
|
|
|
|
/** I2C address the keyboard controller answered at (0x6C or 0x6D), or 0 while it
|
|
* has not been found yet. The address identifies the board hardware revision. */
|
|
uint8_t m9KeyboardAddr();
|
|
|
|
/** Register a callback invoked once, with the address, as soon as the controller is
|
|
* found (immediately from m9KeyboardBegin, or later from the poll re-probe). */
|
|
void m9KeyboardSetFoundCallback(void (*cb)(uint8_t addr));
|
|
|
|
#endif
|