mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-11-08 16:17:24 +00:00
T-Deck Plus receivers talk 38400 or 9600 depending on the unit, and newer ThinkNode M9s 115200, so owners had to find Settings > GPS > Serial speed. Both boards now build their GPS on WadaNmeaLocationProvider with the UART probe the Pager, T-Deck Pro and P4 use: it tries 9600, 19200, 38400, 57600 and 115200 until a sentence with a valid checksum arrives, and locks there. Unlike those boards it runs only while the user has not picked a speed (CAP_GPS_BAUD_FIND): Serial speed gets an Auto tile, the default, and a picked speed is used as it is. The speed Auto finds is kept in its own NVS key (gps_bd_fd), apart from the user's choice and outside the settings blob, so an older firmware never reads it as a choice; touchPrefsGetGpsBaud returns it when nothing is picked, so the core opens the UART at the right speed on the next start. It is written once per change of receiver, from the loop task. The T-Deck also gets the provider's u-blox GNSS stop/start while GPS rests (it has no enable line); receivers that do not speak UBX ignore it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
219 lines
9.9 KiB
C++
219 lines
9.9 KiB
C++
#include "target.h"
|
|
#include <Arduino.h>
|
|
|
|
ThinkNodeM9Board board;
|
|
|
|
// LR1110 has no std_init() helper in the core (unlike CustomSX1262/SX1268/
|
|
// LLCC68) — confirmed against MeshCore's own LR1110 boards (thinknode_m3,
|
|
// minewsemi_me25ls01). Module() takes the same (NSS, IRQ/DIO1, RESET, BUSY,
|
|
// spi) signature either way. M9: NSS=39, DIO1=42, RESET=45, BUSY=41,
|
|
// SCLK=40, MISO=38, MOSI=47 — all on the SAME physical SPI bus as the LCD
|
|
// (CS=16) and the microSD slot (CS=48), so radio and display share the
|
|
// literal global `SPI` object — matching how T-Deck/Heltec V4's radio (a
|
|
// plain default-constructed `static SPIClass spi;`, not an explicit-host
|
|
// instance) and the LR1110 reference boards (thinknode_m3, me25ls01, which
|
|
// also pass the global `SPI` to their Module()) both do it. The bus itself
|
|
// is begun once, in ThinkNodeM9Board::begin() (see M9Board.cpp for why that
|
|
// has to happen there rather than mirroring T-Deck/Heltec's "begin it inside
|
|
// radio_init()" exactly — their displays self-init on a separate dedicated
|
|
// bus, M9's display is on ST7789LCDDisplay's "default" branch and needs the
|
|
// global SPI already begun before display.begin() runs).
|
|
RADIO_CLASS radio =
|
|
new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BUSY, SPI);
|
|
|
|
WRAPPER_CLASS radio_driver(radio, board);
|
|
|
|
ESP32RTCClock fallback_clock;
|
|
// The M9's PCF8563 goes through the board adapter rather than the core's
|
|
// address-only probe (issue #383). The core path read the chip's calendar
|
|
// registers WITHOUT consulting its VL bit, so after a hard shutdown the stale
|
|
// register contents came back as real wall time and the clock appeared frozen
|
|
// at the moment the slider was pulled — the reported M9 symptom. The adapter
|
|
// refuses an integrity-lost read, clears a latched STOP bit, and reads back
|
|
// every write.
|
|
HardwareRtcClock hw_rtc(fallback_clock);
|
|
ClockFloorRTC rtc_clock(
|
|
hw_rtc); // wraps AutoDiscover: monotonic send-timestamp floor (#89)
|
|
// Wadamesh's own provider rather than the core's MicroNMEALocationProvider:
|
|
// identical behaviour, plus the RMC speed/course the core keeps private (see
|
|
// src/helpers/WadaNmeaLocationProvider.h for why it is a copy, not a subclass).
|
|
// With the UART probe: M9 receivers ship at 115200 or 9600 depending on the
|
|
// revision, so the speed is found while the user has not picked one in Settings
|
|
// (CAP_GPS_BAUD_FIND). The ESP receives on PIN_GPS_TX (the core's naming).
|
|
WadaNmeaLocationProvider gps(Serial1, &rtc_clock, GPS_RESET, GPS_EN,
|
|
PIN_GPS_TX, PIN_GPS_RX, GPS_BAUD_RATE, true /*keep_choice*/);
|
|
EnvironmentSensorManager sensors(gps);
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
// periph_power gates the LCD/GPS/sensor rail (GPIO18, active-low P-MOS).
|
|
// Passing &board.periph_power lets ST7789LCDDisplay::begin()/turnOff()
|
|
// claim/release it alongside the panel's own lifecycle. The backlight
|
|
// (GPIO17, PNP) is handled separately by ThinkNodeM9Board — see M9Board.h
|
|
// for why it's NOT routed through PIN_TFT_LEDA_CTL.
|
|
DISPLAY_CLASS display(&board.periph_power);
|
|
// (No MomentaryButton here: the M9 has NO user/BOOT button — schematic-
|
|
// confirmed, only a power-cut slider and reset. PIN_USER_BTN is undefined for
|
|
// this env so UITask's button poll compiles out too.)
|
|
#endif
|
|
|
|
#ifndef LORA_CR
|
|
#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
|
|
// ESP32Board::begin() (PIN_BOARD_SDA/SCL) well before this runs.
|
|
hw_rtc.begin(Wire, HardwareRtcClock::Chip::PCF8563);
|
|
rtc_clock.noteHardwareClock(hw_rtc.present());
|
|
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)
|
|
// QMC6309 on the same peripheral bus as the RTC, behind the GPIO18 rail that
|
|
// board.begin() claimed well before this runs. Absent / still-booting chips
|
|
// are re-probed lazily from the read path, so a miss here is not fatal.
|
|
m9CompassBegin(Wire);
|
|
#endif
|
|
#if defined(HAS_M9_IMU)
|
|
m9ImuBegin(Wire); // QMI8658 at 0x6B, same bus, same lazy re-probe
|
|
#endif
|
|
|
|
#ifdef LR11X0_DIO3_TCXO_VOLTAGE
|
|
float tcxo = LR11X0_DIO3_TCXO_VOLTAGE;
|
|
#else
|
|
// Fallback = the hardware-confirmed value, NOT 0. An earlier theory (Y1 =
|
|
// self-powered active oscillator, so disable RadioLib's TCXO bias) was
|
|
// disproven on the real unit: with tcxo=0 the chip boots on its internal
|
|
// RC (SPI alive) but the first command needing the true 32 MHz clock is
|
|
// rejected — radio init fails -707. The schematic's "VTCXO" rail feeding
|
|
// Y1 is the LR1110's own TCXO-supply output, so DIO3 must drive it at
|
|
// 3.3 V — see the LR11X0_DIO3_TCXO_VOLTAGE comment in platformio.ini for
|
|
// the empirical confirmation.
|
|
float tcxo = 3.3f;
|
|
#endif
|
|
|
|
// SPI bus itself was already begun once in ThinkNodeM9Board::begin() (the
|
|
// display needs it before display.begin() runs) — this is the manual
|
|
// equivalent of what CustomSX1262/SX1268's std_init() would otherwise do,
|
|
// since CustomLR1110 has no std_init() helper.
|
|
int status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR,
|
|
RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE,
|
|
LORA_TX_POWER, 16, tcxo);
|
|
if (status != RADIOLIB_ERR_NONE) {
|
|
// One retry after a settle: the first begin() enables the TCXO supply, and
|
|
// if Y1's startup outruns RadioLib's fixed internal wait the first
|
|
// calibration can fail while the second attempt finds a stable clock.
|
|
delay(150);
|
|
status = radio.begin(LORA_FREQ, LORA_BW, LORA_SF, LORA_CR,
|
|
RADIOLIB_LR11X0_LORA_SYNC_WORD_PRIVATE, LORA_TX_POWER,
|
|
16, tcxo);
|
|
}
|
|
// Print the chip's own identification in BOTH outcomes: device type,
|
|
// transceiver FW revision and pending error flags. Preprod M9 units ship
|
|
// with old LR1110 firmware (pre-0x0308), which is exactly what this line
|
|
// catches — see scripts/build/patch_radiolib_lr11x0.py for the matching
|
|
// old-FW tolerance in RadioLib's config(). Safe to call even after a
|
|
// failed begin(): the SPI link is configured before the failing step.
|
|
{
|
|
LR11x0VersionInfo_t vinfo;
|
|
uint16_t chip_errors = 0;
|
|
if (radio.getVersionInfo(&vinfo) == RADIOLIB_ERR_NONE) {
|
|
radio.getErrors(&chip_errors);
|
|
Serial.printf("LR1110: hw=0x%02X device=0x%02X fw=%u.%u wifi=%u.%u "
|
|
"gnss=%u.%u errors=0x%04X\n",
|
|
vinfo.hardware, vinfo.device, vinfo.fwMajor, vinfo.fwMinor,
|
|
vinfo.fwMajorWiFi, vinfo.fwMinorWiFi, vinfo.fwGNSS,
|
|
vinfo.almanacGNSS, chip_errors);
|
|
} else {
|
|
Serial.println("LR1110: getVersionInfo failed (chip not answering)");
|
|
}
|
|
}
|
|
|
|
if (status != RADIOLIB_ERR_NONE) {
|
|
Serial.print("ERROR: radio init failed: ");
|
|
Serial.println(status);
|
|
return false;
|
|
}
|
|
|
|
radio.setCRC(2);
|
|
radio.explicitHeader();
|
|
|
|
// RF-switch DIO table: pin assignment is SCHEMATIC-CONFIRMED
|
|
// (Think_Node_M9_V1_0.pdf, the LR1110/U7 block) — DIO5 (pin 20) -> R20 -> net
|
|
// RFSW0_V1 -> switch IC (U8) V1; DIO6 (pin 19) -> R19 -> net RFSW1_V2 -> U8
|
|
// V2. DIO7/DIO8 are unconnected on this board (no net, dangling stubs) —
|
|
// unlike t1000-e/me25ls01's 4-pin DIO5-8 scheme, this is a plain 2-pin switch
|
|
// into a single antenna (U8 RFC -> C74 -> L12 -> ANT1), matching
|
|
// thinknode_m3's table shape exactly. What's NOT independently re-derived:
|
|
// the per-mode HIGH/LOW truth table below — U8's part number isn't printed on
|
|
// the schematic, so this reuses the conventional STBY/RX/TX/TX_HP polarity
|
|
// every other MeshCore LR1110 board's 2-pin table uses (thinknode_m3, same
|
|
// shape). If TX/RX work but seem swapped or dead, flip the RX/TX_HP rows here
|
|
// first.
|
|
#ifdef RF_SWITCH_TABLE
|
|
static const uint32_t rfswitch_dios[Module::RFSWITCH_MAX_PINS] = {
|
|
RADIOLIB_LR11X0_DIO5, // -> RFSW0_V1 (U8 V1)
|
|
RADIOLIB_LR11X0_DIO6, // -> RFSW1_V2 (U8 V2)
|
|
RADIOLIB_NC, RADIOLIB_NC, RADIOLIB_NC};
|
|
static const Module::RfSwitchMode_t rfswitch_table[] = {
|
|
{LR11x0::MODE_STBY, {LOW, LOW}}, {LR11x0::MODE_RX, {HIGH, LOW}},
|
|
{LR11x0::MODE_TX, {HIGH, HIGH}}, {LR11x0::MODE_TX_HP, {LOW, HIGH}},
|
|
{LR11x0::MODE_TX_HF, {LOW, LOW}}, {LR11x0::MODE_GNSS, {LOW, LOW}},
|
|
{LR11x0::MODE_WIFI, {LOW, LOW}}, END_OF_MODE_TABLE,
|
|
};
|
|
radio.setRfSwitchTable(rfswitch_dios, rfswitch_table);
|
|
#endif
|
|
#ifdef RX_BOOSTED_GAIN
|
|
radio.setRxBoostedGainMode(RX_BOOSTED_GAIN);
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
mesh::LocalIdentity radio_new_identity() {
|
|
RadioNoiseListener rng(radio);
|
|
return mesh::LocalIdentity(&rng);
|
|
}
|
|
|
|
SPIClass *m9SharedSPI() {
|
|
return &SPI; // global instance, shared by radio + display + SD; begun once in
|
|
// M9Board::begin()
|
|
}
|
|
|
|
bool wadaGpsMotion(float *speed_kmh, float *course_deg) {
|
|
return gps.motion(speed_kmh, course_deg);
|
|
}
|