0

I'm getting 10 updates/sec from my GPS unit (The MTK3339 used in the Adafruit breakout board), and using it to update a clock display. This works well enough, though there's one minor problem that I'd like to solve and one major one that I have to solve.

The minor issue first; I don't get an update every 100ms. I get one roughly every 100ms... plus or minus about 20-30ms. If a late and an early update arrive back-to-back right when the display goes from n to n+1 seconds, it's noticeable that it's not ticking at a regular cadence. It's not fatal, but the arythmic stutter of it is deeply painful to watch.

The major issue is - what do I do when the GPS can't find a signal? Maybe the device is driving through a tunnel for a minute or there's some other sky obstruction. I still want the clock to update in a best-guess sort of way.

You can't just update the seconds display and then, 1000 (arduino-measured) milliseconds later, update the clock if a new message hasn't arrived. 1000 real-world milliseconds can be anywhere from 700 - 1400ms measured by an arduino, depending on the board and the quality of the crystal. Worse yet, the discrepancy is far from consistent within a single crystal. Seeing the processor speed up and slow down by several percent over the course of a few minutes isn't uncommon.

So what I'd like is a mechanism that will constantly smooth the received (GPS) time to give a best-estimate of current actual time. I'm okay with it being a second or two off (at most) from real time, as long as the cadence feels like actual seconds, and a loss of GPS for a few minutes can be quickly corrected when the signal is re-acquired.

I'm somewhat aware of (strikethrough)DIP(/strikethrough) PID methods, but I'm not sure how to apply them here. It seems that someone must have solved the problem for the sake of syncing internet time, so I shouldn't be reinventing a solution.

  • why not use the Time library? – Juraj Sep 14 at 9:58
  • Re “[one true second] can be anywhere from 700 - 1400ms measured by an arduino”: Either your Arduino is physically broken, or you are doing something terribly wrong in your code. – Edgar Bonet Sep 14 at 12:36
  • Many things can interfere with accurate Arduino time counting; without context it's impossible to know why you're seeing such results--that's pretty far off of anything I've seen on pretty much any Arduino or clone. Why not add an RTC? – Dave Newton Sep 15 at 13:04
2

What you want, namely something to “smooth the received (GPS) time”, is called a GPS disciplined oscillator. Obviously, you are not after the high accuracy of commercial GPSDOs, but the working principle of what you are describing is essentially the same.

what do I do when the GPS can't find a signal?

In GPSDO lingo, this is called the “holdover” state. At this point you rely solely on your local oscillator (here, millis() or micros()), and steer it to correct for is natural drift, as determined while the GPS reception was good. Or you can go fancy, measure the chip temperature, and apply a temperature compensation.

I'm somewhat aware of DIP methods, but I'm not sure how to apply them here

I guess you mean “PID”.

You apply this to the phase of the oscillator. If the local oscillator is ahead of the GPS (positive phase error), you lower its frequency. For example, you could increment the seconds count every 1,000,100 microseconds (as measured by micros()), instead of every 1,000,000. This will make your clock a little bit slow, and resynchronize it with the GPS. Conversely, if your clock is behind the GPS, you slightly increase its frequency to make it run faster.

In other words, the error value of your PID is the phase error of your clock, and the control variable is the frequency adjustment. By applying the PID technique this way, you are building what is known as a phase-locked loop, or PLL. This is the standard way of implementing a GPSDO.

It seems that someone must have solved the problem for the sake of syncing internet time

Sure, but NTP implementations can be quite complex, as they have to deal with multiple servers having different round-trip delays and different levels of trust. I am not aware of any Arduino library that does anything similar. You may search for “time synchronization” or “PLL”. If you don't find any, you can pick a PID library and apply it to your oscillator.

A couple of final remarks:

  1. You wrote “speed up and slow down by several percent over the course of a few minutes isn't uncommon”. This shows there is something awfully wrong with either your Arduino or your code. Over a few minutes, you expect frequency variations of the order of one part per million on an Arduino clocked off a ceramic resonator, and a few parts per billion if yours is clocked off a crystal. See this experimental study on the stability of an Arduino clock. This stability problem is the very first thing you should investigate and fix.

  2. Many GPS modules have the option to give you a 1PPS (one pulse per second) signal. If you can use this signal, you may get a quite accurate measurement of your phase error. Relying on the NMEA sentences will give you lots of jitter, which you would then have to compensate for by setting the time constant of the PLL to a very long value.

0

An accuracy of 700-1400 ms seems like a big issue on your side. Usually it's like +- 10-20 ms. Are you able to provide your code? Or at least important parts of it? Is it possible that you used the delay function?

