Whilst in the middle of something else (about which I shall no doubt witter on at great length on a later occasion) I found that I needed to set an Arduino’s system clock based upon NMEA data coming from a GPS receiver. No problem thinks I … there’s that handy example in the Time library in the arduino.cc playground – I’ll use that and be done in a jiffy.

Have you ever tried doing something in a jiffy? I can save you the bother and tell you it’s not as quick as you think. The example included in the download is TimeGPS.pde, but it’s a touch outdated now that Mikal Hart’s NewSoftSerial library has been rolled up into the core (since 1.0) and renamed SoftwareSerial. The problem I had was that, even after updating the library references, the thing just still didn’t work. The premise of operation is that you can set a function to be your “time sync provider” and that function will be invoked if either the system time hasn’t been set or the sync refresh interval has passed. That said, the sync provider function was being called once, before any GPS data had been received, so there was never going to be any date/time available, after which nothing else appeared to happen.

You'll be waiting a long time for this to do anything

Some time spent debugging successfully identified the problems, so for the delectation of anyone who happens to stumble across this entry before the download version is updated, I’ll let you in on what I discovered. An updated copy of the example sketch follows, containing the following modifications;

  • If you set ECHO_NMEA to true, you’ll be able to see the raw incoming data from the GPS receiver. This will quickly enable you to see if it’s the source data or your processing of it that’s giving you problems.
  • The sync provider function is invoked as the final part of the setSyncProvider() call, but in our case it will fail because we haven’t had any data from the GPS yet, because we’re still in setup()
  • When TinyGPS receives a valid NMEA string gps.encode() returns true, so we can use this as a trigger for the sync by calling now() if the system time has not yet been set.
  • Within the sync provider function, we first need to check that the fix_age is neither invalid nor stale. Once happy with that, we then check that the date and time held by TinyGPS are valid – I was experiencing problems because sometimes the first NMEA sentence received was a GPGGA, which doesn’t include date information, and other times the first sentence was a GPRMC, which does have date info. Only when we’re happy with the fix age, the date and the time can we then return a time value, from which the system clock will be set.

The fixed version gracefully handles the invalid data

/*
 * TimeGPS.pde
 * example code illustrating time synced from a GPS
 * 
 */

#include <Time.h>
#include <TinyGPS.h>       //http://arduiniana.org/libraries/TinyGPS/
#include <SoftwareSerial.h> 
// TinyGPS and SoftwareSerial libraries are the work of Mikal Hart

#define ECHO_NMEA false  // Set to false if you don't want to see the raw GPS data

TinyGPS gps; 
SoftwareSerial serial_gps(3, 2);  // receive on pin 3

const int offset = 1;   // offset hours from gps time (UTC)
time_t prevDisplay = 0; // when the digital clock was displayed

void setup()
{
  Serial.begin(9600);
  serial_gps.begin(9600);
  Serial.println("Waiting for GPS time ... ");
  setSyncProvider(gpsTimeSync);
}

void loop()
{
  char c;
  while (serial_gps.available()) 
  {
    // Echo the raw NMEA data, if required
    c = serial_gps.read();
    if (ECHO_NMEA) {
      Serial.print(c);
    }
    if (gps.encode(c)) {
      if (ECHO_NMEA) {
        Serial.println();
      }
      // If the time hasn't been set, retry now that we've received valid data from the GPS
      if (timeStatus() == timeNotSet) { 
        now();
      }
    }
  }
  if(timeStatus()!= timeNotSet) 
  {
     if( now() != prevDisplay) //update the display only if the time has changed
     {
       prevDisplay = now();
       digitalClockDisplay();  
     }
  }  
}

void digitalClockDisplay() {
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(" ");
  Serial.print(month());
  Serial.print(" ");
  Serial.print(year()); 
  Serial.println(); 
}

void printDigits(int digits) {
  // utility function for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if(digits < 10)
     Serial.print('0');   
  Serial.print(digits); 
}

time_t gpsTimeSync() {
  //  returns time if avail from gps, else returns 0
  unsigned long fix_age = 0 ;   
  unsigned long date, time;
  gps.get_datetime(&date, &time, &fix_age);
  // Ignore if fix age is invalid or stale
  if (fix_age == TinyGPS::GPS_INVALID_AGE || fix_age > 1000) {
    Serial.print("TimeSync failed due to "); Serial.print(fix_age == TinyGPS::GPS_INVALID_AGE ? "invalid" : "stale"); Serial.println(" fix age");
    return 0;
  }
  // Ignore if no valid date or time yet received, such as when GPGGA is received before a GPRMC, because there's no date in a GPGGA
  if (date == TinyGPS::GPS_INVALID_DATE || time == TinyGPS::GPS_INVALID_TIME) {
    Serial.print("TimeSync failed due to invalid "); Serial.println(date == TinyGPS::GPS_INVALID_DATE ? "date" : "time");
    return 0;
  }
  return gpsTimeToArduinoTime(); // return time only if updated recently by gps 
}

time_t gpsTimeToArduinoTime() {
  // returns time_t from gps date and time with the given offset hours
  tmElements_t tm;
  int year;
  gps.crack_datetime(&year, &tm.Month, &tm.Day, &tm.Hour, &tm.Minute, &tm.Second, NULL, NULL);
  tm.Year = year - 1970; 
  time_t time = makeTime(tm);
  return time + (offset * SECS_PER_HOUR);
}

I’ll link to this post on the support thread for the Time library in the Arduino forum – it’s not clear by whom or where the library and it’s examples are maintained, so I can’t just fork the Git repository.