diff --git a/Adafruit_GPS.cpp b/Adafruit_GPS.cpp index 4f84a8a..7e90722 100755 --- a/Adafruit_GPS.cpp +++ b/Adafruit_GPS.cpp @@ -76,85 +76,36 @@ boolean Adafruit_GPS::parse(char *nmea) { } else { return false; } - int32_t degree; - long minutes; - char degreebuff[10]; // look for a few common sentences + char *p = nmea; + if (strStartsWith(nmea, "$GPGGA")) { // found GGA - char *p = nmea; // get time p = strchr(p, ',')+1; - float timef = atof(p); - uint32_t time = timef; - hour = time / 10000; - minute = (time % 10000) / 100; - seconds = (time % 100); - - milliseconds = fmod(timef, 1.0) * 1000; + parseTime(p); // parse out latitude p = strchr(p, ',')+1; - if (',' != *p) - { - strncpy(degreebuff, p, 2); - p += 2; - degreebuff[2] = '\0'; - degree = atol(degreebuff) * 10000000; - strncpy(degreebuff, p, 2); // minutes - p += 3; // skip decimal point - strncpy(degreebuff + 2, p, 4); - degreebuff[6] = '\0'; - minutes = 50 * atol(degreebuff) / 3; - latitude_fixed = degree + minutes; - latitude = degree / 100000 + minutes * 0.000006F; - latitudeDegrees = (latitude-100*int(latitude/100))/60.0; - latitudeDegrees += int(latitude/100); - } - + parseLat(p); p = strchr(p, ',')+1; - if (',' != *p) - { - if (p[0] == 'S') latitudeDegrees *= -1.0; - if (p[0] == 'N') lat = 'N'; - else if (p[0] == 'S') lat = 'S'; - else if (p[0] == ',') lat = 0; - else return false; - } + if(!parseLatDir(p)) return false; // parse out longitude p = strchr(p, ',')+1; - if (',' != *p) - { - strncpy(degreebuff, p, 3); - p += 3; - degreebuff[3] = '\0'; - degree = atol(degreebuff) * 10000000; - strncpy(degreebuff, p, 2); // minutes - p += 3; // skip decimal point - strncpy(degreebuff + 2, p, 4); - degreebuff[6] = '\0'; - minutes = 50 * atol(degreebuff) / 3; - longitude_fixed = degree + minutes; - longitude = degree / 100000 + minutes * 0.000006F; - longitudeDegrees = (longitude-100*int(longitude/100))/60.0; - longitudeDegrees += int(longitude/100); - } - + parseLon(p); p = strchr(p, ',')+1; - if (',' != *p) - { - if (p[0] == 'W') longitudeDegrees *= -1.0; - if (p[0] == 'W') lon = 'W'; - else if (p[0] == 'E') lon = 'E'; - else if (p[0] == ',') lon = 0; - else return false; - } - + if(!parseLonDir(p)) return false; + p = strchr(p, ',')+1; if (',' != *p) { fixquality = atoi(p); + if(fixquality > 0){ + fix = true; + lastFix = recvdTime; + } else + fix = false; } p = strchr(p, ',')+1; @@ -183,86 +134,29 @@ boolean Adafruit_GPS::parse(char *nmea) { } return true; } + if (strStartsWith(nmea, "$GPRMC")) { - // found RMC - char *p = nmea; - + // found RMC // get time p = strchr(p, ',')+1; - float timef = atof(p); - uint32_t time = timef; - hour = time / 10000; - minute = (time % 10000) / 100; - seconds = (time % 100); - - milliseconds = fmod(timef, 1.0) * 1000; + parseTime(p); + // fix or no fix p = strchr(p, ',')+1; - // Serial.println(p); - if (p[0] == 'A') - fix = true; - else if (p[0] == 'V') - fix = false; - else - return false; + if(!parseFix(p)) return false; // parse out latitude p = strchr(p, ',')+1; - if (',' != *p) - { - strncpy(degreebuff, p, 2); - p += 2; - degreebuff[2] = '\0'; - long degree = atol(degreebuff) * 10000000; - strncpy(degreebuff, p, 2); // minutes - p += 3; // skip decimal point - strncpy(degreebuff + 2, p, 4); - degreebuff[6] = '\0'; - long minutes = 50 * atol(degreebuff) / 3; - latitude_fixed = degree + minutes; - latitude = degree / 100000 + minutes * 0.000006F; - latitudeDegrees = (latitude-100*int(latitude/100))/60.0; - latitudeDegrees += int(latitude/100); - } - + parseLat(p); p = strchr(p, ',')+1; - if (',' != *p) - { - if (p[0] == 'S') latitudeDegrees *= -1.0; - if (p[0] == 'N') lat = 'N'; - else if (p[0] == 'S') lat = 'S'; - else if (p[0] == ',') lat = 0; - else return false; - } + if(!parseLatDir(p)) return false; // parse out longitude p = strchr(p, ',')+1; - if (',' != *p) - { - strncpy(degreebuff, p, 3); - p += 3; - degreebuff[3] = '\0'; - degree = atol(degreebuff) * 10000000; - strncpy(degreebuff, p, 2); // minutes - p += 3; // skip decimal point - strncpy(degreebuff + 2, p, 4); - degreebuff[6] = '\0'; - minutes = 50 * atol(degreebuff) / 3; - longitude_fixed = degree + minutes; - longitude = degree / 100000 + minutes * 0.000006F; - longitudeDegrees = (longitude-100*int(longitude/100))/60.0; - longitudeDegrees += int(longitude/100); - } - + parseLon(p); p = strchr(p, ',')+1; - if (',' != *p) - { - if (p[0] == 'W') longitudeDegrees *= -1.0; - if (p[0] == 'W') lon = 'W'; - else if (p[0] == 'E') lon = 'E'; - else if (p[0] == ',') lon = 0; - else return false; - } + if(!parseLonDir(p)) return false; + // speed p = strchr(p, ',')+1; if (',' != *p) @@ -284,16 +178,68 @@ boolean Adafruit_GPS::parse(char *nmea) { day = fulldate / 10000; month = (fulldate % 10000) / 100; year = (fulldate % 100); + lastDate = recvdTime; } - // we dont parse the remaining, yet! return true; } + if (strStartsWith(nmea, "$GPGLL")) { - // found GLL - char *p = nmea; - + // found GLL // parse out latitude p = strchr(p, ',')+1; + parseLat(p); + p = strchr(p, ',')+1; + if(!parseLatDir(p)) return false; + + // parse out longitude + p = strchr(p, ',')+1; + parseLon(p); + p = strchr(p, ',')+1; + if(!parseLonDir(p)) return false; + + // get time + p = strchr(p, ',')+1; + parseTime(p); + + // fix or no fix + p = strchr(p, ',')+1; + if(!parseFix(p)) return false; + + return true; + } + + // we dont parse the remaining, yet! + return false; +} + +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for time + @param p Pointer to the location of the token in the NMEA string +*/ +/**************************************************************************/ +void Adafruit_GPS::parseTime(char *p) { + // get time + float timef = atof(p); + uint32_t time = timef; + hour = time / 10000; + minute = (time % 10000) / 100; + seconds = (time % 100); + + milliseconds = fmod(timef, 1.0) * 1000; + lastTime = recvdTime; +} + +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for latitude angle + @param p Pointer to the location of the token in the NMEA string +*/ +/**************************************************************************/ +void Adafruit_GPS::parseLat(char *p) { + int32_t degree; + long minutes; + char degreebuff[10]; if (',' != *p) { strncpy(degreebuff, p, 2); @@ -310,8 +256,16 @@ boolean Adafruit_GPS::parse(char *nmea) { latitudeDegrees = (latitude-100*int(latitude/100))/60.0; latitudeDegrees += int(latitude/100); } +} - p = strchr(p, ',')+1; +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for latitude direction + @param p Pointer to the location of the token in the NMEA string + @return True if we parsed it, false if it has invalid data +*/ +/**************************************************************************/ +boolean Adafruit_GPS::parseLatDir(char *p) { if (',' != *p) { if (p[0] == 'S') latitudeDegrees *= -1.0; @@ -320,9 +274,19 @@ boolean Adafruit_GPS::parse(char *nmea) { else if (p[0] == ',') lat = 0; else return false; } + return true; +} - // parse out longitude - p = strchr(p, ',')+1; +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for longitude angle + @param p Pointer to the location of the token in the NMEA string +*/ +/**************************************************************************/ +void Adafruit_GPS::parseLon(char *p) { + int32_t degree; + long minutes; + char degreebuff[10]; if (',' != *p) { strncpy(degreebuff, p, 3); @@ -339,8 +303,16 @@ boolean Adafruit_GPS::parse(char *nmea) { longitudeDegrees = (longitude-100*int(longitude/100))/60.0; longitudeDegrees += int(longitude/100); } +} - p = strchr(p, ',')+1; +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for longitude direction + @param p Pointer to the location of the token in the NMEA string + @return True if we parsed it, false if it has invalid data +*/ +/**************************************************************************/ +boolean Adafruit_GPS::parseLonDir(char *p) { if (',' != *p) { if (p[0] == 'W') longitudeDegrees *= -1.0; @@ -349,30 +321,59 @@ boolean Adafruit_GPS::parse(char *nmea) { else if (p[0] == ',') lon = 0; else return false; } + return true; +} - // get time - p = strchr(p, ',')+1; - float timef = atof(p); - uint32_t time = timef; - hour = time / 10000; - minute = (time % 10000) / 100; - seconds = (time % 100); - - milliseconds = fmod(timef, 1.0) * 1000; - - p = strchr(p, ',')+1; - // Serial.println(p); - if (p[0] == 'A') +/**************************************************************************/ +/*! + @brief Parse a part of an NMEA string for whether there is a fix + @param p Pointer to the location of the token in the NMEA string + @return True if we parsed it, false if it has invalid data +*/ +/**************************************************************************/ +boolean Adafruit_GPS::parseFix(char *p) { + if (p[0] == 'A'){ fix = true; + lastFix = recvdTime; + } else if (p[0] == 'V') fix = false; else return false; - return true; - } +} - return false; +/**************************************************************************/ +/*! + @brief Time in seconds since the last position fix was obtained. Will + fail by rolling over to zero after one millis() cycle, about 6-1/2 weeks. + @return float value in seconds since last fix. +*/ +/**************************************************************************/ +float Adafruit_GPS::secondsSinceFix() { + return (millis()-lastFix) / 1000.; +} + +/**************************************************************************/ +/*! + @brief Time in seconds since the last GPS time was obtained. Will fail + by rolling over to zero after one millis() cycle, about 6-1/2 weeks. + @return float value in seconds since last GPS time. +*/ +/**************************************************************************/ +float Adafruit_GPS::secondsSinceTime() { + return (millis()-lastTime) / 1000.; +} + +/**************************************************************************/ +/*! + @brief Time in seconds since the last GPS date was obtained. Will fail + by rolling over to zero after one millis() cycle, about 6-1/2 weeks. + @return float value in seconds since last GPS date. +*/ +/**************************************************************************/ +float Adafruit_GPS::secondsSinceDate() { + return (millis()-lastDate) / 1000.; } /**************************************************************************/ @@ -423,6 +424,7 @@ char Adafruit_GPS::read(void) { //Serial.println("----"); lineidx = 0; recvdflag = true; + recvdTime = millis(); // time we got the end of the string } return c; diff --git a/Adafruit_GPS.h b/Adafruit_GPS.h old mode 100755 new mode 100644 index 2e2f056..4ee45c6 --- a/Adafruit_GPS.h +++ b/Adafruit_GPS.h @@ -55,11 +55,15 @@ #define PMTK_SET_BAUD_57600 "$PMTK251,57600*2C" ///< 57600 bps #define PMTK_SET_BAUD_9600 "$PMTK251,9600*17" ///< 9600 bps -#define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the second sentence (GPRMC) -#define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn on GPRMC and GGA -#define PMTK_SET_NMEA_OUTPUT_GGAONLY "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the GGA +#define PMTK_SET_NMEA_OUTPUT_GLLONLY "$PMTK314,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the GPGLL sentence +#define PMTK_SET_NMEA_OUTPUT_RMCONLY "$PMTK314,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the GPRMC sentence +#define PMTK_SET_NMEA_OUTPUT_VTGONLY "$PMTK314,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on only the GPVTG +#define PMTK_SET_NMEA_OUTPUT_GGAONLY "$PMTK314,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the GPGGA +#define PMTK_SET_NMEA_OUTPUT_GSAONLY "$PMTK314,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the GPGSA +#define PMTK_SET_NMEA_OUTPUT_GSVONLY "$PMTK314,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0*29" ///< turn on just the GPGSV +#define PMTK_SET_NMEA_OUTPUT_RMCGGA "$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn on GPRMC and GPGGA #define PMTK_SET_NMEA_OUTPUT_ALLDATA "$PMTK314,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn on ALL THE DATA -#define PMTK_SET_NMEA_OUTPUT_OFF "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn off output +#define PMTK_SET_NMEA_OUTPUT_OFF "$PMTK314,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*28" ///< turn off output // to generate your own sentences, check out the MTK command datasheet and use a checksum calculator @@ -115,6 +119,9 @@ class Adafruit_GPS { char read(void); boolean parse(char *); + float secondsSinceFix(); + float secondsSinceTime(); + float secondsSinceDate(); boolean wakeup(void); boolean standby(void); @@ -168,6 +175,18 @@ class Adafruit_GPS { uint8_t LOCUS_percent; ///< Log life used percentage private: + void parseTime(char *); + void parseLat(char *); + boolean parseLatDir(char *); + void parseLon(char *); + boolean parseLonDir(char *); + boolean parseFix(char *); + // Make all of these times far in the past by setting them near the middle of the + // millis() range. Timing assumes that sentences are parsed promptly. + uint32_t lastFix = 2000000000L; // millis() when last fix received + uint32_t lastTime = 2000000000L; // millis() when last time received + uint32_t lastDate = 2000000000L; // millis() when last date received + uint32_t recvdTime = 2000000000L; // millis() when last full sentence received boolean paused; uint8_t parseResponse(char *response); diff --git a/examples/GPS_HardwareSerial_Timing/.uno.test.skip b/examples/GPS_HardwareSerial_Timing/.uno.test.skip new file mode 100644 index 0000000..e69de29 diff --git a/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino b/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino new file mode 100644 index 0000000..d5d1493 --- /dev/null +++ b/examples/GPS_HardwareSerial_Timing/GPS_HardwareSerial_Timing.ino @@ -0,0 +1,136 @@ +// Test code for Ultimate GPS Using Hardware Serial (e.g. GPS Flora or FeatherWing) +// +// This code is similar to GPS_HardwareSerial_Parsing, except for the additional +// elements to keep track of how long it has been since time and fix data have been +// received. This approach lets you keep an up to date clock based on GPS time at +// any time in between GPS fixes. +// +// This code shows how to listen to the GPS module via polling. Best used with +// Feathers or Flora where you have hardware Serial and no interrupt +// +// Tested and works great with the Adafruit GPS FeatherWing +// ------> https://www.adafruit.com/products/3133 +// or Flora GPS +// ------> https://www.adafruit.com/products/1059 +// but also works with the shield, breakout +// ------> https://www.adafruit.com/products/1272 +// ------> https://www.adafruit.com/products/746 +// +// Pick one up today at the Adafruit electronics shop +// and help support open source hardware & software! -ada + +#include + +// what's the name of the hardware serial port? +#define GPSSerial Serial1 + +// Connect to the GPS on the hardware port +Adafruit_GPS GPS(&GPSSerial); + +// Set GPSECHO to 'false' to turn off echoing the GPS data to the Serial console +// Set to 'true' if you want to debug and listen to the raw GPS sentences +#define GPSECHO true + +uint32_t timer = millis(); + + +void setup() +{ + //while (!Serial); // uncomment to have the sketch wait until Serial is ready + + // connect at 115200 so we can read the GPS fast enough and echo without dropping chars + // also spit it out + Serial.begin(115200); + Serial.println("Adafruit GPS library basic test!"); + + // 9600 NMEA is the default baud rate for Adafruit MTK GPS's- some use 4800 + GPS.begin(9600); + // uncomment this line to turn on RMC (recommended minimum) and GGA (fix data) including altitude + GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCGGA); + // uncomment this line to turn on only the "minimum recommended" data + //GPS.sendCommand(PMTK_SET_NMEA_OUTPUT_RMCONLY); + // For parsing data, we don't suggest using anything but either RMC only or RMC+GGA since + // the parser doesn't care about other sentences at this time + // Set the update rate (uncomment the one you want.) + //GPS.sendCommand(PMTK_SET_NMEA_UPDATE_1HZ); // 1 Hz update rate + //GPS.sendCommand(PMTK_SET_NMEA_UPDATE_200_MILLIHERTZ); // 5 second update time + GPS.sendCommand(PMTK_SET_NMEA_UPDATE_100_MILLIHERTZ); // 10 second update time + // For the parsing code to work nicely and have time to sort thru the data, and + // print it out we don't suggest using anything higher than 1 Hz + + // Request updates on antenna status, comment out to keep quiet + GPS.sendCommand(PGCMD_ANTENNA); + + delay(1000); + + // Ask for firmware version + GPSSerial.println(PMTK_Q_RELEASE); +} + +void loop() // run over and over again +{ + // read data from the GPS in the 'main loop' + char c = GPS.read(); + // if you want to debug, this is a good time to do it! + if (GPSECHO) + if (c) Serial.print(c); + // if a sentence is received, we can check the checksum, parse it... + if (GPS.newNMEAreceived()) { + // a tricky thing here is if we print the NMEA sentence, or data + // we end up not listening and catching other sentences! + // so be very wary if using OUTPUT_ALLDATA and trytng to print out data + //Serial.println(GPS.lastNMEA()); // this also sets the newNMEAreceived() flag to false + if (!GPS.parse(GPS.lastNMEA())) // this also sets the newNMEAreceived() flag to false + return; // we can fail to parse a sentence in which case we should just wait for another + } + // if millis() or timer wraps around, we'll just reset it + if (timer > millis()) timer = millis(); + + // approximately every 2 seconds or so, random intervals, print out the current stats + static unsigned nextInterval = 2000; + if (millis() - timer > nextInterval) { + timer = millis(); // reset the timer + nextInterval = 1500 + random(1000); + // Time in seconds keeps increasing after we get the NMEA sentence. + // This estimate will lag real time due to transmission and parsing delays, + // but the lag should be small and should also be consistent. + float s = GPS.seconds + GPS.milliseconds/1000. + GPS.secondsSinceTime(); + int m = GPS.minute; + int h = GPS.hour; + int d = GPS.day; + // Adjust time and day forward to account for elapsed time. + // This will break at month boundaries!!! Humans will have to cope with April 31,32 etc. + while(s > 60){ s -= 60; m++; } + while(m > 60){ m -= 60; h++; } + while(h > 24){ h -= 24; d++; } + // ISO Standard Date Format, with leading zeros https://xkcd.com/1179/ + Serial.print("\nDate: "); + Serial.print(GPS.year+2000, DEC); Serial.print("-"); + if(GPS.month < 10) Serial.print("0"); + Serial.print(GPS.month, DEC); Serial.print("-"); + if(d < 10) Serial.print("0"); + Serial.print(d, DEC); + Serial.print(" Time: "); + if(h < 10) Serial.print("0"); + Serial.print(h, DEC); Serial.print(':'); + if(m < 10) Serial.print("0"); + Serial.print(m, DEC); Serial.print(':'); + if(s < 10) Serial.print("0"); + Serial.println(s, 3); + Serial.print("Fix: "); Serial.print((int)GPS.fix); + Serial.print(" quality: "); Serial.println((int)GPS.fixquality); + Serial.print("Times [s] since last fix: "); Serial.print(GPS.secondsSinceFix(),3); + Serial.print(", GPS time: "); Serial.print(GPS.secondsSinceTime(),3); + Serial.print(", GPS date: "); Serial.println(GPS.secondsSinceDate(),3); + if (GPS.fix) { + Serial.print("Location: "); + Serial.print(GPS.latitude, 4); Serial.print(GPS.lat); + Serial.print(", "); + Serial.print(GPS.longitude, 4); Serial.println(GPS.lon); + Serial.print("Speed (knots): "); Serial.println(GPS.speed); + Serial.print("Angle: "); Serial.println(GPS.angle); + Serial.print("Altitude: "); Serial.println(GPS.altitude); + Serial.print("Satellites: "); Serial.println((int)GPS.satellites); + } + } +}