Rather than holding onto individual fields, the concept of a fix is used to group data members of the GPS acquisition into a C structure (a struct type called gps_fix). This also facilitates merging pieces received at different times (i.e., in separate sentences) into a single easy-to-use structure.
The main NMEAGPS gps; object you declare in your sketch parses received characters, gradually assembling a fix. Most programs will call gps.read() to obtain the completed fix structure (see Usage below).
Given a variable declaration of type gps_fix:
gps_fix fix;
...this fix variable (or any other variable of type gps_fix) contains the following members:
fix.status, a status codeenumvalues STATUS_NONE, STATUS_EST, STATUS_TIME_ONLY, STATUS_STD or STATUS_DGPS
- a location structure (i.e., latitude and longitude), accessed with
fix.latitudeL()andfix.longitudeL()for the higher-precision integer degrees, scaled by 10,000,000 (10 significant digits)fix.latitude()andfix.longitude()for the lower-precision floating-point degrees (~7 significant digits)- NOTE: these lat/lon values are
- positive for North or East degrees and negative for South or West degrees.
- stored in a 'fix.location' structure, like a 2D coordinate. The
location_tclass provides additional methods for distance, bearing and offset calculations, as described here.
fix.latitudeDMSandfix.latitudeDMSare structures (see DMS.h) that each containfix.longitudeDMS.degreesin integer degreesfix.latitudeDMS.degrees, in integer minutesfix.longitudeDMS.seconds_whole, in integer secondsfix.latitudeDMS.seconds_frac, in integer thousandths of a secondfix.latitudeDMS.secondsF(), in floating-point seconds- hemisphere indicator, accessed with
fix.longitudeDMS.hemisphere(enum values NORTH_H, SOUTH_H, EAST_H or WEST_H)fix.longitudeDMS.EW()(char valuesEorW)fix.latitudeDMS.NS()(char valuesNorS)
- NOTE: An integer degree value (scaled by 107 can be used to set the DMS structure by using
fix.latitudeDMS.From( otherLatitude );
- an altitude (above ellipsoid, aka Mean Sea Level), accessed with
fix.altitude_cm(), in integer centimetersfix.altitude(), in floating-point metersfix.alt.whole, in integer metersfix.alt.frac, in integer centimeters, to be added to the whole part
- a speed, accessed with
fix.speed_kph(), in floating-point kilometers per hourfix.speed_mph(), in floating-point miles per hourfix.speed(), in floating-point knots (nautical miles per hour)fix.speed_mkn(), in integer knots, scaled by 1000fix.spd.whole, in integer knotsfix.spd.frac, in integer thousandths of a knot, to be added to the whole part
- a heading, accessed with
fix.heading_cd(), in integer hundredths of a degreefix.heading(), in floating-point degrees
- velocity components in the North, East and Down directions, accessed with
fix.velocity_north, in integer cm/sfix.velocity_east, in integer cm/sfix.velocity_down, in integer cm/s
fix.hdop,fix.vdopandfix.pdop, in integer thousandths of the DOP.- Dilution of Precision is a unitless measure of the current satellite constellation geometry WRT how 'good' it is for determining a position. This is independent of signal strength and many other factors that may be internal to the receiver. It cannot be used to determine position accuracy in meters. Instead, use the LAT/LON/ALT error in cm members, which are populated by GST sentences.
- latitude, longitude and altitude error, accessed with
fix.lat_err_cm,fix.lon_err_cmandfix.alt_err_cm, in integer centimetersfix.lat_err(),fix.lon_err()andfix.alt_err(), in floating-point meters
- speed, heading and time errors, accessed with
fix.spd_err_mmps, in integer mm/sfix.hdg_errE5, in integer degrees * 100000fix.time_err_ns, in integer nanoseconds
or withfix.spd_err()in floating-point m/sfix.hdg_err()in floating-point degreesfix.time_err()in floating-point seconds
- geoid height above ellipsoid (see here for description), accessed with
fix.geoidHeight_cm, in integer centimetersfix.geoidHeight(), in floating-point metersfix.geoidHt.whole, in integer metersfix.geoidHt.frac, in integer centimeters to be added to the whole part
fix.satellites, a satellite count- a date/time structure (see Time.h), accessed with
fix.dateTime.year,fix.dateTime.month,fix.dateTime.date, the day-of-month,fix.dateTime.hours,fix.dateTime.minutes,fix.dateTime.seconds, andfix.dateTime.day, the day-of-the-week. This member is expensive to calculate, so it is uninitialized until you call theset_day()method. If you need the day-of-the-week, be sure to callset_daywhenever theyear,monthordatemembers are changed. In general, callfix.dateTime.set_day()wheneverfixis assigned (e.g.,fix = gps.read()).
Timeoperations allow converting to and from total seconds offset from a de facto starting time (e.g., an epoch date/time "origin"). There are constants in Time.h for NTP, POSIX and Y2K epochs. Simply change thestaticmemberss_epoch_yearands_epoch_weekdayin Time.h, and all date/time operations will be based on that epoch. This does not affect GPS times, but it will allow you to easily convert a GPS time to/from an NTP or POSIX time value (seconds).
The NMEAtimezone.ino example program shows how to convert the GPS time (UTC) into a local time. Basically, aTimestructure is converted to seconds (from the epoch start), then the time zone offset in seconds is added, and then the offset seconds are converted back to a time structure, with corrected day, month, year, hours and minutes members.
fix.dateTime_cs, in integer hundredths of a secondfix.dateTime_ms(), in millisecondsfix.dateTime_us(), in microseconds
- a collection of boolean
validflags for each of the above members, accessed withfix.valid.statusfix.valid.datefor year, month, day-of-monthfix.valid.timefor hours, minutes, seconds and centisecondsfix.valid.locationfor latitude and longitudefix.valid.altitudefix.valid.speedfix.valid.headingfix.valid.hdop,fix.valid.vdopandfix.valid.pdopfix.valid.lat_err,fix.valid.lon_errandfix.valid.alt_errfix.valid.geoidHeight
Because the GPS device may not have a fix, each member of a gps_fix can be marked as valid or invalid. That is, the GPS device may not know the lat/long yet. To check whether the fix member has been received, test the corresponding valid flag (described above). For example, to check if lat/long data has been received:
if (my_fix.valid.location) {
Serial.print( my_fix.latitude() );
Serial.print( ',' );
Serial.println( my_fix.longitude() );
}
You should also know that, even though you have enabled a particular member (see GPSfix_cfg.h), it may not have a value until the related NMEA sentence sets it. And if you have not enabled that sentence for parsing in NMEAGPS_cfg.h, it will never be valid.
There is additional information that is not related to a fix. Instead, it contains information about parsing or a Global Navigation Satellite System. GNSS's currently include GPS (US), GLONASS (Russia), Beidou (China) and Galileo (EU). The main NMEAGPS gps object you declare in your sketch contains:
gps.UTCsecondStart(), the Arduinomicros()value when the current UTC second startedgps.UTCms(), the number of milliseconds since the last received UTC time, calculated frommicros()andgps.UTCsecondStart.gps.UTCus(), the number of microseconds since the last received UTC time, calculated frommicros()andgps.UTCsecondStart.gps.nmeaMessage, the latest received message type. This is an ephemeral value, because multiple sentences are merged into onefixstructure. If you only check this after a complete fix is received, you will only see the LAST_SENTENCE_IN_INTERVAL.- enum values NMEA_GLL, NMEA_GSA, NMEA_GST, NMEA_GSV, NMEA_RMC, NMEA_VTG or NMEA_ZDA
gps.satellies[], an array of satellite-specific information, where each element containsgps.satellies[i].id, satellite IDgps.satellies[i].elevation, satellite elevation in 0-90 integer degreesgps.satellies[i].azimuth, satellite azimuth in 0-359 integer degreesgps.satellies[i].snr, satellite signal-to-noise ratio in 0-99 integer dBHzgps.satellies[i].tracked, satellite being tracked flag, a boolean
gps.sat_count, the number of elements in thegps.satellites[]arraygps.talker_id[], talker ID, a two-character array (not NUL-terminated)gps.mfr_id[], manufacturer ID, a three-character array (not NUL-terminated)- an internal fix structure,
gps.fix(). Most sketches should not usegps.fix()directly!
First, declare an instance of NMEAGPS:
NMEAGPS gps;
Next, tell the gps object to handle any available characters on the serial port:
void loop()
{
while (gps.available( gps_port )) {
The gps object will check if there are any characters available, and if so, read them from the port and parse them into its internal fix. Many characters will have to be read before the current fix is complete, so gps.available will return false until the fix is complete; the body of while loop will be skipped many times, and the rest of loop() will be executed.
When a fix is finally completed, gps.available will return true. Now your sketch can "read" the completed fix structure from the gps object:
void loop()
{
while (gps.available( gps_port )) {
gps_fix fix = gps.read();
The local fix variable now contains all the GPS fields that were parsed from the gps_port. You can access them as described above:
void loop()
{
while (gps.available( gps_port )) {
gps_fix fix = gps.read();
if (fix.valid.time) {
...
Note that the fix variable is local to that while loop; it cannot be accessed elsewhere in your sketch. If you need to access the fix information elsewhere, you must declare a global fix variable:
gps_fix currentFix;
void loop()
{
while (gps.available( gps_port )) {
currentFix = gps.read();
if (currentFix.valid.time) {
...
Any part of your sketch can use the information in currentFix.
Please note that the fix structure is much smaller than the raw character data (sentences). A fix is nominally 1/4 the size of one sentence (~30 bytes vs ~120 bytes). If two sentences are sent during each update interval, a fix could be 1/8 the size required for buffering two sentences.
In this fix-oriented program structure, the methods gps.available and gps.read are manipulating entire gps_fix structures. Multiple characters and sentences are used internally to fill out a single fix: members are "merged" from sentences into one fix structure (described here).
That program structure is very similar to the typical serial port reading loop:
void loop()
{
while (serial.available()) {
char c = serial.read();
... do something with the character ...;
}
However, the fix-oriented methods operate on complete fixes, not individual characters, fields or sentences.
Note: If you find that you need to filter or merge data with a finer level of control, you may need to use a different Merging option, Coherency, or the more-advanced Character-Oriented methods.
Some examples of accessing fix values:
gps_fix fix_copy = gps.read();
int32_t lat_10e7 = fix_copy.lat; // scaled integer value of latitude
float lat = fix_copy.latitude(); // float value of latitude
Serial.print( fix_copy.latDMS.degrees );
Serial.print( ' ' );
Serial.print( fix_copy.latDMS.minutes );
Serial.print( F("' " );
Serial.print( fix_copy.latDMS.seconds );
if (fix_copy.dateTime.month == 4) // test for the cruelest month
cry();
// Count how satellites are being received for each GNSS
for (uint8_t i=0; i < gps.sat_count; i++) {
if (gps.satellites[i].tracked) {
if (gps.satellites[i] . id <= 32)
GPS_satellites++;
if (gps.satellites[i] . id <= 64)
SBAS_satellites++;
if (gps.satellites[i] . id <= 96)
GLONASS_satellites++;
}
}
And some examples of accessing valid flags in a fix structure:
if (fix_copy.valid.location)
// we have a lat/long!
if (fix_copy.valid.time)
// the copy has hours, minutes and seconds
Here's an example for accessing the altitude
if (fix_copy.valid.altitude) {
z2 = fix_copy.altitude_cm();
vz = (z2 - z1) / dt;
z1 = z2;
// Note: if you only care about meters, you could also do this:
// z = fix_copy.alt.whole;
}
You can also check a collection of flags before performing a calculation involving multiple members:
if (fix_copy.valid.altitude && fix_copy.valid.date && fix_copy.valid.time) {
dt = (clock_t) fix_copy.dateTime - (clock_t) fix_copy.dateTime;
dz = fix_copy.alt.whole - last_alt; // meters
vz = dz / dt; // meters per second vertical velocity
}
Bonus: The compiler will optimize this into a single bit mask operation.
The example printing utility file, Streamers.cpp shows how to access each fix member and print its value.
Except for status, each of these gps_fix members is conditionally compiled; any, all, or no members can be selected for parsing, storing and merging. This allows you to configuring NeoGPS to use the minimum amount of RAM for the particular members of interest. See Configurations for how to edit GPSfix_cfg.h and NMEAGPS_cfg.h, respectively.
Integers are used for all members, retaining full precision of the original data.
gps_fix fix = gps.read();
if (fix.valid.location) {
// 32-bit ints have 10 significant digits, so you can detect very
// small changes in position:
d_lat = fix_copy.lat - last_lat;
}
Optional floating-point accessors are provided for many members.
if (fix_copy.valid.location) {
float lat = fix_copy.latitude();
// floats only have about 6 significant digits, so this
// computation is useless for detecting small movements:
foobar = (lat - target_lat);