GPS¶
LOCATIONPROVIDER · NMEA · ENABLE PIN · TIME SYNC
A GPS receiver does two things in a MeshCore node: it gives a position, and it sets the clock. The first is optional, the second is on boards without an RTC the only real time the node has. This chapter describes how the firmware reads the receiver, switches it on and off, and what happens when there is no fix.
[!NOTE] Source. This page has been verified against the firmware itself:
MeshCorev1.16.0, commit03b6ef4, 28 July 2026 — filessrc/helpers/sensors/LocationProvider.h,src/helpers/sensors/MicroNMEALocationProvider.h,src/helpers/sensors/EnvironmentSensorManager.cppand thePIN_GPS_*flags invariants/.
One interface, two kinds of receiver¶
The firmware knows location only as LocationProvider: fourteen methods
saying where you are, what time it is and whether that is to be trusted.
src/helpers/sensors/LocationProvider.h r.6-25
class LocationProvider {
protected:
bool _time_sync_needed = true;
public:
virtual void syncTime() { _time_sync_needed = true; }
virtual bool waitingTimeSync() { return _time_sync_needed; }
virtual long getLatitude() = 0;
virtual long getLongitude() = 0;
virtual long getAltitude() = 0;
virtual long satellitesCount() = 0;
virtual bool isValid() = 0;
virtual long getTimestamp() = 0;
virtual void sendSentence(const char * sentence);
virtual void reset() = 0;
virtual void begin() = 0;
virtual void stop() = 0;
virtual void loop() = 0;
virtual bool isEnabled() = 0;
Two things stand out in that. isValid() is separate from isEnabled(): an
enabled receiver without a fix is not the same as a disabled one. And
_time_sync_needed starts at true — the node assumes its clock is wrong
until the GPS proves otherwise.
There are two routes to such a provider:
| Route | Class | Connection |
|---|---|---|
| serial | MicroNMEALocationProvider |
UART, PIN_GPS_RX / PIN_GPS_TX |
| I²C | through the sensor table, RAK12500 at 0x42 |
see The I²C Bus |
The serial route is the ordinary one. Eighteen variant files set a
-D PIN_GPS_RX= line; repeat with
grep -rl -- "-D PIN_GPS_RX=" variants/ | wc -l.
NMEA in a hundred-byte buffer¶
The serial provider reads the receiver using the MicroNMEA library and hands it a fixed buffer:
src/helpers/sensors/MicroNMEALocationProvider.h r.36-40
class MicroNMEALocationProvider : public LocationProvider {
char _nmeaBuffer[100];
MicroNMEA nmea;
mesh::RTCClock* _clock;
Stream* _gps_serial;
A hundred bytes is one NMEA sentence. The provider gets a Stream — the
same abstraction used for the companion connection — and a clock, because
as soon as a valid time arrives it passes it on. On the Heltec V3 that
Stream is literally Serial1:
variants/heltec_v3/target.cpp r.18-21
#if ENV_INCLUDE_GPS
#include <helpers/sensors/MicroNMEALocationProvider.h>
MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock);
EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea);
Without ENV_INCLUDE_GPS a sensor manager without a location source is
created and the entire GPS code disappears from the build.
The enable pin and its four layers¶
A GPS receiver searching for satellites costs more current than the rest of the node put together. That is why it can be switched off. Which pin does that is determined in four steps:
src/helpers/sensors/MicroNMEALocationProvider.h r.8-18
#ifndef GPS_EN
#ifdef PIN_GPS_EN
#define GPS_EN PIN_GPS_EN
#else
#define GPS_EN (-1)
#endif
#endif
#ifndef PIN_GPS_EN_ACTIVE
#define PIN_GPS_EN_ACTIVE HIGH
#endif
If GPS_EN is already set somewhere, that wins. Otherwise PIN_GPS_EN
from the variant file. If that is missing too it becomes -1 and the
receiver cannot be switched off. The same cascade appears again for the
reset line, with LOW as the default active level instead of HIGH.
Those layers exist because some boards put their pins in a variant.h and
others in platformio.ini. Anyone adding a board and ending up with -1
notices it not in an error message but in the battery life.
On the RAK4631 it works differently again: there the GPS shares the power
switch WB_IO2 with other modules, and gpsIsAwake(WB_IO2) checks whether
it is already on (src/helpers/sensors/EnvironmentSensorManager.cpp
r.788). How such a shared rail is counted is in The Display.
Time is the second product¶
waitingTimeSync() and syncTime() are in the interface for a reason. A
node without an RTC does not know the time after a restart, and time is not
decoration in MeshCore: messages carry a timestamp and key exchange leans
on it. On such boards the GPS is the only source.
If an RTC is fitted, it is searched for separately on the I²C bus — four addresses, see The I²C Bus. The GPS is then the correction, not the source.
Sources¶
Firmware, commit 03b6ef4 (v1.16.0, 28 July 2026):
src/helpers/sensors/LocationProvider.h— the interfacesrc/helpers/sensors/MicroNMEALocationProvider.h— the serial implementation and the pin cascadesrc/helpers/sensors/EnvironmentSensorManager.cpp— the I²C route and the power switchvariants/heltec_v3/target.cpp— how one board creates the provider
Related in this documentation:
- The I²C Bus — GPS over I²C, and the clock
- The Display — the shared power rail
- GPS libraries — MicroNMEA and the u-blox library
- Node Matrix — which board has GPS
Translated from Dutch by Anthropic Claude