Using frequency of recieving messages from GPS module as time reference

I need to calculate time passed up to 24h, with maximum error, let's say 0.5%. millis() would probably be accurate enough for this (or not?) but in my code, it would be more convenient to use frequency of receiving messages form GPS module by UART (1Hz). Assuming that i will always "catch" the data in main loop, process what i need and skip the rest (whole thing is taking much less than 1 second), incrementing a variable every time data is available will give me number of seconds passed. Example code:

setup() {
  //......
  Serial.setTimeout(20);
}
loop() {
  if(Serial.available()) {
    secondsPassed++;
    //do something with data on serial
    char tmp;
    while (Serial.readBytes(tmp, 1)) {}; //skip the rest of data in current gps message
  }
}

How accurate could it be?
GPS module is neo-6m

Is there a reason why you don't want to use the GPS time information? GPS units are used in stationary project in order to make use of the time output.

I bet the 1Hz output frequency is very reliable but it won't be better than the GPS time data.

Is there a reason why you don't want to use the GPS time information?

Because it will require additional processing. I haven't been thinking much about this, but I would probably need to parse time and data string to numbers, include that day and even year could change during the counted time (i guess i would need to use information about days in month etc., or not?).

If all you need is 0.5% accuracy you're probably fine with millis(). What you propose will also likely work reasonably well. But the sketch you posted won't. The while loop will exit prematurely.

Parsing an NMEA string to obtain the date/time and producing a 32-bit number from it won't require that many cycles on average.

Many GPS modules feature an LED that blinks once per second when a valid fix is present, or a 1PPS output pin. You can tap into either and use the pulse as an extremely accurate time marker (good to a nanosecond or so per second).

It is simple to use that pulse to count CPU clock cycles per second, and work out a correction factor for the Arduino time base. Here is a program to do just that, modified from one by Nick Gammon. It counts the number of CPU clock cycles per pulse, which should be around 16000000. Note that the Arduino clock is temperature sensitive, so calibration must be ongoing, or everything maintained at constant temperature.

I used a UBlox LEA6H module, and tapped into the blue LED line for the 1PPS input. Be sure to connect the module and Arduino grounds! Naturally, you must have either a very thin roof overhead, or be out of doors for this to work.

// Frequency timer using input capture unit
// Author: Nick Gammon
// Date: 31 August 2013
// Input: Pin D8 
// mods for 1 PPS input, and output average CPU clock cycles per second, over 10 second interval. J Remington


volatile boolean first;
volatile boolean triggered;
volatile unsigned int overflowCount;
volatile unsigned long startTime;
volatile unsigned long finishTime;

// timer overflows (every 65536 counts)
ISR (TIMER1_OVF_vect) 
{
  overflowCount++;
}  // end of TIMER1_OVF_vect

ISR (TIMER1_CAPT_vect)
  {
  // grab counter value before it changes any more
  unsigned int timer1CounterValue;
  timer1CounterValue = ICR1;  // see datasheet, page 117 (accessing 16-bit registers)
  unsigned long overflowCopy = overflowCount;
  
  // if just missed an overflow
  if ((TIFR1 & bit (TOV1)) && timer1CounterValue < 0x7FFF)
    overflowCopy++;
  
  // wait until we noticed last one
  if (triggered)
    return;

  if (first)
    {
    startTime = (overflowCopy << 16) + timer1CounterValue;
    first = false;
    return;  
    }
    
  finishTime = (overflowCopy << 16) + timer1CounterValue;
  triggered = true;
  TIMSK1 = 0;    // no more interrupts for now
  }  // end of TIMER1_CAPT_vect
  
void prepareForInterrupts ()
  {
  noInterrupts ();  // protected code
  first = true;
  triggered = false;  // re-arm for next time
  // reset Timer 1
  TCCR1A = 0;
  TCCR1B = 0;
  
  TIFR1 = bit (ICF1) | bit (TOV1);  // clear flags so we don't get a bogus interrupt
  TCNT1 = 0;          // Counter to zero
  overflowCount = 0;  // Therefore no overflows yet
  
  // Timer 1 - counts clock pulses
  TIMSK1 = bit (TOIE1) | bit (ICIE1);   // interrupt on Timer 1 overflow and input capture
  // start Timer 1, no prescaler
  TCCR1B =  bit (CS10) | bit (ICES1);  // plus Input Capture Edge Select (rising on D8)
  interrupts ();
  }  // end of prepareForInterrupts
  

void setup () 
  {
  Serial.begin(115200);       
  Serial.println("Frequency Counter");
 
  pinMode(8,INPUT_PULLUP);
  pinMode(7,OUTPUT);
  digitalWrite(7,LOW);
  pinMode(13,OUTPUT);
  digitalWrite(13,HIGH);


  // set up for interrupts
  prepareForInterrupts ();   
  } // end of setup

void loop () 
  {
    static unsigned long average=0;
    static int n=0;
  // wait till we have a reading
  if (!triggered)
    return;
 
  PINB |= (1<<5); //blink LED
  
  // period is elapsed time
  unsigned long elapsedTime = finishTime - startTime;
  
  Serial.println (elapsedTime);
  average += elapsedTime;
  n++;
  if(n == 10) {
    Serial.print("CPU cycles, average of ten intervals: ");
    Serial.println(average/10);
    n=0;
    average=0;
  }


  // so we can read it  
  delay (500);

  prepareForInterrupts ();   
}   // end of loop

