Skip to content

feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel)#2640

Open
disq wants to merge 12 commits into
meshcore-dev:devfrom
disq:gps-fixes
Open

feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel)#2640
disq wants to merge 12 commits into
meshcore-dev:devfrom
disq:gps-fixes

Conversation

@disq

@disq disq commented May 28, 2026

Copy link
Copy Markdown
Contributor

Re-application of #2401

Base Core GPS @ slot Status
19003 RAK4631 RAK12500 (I²C) @ C ✅ tested
19003 RAK4631 RAK12501 (serial) @ C ✅ tested
19007 RAK3401+13302 RAK12500 @ A ✅ tested
19007 RAK3401+13302 RAK12501 @ A ✅ tested
19007 RAK4631 RAK12501 @ A ✅ tested
19007 RAK4631 RAK12500 @ A ✅ tested

All 19003 tests had RTC in slot D and all 19007 tests had RTC in slot C.
All RAK4621 tests had -UPIN_USER_BTN to prevent P0.09 conflict with RTC on 19003. (including 19007 tests)


Changes beyond the original #2401 re-application

RAK3401 GPS enablement (b6e6730)

  • Restore -D RAK_BOARD on [rak3401] (removed in the feat: Enable GPS on RAK 1W kit #2401 revert due to "RadioLib error -707"). The real cause was rakGPSInit()'s WB_IO4 fallback probe pulsing pin 4 (= SX126X_RESET on RAK3401) LOW for 500 ms, hard-resetting the radio after radio_init(). The fixes below make RAK_BOARD safe to re-enable.
  • rakGPSInit() WB_IO2 probe: WB_IO2 also controls the 3V3_S switched peripheral rail; a failed probe left it as INPUT, dropping the rail (and the RTC/display/GPS with it). Restore WB_IO2 HIGH before falling through to the WB_IO4/WB_IO5 probes.
  • Add -D FORCE_GPS_ALIVE on the RAK3401 base env so stop_gps() can't later pull gpsResetPin (which may resolve to the radio reset / rail pin) LOW after init.

RAK4631 GPS enablement — FORCE_GPS_ALIVE parity (47d3753)

  • Add -D FORCE_GPS_ALIVE to the [rak4631] base, matching RAK3401. Without it, rakGPSInit() ends by calling stop_gps() to park the receiver. On RAK4631 (PIN_GPS_EN=-1, GPS powered from the always-on 3V3_S rail) that "stop" is a software GNSS-stop the receiver did not reliably resume from over I²C, so the GPS booted dark (SIV stuck at 0). The flag compiles out that single boot-time stop; the runtime start_gps()/stop_gps() paths are unchanged.
  • Move the GPS UART pin defines (PIN_GPS_TX/PIN_GPS_RX) from platformio.ini into variant.h (value-identical), alongside the other GPS constants — again matching RAK3401.

No dedicated GPS enable pin: PIN_GPS_EN = -1 (14f9d08)

  • Make -1 a real "no GPS enable pin" sentinel. Default gpsResetPin changed from 0 (a real GPIO — P0.00 / LFXO on nRF52) to -1 (0xFFFFFFFF, an out-of-range index that pinMode()/digitalWrite() treat as a no-op), and gpsIsAwake()'s serial branch now tests PIN_GPS_EN >= 0 instead of a truthy PIN_GPS_EN (which wrongly accepted -1). Adds -D PIN_GPS_EN=-1 to RAK3401 so it matches RAK4631. Aligns the WisBlock path with MicroNMEALocationProvider, where GPS_EN already defaults to -1.

GPS stop/start decoupled from the shared 3V3_S rail (ff82e66)

  • start_gps()/stop_gps() now branch on whether a dedicated enable pin exists. With PIN_GPS_EN >= 0, behaviour is unchanged (drive the pin). With no enable pin (the common WisBlock case, where gpsResetPin resolves to WB_IO2 = the shared 3V3_S rail), they no longer toggle that pin — they idle/resume just the GNSS engine in software and leave the rail HIGH:
    • I²C u-blox (RAK12500): UBX-CFG-RST controlled GNSS stop (resetMode 0x08) / start (0x09) over I²C — stops RF/tracking but keeps the receiver's host interface alive, so it restarts reliably. (UBX-RXM-PMREQ backup was rejected: on this hardware it only wakes on EXTINT/UART, not I²C — confirmed dead.)
    • Serial MTK/Quectel: $PMTK161,0 standby / single 0xFF wake byte.
  • Previously both paths unconditionally drove gpsResetPin; in the no-enable-pin case that meant stop_gps() pulled WB_IO2 LOW, browning out everything on the 3V3_S rail (RTC, I²C peripherals, boost) and relying on it coming back. GPS-off is now non-destructive, and the receiver warm/hot-starts.

Stale PVT data fix (b6e6730)

  • RAK12500LocationProvider passed maxWait of 2/8 ms to the ublox getters — below the I²C response window, so polls timed out and the SparkFun library returned stale/garbage PVT (observed: a current latitude paired with a longitude from a previous fix). Bumped to a 250 ms ceiling.
  • Switched getAltitude() (height above WGS84 ellipsoid) to getAltitudeMSL() for more intuitive altitude.

Cold-start false fix (f2ad72e)

  • Require both gnssFixOK and fixType == 3 before reporting coordinates. Ublox reports gnssFixOK == true with bogus almanac-derived coords during cold start; requiring a 3D fix filters those out.

Main-loop / button responsiveness fix (a13098a)

  • The 250 ms maxWait above, combined with RAK12500LocationProvider::loop() running on every main-loop iteration, meant the loop spent most of its time blocked on redundant I²C polls (the GNSS solution only updates at the 1 Hz nav rate). This made the AIN1 button sluggish and dropped double-clicks ("prev").
  • Throttle the poll to ~1 Hz inside the provider, and gate all field reads on a single getPVT(250) (returning early on a timeout so no getter fires its own ~1100 ms default poll on a stale cache). The throttle lives in the provider rather than the shared outer loop because the serial/NMEA providers need loop() every iteration to avoid dropping bytes.

RAK12501 (Quectel) serial GPS on RAK19007 Slot A (9e8cbf6)

  • Correct the GPS UART RX/TX orientation in the RAK3401 variant (host TX → GPS RX, host RX → GPS TX). The previous straight-through mapping (written for a u-blox module) left no bytes flowing in either direction, so the RAK12501 (Quectel, serial) in Slot A was never detected. Pure pin-assignment fix — baud (9600), power rail, reset/enable handling, and the LocationProvider path are untouched.

UI: real GPS fix type on the GPS screen (8f867bf)

  • Add getFixType() to the LocationProvider interface (default derived from isValid(); RAK12500LocationProvider overrides it with the actual u-blox fix type). The ui-new GPS screen now shows off / no fix / 2D / 3D instead of a binary fix/no fix, and the satellite count updates during acquisition (SIV is refreshed every tick rather than only on a 3D lock). Display/diagnostics only — isValid() and when a position is reported/logged are unchanged (still strict 3D).

Debug-print cleanup (c9dfd1b)

  • Dropped a duplicate lat/lon debug print and throttled the remaining one (~every 10th update tick).

Docs: PIN_USER_BTN=9 / Slot D note (13d3496)

  • Documentation-only. Annotate the three -D PIN_USER_BTN=9 RAK4631 envs noting P0.09 = WisBlock IO5 (Slot D): a Slot D module that drives IO5 (e.g. the RAK12002 RTC's RV-3028 CLKOUT) pins it LOW, which reads as a permanently-held user button → boots into CLI rescue → the BLE/USB companion link appears dead. No binary change.

GPS screen: accurate fix-state display (d2e4748)

  • Add LocationProvider::getFixType(); the companion GPS page now shows off / no GPS / no fix / 2D / 3D with a live satellite count, instead of a binary fix/no-fix.
  • Render --- for position until there's a real fix, and reject what would otherwise flash as bogus coords: the ublox cold-start fixType==3-with-junk frames (gated on SIV>0 + in-range coords) and MicroNMEA's 999° no-fix sentinel (both normalised to 0).
  • Position renders eagerly on-screen as soon as there's a 2D/3D solution; telemetry stays strict — isValid()/node_lat only report an accuracy-OK (gnssFixOk) 3D fix.
  • getLocationProvider() returns NULL when no GPS is detected → the screen reads "No GPS detected" instead of a phantom unfixed GPS.

Scope / affected boards

The RAK12500LocationProvider, rakGPSInit(), and start_gps()/stop_gps() changes compile under RAK_WISBLOCK_GPS (ENV_INCLUDE_GPS && RAK_BOARD && !RAK_WISMESH_TAG), so they affect every RAK I2C-ublox GPS board, not just the RAK 1W kit: rak3401, rak4631 (all envs except the two RS232 bridges, which -UENV_INCLUDE_GPS), and gat562_30s_mesh_kit. (gat562_mesh_watch13 has GPS disabled; rak_wismesh_tag is excluded and keeps its real PIN_GPS_EN=34.)

Implications for the already-shipping RAK4631 / GAT562 boards:

  • 1 Hz poll throttle — strictly beneficial; the GPS poll previously ran on every main-loop iteration. (The sluggish-button symptom was only visible on RAK3401 because of its AIN1 analog button, but the redundant blocking affected all of these boards.)
  • fixType == 3 gating — coordinates are now withheld until a 3D fix (filters cold-start false fixes).
  • getAltitudeMSL() — altitude is now height above mean sea level instead of the WGS84 ellipsoid.
  • rakGPSInit() WB_IO2 rail-restore — safety fix for a failed first probe dropping the 3V3_S peripheral rail.
  • Rail decouple + software GNSS stop/start — on boards with no dedicated GPS enable pin, toggling GPS no longer browns out the shared 3V3_S rail (RTC / I²C peripherals stay powered), and GPS warm/hot-starts. Boards with a real PIN_GPS_EN >= 0 are unchanged.
  • PIN_GPS_EN = -1 sentinel — hardens the no-enable-pin path; no change for boards with a real enable pin.
  • GPS screen fix-type / live sat count — ui-new display change only; no effect on reported/logged position.

disq and others added 2 commits May 28, 2026 16:18
Three related issues hit by RAK3401 + RAK19007 + RAK12500 (I²C ublox).

1. -D RAK_BOARD restored on [rak3401].
   It was removed in 68363d9 (revert of meshcore-dev#2401) with the symptom
   "RadioLib error -707", because adding RAK_BOARD enables rakGPSInit(),
   whose WB_IO4 fallback probe pulses pin 4 (= SX126X_RESET on RAK3401)
   LOW for 500 ms — hard-resetting the SX1262 after radio_init() has
   already configured it, and (via stop_gps + gpsResetPin = WB_IO4)
   leaving it held in reset afterwards.
   Without RAK_BOARD, RAK_WISBLOCK_GPS isn't auto-defined, the I²C
   ublox path is unreachable, and the firmware falls back to
   Serial1-NMEA detection — which silently fails on a serial-less
   RAK12500. Fixes 2 and 3 below make RAK_BOARD safe to re-enable.

2. RAK12500LocationProvider passed maxWait of 2/8 ms to getLatitude()
   etc. These are below the ublox I²C response window, so the polls
   routinely timed out and the SparkFun library returned stale/garbage
   PVT bytes. Observed: getGnssFixOk() returning true with a current
   latitude but a longitude from some previous fix. Bumped to 250 ms.
   Also switched getAltitude() (height above WGS84 ellipsoid) to
   getAltitudeMSL() for more intuitive altitude readings.

3. rakGPSInit() probes WB_IO2 first. On failure, gpsIsAwake() leaves
   the probed pin as INPUT. WB_IO2 also controls the 3V3_S switched
   peripheral rail on these RAK base boards, so a failed first probe
   left I²C peripherals (RTC, display, the GPS itself) unpowered
   before the WB_IO4/WB_IO5 fallbacks ran. The else-branch now
   restores WB_IO2 HIGH before falling through.

   On RAK3401, those fallback probes (WB_IO4 = pin 4 = SX126X_RESET,
   WB_IO5 = pin 9 = SX126X_BUSY) are also dangerous, see point 1.
   Adding -D FORCE_GPS_ALIVE in the RAK3401 base env skips stop_gps()
   after detection, so even if gpsResetPin ends up = WB_IO2 (kills
   the rail) or pin 4 (= radio reset), it can't be pulled LOW again
   after init.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The loop() function printed lat/lon, then computed altitude, then
printed lat/lon/alt — making the first print pure noise. Every
gps_update_interval_sec (default 1 s) the log got two near-identical
lines, drowning out anything else when MESH_DEBUG=1 is on.

Drop the first MESH_DEBUG_PRINTLN entirely (in both the RAK_WISBLOCK_GPS
and the non-RAK branches). Throttle the surviving lat/lon/alt line to
roughly every 10th update via a static skip counter, so a debug build
gets a position sample every ~10 s instead of every second while still
covering the typical GPS visibility window.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment thread src/helpers/sensors/EnvironmentSensorManager.cpp Outdated
Comment thread src/helpers/sensors/EnvironmentSensorManager.cpp Outdated
Comment thread src/helpers/sensors/EnvironmentSensorManager.cpp
Comment thread src/helpers/sensors/EnvironmentSensorManager.cpp
Comment thread src/helpers/sensors/EnvironmentSensorManager.cpp
Comment thread src/helpers/sensors/EnvironmentSensorManager.cpp
@disq disq changed the title feat: Enable GPS on RAK 1W kit feat: Enable GPS on RAK 1W kit 🤖🤖 May 28, 2026
Ublox modules sometimes report gnssFixOK=true with bogus coordinates
during cold start, before any actual fix has been acquired — e.g.
factory-almanac defaults that look like real positions. Observed: an
unfixed module reporting plausible-looking but wrong-hemisphere
coordinates persistently, with _location->isValid() returning true.

Tighten RAK12500LocationProvider::loop() to require BOTH gnssFixOK
(DOP/accuracy masks satisfied) AND fixType == 3 (3D fix). fixType
values: 0=no fix, 1=dead reckoning only, 2=2D, 3=3D, 4=GNSS+DR,
5=time only. Only the 3D fix is a position we want to publish.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@disq disq marked this pull request as draft June 1, 2026 09:56
disq and others added 2 commits June 1, 2026 10:57
# Conflicts:
#	src/helpers/sensors/EnvironmentSensorManager.cpp
…n loop

The stale-PVT fix bumped every ublox getXxx() maxWait to 250 ms. Each getter
does a synchronous I²C poll, and RAK12500LocationProvider::loop() runs on every
main-loop iteration — so the loop spent most of its time blocked on redundant
GPS polls (the GNSS solution only updates at the 1 Hz nav rate anyway). That
stall made the AIN1 button feel sluggish and drop double-clicks ("prev"), which
is what the separate interrupt-driven AIN1 button work was chasing — turns out
unnecessary once this is fixed.

Throttle the poll to once per nav epoch (~1 Hz) inside the provider, and gate
all field reads on a single getPVT(250): if that poll times out we return early
and keep the last values, so no getter fires its own ~1100 ms default-maxWait
poll on a stale cache. The throttle lives in the provider (not the shared outer
loop) because the serial/NMEA providers must keep getting loop() every iteration
to avoid dropping bytes. Same proven polling logic that was getting fixes, just
not hammered every iteration.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@disq disq marked this pull request as ready for review June 1, 2026 10:21
@disq disq changed the title feat: Enable GPS on RAK 1W kit 🤖🤖 feat: Enable GPS on RAK 1W kit + fix RAK12500 i2c-ublox polling (affects all RAK ublox boards) Jun 1, 2026
@disq disq changed the title feat: Enable GPS on RAK 1W kit + fix RAK12500 i2c-ublox polling (affects all RAK ublox boards) feat: Enable GPS on RAK 1W kit + fix RAK12500 i2c-ublox polling (affects all RAK ublox boards) 🤖🤖 Jun 1, 2026
@disq disq changed the title feat: Enable GPS on RAK 1W kit + fix RAK12500 i2c-ublox polling (affects all RAK ublox boards) 🤖🤖 feat: Enable GPS on RAK 1W kit: fix RAK12500 i2c-ublox polling and RAK12501 (affects all RAK ublox boards) 🤖🤖 Jun 11, 2026
disq and others added 2 commits June 11, 2026 19:30
gpsResetPin now initializes to -1 so start_gps/stop_gps pin writes are
out-of-range no-ops when no enable pin exists, instead of driving
pin 0 (P0.00 = LFXO crystal on nRF52).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
P0.09 is WisBlock IO5 (Slot D). A module driving that line, e.g. the
RAK12002 RTC whose RV-3028 CLKOUT is push-pull and driven low when
disabled, reads as a held user button. The resulting long-press within
8s of boot enters CLI rescue, which stops servicing the BLE/USB
companion interface.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
disq added a commit to disq/MeshCore that referenced this pull request Jun 16, 2026
disq added a commit to disq/MeshCore that referenced this pull request Jun 16, 2026
@disq disq changed the title feat: Enable GPS on RAK 1W kit: fix RAK12500 i2c-ublox polling and RAK12501 (affects all RAK ublox boards) 🤖🤖 feat: Reliable GPS on all RAK WisBlock ublox boards (RAK12500 + RAK12501 Quectel) Jun 24, 2026
@disq disq changed the title feat: Reliable GPS on all RAK WisBlock ublox boards (RAK12500 + RAK12501 Quectel) feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel) Jun 24, 2026
Show off / no-GPS / no-fix / 2D / 3D with a live sat count, and "---" until there's a real position.
Reject ublox cold-start junk and MicroNMEA's 999-deg no-fix sentinel (both -> 0 = no data).
Position renders eagerly on-screen, telemetry still reports only an accuracy-OK 3D fix.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants