UBlox Neo 6M GPS time is 45 seconds behind

Gentlemen and ladies, I am using TinyGPS library to read the time from a UBlox Neo 6M GPS module. Running the gps_time example code coming with TinyGPS, the time is about 45 seconds behind that reported by: 1. A radio-controlled clock that sets itself to what I assume is the shortwave NIST/WWV signal from Colorado; 2. The time shown on my cell phone; 3. An app on my phone that claims to show GPS time 1, 4. My desktop workstation (shown in attached picture), a linux machine that sets its clock using network-time-protocol (NTP), 5. A server in a data-center in Texas running FreeBSD also setting itself by NTP. (all of the above are within a second or two of each other)

Did anyone have a similar experience? Does anyone have any explanation about the difference? (Could it be the software on the GPS module?) Thank you. FlorinC

The GPS module has to have a valid satellite fix before it can report the correct UTC time. Print out the $GPRMC sentence to check.

Thanks. The TinyGPS library makes sure that the GPS time string is valid before extracting the time. After getting a satellite fix (takes at least 2 minutes in my house), the time is off by tens of seconds. Also, I have 2 GPS clocks running the (almost) same code and the displayed time differs by 20 seconds between them. I suspect it's an internal software problem with the module (e.g. received strings are delayed?), but I wish someone else confirms my observation.

What does the $GPRMC sentence state?
The GPS system wouldn’t work at all if the time were wrong, even by a few nanoseconds.

This is what the valid sentence (used for setting up the time) looks like:

$GPRMC,225604.00,A,4345.46684,N,07925.17303,W,0.070,130816,A6E
$GPVTG,T,M,0.070,N,0.129,K,A
2E
$GPGGA,225604.00,4345.46$GPRMC,225605.00,A,4345.46590,N,07925.17325,W,0.078,130816,A65
$GPVTG,T,M,0.078,N,0.145,K,A
2C
$GPGGA,225605.00,4345.46590,N240,0,95022,

GPS TIME=22:56:06, GPS DATE=2016/08/13

And the relevant function:

boolean checkGPS()
{
  boolean newData = false;

  // for one second, parse GPS data for relevant time values;
  for (unsigned long start = millis(); millis() - start < 1000;)
  {
    while (ss.available())
    {
      char c = ss.read();
#ifdef _DEBUG_
      Serial.write(c);
#endif
      if (gps.encode(c)) // Did a new valid sentence come in?
        newData = true;
    }
  }

  if (newData)
  {
    float flat, flon;
    unsigned long age, fix_age;
    int gpsyear;
    byte gpsmonth, gpsday, gpshour, gpsminute, gpssecond, hundredths;

    gps.f_get_position(&flat, &flon, &age);
    gps.crack_datetime(&gpsyear, &gpsmonth, &gpsday, &gpshour, &gpsminute, &gpssecond, &hundredths, &fix_age);

    // estimate time zone from longitude;
    int8_t gpstimezone = round(flon/15);

#ifdef _DEBUG_
    char buf[50] = {0};
    sprintf(buf, "GPS TIME=%02d:%02d:%02d, GPS DATE=%4d/%02d/%02d", gpshour, gpsminute, gpssecond+1, gpsyear, gpsmonth, gpsday);
    Serial.println();
    Serial.println(buf);
    Serial.print("Timezones from GPS data is ");
    Serial.println(gpstimezone);
#endif

    // determine if the time is off (and therefore must be set);
    if (gpsminute != minute || gpssecond != second)
    {
      minute = gpsminute;
      second = gpssecond;

      if (gpsAutoHour)
      {
        // automatically set the hour based on timezone/longitude;
        hour = gpshour + gpstimezone;

        // set to summer time if DST is selected;
        if (doDST && isSummerTime())
        {
#ifdef _DEBUG_
          Serial.println("Adjusting time for summer (adding an hour)");
#endif
          hour++;
        }
      }

      setTime(hour, minute, second);
#ifdef _DEBUG_
      Serial.print("RTC synced from GPS to ");
      Serial.print(hour);
      Serial.print(":");
      Serial.print(minute);
      Serial.print(":");
      Serial.println(second);
#endif
    }
  }

  return newData;
}

$GPRMC,225604.00,A,4345.46684,N,07925.17303,W,0.070,,130816,,,A*6E

And what was the [u]actual UTC time[/u] when that sentence was emitted, for example, as presented by http://www.timeanddate.com/worldclock/timezone/utc ?

I’m not sure I understand this (since I am not very familiar with GPS and NMEA sentences). I thought (probably wrongly) that the UTC time is included in the string.
How do I determine the actual UTC time when the sentence was emitted?
Thanks again.

Edited:
OK, I see you added a link to the UTC time, thanks.

The GPRMC sentence quoted was valid, and the time field "225604.00" [u]should contain[/u] the correct, current UTC time, accurate to about 1 second (since it takes time to print the message, etc.).

Update: I just checked with one of my GPS modules, and the time that it reports is within 1 second of the time reported for UTC by timeanddate.com

Thanks for bearing with me (I rarely brainstorm by myself :).
Here is an interesting one:

$GPRMC,235433.00,A,4345.43925,N,07925.17177,W,0.326,130816,A69
$GPVTG,T,M,0.326,N,0.603,K,A
21
$GPGGA,235433.00,4345.43$GPRMC,235535.00,A,4345.43844,N,07925.16937,W,0.270,130816,A67
$GPVTG,T,M,0.270,N,0.500,K,A
23
$GPGGA,235535.00,4345.438,$100,
32G70
GPS TIME=23:55:36, GPS DATE=2016/08/13
Timezones from GPS data is -5
RTC synced from GPS to 19:55:35

It looks like the TinyGPS library (that I am currently using) returns the time from the GPGGA line (235535), which differs from the GPRMC time (235433) by about a minute.

This may be the problem.

The previous NMEA sentence I captured had the GPGGA and GPRMC times almost identical (1 sec difference).

That explains it and is a bit of a surprise to me. The GGA sentence tells you when [u]the fix was taken[/u], which is NOT the current time.

However, TinyGPS also reports the "age of the fix" which is evidently what it wants to know.

It appears that TinyGPS reports the GPS time from the last valid sentence received, which in this case is not a good idea.

A segment of the TinyGPS code follows. It would be easy to change this behavior by commenting out the _time = _new_time; in the GGA case.

     switch(_sentence_type)
        {
        case _GPS_SENTENCE_GPRMC:
          _time      = _new_time;
          _date      = _new_date;
          _latitude  = _new_latitude;
          _longitude = _new_longitude;
          _speed     = _new_speed;
          _course    = _new_course;
          break;
        case _GPS_SENTENCE_GPGGA:
          _altitude  = _new_altitude;
          _time      = _new_time;
          _latitude  = _new_latitude;
          _longitude = _new_longitude;
          _numsats   = _new_numsats;
          _hdop      = _new_hdop;
          break;

Thanks very much for your help. I also found a bug in the DS1307 library I was using, where the start() resets the seconds.

All a learning experience!

Cheers

jremington: That explains it and is a bit of a surprise to me. The GGA sentence tells you when the fix was taken, which is NOT the current time.

Hmm, I'm not sure that explains it. All GPS sentences in the same update interval will have the same UTC fix time (also called "current time"). From the sample data,

$GPRMC,235433.00,A,4345.43925,N,07925.17177,W,0.326,,130816,,,A*69 $GPVTG,,T,,M,0.326,N,0.603,K,A*21 $GPGGA,235433.00,4345.43*$GPRMC*,235535.00,...

...it appears to be dropping GPS characters after the 3rd sentence starts, after 127 bytes. The GPGGA is truncated, and a 2nd GPRMC starts. The next GPGGA is total nonsense:

$GPGGA,235535.00,4345.438,,$100,, 32G70

Whut? Not only that, the next GPRMC sentence is 1 minute later (the CS is correct). From just this output, I would have guessed that there are delays or "other work" that caused GPS characters to get dropped.

This is the classic symptom for trying to print too much. @florinc, I cannot find a "gps_time.ino" example program for TinyGPS. I certainly can't find anything that outputs those messages. Could you post your program, please?

And if you're up to it, I would suggest trying my library, NeoGPS. Many users run into the "dropping characters" problem with the TinyGPS examples. The NeoGPS examples are structured quite differently and are much more robust. You should get rock-solid GPS times with the first example, NMEA.ino, from both the GGA and the RMC sentences. NeoGPS is also smaller, faster and configurable to parse only the messages and fields that you really use.

If not, I wouldn't patch that particular symptom (it's not a bug) in TinyGPS until you really know what's causing it. I suspect there's something about your sketch timing.

Cheers, /dev

All GPS sentences in the same update interval will have the same UTC fix time (also called "current time").

Sorry but I don't buy this. The module I tested does print out different times for RMC and GGA sentences. I imagine that this behavior is manufacturer-specific.

I agree that there is a printing problem.

Mr. /dev, thanks. I posted the function that displays the GPS sentence in reply #4, please take a look. I will try your NeoGPS library, thanks for that too.

The module I tested does print out different times for RMC and GGA sentences.

o_O Could I ask what brand, what baud rate, and were the times different by 1 second or a fraction? They usually send the “solution” time, which would be the received GPS time. If they are sending the RTC time, what were they thinking? :stuck_out_tongue: That would make it dependent on baud rate.

I posted the function that displays the GPS sentence in reply #4

Ok, not the whole sketch, but it does reveal the problem. Sorry I missed that.

If you read the Troubleshooting section, you may remember that the GPS device emits a batch of sentences consecutively, once per update interval (usually 1 second). Then there is a “quiet time” until the next interval begins. For example, if the GPS is running at 9600 (I can’t see that :frowning: ), then receiving 3 sentences of ~200 chars will take about 200ms. Then the quiet time will be about 800ms, until the next set of sentences begin.

