feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel) - #2640
feat: Reliable GPS on all RAK WisBlock GPS boards (RAK12500 ublox + RAK12501 Quectel)#2640disq wants to merge 14 commits into
Conversation
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>
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>
# 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>
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>
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.
|
Running this on RAK10724 kit with RAK12501, and it fixed my GPS, thanks! |
|
RAK4631 and RAK125001 work for me with the current code. |
| pinMode(WB_IO2, OUTPUT); | ||
| digitalWrite(WB_IO2, HIGH); | ||
|
|
||
| if(gpsIsAwake(WB_IO4)){ |
There was a problem hiding this comment.
Why we need to check at WB_IO4 and IO5?
WB_IO2 is the only 3v3_S pin.
| // takes I²C peripherals (RTC, display, the GPS itself) down with it. | ||
| // Restore it before probing other sockets. | ||
| pinMode(WB_IO2, OUTPUT); | ||
| digitalWrite(WB_IO2, HIGH); |
There was a problem hiding this comment.
In my test, we need a delay(500) after set HIGH.
Else RAK12501 (L76K) can not start on time.
| -D PIN_BOARD_SDA=13 | ||
| -D PIN_GPS_TX=PIN_SERIAL1_RX | ||
| -D PIN_GPS_RX=PIN_SERIAL1_TX | ||
| -D FORCE_GPS_ALIVE |
There was a problem hiding this comment.
Why we need to force gps alive for RAK4631?
We can safely turn it of if there is not sensor using sensor slots.
| #ifdef RAK_WISBLOCK_GPS | ||
| pinMode(gpsResetPin, OUTPUT); | ||
| digitalWrite(gpsResetPin, HIGH); | ||
| #if defined(PIN_GPS_EN) && (PIN_GPS_EN >= 0) |
There was a problem hiding this comment.
PIN_GPS_EN is -1 for RAK3401 and RAK4601, so this always returns false.
I guess this should be an "if" instead of #if.
For RAK4631, PIN_GPS_EN is initialized as -1 and later gpsResetPin is set to WB_IO2.
| #ifdef RAK_WISBLOCK_GPS | ||
| pinMode(gpsResetPin, OUTPUT); | ||
| digitalWrite(gpsResetPin, LOW); | ||
| #if defined(PIN_GPS_EN) && (PIN_GPS_EN >= 0) |
There was a problem hiding this comment.
PIN_GPS_EN is -1 for RAK3401 and RAK4601, so this always returns false.
I guess this should be an "if" instead of #if.
For RAK4631, PIN_GPS_EN is initialized as -1 and later gpsResetPin is set to WB_IO2.
IoTThinks
left a comment
There was a problem hiding this comment.
I guess the only hope to have GPS powersaving for RAK3401 is with I2C RAK12500.
Have you tested the power consumption of stop_gps() on RAK3401 with I2C RAK12500?
For RAK4631, if there is no sensor, we can just turn off RAK12501 using WB_IO2.
With RAK12500, we can hope your I2C command can lower power consumption as same as with RAK3401.
| Wire.write(ubxGnssStop, sizeof(ubxGnssStop)); | ||
| Wire.endTransmission(); | ||
| } else if (serialGPSFlag) { | ||
| Serial1.println("$PMTK161,0*28"); // Quectel/MTK standby |
There was a problem hiding this comment.
I have tested this command. It has no effect on power consumption.
In Quectel forum, they said "The L76K does not support the PMTK command."
https://forums.quectel.com/t/l76k-cold-and-hot-start-for-low-power-periodic-location-acquisition/49439/3
| if (i2cGPSFlag) { | ||
| // UBX-CFG-RST "controlled GNSS stop": stops RF/tracking but keeps the host | ||
| // interface alive, so it restarts reliably over I2C. (PMREQ backup only wakes | ||
| // on EXTINT/UART, not I2C -- confirmed dead on this hardware.) |
There was a problem hiding this comment.
Could you please help to check if stop_gps() can reduce power consumption for RAK3401 and RAK 12500 I2C?
Re-application of #2401
All 19003 tests had RTC in slot D and all 19007 tests had RTC in slot C.
All RAK4621 tests had
-UPIN_USER_BTNto prevent P0.09 conflict with RTC on 19003. (including 19007 tests)Changes beyond the original #2401 re-application
RAK3401 GPS enablement (b6e6730)
-D RAK_BOARDon[rak3401](removed in the feat: Enable GPS on RAK 1W kit #2401 revert due to "RadioLib error -707"). The real cause wasrakGPSInit()'s WB_IO4 fallback probe pulsing pin 4 (= SX126X_RESET on RAK3401) LOW for 500 ms, hard-resetting the radio afterradio_init(). The fixes below makeRAK_BOARDsafe 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.-D FORCE_GPS_ALIVEon the RAK3401 base env sostop_gps()can't later pullgpsResetPin(which may resolve to the radio reset / rail pin) LOW after init.RAK4631 GPS enablement —
FORCE_GPS_ALIVEparity (47d3753)-D FORCE_GPS_ALIVEto the[rak4631]base, matching RAK3401. Without it,rakGPSInit()ends by callingstop_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 runtimestart_gps()/stop_gps()paths are unchanged.PIN_GPS_TX/PIN_GPS_RX) fromplatformio.iniintovariant.h(value-identical), alongside the other GPS constants — again matching RAK3401.No dedicated GPS enable pin:
PIN_GPS_EN = -1(14f9d08)-1a real "no GPS enable pin" sentinel. DefaultgpsResetPinchanged from0(a real GPIO — P0.00 / LFXO on nRF52) to-1(0xFFFFFFFF, an out-of-range index thatpinMode()/digitalWrite()treat as a no-op), andgpsIsAwake()'s serial branch now testsPIN_GPS_EN >= 0instead of a truthyPIN_GPS_EN(which wrongly accepted-1). Adds-D PIN_GPS_EN=-1to RAK3401 so it matches RAK4631. Aligns the WisBlock path withMicroNMEALocationProvider, whereGPS_ENalready 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. WithPIN_GPS_EN >= 0, behaviour is unchanged (drive the pin). With no enable pin (the common WisBlock case, wheregpsResetPinresolves 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:UBX-CFG-RSTcontrolled GNSS stop (resetMode0x08) / 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.)$PMTK161,0standby / single0xFFwake byte.gpsResetPin; in the no-enable-pin case that meantstop_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)
RAK12500LocationProviderpassed 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.getAltitude()(height above WGS84 ellipsoid) togetAltitudeMSL()for more intuitive altitude.Cold-start false fix (f2ad72e)
gnssFixOKandfixType == 3before reporting coordinates. Ublox reportsgnssFixOK == truewith bogus almanac-derived coords during cold start; requiring a 3D fix filters those out.Main-loop / button responsiveness fix (a13098a)
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").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 needloop()every iteration to avoid dropping bytes.RAK12501 (Quectel) serial GPS on RAK19007 Slot A (9e8cbf6)
LocationProviderpath are untouched.UI: real GPS fix type on the GPS screen (8f867bf)
getFixType()to theLocationProviderinterface (default derived fromisValid();RAK12500LocationProvideroverrides it with the actual u-blox fix type). The ui-new GPS screen now showsoff/no fix/2D/3Dinstead of a binaryfix/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)
Docs:
PIN_USER_BTN=9/ Slot D note (13d3496)-D PIN_USER_BTN=9RAK4631 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)
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.---for position until there's a real fix, and reject what would otherwise flash as bogus coords: the ublox cold-startfixType==3-with-junk frames (gated onSIV>0+ in-range coords) and MicroNMEA's999°no-fix sentinel (both normalised to0).isValid()/node_latonly report an accuracy-OK (gnssFixOk) 3D fix.getLocationProvider()returnsNULLwhen no GPS is detected → the screen reads "No GPS detected" instead of a phantom unfixed GPS.Scope / affected boards
The
RAK12500LocationProvider,rakGPSInit(), andstart_gps()/stop_gps()changes compile underRAK_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), andgat562_30s_mesh_kit. (gat562_mesh_watch13has GPS disabled;rak_wismesh_tagis excluded and keeps its realPIN_GPS_EN=34.)Implications for the already-shipping RAK4631 / GAT562 boards:
fixType == 3gating — 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.PIN_GPS_EN >= 0are unchanged.PIN_GPS_EN = -1sentinel — hardens the no-enable-pin path; no change for boards with a real enable pin.