PPS from Ultimate GPS synch with Arduino UNO

My immediate question is regarding syncing the Arduino's clock with the PPS output pin of a GPS. I have not found an example of code that clearly handles this and my own attempts thus far have been off the mark. I will explain more about my project in case my immediate question isn't relevant or somebody sees a smarter approach that doesn't require it.

I have an Adafruit ultimate GPS shield connected to an UNO. Using TinyGPS, I have been able to set the Arduino system clock using the serial data from the GPS. For my purposes, this is right on the edge of being usable, however serial is slow and I want more accuracy. I am looking to take it to the next step and set the clock using the PPS output of the GPS. My understanding is that it is possible to have a very accurate measurement of absolute time in doing so, but I am unsure about the best way to execute the code.

The end goal is to create multiple units that toggle a high current power source on and off at a specific interval, starting at a specific time, all referenced to absolute time because these units will be miles apart. I am using the built in LED to model this, however a MOSFET will be connected to the output in the final version.

The program flow should be (but isn't):

  1. initialize GPS and obtain a satellite lock. This causes the PPS pin to become active.
  2. set the system clock (and RTC clock when added) using the PPS on the GPS. Timezone needs to be adjusted for.
  3. once the correct system time has been set, find out what UNIX time it was at 7am on the present day. This is the beginTime function and returns the value as beginTime.
  4. starting at the beginTime, count up using (onTime + offTime) until one interval beyond now(). This is the countTime function and returns the value as beginTime.
  5. once now() = beginTime, the ledFunction is called and the light begins to blink on and off according to the "4 secs on, 2 secs off, starting at 7am" defined in setup.
  6. display the accurate time and some other stats on the LCD screen, provided refreshing the screen isn't messing up my timing accuracy.
  7. continue to adjust clock's accuracy using PPS
  8. once midnight rolls over and the day count increments, a new beginTime will be calculated. The MOSFET is to be held ON until the 7am start time and toggling begins again.

(attaching code in next post)

Here is my code thus far:

-I briefly had a Chronodot connected so some code is present/commented from that. I would like to use the Chronodot and see if I can synch up the square wave pin to the GPS PPS pin. This would allow me to continue to "blink" accurately even if the GPS lost signal. Any code examples would be great.
-I have a 20x4 LCD screen connected and use it for debugging. I realize it is quite slow and serial comments will not be in the final version as this needs to be quick.

//#include <Adafruit_MCP23008.h>
#include <LiquidCrystal.h>
#include <Wire.h>
#include <Time.h>
#include <TinyGPS.h>       // http://arduiniana.org/libraries/TinyGPS/
#include <SoftwareSerial.h>
#include <DS1307RTC.h>

SoftwareSerial SerialGPS = SoftwareSerial(8, 7);  //TX,RX
TinyGPS gps;
LiquidCrystal lcd(0);

// Offset hours from gps time (UTC)
//const int offset = 1;   // Central European Time
//const int offset = -5;  // Eastern Standard Time (USA)
//const int offset = -4;  // Eastern Daylight Time (USA)
//const int offset = -8;  // Pacific Standard Time (USA)
//const int offset = -7;  // Pacific Daylight Time (USA)
const int offset = -6;  //timezone where I am



tmElements_t tm;  //makes the makeTime function work I think (anyone?)


time_t prevDisplay = 0; // when the digital clock was displayed


unsigned long beginTime = 0;    //result of the beginTime function which determines UNIX start time
unsigned long countTime = 0;    //result of the countTime function which determines next ledAstate change

volatile unsigned long indexTime = 0;   //used for pps_interrupt function (not in use)
volatile unsigned long pps_start_ms;    //used for pps_interrupt function (not in use)

//Handles reults from GPS date/time crack
unsigned long age;
int Year;
byte Month, Day, Hour, Minute, Second, Hundredths;

//Used for blinking the light in ledFunction.  Will be for driving a MOSFET in final version.
byte ledAstate = HIGH;
byte ledAprevious = HIGH;
const byte ledApin = 13;

const int onTime = 4;   //time in seconds the MOSFET will be "ON".  User input between 0-10 in final version.
const int offTime = 2;   //time in seconds the MOSFET will be "OFF".  User input between 0-10 in final version.
const int beginHour = 7;    //24 hour reference (7 = 7am) for calculating beging UNIX time to feed the beginTime function.  User input final version.




void pps_interrupt() {
  
//Here is the interrupt from the PPS pin on the GPS.  I know I am catching the interrupt properly as I have tested it, however I am just not sure what code to insert here to synch the clock to the GPS PPS.  I have tried stuff like setTime = now() +1... I can see a few approaches to this but I would like some help with the best/better solution.  I am sure there is an existing example code somewhere, but I have either not been able to find it.//

}
void setup()
{
  Serial.begin(115200);
  SerialGPS.begin(9600);


  //  setSyncProvider(RTC.get);   // the function to get the time from the RTC
  //  if (timeStatus() != timeSet)
  //     Serial.println("Unable to sync with the RTC");
  //  else
  //     Serial.println("RTC has set the system time");

  pinMode(ledApin, OUTPUT);
  digitalWrite(ledApin, HIGH);

  pinMode(2, INPUT);
  attachInterrupt(0, pps_interrupt, RISING);

  // set up the LCD's number of rows and columns:
  lcd.begin(20, 4);
  lcd.setBacklight(HIGH);
  lcd.begin(20, 4);
  lcd.print("GPS Interruption:");
  lcd.setCursor(0, 1);
  lcd.print("Time:     :  :  .");
  //  lcd.setCursor(0, 2);
  //  lcd.print("Fix:");
  //  lcd.setCursor(0, 3);
  //  lcd.print("Bat:     V        mA");



  delay(1000);
}

void loop()
{

  //indexTime = RTC.get();

  while (SerialGPS.available()) {
    if (gps.encode(SerialGPS.read())) { // process gps messages
      // when TinyGPS reports new data...
      gps.crack_datetime(&Year, &Month, &Day, &Hour, &Minute, &Second, &Hundredths, &age);


      // set the Time to the latest GPS reading
      if (age < 300 ) {
        setTime(Hour, Minute, Second, Day, Month, Year);
        adjustTime(offset * SECS_PER_HOUR);

        Serial.print("Time set 1");
        Serial.println();


      }

    }
  }
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) {  //update the display only if the time has changed
      prevDisplay = now();       //store updated display for comparison
      //digitalClockDisplay();   //call serial clock display function
      lcdDisplay();              //call LCD display function
    }
  }

  ledFunction();
  beginFunction();
  countFunction();

}


