For the sake of fun and learning, I'm trying to make the most accurate wall clock I possibly can, driven by GPS. I'm starting with a u-blox ZOE-M8Q breakout board I got from SparkFun, and a Raspberry Pi Pico microcontroller.
At a glance, things appear to work just fine. The GPS receiver ACKs my config messages, it sends the messages I want, the checksums match, the reported time and location seem close to right, the validity flags are good, it sees about 11 satellites, and the PPS signal is close to aligned with the top of the second (comparing by eye with my NTP-synced computer).
I want to time events with better resolution than a second, so I have the microcontroller counting the ticks of its 125 MHz clock between PPS pulses. I'm using a PIO state machine to do this, and can achieve a resolution of two clock cycles (the counter ticks at 62.5 MHz, and then I multiply by 2). As expected, the number of ticks per PPS pulse varies slightly. There's high frequency noise of about +/- 4 clock cycles, and gradual (over the course of hours) drift of +/- 200 clock cycles or so, but overall the cycle count per PPS pulse is remarkably close to 125,000,000. So far, so good.
Section 9.2 (Navigation Epochs) of the receiver description states:
when selecting the next navigation epoch, the receiver will always try to use the 1 kHz clock tick which it estimates to be closest to the desired fix period as measured in GNSS system time. Consequently the number of 1 kHz clock ticks between fixes will occasionally vary (so when producing one fix per second, there will normally be 1000 clock ticks between fixes, but sometimes, to correct drift away from GNSS system time, there will be 999 or 1001).
This is where the problem starts. Based on the above description, I expect to see two phenomena occasionally happen at the same time:
- The reported time in a UBX-NAV-TIMEGPS message will be ~999 ms or ~1001 ms after the previous time, instead of the usual ~1000 ms.
- The actual elapsed time measured in 125 MHz ticks of the microcontroller's clock between the previous two PPS pulses will be ~999 ms or ~1001 ms, instead of the usual ~1000 ms.
In actual fact, I observe phenomenon #1, but not phenomenon #2. I have double checked this with a logic analyzer to rule out a bug in my code. The actual elapsed time between pulses does not change when there is a 1 ms discontinuity in the reported times. This means that either the pulses before the discontinuity do not have accurate timestamps reported in the UBX-NAV-TIMEGPS message, or the pulses after the discontinuity do not (or more likely both are inaccurate). The UBX-NAV-TIMEGPS message is reporting a Time Accuracy Estimate of under 40 nanoseconds, so these results should be impossible.
Here is some data I captured. pps_duration is the number of 125 MHz cycles I counted, and the other fields are as described for a UBX-NAV-TIMEGPS message on page 397 of the receiver description. On that page, it specifies that "The precise GPS time of week in seconds is: (iTOW * 1e-3) + (fTOW * 1e-9)
". You can see the discontinuity in the reported time when fTOW goes negative, but pps_duration does not budge.
pps_duration week iTOW fTOW tAcc leapS valid
125000008 2225 79723000 493880 32 18 0x07
125000008 2225 79724000 494480 32 18 0x07
125000010 2225 79725000 495079 32 18 0x07
125000010 2225 79726000 495677 32 18 0x07
125000008 2225 79727000 496276 32 18 0x07
125000008 2225 79728000 496877 32 18 0x07
125000008 2225 79729000 497476 32 18 0x07
125000012 2225 79730000 498075 33 18 0x07
125000006 2225 79731000 498676 33 18 0x07
125000008 2225 79732000 499276 33 18 0x07
125000008 2225 79733000 499876 34 18 0x07
125000008 2225 79734001 -499524 34 18 0x07
125000010 2225 79735000 -498924 34 18 0x07
125000008 2225 79736000 -498324 34 18 0x07
125000008 2225 79737000 -497724 35 18 0x07
125000006 2225 79738000 -497123 35 18 0x07
125000008 2225 79739000 -496523 35 18 0x07
125000006 2225 79740000 -495921 35 18 0x07
125000010 2225 79741000 -495321 35 18 0x07
125000006 2225 79742000 -494722 36 18 0x07
125000008 2225 79743000 -494122 36 18 0x07
125000006 2225 79744000 -493522 36 18 0x07
125000006 2225 79745000 -492923 36 18 0x07
I had my logic analyzer running at the same time, and it saw the same thing (T0 marks the time of the pulse just before the first message reporting a negative fTOW).
Here is a graph of pps_duration and fTOW over time.
Obviously I have misunderstood what this chip does, or I have misconfigured it in some way. Can you help me see where I have gone wrong? Can you suggest additional experiments I should perform? Do I need to buy one of the "Time & Frequency Sync products" mentioned in the receiver description, instead of the ZOE-M8Q?