What’s happening is that you are taking a 1-second sample of the character data, perhaps 45 seconds apart (I can’t see what else you are doing :frowning: ). In the first call to checkGPS, you may be getting the last two sentences. 45 seconds later, you read some more during the GPS quiet time and maybe get one new sentence. This mixes old sentences with new sentences, or is “incoherent”, time-wise (see this page).

If you are doing other things that prevent the GPS data from being read (I can’t see that :frowning: ), you have a few choices:

1) Read for at least 2 seconds, and make sure that you see the GPS quiet time begin: no characters will available for >10ms or so (depends on baud rate and GPS configuration commands, which I can’t see :frowning: ).

2) Read and parse all the time (e.g., in loop)to make sure the GPS values have the latest and greatest values, but only use them in checkGPS

3) Use a more robust GPS library with interrupt-driven parsing. :slight_smile:

For option 1, you could use NeoGPS like this in checkGPS:

boolean checkGPS()
{
  uint8_t fixes = 0;
  gps_fix fix;

  // for a few seconds, parse GPS data for relevant time values;
  unsigned long start = millis();
  do {

    if (gps.available( ss )) {
      // A new fix is available, merged together from all 3 sentences.
      fix = gps.read();

      // skip invalid (empty) fixes and the first partial fix
      if ((fix.valid.date && fix.valid.time && fix.valid.location) &&
          (fixes++ > 0)) { 

        // estimate time zone from longitude;
        int8_t gpstimezone = fix.longitudeL()/150000000L; // integer degrees * 10,000,000

        #ifdef _DEBUG_
          Serial.print( F("GPS DATE/TIME=") );
          Serial << fix.dateTime;   // output format provided by Time.H
          Serial.print( F("\nTimezones from GPS data is ") );
          Serial.println( gpstimezone );
        #endif

        // determine if the time is off (and therefore must be set);
        if ((fix.dateTime.minutes != minute) || (fix.dateTime.seconds != second))
        {
          minute = fix.dateTime.minutes;
          second = fix.dateTime.seconds;

          if (gpsAutoHour)
          {
            // automatically set the hour based on timezone/longitude;
            hour = fix.dateTime.hours + gpstimezone;

            // set to summer time if DST is selected;
            if (doDST && isSummerTime())
            {
              #ifdef _DEBUG_
                Serial.println( F("Adjusting time for summer (adding an hour)") );
              #endif
              hour++;
            }

            // Offsetting hour can push it outside the range 0..23
            if (hour > 23)
              hour -= 24;
            else if (hour < 0)
              hour += 24;
          }

          setTime(hour, minute, second);

          #ifdef _DEBUG_
            Serial.print( F("RTC synced from GPS to ") );
            if (hour < 10) Serial.print( '0' );
            Serial.print(hour);
            Serial.print(":");
            if (minute < 10) Serial.print( '0' );
            Serial.print(minute);
            Serial.print(":");
            if (second < 10) Serial.print( '0' );
            Serial.println(second);
          #endif
        }

        // Used the second complete fix, all done!
        return true;
      }

    }

  } while (millis() - start < 5000UL);

  return false;
}

For option 2, you could use NeoGPS like this:

void loop()
{
  // Read all the time, keeping the fix up-to-date
  if (gps.available( ss ))
    fix = gps.read();

  // Sync the RTC every 5 seconds when we have good data
  if ((fix.valid.date && fix.valid.time && fix.valid.location) &&
      (millis() - lastCheck > 5000UL)) {
    lastCheck = millis();
    checkGPS();
  }
}

The checkGPS routine would be almost the same, except for the beginning (no while/if tests):

void checkGPS()
{
  // estimate time zone from longitude;
  int8_t gpstimezone = fix.longitudeL()/150000000L;

  #ifdef _DEBUG_
    Serial.print( F("GPS DATE/TIME=") );
       ...

For option 3, you would need the one of the Neo–Serial libraries. I strongly recommend using pins 8 and 9 for the GPS device so you can use AltSoftSerial, but I suspect you are using SoftwareSerial. For this option, you would need NeoSWSerial:

NeoSWSerial ss( ?, ? );

void gpsISR( uint8_t c )
{
  gps.handle( c );
}

void setup()
{
  Serial.begin( 9600 );
  Serial.println( F("florinc") );

  ss.attachInterrupt( gpsISR );
  ss.begin( 9600 );

  lastCheck = millis();
}


void loop()
{
  // Keep reading the fixes as the ISR makes them
  if (gps.available())
    fix = gps.read();

SoftwareSerial blocks interrupts for long periods of time; NeoSWSerial is much more efficient, and allows handling the received characters during the interrupt. AltSoftSerial is better than either. Use it for option 1 or 2, or use NeoICSerial for option 3.

Cheers,
/dev