void ledFunction()  {
  //Function to control the led pin state with interrupts disabled (is that a good idea or not?).  This will drive a MOSFET in the final version.  
  //  Serial.print("led Function Called");
  //  Serial.println();
  byte oldSREG = SREG;   // remember if interrupts are on or off
  noInterrupts ();   // turn interrupts off
  if ( now() == countTime) {
    ledAprevious = ledAstate;
    ledAstate = HIGH;
  }
  if (now() == (countTime - offTime)) {
    ledAprevious = ledAstate;
    ledAstate = LOW;
  }
  if (ledAstate != ledAprevious)  {
    digitalWrite(ledApin, ledAstate);
  }
  SREG = oldSREG;    // turn interrupts back on, if they were on before
}

void beginFunction() {
  //Function determines UNIX start point for 7am of current day
  //and returns value as beginTime
  //  Serial.print("begin Function Called");
  //  Serial.println();
  tm.Second = 0;
  tm.Hour = beginHour;
  tm.Minute = 0;
  tm.Day = day();
  tm.Month = month();
  tm.Year = year() - 1970;
  beginTime = makeTime(tm);
}



void countFunction() {
  //Function counts one cycle past now() to obtain countTime variable to feed the LED Function
  //  Serial.print("count Function Called");
  //  Serial.println();
  countTime = beginTime;
  while (countTime < now())  {
    countTime = (countTime + onTime + offTime);
  }

}

void lcdDisplay() {
  // set the cursor to column 0, line 1
  // (note: line 1 is the second row, since counting begins with 0):
  //  Serial.print("LCD Function Called");
  //  Serial.println();
  lcd.setCursor(19, 0);
  lcd.print(ledAstate);

  lcd.setCursor(8, 1);
  if (hour() < 10) {
    lcd.print("0 ");
    lcd.setCursor(9, 1);
    lcd.print(hour());
  }
  else {
    lcd.setCursor(8, 1);
    lcd.print(hour());
  }

  lcd.setCursor(11, 1);
  if (minute() < 10) {
    lcd.print("0 ");
    lcd.setCursor(12, 1);
    lcd.print(minute());
  }
  else {
    lcd.setCursor(11, 1);
    lcd.print(minute());
  }

  lcd.setCursor(14, 1);
  if (second() < 10) {
    lcd.print("0 ");
    lcd.setCursor(15, 1);
    lcd.print(second());
  }
  else {
    lcd.setCursor(14, 1);
    lcd.print(second());
  }

  lcd.setCursor(6, 2);
  lcd.print(RTC.get());
  lcd.setCursor(6, 3);
  lcd.print(now());
}


void digitalClockDisplay() {
  

}

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);
}

Oh my. I did that. It's tested and working as far as I can tell (my worst problem is not having a trustworthy source I can compare it to). But it's unpublished and in development. I will try to help out, but it's late here and I can't look at it now.

I have a complete project built and running - LED clock with RTC and Neo-6M GPS. There are some timing tricks that you have to pay attention to. The clock evaluates the GPS data and synchronizes the RTC time whenever the GPS is valid (and turns on an LED bar to indicate that). If GPS is lost, it ticks away happily on the RTC.

You may wonder, why would I want such a thing to be PPS accurate. Well, of course it doesn't, but I wanted it to be a base for future projects. Currently, the RTC PPS is only synchronized to the GPS PPS with about 30 microseconds delay (as I recall). So it's not all the way yet. But it's getting there.

Edit - okay, today I looked at your code. I see that you haven't begun to write any PPS code, so I can't really help much.