If so, you should definitely read into the millis function, and how to use it as a timer. This will significantly improve the "accuracy". Because upon calling the delay function, the chip not only waits a given amount of seconds. The chip also calls the yield function. This one is mostly used to make sure that all background work is done before continuing.

What also might me a problem is every type of modification of the chip clock. I've seen some code where people modify the frequency of the PWM-signal. This also affects the clock in every other way.

  • A ceramic resonator (like, e.g., on the Uno) is usually something like 0.1% off (1 ms per second). Worst case 0.5% off. A crystal is way better. – Edgar Bonet Sep 14 at 12:34
0

You can't just update the seconds display and then, 1000 (arduino-measured) milliseconds later, update the clock if a new message hasn't arrived.

Why not? I'd do just that:

  • Maintain an in-memory clock
  • Update whenever a GPS message arrives
  • Every 1000ms (by millis), tick the clock if a message hadn't arrived, and in any case, update the display.

Something like:

SimpleTimer Timers;
unsigned long MyClock_sec = 0;
bool clockIsTicked = false;

void setup()
{
   Timers.setInterval(10L,  GpsCallback);     // if msg, upd MyClock & set flag
   Timers.setInterval(1000, DisplayCallback); // if no flag, ++MyClock; upd display
}

void loop(){
   Timers.run();
}

void GpsCallback(void){
   // if gps message {
   //    MyClock = <<GPS_TIME>>;
   //    ++clockIsTicked;
   // }
}

void DisplayCallback(void){
   if( !clockIsTicked )
      ++MyClock;

   // update the display
   clockIsTicked = false;
}

(Not compiled or tested - consider it a pseudo-code outline).

0

What if you started with the most accurate Arduino clock you could build, then updated the time via GPS periodically? To do this, you’ll need an Arduino with a good quality crystal and the TimerOne library.

Example of a Pro Mini with good quality crystal

Pro Mini with good quality crystal

Using this sketch with a Pro Mini and TM1637 I2C clock module, I was able to build a clock that is "slow" by 45 seconds per year. No GPS or NTP required :)

// Start by commenting out the "Fine" Time Correction code
// and determining the "Coarse" Time Correction first.
#include <TimerOne.h>

int8_t secondCounter = 0;
int8_t minuteCounter = 0;
int8_t hourCounter = 12;
volatile int8_t oneSecondElapsed = 0;

// "Coarse" Time Correction.
// 1000000 SHOULD = 1 second. Adjust this value to compensate for a "fast"
// or "slow" oscillator. On my Pro Mini, 1000032 is 0.5 second too slow in
// 24 hrs, 1000000 is too fast by 1.5 seconds in 24 hrs. The "fix" is to
// alternate the number between 1000032 and 1000000, then restart the timer
// twice every minute.
// NOTE: Using the number 1000000 to 1000031 makes no difference because of
// the "granularity" of the calculation done in TimerOne.cpp, setPeriod().
// It's every 32 when the change will actually make a difference. Use multiples
// of 32 e.g. 999968, 1000000, 1000032, 1000064, 1000096, 1000128, etc.
unsigned long oneSecond = 1000032;

void setup(){

  Timer1.initialize(oneSecond);
  Timer1.attachInterrupt(oneSecondTimerISR);

  Serial.begin(9600);
  displayTime();

}

void loop(){

  // Update the time variables every second.
  if(oneSecondElapsed){

    // 0 to 60 seconds counter.
    secondCounter++;

    // "Fine" Time Correction.
    // Alternate between 16 seconds at 1000000 (faster than actual time),
    // and 44 seconds at 1000032 (slower than actual time).
    // 15 seconds and 45 seconds was slow by 1/2 second after 2 days.
    // 16 seconds and 44 seconds was slow by 1/2 second after 7 days.
    if(secondCounter == 14){
      oneSecond = 1000000; // Faster than actual time.
      Timer1.initialize(oneSecond);
    }
    else if(secondCounter == 30){
      oneSecond = 1000032; // Slower than actual time.
      Timer1.initialize(oneSecond);
    }

    // Time keeping variables.
    if(secondCounter > 59){
      secondCounter = 0;
      minuteCounter++;
      if(minuteCounter > 59){
        minuteCounter = 0;
        hourCounter++;
      }
      if(hourCounter > 23){
        hourCounter = 0;
      }
    }
    oneSecondElapsed = 0;
    displayTime();
  }
}

void oneSecondTimerISR(){
  oneSecondElapsed = 1;
}

void displayTime(){
  if(hourCounter < 10){
    Serial.print("0");
  }
  Serial.print(hourCounter);
  Serial.print(":");
  if(minuteCounter < 10){
    Serial.print("0");
  }
  Serial.print(minuteCounter);
  Serial.print(":");
  if(secondCounter < 10){
    Serial.print("0");
  }
  Serial.println(secondCounter);
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.