thinknode_m9: pick GPS enable polarity from the board revision

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>
This commit is contained in:
Pratik Patel authored and Kaj Schittecat committed 2026-10-13 08:22:55 +02:00
1 parent 9cd10dfe30
commit 8dbb349fc6
5 files changed
+74 -5

No files matched your search

+21 -4
View File
@@ -35,6 +35,12 @@ class WadaNmeaLocationProvider : public LocationProvider {
Stream* _gps_serial;
RefCountedDigitalPin* _peripher_power;
int8_t _claims = 0;
// Runtime enable level (defaults to the compile-time GPS_EN_ACTIVE). Boards whose
// GPS enable polarity differs between hardware revisions override it via setEnActive().
bool _en_active = (GPS_EN_ACTIVE);
// Optional board hook run at the end of the power-up sequence in begin(), after the enable and reset pins are set
// (e.g. a wake pulse some GPS modules need). Null on boards that don't set one.
void (*_post_enable)() = nullptr;
int _pin_reset;
int _pin_en;
unsigned long next_check = 0;
@@ -139,7 +145,7 @@ public:
}
if (_pin_en != -1) {
pinMode(_pin_en, OUTPUT);
digitalWrite(_pin_en, !GPS_EN_ACTIVE);
digitalWrite(_pin_en, !_en_active);
}
}
@@ -157,6 +163,16 @@ public:
// GNSS engine stopped (see ubxGnss). Opt-in per board, once its module is known to wake.
void setUbxStandby(bool on) { _ubx_standby = on; }
// Override the enable pin's active level at runtime (e.g. once the hardware
// revision is known). Re-drives the pin so an already-running GPS stays on and an
// idle one stays off under the new polarity.
void setEnActive(bool active_high) {
_en_active = active_high;
if (_pin_en != -1) digitalWrite(_pin_en, _claims > 0 ? _en_active : !_en_active);
}
void setPostEnableHook(void (*hook)()) { _post_enable = hook; }
void claim() {
_claims++;
if (_peripher_power) _peripher_power->claim();
@@ -173,8 +189,9 @@ public:
// What waited in the UART while the receiver rested is old news, and it would read as
// the stream being back before the receiver has woken.
if (_ubx_standby) while (_gps_serial->available()) _gps_serial->read();
if (_pin_en != -1) digitalWrite(_pin_en, GPS_EN_ACTIVE);
if (_pin_en != -1) digitalWrite(_pin_en, _en_active);
if (_pin_reset != -1) digitalWrite(_pin_reset, !GPS_RESET_ACTIVE);
if (_pin_en != -1 && _post_enable) _post_enable();
if (_probe_serial) {
_active_baud = touchPrefsGetGpsBaud(_probe_default_baud);
_probe_cursor = 0;
@@ -198,7 +215,7 @@ public:
}
void stop() override {
if (_pin_en != -1) digitalWrite(_pin_en, !GPS_EN_ACTIVE);
if (_pin_en != -1) digitalWrite(_pin_en, !_en_active);
// LOW, not GPS_RESET_ACTIVE — the zero-current park level for both reset
// circuits (see the constructor comment; on the M9 "asserted" = GPIO HIGH
// through R46 into Q16's base = a constant drain while the GPS is off).
@@ -213,7 +230,7 @@ public:
bool isEnabled() override {
// Read the enable pin directly if present: the GPS can be switched outside
// of here.
if (_pin_en != -1) return digitalRead(_pin_en) == GPS_EN_ACTIVE;
if (_pin_en != -1) return digitalRead(_pin_en) == (int)_en_active;
return true; // no enable pin, so it must be active
}
+8 -1
View File
@@ -27,6 +27,8 @@ 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 void (*s_found_cb)(uint8_t) = nullptr;
static bool s_found_cb_done = false;
static TwoWire* s_bus = nullptr; // set once the controller answers
static uint8_t s_address = PIN_KB_ADDR;
static uint32_t s_last_probe_ms = 0;
@@ -59,10 +61,15 @@ static void kbTryFindBus() {
const uint8_t addresses[] = {PIN_KB_ADDR, M9_KB_V11_ADDR};
for (uint8_t address : addresses) {
if (kbProbe(Wire1, "Wire1 (20/21)", address) ||
kbProbe(Wire, "Wire (7/6)", address)) return;
kbProbe(Wire, "Wire (7/6)", address)) break;
}
if (s_bus && s_found_cb && !s_found_cb_done) { s_found_cb_done = true; s_found_cb(s_address); }
}
uint8_t m9KeyboardAddr() { return s_bus ? s_address : 0; }
void m9KeyboardSetFoundCallback(void (*cb)(uint8_t addr)) { s_found_cb = cb; s_found_cb_done = false; }
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
+8
View File
@@ -71,4 +71,12 @@ int m9KeyboardReadKey();
* 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
+7
View File
@@ -83,6 +83,13 @@ This turn's corrections vs. the earlier (incorrect) attempt:
asserts reset). Both `PIN_GPS_EN_ACTIVE` and `PIN_GPS_RESET_ACTIVE` are now
overridden in `platformio.ini`. Fixed a total loss of GPS fix (module was
held disabled/in-reset on every boot).
**Hardware revisions:** that polarity is for the original board (keyboard
controller at 0x6C). Newer units (keyboard at 0x6D, e.g. the Meshtastic SKU
LMM15409D2) have GPS EN **active-HIGH**; with it driven LOW they never get a
fix. `target.cpp` selects the level at runtime from the keyboard address
(`WadaNmeaLocationProvider::setEnActive()`), the same split Meshtastic's
`ELECROW-ThinkNode-M9` variant makes (`m9Version > 1 ? HIGH : LOW`). The
0x6C path keeps the compile-time default and is untested on a 0x6C unit.
- microSD CS is **GPIO48**, not "deferred pending SDMMC pins" — this board
never needed SD_MMC at all; the original SPI-CS approach was correct, the
pin number was just wrong (see the pin table above and "Bonus schematic
+30
View File
@@ -56,6 +56,35 @@ DISPLAY_CLASS display(&board.periph_power);
#define LORA_CR 5
#endif
#if defined(HAS_M9_KEYBOARD)
// The keyboard controller's I2C address identifies the board hardware revision:
// units whose keyboard answers at 0x6D carry the newer GPS wiring, whose enable pin
// is active HIGH (same split Meshtastic and Elecrow's firmware make), followed by a
// short wake pulse on GPIO10. 0x6C boards keep the compile-time default
// (PIN_GPS_EN_ACTIVE, LOW) and no pulse.
// Elecrow's own firmware pulses GPIO10 (the module's ON_OFF/wake line) after
// driving EN active on these units: 50 ms, HIGH for 5 ms, then 50 ms settle.
static void m9GpsWakePulse() {
pinMode(10, OUTPUT);
delay(50);
digitalWrite(10, HIGH);
delay(5);
digitalWrite(10, LOW);
delay(50);
}
static void m9OnKeyboardFound(uint8_t addr) {
if (addr == 0x6D) {
gps.setEnActive(true);
gps.setPostEnableHook(m9GpsWakePulse);
if (gps.isEnabled()) m9GpsWakePulse(); // GPS was already powered before the keyboard was found
Serial.println("M9 GPS: enable pin active HIGH (keyboard at 0x6D)");
} else {
Serial.printf("M9 GPS: enable pin active level unchanged (keyboard at 0x%02X)\n", addr);
}
}
#endif
bool radio_init() {
fallback_clock.begin();
// peripheral I2C bus (7/6) — RTC PCF8563 @ 0x51, brought up by
@@ -65,6 +94,7 @@ bool radio_init() {
if (hw_rtc.adopted()) rtc_clock.noteHardwareTime();
#if defined(HAS_M9_KEYBOARD)
m9KeyboardSetFoundCallback(m9OnKeyboardFound);
m9KeyboardBegin(); // own bus, Wire1 (20/21) — no contention with Wire
#endif
#if defined(HAS_M9_COMPASS)