I've been using the PPS signal to measure oscillator frequency. I feed it into the input capture pin so I know to within a clock cycle when PPS goes high. The number of clocks between successive PPS pulses allows me to calculate the Uno's resonator frequency and I can use that in turn to calibrate against an addition signal. As long as the temperature isn't varying too quickly this works pretty well.

The GPS I'm using has an internal battery backed RTC which maintains the time quite well during relatively brief periods when the GPS loses fix. But the PPS signal goes away when there's no fix; I presume that's the sole reason for adding an external RTC. Rather than trying to synchronize the RTC square wave output to the PPS signal I think it makes more sense to simply determine the offset. You wouldn't even have to actually set the time/date in the RTC since the GPS provides that.

Here's a simple sketch I wrote to measure the frequency of the Uno's resonator. Maybe it will help you. To use this sketch just connect PPS to digital pin 8 (and also ground between the GPS and Uno) and the sketch measures the resonator frequency and error in parts per million, once each second.

// Check processor clock accuracy against GPS
//
// Connect the GPS to the Uno via 5V, GND.
// Connect the PPS signal to the input capture pin D8.

#define PPS_INPUT_PIN         8	  // 328 pin 14 (DIP), pin 12 (TQFP) (input capture)

// Pin operation macros
#define INPUT_PORT(pin)       (pin < 8 ? PIND : (pin < 14 ? PINB : PINC))
#define OUTPUT_PORT(pin)      (pin < 8 ? PORTD : (pin < 14 ? PORTB : PORTC))
#define PIN_MASK(pin)         (pin < 8 ? 1<<pin : (pin < 14 ? 1<<pin-8 : 1<<pin-14))
#define SET_PIN(pin, level)   (level == LOW ? (OUTPUT_PORT(pin) &= ~PIN_MASK(pin)) : (OUTPUT_PORT(pin) |= PIN_MASK(pin)))

volatile uint16_t captureTicks = 0;
volatile uint16_t captureRolls = 0;
volatile uint16_t rollovers = 0;
volatile bool captureFlag = false;

uint32_t prevCaptureTime = 0;

// =======================================================================================
ISR (TIMER1_CAPT_vect)
{
  captureTicks = ICR1;
  captureRolls = rollovers;
  captureFlag = true;
}

ISR (TIMER1_OVF_vect)
{
  rollovers++;
}
// =========================================================================================

void setup()
{
  Serial.begin(115200);
  Serial.println("\nHello\n");
  
  pinMode(PPS_INPUT_PIN, INPUT);
  // Set up timer 1 for input capture of PPS signal
  TCCR1A = 0;            // reset timer1
  TCCR1B = 0;             
  TCNT1 = 0;
  TCCR1B = bit(ICES1) | bit(CS10);        // no prescale; capture on rising edge
  TIMSK1 = bit(ICIE1) | bit(TOIE1);       // enable capture interrupt; enable overflow interrupt 
}

void loop()
{
  if (captureFlag) {
    uint16_t t = captureTicks;
    uint16_t r = captureRolls;
    captureFlag = false;
    
    uint32_t captureTime = (uint32_t(r) << 16) + uint32_t(t);
    uint32_t elapsed = captureTime - prevCaptureTime;
    prevCaptureTime = captureTime;
    Serial.print(elapsed);
    Serial.print(" cycles/sec, ");
    Serial.print(float(elapsed)/16.0 - 1000000.0, 1);
    Serial.print(" ppm");
    Serial.println();
  }
}

Check this out: KO7M - Ham Radio Blog: Arduino Due Timers (Part 1)

It gives quite a lot of detail about the timer counters on the Duo's ARM chip. Also the comments are quite relevant. There's some sample code in the comments that gets especially relevant to using GPS NMEA and PPS inputs to the board in order to do effective event timing.

aarg:
I have a complete project built and running - LED clock with RTC and Neo-6M GPS. There are some timing tricks that you have to pay attention to. The clock evaluates the GPS data and synchronizes the RTC time whenever the GPS is valid (and turns on an LED bar to indicate that). If GPS is lost, it ticks away happily on the RTC.

I currently work on the same topic, but did not reach 30ms precision with
my code

Could you share your code to me?

google w8bh clock. you want both PDFs. what you need is in the 2nd PDF, clock2.pdf. The first one contains much useful information

the attached code will get you a clock for an UNO 1 step more accurate than your standard Arduino GPS clock, but the code ties up 80% of the RAM with variables and defines.

do not twiddle and fiddle, anything you do to enhance the code will disrupt some other function. any addition cuts too deep into remaining RAM. if you need to add to this, move up to a MEGA.

I altered w8bhs method of handling the interrupt, to make initial lockup faster.

CLOCK_ONLY_WITH_LCD_UTC_MST_AND_STATUS.ino (18.4 KB)

Thanks for your answer. I had found that code before already, and my implementation is based on it.
But how precise is the routine? I still see deviations up to 1sec on my RTC (DS3231), depending on the moment it syncs with the GPS pps pulse. I need an algorithm which achieves ~200ms when syncing the RTC from GPS, based on pps pulse.