Hi,
What is the application that needs all this parsing and accuracy?

Tom.... :slight_smile:

jboyton:
But the sketch you posted won't. The while loop will exit prematurely.

Why would it? If buffer gets empty it will wait 20ms for next char, isn't it enough?

jremington:
Many GPS modules feature an LED that blinks once per second when a valid fix is present, or a 1PPS output pin. You can tap into either and use the pulse as an extremely accurate time marker (good to a nanosecond or so per second).

As far as i know my module has configurable time pulse output, but the problem is it's on another PCB and only VCC, GND, RX and TX are lead out to goldpins.

TomGeorge:
What is the application that needs all this parsing and accuracy?

I need to save location data, and continuously display current speed, average speed and distance. I don't want average speed and distance data to be much different than this data recalculated (on a PC for example) more precisely basing on saved location data.

As far as i know my module has configurable time pulse output, but the problem is it’s on another PCB and only VCC, GND, RX and TX are lead out to goldpins.

Please post a link to your module.

cattledog:
Please post a link to your module.

https://www.openimpulse.com/blog/products-page/product-category/gy-neo6mv2-gps-module/

As far as i know my module has configurable time pulse output, but the problem is it's on another PCB and only VCC, GND, RX and TX are lead out to goldpins.

Yep, Pin 3. Add a wire.

dawidmt:
Why would it? If buffer gets empty it will wait 20ms for next char, isn't it enough?

What 20ms delay?

I misread the code thinking you were using read() instead of readBytes(). As written it won't exit the while loop prematurely. The opposite will happen. The readBytes function waits up to one second for a byte before timing out. Unless you have your GPS programmed to output at less 1Hz you'll be stuck in that while loop.

It's a minor point though. You would have figured it out soon enough.

The only problem with using the PPS signal as the sole source of timing is that it might disappear if your GPS loses fix. It does that on mine. If you have any significant outages your clock will stop during that period.

The GPS sentences keep coming however. And the internal RTC keeps them pretty accurate. At least that's how my GPS works.

jboyton:
What 20ms delay?

I used Serial.setTimeout(20) in setup.

jboyton:
The only problem with using the PPS signal as the sole source of timing is that it might disappear if your GPS loses fix. It does that on mine.

When mine doesn't have fix it sends messages, just without data.

dawidmt:
I used Serial.setTimeout(20) in setup.

I missed that too.

dawidmt:
When mine doesn't have fix it sends messages, just without data.

Which is why what you're proposing would probably work.

Sometimes my GPS sends extra messages (when it has lost fix) that are in between seconds. That could just be a feature of the module I have. And it wouldn't add that many extra ticks, certainly not enough to throw it off by 0.5%.

Given the tiny amount of code and processing time required to convert a GPS message into a number I wonder what it is you're doing that leaves you with so little flash and execution bandwidth.

jboyton:
Given the tiny amount of code and processing time required to convert a GPS message into a number I wonder what it is you're doing that leaves you with so little flash and execution bandwidth.

I think just don't need to do it if i had simpler solution that will be working fine. And about flash, i'm using 128x64 OLED display (display movement data - speed, distance etc.), sd card (write all location data), bluetooth (transfer data to another device) and i know i will use majority if not almost all the flash memory (arduino uno).

I need to save location data, and continuously display current speed, average speed and distance. I don’t want average speed and distance data to be much different than this data recalculated (on a PC for example) more precisely basing on saved location data.

Well, there’s the NeoGPS library. You can configure it to only parse location fields (not date/time field, not alt, etc.) from just one sentence (RMC?). It’s smaller and faster than other libraries, especially with this configuration. You’d have to calculate the average speed and distance using the Haversine formula, which would pull in the floating-point library.

Or you could parse speed as well, and use that to calculate distance. You could do this with integer math and save a few K of program space.

The speed will wander a little, just like the location. I’m not sure you’d get a different calculated speed on a PC, just using locations. The ublox has an internal “model” you can choose that helps it filter out some of the wandering.

Cheers,
/dev

dawidmt:
I think just don’t need to do it if i had simpler solution that will be working fine. And about flash, i’m using 128x64 OLED display (display movement data - speed, distance etc.), sd card (write all location data), bluetooth (transfer data to another device) and i know i will use majority if not almost all the flash memory (arduino uno).

So it isn’t execution time, it’s flash that you’re concerned about. I can relate to that mode where you’re worried about every byte. But since you’re already processing GPS fields you likely will have most of the code there anyway. I’ll bet your trick will save you less than 100 bytes. Maybe that’s worth it to you, I can’t say.

/dev:
Well, there's the NeoGPS library. You can configure it to only parse location fields (not date/time field, not alt, etc.) from just one sentence (RMC?). It's smaller and faster than other libraries, especially with this configuration. You'd have to calculate the average speed and distance using the Haversine formula, which would pull in the floating-point library.

Or you could parse speed as well, and use that to calculate distance. You could do this with integer math and save a few K of program space.
/dev

Thanks, buy I already did parsing using only Serial object - it seemed simple, so i wasn't looking for any library. There is time and date in RMC, but need to parse little further to get altitude (i don't use it for calculations, just save it) and speed. I've also already used Haversine formula.
I thought that using speed to calculate distance could be inaccurate, but i don't really know how much.