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.
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.
/* * 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.
The “Fixed TimeGPS.pde” has some serious errors in it beginning on line 75
void printDigits(int digits) is fine as far as it goes however there is another function call buried in line 75
Line 75: ERROR
if(digits 1000) {
Line 75: Properly parsed…
if(digits 1000)
{
and line 76:
Serial.print(“TimeSync failed due to “);
Serial.print(fix_age == TinyGPS::GPS_INVALID_AGE ? “invalid” : “stale”); Serial.println(” fix age”);
and lines 77 & 78
return 0;
}
Lines 80 – 97
time_t gpsTimeSync(){ THIS IS A RE DEFINITION OF LINE 75
// 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
}
You’re right Robert – thanks for pointing it out.
I’ve hopefully corrected it now and it certainly compiles, I just can’t test it at the minute as I’m away from any suitable hardware, so please do let me know if there’s still a problem.
The problem stems from the ridiculous amount of character escaping that must be done to have the code show up as you see it above. One misplaced character combination and WordPress seems to interpret it funnily and all hell breaks loose. I think this one was due to having a closing bracket and an opening brace right next to each other – put a space between them and everything’s back to normal. Finding a gizmo to take care of all of that for me is one of many on my big list of things to do.
Cheers, Stu