After wrestling with the terrible NMEA protocol for the nth time in my career, and realizing how many Arduino libraries are so wasteful of RAM, I decided to write a fully configurable GPS library, NeoGPS. It is positively stingy with RAM. Tight-fisted, even.
- The minimal NeoGPS configuration requires only 10 bytes
- The full NeoGPS configuration requires only 43 bytes
The best implementation I've found so far is TinyGPS, and it has been a great inspiration. It uses 120 bytes per instance plus another 60 bytes of character data. A similarly-configured NeoGPS requires 73 bytes.
TinyGPS is also a great example of how to support different program structures. Most people will use it in a polled fashion: check for serial byte, then process serial byte. But it also supports serial interrupt processing, even though the Arduino framework does not. (google for "Arduino Serial Interrupt" to get an idea of that general approach... it requires modifying HardwareSerial.cpp
)
Most libraries have extra buffers so that parts of the sentence can be parsed all at once. For example, an extra field buffer may hold on to all the characters between commas. That buffer is then parsed into a single data item, like heading
. Some libraries even hold on to the entire sentence before attempting to parse it. This also requires extra CPU time to copy the bytes and index through them... again.
-
NeoGPS parses each character immediately into the data item. When the delimiting comma is received, the data item has been fully computed and is marked as
valid
.Most libraries parse all fields of their selected sentences. Although most people use GPS for obtaining lat/long, some need only time, or even just one pulse-per-second. -
NeoGPS configures each item separately. Disabled items are conditionally compiled, which means they will not use any RAM, program space or CPU time.Most libraries treat sentences as independent from each other. A long-running frustration has been that multiple GPS sentences must be received in order to know all the information about the current fix: position, velocity, accuracy, etc.
-
NeoGPS can group information from multiple sentences into one fix.If you need a coherent fix, you must also correlate the sentences by time. By coherent, I mean that the Position at a particular time is grouped with the Velocity at that same time. BTW, Some sentences have time, some do not. Who thought this up, anyway?
-
NeoGPS can correlate multiple sentences into one coherent fix.Most libraries require float-point support. This can be caused by their data types, the use of
scanf
, or even distance/heading calculations. -
NeoGPS uses integer representations for all members, preserving their full accuracy.Optional accessors can convert the members to floating-point, if that's what you really need.
Furthermore, GPS device manufacturers have recognized the inherent deficiencies in the NMEA protocol, and frequently provide their own proprietary NMEA sentences or even their own protocol.
- Classes can be derived from NeoGPS to implement additional protocols or NMEA sentences.My particular device is the u-blox NEO-6M, so I have provided a derived class
ubloxNMEA
for its proprietary NMEA sentences. For most devices, it should be as simple as identifying the sentences in a table and parsing the specific types in parseField
I have also provided a second derived class ubloxGPS for the UBX protocol. This class shows how to chain multiple protocols from the same device. It also shows how to accumulate a fix as it is received, without any buffering.
Tradeoffs
There's a price for everything, hehe...
-
Parsing without buffers, or in place, means that you must be more careful about when you access data items.In general, you should wait to access the fix until after the entire sentence has been parsed (See
loop()
in NMEA.ino). Member functioncoherent()
can also be used to determine when it is safe. If you need to access the fix at any time, you will have to double-buffer the fix (See NMEAGPS.h comments regarding asafe_fix
). Also, data errors can cause invalid field values to be set before the CRC is fully computed. The CRC will catch most of those, and the fix members will be marked as invalid at that time. -
Configurability means that the code is littered with
#ifdef
sections.I've tried to increase white space and organization to make it more readable, but let's be honest... conditional compilation is ugly. -
Accumulating parts of a fix into group means knowing which parts are
valid
.Before accessing a part, you must check its valid flag. Fortunately, this adds only one bit per member. See GPSfix.cpp for an example of accessing every data member. -
Correlating timestamps for coherency means extra date/time comparisons for each sentence before it is fused.See NMEAfused.ino for code that determines when a new time interval has been entered.
-
Full C++ OO implementation is more advanced than most Arduino libraries, but it's is a good way to support future capabilities.You've been warned!
-
"fast, good, cheap... pick two."Although most of the RAM reduction is due to eliminating buffers, some of it is from trading RAM for additional code. And, as I mentioned, the readabilty (i.e., goodness) suffers from its configurability.
Well, if you're super constrained by RAM or need better performance or fix fusion, please take a look.
Cheers,
/dev
P.S. There are a few things I will be adding shortly: GPGST, GPGSA, and GPGSV sentences, along with their field types, and maybe some of the most popular proprietary sentences (with derived classes).
P.P.S. I am currently using 1.0.5