diff --git a/src/helpers/WadaNmeaLocationProvider.h b/src/helpers/WadaNmeaLocationProvider.h index 3906c61..925fa97 100644 --- a/src/helpers/WadaNmeaLocationProvider.h +++ b/src/helpers/WadaNmeaLocationProvider.h @@ -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 } diff --git a/variants/thinknode_m9/M9Keyboard.cpp b/variants/thinknode_m9/M9Keyboard.cpp index 9e89c97..0942abf 100644 --- a/variants/thinknode_m9/M9Keyboard.cpp +++ b/variants/thinknode_m9/M9Keyboard.cpp @@ -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 diff --git a/variants/thinknode_m9/M9Keyboard.h b/variants/thinknode_m9/M9Keyboard.h index 828e14b..44044fd 100644 --- a/variants/thinknode_m9/M9Keyboard.h +++ b/variants/thinknode_m9/M9Keyboard.h @@ -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 diff --git a/variants/thinknode_m9/M9_PORT.md b/variants/thinknode_m9/M9_PORT.md index 7f3f346..c500cf6 100644 --- a/variants/thinknode_m9/M9_PORT.md +++ b/variants/thinknode_m9/M9_PORT.md @@ -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 diff --git a/variants/thinknode_m9/target.cpp b/variants/thinknode_m9/target.cpp index 0a77adf..2864c8b 100644 --- a/variants/thinknode_m9/target.cpp +++ b/variants/thinknode_m9/target.cpp @@ -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)