Linux Raspberry Pi

Microsecond accurate NTP with a Raspberry Pi and PPS GPS


Lots of acronyms in that title. If I expand them out, it says – “microsecond accurate network time protocol with a Raspberry Pi and pulse per second global positioning system [receiver]”. What it means is you can get super accurate timekeeping (1 microsecond = 0.000001 seconds) with a Raspberry Pi and a GPS receiver (the GT-U7 is less than $12) that spits out pulses every second. By following this guide, you will your very own Stratum 1 NTP server at home!

Why would you need time this accurate at home?

You don’t. There aren’t many applications for this level of timekeeping in general, and even fewer at home. But this blog is called Austin’s Nerdy Things so here we are. Using standard, default internet NTP these days will get your computers to within a 10-20 milliseconds of actual time (1 millisecond = 0.001 seconds). By default, Windows computers get time from MacOS computers get time from Linux devices get time from [entity], like PPS gets you to the next SI prefix in terms of accuracy (milli -> micro), which means 1000x more accurate timekeeping.

YouTube video link

If you prefer a video version –

Materials needed


0 – Update your Pi and install packages

This NTP guide assumes you have a Raspberry Pi ready to go. If you don’t, I have a 16 minute video on YouTube that goes through flashing the SD card and initial setup –

You should update your Pi to latest before basically any project. We will install some other packages as well. pps-tools help us check that the Pi is receiving PPS signals from the GPS module. We also need GPSd for the GPS decoding of both time and position. I use chrony instead of NTPd because it seems to sync faster than NTPd in most instances and also handles PPS without compiling from source (the default Raspbian NTP doesn’t do PPS) Installing chrony will remove ntpd.

sudo apt update
sudo apt upgrade
sudo rpi-update
sudo apt install pps-tools gpsd gpsd-clients python-gps chrony

1 – add GPIO and module info where needed

In /boot/config.txt, add ‘dtoverlay=pps-gpio,gpiopin=18’ to a new line. This is necessary for PPS. If you want to get the NMEA data from the serial line, you must also enable UART and set the initial baud rate.

sudo bash -c "echo '# the next 3 lines are for GPS PPS signals' >> /boot/config.txt"
sudo bash -c "echo 'dtoverlay=pps-gpio,gpiopin=18' >> /boot/config.txt"
sudo bash -c "echo 'enable_uart=1' >> /boot/config.txt"
sudo bash -c "echo 'init_uart_baud=9600' >> /boot/config.txt"

In /etc/modules, add ‘pps-gpio’ to a new line.

sudo bash -c "echo 'pps-gpio' >> /etc/modules"


sudo reboot

2 – wire up the GPS module to the Pi

I used the Adafruit Ultimate GPS breakout. It has 9 pins but we are only interested in 5. There is also the Adafruit GPS hat which fits right on the Pi but that seems expensive for what it does (but it is significantly neater in terms of wiring).

Pin connections:

  1. GPS PPS to RPi pin 12 (GPIO 18)
  2. GPS VIN to RPi pin 2 or 4
  3. GPS GND to RPi pin 6
  4. GPS RX to RPi pin 8
  5. GPS TX to RPi pin 10
  6. see 2nd picture for a visual
Adafruit Ultimate GPS Breakout V3
We use 5 wires total. GPS PPS to pin 12 (GPIO 18), GPS VIN to pin 2 or 4, GPS GND to pin 6, GPS RX to pin 8, GPS TX to pin 10.
GPS with wires attached to the board (but not to the Pi) and the antenna. The antenna has a SMA connector, and there is another adapter that is SMA to u.fl to plug into the GPS board.

Now place your GPS antenna (if you have one) in a spot with a good view of the sky. If you don’t have an antenna, you might have to get a bit creative with how you locate your Raspberry Pi with attached GPS.

I honestly have my antenna in the basement (with only the kitchen and attic above) and I generally have 8-10 satellites locked all the time (11 as of writing). Guess that means the antenna works better than expected! Either way, better exposure to the sky will in theory work better. Pic:

My super awesome placement of the GPS antenna on top of the wood “cage” (?) I built to hold my 3d printer on top of my server rack. I guess this is a testimony for how well the GPS antenna works? It has 11 satellites locked on as of writing, with a HDOP of 0.88. The components in this rack (Brocade ICX6450-48P, 1U white box with Xeon 2678v3/96GB memory/2x480GB enterprise SSDs/4x4TB HDDs, Dell R710 with 4x4TB and other stuff) will be detailed in an upcoming post.

2.5 – free up the UART serial port for the GPS device

Run raspi-config -> 3 – Interface options -> I6 – Serial Port -> Would you like a login shell to be available over serial -> No. -> Would you like the serial port hardware to be enabled -> Yes.

Finish. Yes to reboot.

3 – check that PPS is working

First, check that the PPS module is loaded:

lsmod | grep pps

The output should be like:

[email protected]:~ $ lsmod | grep pps
pps_gpio               16384  0
pps_core               16384  1 pps_gpio

Second, check for the PPS pulses:

sudo ppstest /dev/pps0

The output should spit out a new line every second that looks something like this (your output will be a bit farther from x.000000 since it isn’t yet using the GPS PPS):

[email protected]:~ $ sudo ppstest /dev/pps0
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1618799061.999999504, sequence: 882184 - clear  0.000000000, sequence: 0
source 0 - assert 1618799062.999999305, sequence: 882185 - clear  0.000000000, sequence: 0
source 0 - assert 1618799063.999997231, sequence: 882186 - clear  0.000000000, sequence: 0
source 0 - assert 1618799064.999996827, sequence: 882187 - clear  0.000000000, sequence: 0
source 0 - assert 1618799065.999995852, sequence: 882188 - clear  0.000000000, sequence: 0

4 – change GPSd to start immediately upon boot

Edit /etc/default/gpsd and change GPSD_OPTIONS=”” to GPSD_OPTIONS=”-n” and change DEVICES=”” to DEVICES=”/dev/ttyS0 /dev/pps0″, then reboot. My full /etc/default/gpsd is below:

[email protected]:~ $ sudo cat /etc/default/gpsd
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time

# Use USB hotplugging to add new USB devices automatically to the daemon

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="/dev/ttyS0 /dev/pps0"

# Other options you want to pass to gpsd
sudo reboot

4.5 – check GPS for good measure

To ensure your GPS has a valid position, you can run gpsmon or cgps to check satellites and such. This check also ensures GPSd is functioning as expected. If your GPS doesn’t have a position solution, you won’t get a good time signal. If GPSd isn’t working, you won’t get any updates on the screen. The top portion will show the analyzed GPS data and the bottom portion will scroll by with the raw GPS sentences from the GPS module.

gpsmon showing 10 satellites used for the position with HDOP of 0.88. This indicates a good position solution which means the time signals should be good as well. The PPS of 0.000000684 indicates the Raspberry Pi is only 684 nanoseconds off of GPS satellite time.

5 – edit config files

For chrony, add these two lines to the /etc/chrony/chrony.conf file somewhere near the rest of the server lines:

refclock SHM 0 refid NMEA offset 0.200
refclock PPS /dev/pps0 refid PPS lock NMEA

My entire /etc/chrony/chrony.conf file looks like this:

###### below this line are custom config changes #######
server iburst minpoll 3 maxpoll 5
server iburst

# delay determined experimentally by setting noselect then monitoring for a few hours
# 0.325 means the NMEA time sentence arrives 325 milliseconds after the PPS pulse
# the delay adjusts it forward
refclock SHM 0 delay 0.325 refid NMEA
refclock PPS /dev/pps0 refid PPS

allow # my home network
###### above this line are custom config changes #######

###### below this line is standard chrony stuff #######
keyfile /etc/chrony/chrony.keys
driftfile /var/lib/chrony/chrony.drift
#log tracking measurements statistics
logdir /var/log/chrony
maxupdateskew 100.0
hwclockfile /etc/adjtime
makestep 1 3

Restart chrony, wait a few minutes, and verify.

sudo systemctl restart chrony

5 – verify the NTP server is using the GPS PPS

Right after a chrony restart, the sources will look like this (shown by running ‘chronyc sources’)

[email protected]:~ $ chronyc sources
210 Number of sources = 9
MS Name/IP address         Stratum Poll Reach LastRx Last sample
#? NMEA                          0   4     0     -     +0ns[   +0ns] +/-    0ns
#? PPS                           0   4     0     -     +0ns[   +0ns] +/-    0ns
^?     0   3     3     -     +0ns[   +0ns] +/-    0ns
^?             1   6     3     1  -2615us[-2615us] +/- 8218us
^?             1   6     1     3  -2495us[-2495us] +/- 7943us
^?            0   6     0     -     +0ns[   +0ns] +/-    0ns
^?                 3   6     1     4  -4866us[-4866us] +/-   43ms
^? usdal4-ntp-002.aaplimg.c>     1   6     1     4  -2143us[-2143us] +/-   13ms
^?           3   6     1     3  -3747us[-3747us] +/- 9088us

The # means locally connected source of time. The question marks mean it is still determine the status of each source.

After a couple minutes, you can check again:

[email protected]:~ $ chronyc -n sources
210 Number of sources = 9
MS Name/IP address         Stratum Poll Reach LastRx Last sample
#x NMEA                          0   4   377    23    -37ms[  -37ms] +/- 1638us
#* PPS                           0   4   377    25   -175ns[ -289ns] +/-  126ns
^?                     0   5   377     -     +0ns[   +0ns] +/-    0ns
^-                  1   6   177    22  -3046us[-3046us] +/- 8233us
^- 2610:20:6f96:96::4            1   6    17    28  -2524us[-2524us] +/- 7677us
^?                1   6     3    30  -3107us[-3107us] +/- 8460us
^-                 3   6    17    28  -8233us[-8233us] +/-   47ms
^-                  1   6    17    29  -3048us[-3048us] +/-   14ms
^- 2606:4700:f1::123             3   6    17    29  -3325us[-3325us] +/- 8488us

For the S column, * means the source that is active. + means it is considered a good source and would be used if the current one is determined to be bad or is unavailable. The x shown for the NMEA source means it is a “false ticker”, which means it isn’t being used. In our case that is fine because the PPS source is active and valid. Anything else generally means it won’t be used.

In this case, chrony is using the PPS signal. The value inside the brackets is how far off chrony is from the source. It is showing that we are 289 nanoseconds off of GPS PPS time. This is very, very close to atomic clock level accuracy. The last column (after the +/-) includes latency to the NTP source as well as how far out of sync chrony thinks it is (for example, the server is 12.5 milliseconds away one-way via ping):

[email protected]:~ $ ping -c 5
PING ( 56(84) bytes of data.
64 bytes from icmp_seq=1 ttl=54 time=25.2 ms
64 bytes from icmp_seq=2 ttl=54 time=27.7 ms
64 bytes from icmp_seq=3 ttl=54 time=23.8 ms
64 bytes from icmp_seq=4 ttl=54 time=24.4 ms
64 bytes from icmp_seq=5 ttl=54 time=23.4 ms

--- ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4007ms
rtt min/avg/max/mdev = 23.403/24.954/27.780/1.547 ms

For a full list of information about how to interpret these results, check here –

6 – results

A day after the bulk of writing this post, I turned on logging via the .conf file and restarted chrony. It settled to microsecond level accuracy in 57 seconds. For the offset column, the scientific notation e-7 means the results are in nanoseconds. This means that for the 19:54:35 line, the clock is -450.9 nanoseconds off of the PPS signal. There is that e-10 line in there which says that it is 831 picoseconds off PPS (I had to look up SI prefixes to make sure pico came after nano! also I doubt the Pi can actually keep track of time that closely.). After the initial sync, there is only 1 line in the below log that is actually at the microsecond level (the others are all better than microsecond) – the 20:00:59 entry, which shows the clock is -1.183 microseconds off.

Things that affect timekeeping on the Pi

Thought I’d toss in this section for completeness (i.e. thanks for all the good info Austin but how can I make this even better?). There are a few things that affect how well the time is kept on the Pi:

  • Ambient temperature around the Pi – if you plot the freq PPM against ambient temperature, there will be a clear trend. The more stable the ambient temp, the less variation in timekeeping.
  • Load on the Pi – similar to above. Highly variable loads will make the processor work harder and easier. Harder working processor means more heat. More heat means more variability. These crystals are physical devices after all.
  • GPS reception – they actually making timing GPS chips that prefer satellites directly overhead. They have better ability to filter out multipathing and such. In general, the better the GPS reception, the better the PPS signal.


After running through the steps in this guide, you should now have a functional Stratum 1 NTP server running on your Raspberry Pi with microsecond level accuracy provided by a PPS GPS. This system can obtain time in the absence of any external sources other than the GPS (for example, internet time servers), and then sync up with the extremely precise GPS PPS signal. Our NTP GPS PPS clock is now within a few microseconds of actual GPS time.


I read a ton on and other pages on that domain over the years which has been extremely helpful in getting GPS PPS NTP going for my setup. There is a lot of background info for how/why this stuff works and many useful graphics. Another source is for a short and sweet (maybe too short) set of commands.

54 replies on “Microsecond accurate NTP with a Raspberry Pi and PPS GPS”

You can make your Rasperry Pi considerable more accurate by replacing the current unregulated 19.2MHz crystal oscillator by a 19.2MHz TCXO. Doing that will reduce your clock jitter from hundred of nanoseconds to single digit nanoseconds over longer periods.

I would be interested in learning more about this. Can you tell me if its replacing the oscillator on the board or by connecting the new one through GPIO?

If you decide to by the Adafruit Ultimate GPS Pi Hat instead (which I did) you will need to change line 2 in step 1’s code from:

sudo bash -c “echo ‘dtoverlay=pps-gpio,gpiopin=18’ >> /boot/config.txt”


sudo bash -c “echo ‘dtoverlay=pps-gpio,gpiopin=4’ >> /boot/config.txt”

as the hat uses GPIO 4 for the pps.

Trying to get this to work with an Uputronics GPS/RTS board and can’t get anything out of it … uses the i2c bus which Raspberry apparently supports but trying to get any NMEA data out of it continues to be a mystery

What uputronics board? I was reading about some a week or two ago and I believe only the RTC portion is over the i2c bus. The GPS stuff is all still on serial.

Thanks for the nice tutorial.
I was able to make it kind of work with a rather cheap M6N receiver, that sends NMEA messages (could also easily read the messages using the “cat /dev/ttyS0” command, cpgs showed believable values). Kind of work, because chronyc sources picked the NMEA as the source, not the pps.
Trying to use a Sparkfun M9N chip, I was not able to make it work. cgps only dispalys Lat and Lon if I am lucky and chronyc sources marked both, NMEA and pps, with and “x”.
I have the suspicion, that it has to do with the protocol (“cat /dev/ttyS0” only shows gibberish, but in a nice pace). Can you give me a hint how to make it work using this GPS-receiver?

Given there are some issues with the supply of RPi4 at the moment. Can this NTP server be created on an RPi2 as an alternative?

Yes it can! I believe it will work on any generation of a Pi. The loads of NTP are quite low. But if you want to try it on a 1/2 I wouldn’t put much else on it.

I have an NTP server based on NTPd using just de NMEA dirver included in NTPd on on old Linux mini-pc after enabling PPS. Works fine. Is there an obligation to use the GPSd daemon with SHM on the Raspberry PI? I feel this daemon adds more complexity and ways to fail.

The GPSd daemon is extremely stable. Are you sure you aren’t already using GPSd? As far as I know, NTP does not speak directly with the GPS unit. That task is handled by GPSd. I was under the impression that it isn’t possible to use the NMEA driver without GPSd.

Thanks for this information; it was very helpful.

I struggled to get an output from gpsmon for a while. Other tutorials suggest turning off the serial console so as not to interfere with the GPS UART. I am new enough to this that I rely heavily on “cookbook” solutions; there are more details here that I am unfamiliar with, but I am learning!

Here is the output of chronyc sources:

210 Number of sources = 9
MS Name/IP address Stratum Poll Reach LastRx Last sample
#? NMEA 0 4 0 – +0ns[ +0ns] +/- 0ns
#* PPS 0 4 377 18 +119ns[ +164ns] +/- 156ns
^- 2600:3c01::f03c:91ff:feb> 2 10 377 1027 -4016us[-4016us] +/- 49ms
^- 2601:603:b7f:fec0:feed:f> 2 10 377 644 -1890us[-1890us] +/- 45ms
^- 2 10 377 120 -9951us[-9950us] +/- 154ms
^- 2 10 377 967 -3694us[-3694us] +/- 82ms
^- 1 10 377 654 +1552us[+1552us] +/- 41ms
^- ussjc2-ntp-002.aaplimg.c> 1 10 377 786 -3693us[-3694us] +/- 11ms
^- 1 10 377 0 -2764us[-2764us] +/- 19ms

I was finally able to get an output from gpsmon, but only after running this command;

$ sudo gpsmon /dev/ttyAMA0

Is this correct, or do I need to change something (and if so, how)?
I see the NMEA line in the output above is blank, which is not the case in your example. Is this related to my previous question?

Finally, here is a tracking log output. I note the Freq ppm is quite a bit higher than your example.

Date (UTC) Time IP Address St Freq ppm Skew ppm Offset L Co Offset sd Rem. corr. Root delay Root disp. Max. error
2022-01-22 21:55:37 PPS 1 4.181 0.008 -1.853e-07 N 1 1.308e-07 -3.267e-08 1.000e-09 9.378e-06 2.815e-05
2022-01-22 21:55:53 PPS 1 4.180 0.006 -8.448e-08 N 1 1.238e-07 3.384e-08 1.000e-09 1.009e-05 2.568e-05
2022-01-22 21:56:09 PPS 1 4.180 0.006 -9.915e-08 N 1 1.216e-07 4.291e-08 1.000e-09 9.307e-06 2.633e-05
2022-01-22 21:56:25 PPS 1 4.179 0.004 -9.254e-08 N 1 9.092e-08 3.139e-08 1.000e-09 8.389e-06 2.556e-05
2022-01-22 21:56:41 PPS 1 4.179 0.003 4.639e-08 N 1 8.636e-08 1.112e-08 1.000e-09 8.461e-06 2.460e-05
2022-01-22 21:56:57 PPS 1 4.178 0.003 -6.817e-08 N 1 8.631e-08 -2.583e-08 1.000e-09 8.606e-06 2.457e-05
2022-01-22 21:57:13 PPS 1 4.178 0.003 2.173e-08 N 1 8.049e-08 2.847e-08 1.000e-09 7.331e-06 2.472e-05
2022-01-22 21:57:29 PPS 1 4.178 0.003 -9.590e-08 N 1 8.872e-08 6.239e-09 1.000e-09 8.280e-06 2.340e-05
2022-01-22 21:57:45 PPS 1 4.178 0.003 -7.691e-08 N 1 9.102e-08 2.351e-08 1.000e-09 9.196e-06 2.444e-05
2022-01-22 21:58:01 PPS 1 4.177 0.002 -3.345e-08 N 1 8.725e-08 2.643e-08 1.000e-09 9.915e-06 2.536e-05
2022-01-22 21:58:17 PPS 1 4.177 0.002 4.439e-08 N 1 8.511e-08 3.246e-08 1.000e-09 8.155e-06 2.603e-05
2022-01-22 21:58:33 PPS 1 4.177 0.002 -2.607e-08 N 1 8.186e-08 -1.104e-08 1.000e-09 9.723e-06 2.422e-05
2022-01-22 21:58:50 PPS 1 4.177 0.002 7.627e-09 N 1 7.765e-08 1.317e-08 1.000e-09 8.009e-06 2.579e-05
2022-01-22 21:59:06 PPS 1 4.177 0.001 1.010e-08 N 1 7.416e-08 5.172e-09 1.000e-09 9.017e-06 2.406e-05
2022-01-22 21:59:22 PPS 1 4.177 0.002 -8.711e-08 N 1 8.304e-08 -4.910e-09 1.000e-09 9.745e-06 2.506e-05
2022-01-22 21:59:38 PPS 1 4.177 0.002 -6.299e-08 N 1 8.496e-08 2.788e-08 1.000e-09 8.784e-06 2.587e-05
2022-01-22 21:59:54 PPS 1 4.176 0.002 -1.062e-07 N 1 8.794e-08 2.601e-08 1.000e-09 8.676e-06 2.492e-05
2022-01-22 22:00:10 PPS 1 4.176 0.003 -1.904e-07 N 1 1.383e-07 -9.262e-12 1.000e-09 8.736e-06 2.486e-05
2022-01-22 22:00:26 PPS 1 4.175 0.006 -1.316e-07 N 1 1.361e-07 1.551e-08 1.000e-09 8.321e-06 2.502e-05

Is this the result of the inevitable inaccuracies of the timebase on the Raspberry Pi?

Thanks again.


I should have also mentioned that I am using a NEO-6M module with it’s UART and PPS outputs to the Pi, just as you describe here. I am using a windows machine to run U-Center, from which I can change settings on the ‘6M.

Hi Rob, yes, I do need to add the part about turning off the serial console. I do appreciate that you worked through it yourself though! Freq PPM will vary across hardware, nothing to worry about there. For your sudo gpsd command – did you set /etc/defaults/gpsd to use the correct port? Might be worth trying ttyAMA0 instead of ttyS0.

Hi, after some times running chrony and gpsd I found that:

MS Name/IP address Stratum Poll Reach LastRx Last sample
#x NMEA 0 4 377 17 +177ms[ +177ms] +/- 165ms
#* PPS 0 4 377 17 -2166ns[-3443ns] +/- 1843ns
^- 2 7 377 48 +1490us[+1489us] +/- 35ms
^- 2 6 377 80 +2848us[+2845us] +/- 75ms
^- 2 7 377 151 -12ms[ -12ms] +/- 32ms
^- 3 6 377 179 -1096us[-1104us] +/- 17ms
^- 1 6 377 173 +2551us[+2543us] +/- 20ms
^- 2 6 377 42 +2277us[+2276us] +/- 49ms
^- 1 6 377 46 -865us[ -866us] +/- 19ms
^- 2 7 377 110 -235us[ -245us] +/- 64ms
^- 2 7 377 43 -434us[ -435us] +/- 75ms
^- 1 6 377 172 +2957us[+2948us] +/- 20ms
^- 2 7 377 42 +3418us[+3417us] +/- 67ms
^- 1 7 377 42 -11ms[ -11ms] +/- 27ms

Do you think that PPS are correct?
I’m using a orange pi zero, with u-blox M8N GPS and PPS connected to OPi GPIO.
GPS and serial port are configured to 115200 baud rate.

Thanks for this. I found I had to disable bluetooth to allow GPSD to access the serial port (added ‘dtoverlay=disable-bt’ to /boot/config.txt) otherwise there was a conflict for GPSD accessing the serial port /dev/ttyAMA0. (I’m on a RPi 3B running Raspios Bullseye and the same Adafruit GPS v3)
Looking at your photos, it seems your connections to the RPi header are displaced one position down (e.g. green +5v Vin on the GPS seems to be connected to RPi pin 6 not pin4)

Success! I used an Adafruit Ultimate GPS HAT, PPS GPIO pin #4. It’s nice having a disciplined nanosecond GPS-fed NTP server, but when I mention it at parties people just walk away!

My NMEA and PPS keep going to a status of “X” if I do not have an internet connection which defeats the purpose of my project. I need a time source for a non-internet connected network. Will this not work without internet or do I maybe have a crappy GPS signal?

I believe you can set one or the other to ‘prefer’ so it will lock on. Are you using Chrony or NTP? If Chrony I’ll try a few config tweaks and let you know. What is your NMEA offset value in the .conf file?

Thanks for the great guide; I’ve been planning on doing something like this for years and this made it easy. Is there any special configuration required to mix / use RTC modules? I picked up the NEO-6M and got that rolling with an external antenna. I’m going add a DS3231 realtime clock module soon to hopefully yield a bit more stability. Do you find any benefit to that when using the Adafruit variant with built-in RTC?

I have not hooked up a RTC to the Pi yet. Unfortunately I can’t comment on using the Adafruit variant, but I’d imagine the documentation on that is a bit more complete than trying to mix-and-match from different sources.

I want to thank Austin for all the details, as well as many others.

You can setup a stand alone NTP server without access to the internet. It will just be single source.

I’m using a Raspberry Pi 4B (got it before the chip shortage) and a Uputronics GPS/RTC hat. This board is designed to put out very accurate PPS signals provided by GPS. The details for setting this up with your Raspberry Pi are on the Uputronics website. The board does not support a NEMA output on the Raspberry Pi headers, but you can get those details in a GUI using the Ublox app Uputronics provides using the mini-USB port that the board also has.

Once you get the PPS output from the Uputronics board to the GPIO18 output on Raspberry Pi, you can setup the NTP server.

Here are the key items to configure in the /etc/ntp.conf file to make your stand alone stratum 1 server work …

1. Comment out the pools servers that are listed in the default ntp.conf file
2. Add the following:

# PPS source from GPS hat to discipline clocks
server minpoll 4 maxpoll 4
fudge flag3 1 refid PPS
tos mindist 0.2

# Local clock that will be disciplined by PPS source
server minpoll 4 maxpoll 4 prefer
fudge flag3 1 refid LCL

3. Save the .conf file
4. Restart the ntp server (i.e. sudo service ntp restart)

Give the system some time to settle and once it does you should see an output, using the “ntpq -p -c rl” command that looks like this:

remote refid st t …
oPPS(0) .PPS. 0 1 …
*LOCAL(0) .LCL. 5 1 …

If you look into the details you should see stratum=1 which means the ntp server, which is using the Raspberry Pi local clock as a basis for time, is aligned to the RTC that is coming from the GPS board.

This is as valid as using other NTP servers on the internet since the time discipline is tied to the PPS signal.

So the PPS signal will get your Pi synchronized to GPS with microsecond accuracy.

If you then use the Pi as an NTP master, how precise will the synchronization be between the NTP master and slaves ? My worry is that most Pi’s NICs (except for CM4) do not support hardware timestamping, so I expect the slaves might only be synchronized to ~10 uS or so ?

I’d be curious what the “chronyc sourcestats” command would show, and particularly what the std dev field would typically show, when running on a secondary ntp server. (2nd row of output) is my main PPS chrony server. Generally, chrony will sync my physical machines to within 50us of the pi-ntp source. Keep in mind it is a RPi 3 that uses the USB bus for ethernet which is an inconsistent source of latency.

[email protected]:~# chronyc sourcestats
Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev
============================================================================== 12 6 710 +0.104 0.371 +49us 62us 15 7 906 +0.044 0.089 -23us 18us
prox-3070.home.fluffnet. 25 14 34m -0.038 0.061 -7952ns 45us
prox-7040a.home.fluffnet 7 4 776 +0.095 0.313 +20us 33us
prox-q87t.home.fluffnet. 14 7 28m +0.003 0.058 +23us 27us

Considering to build an NTP server based on this. Silly question, but if I went with a CM4 module (for the hardware timestamping capability), with a mini base board ( and GPS hat right above the CM4 module, will that be an issue making it essentially impossible to have any airflow on the CM4 ?

I have managed to follow steps from other forums to get this working properly. In my project, I would like to be able to output 1pps even after GPS lock is lost. I am using the adafruit ultimate GPS pi hat. I was wondering if anyone had experience with this. I haven’t been able to demonstrate this yet because the darn thing wouldn’t lose its GPS fix the only times I have tried to force it to do so! I would like to output 1pps to an RF cable (SMA) in order to feed that signal to a piece of equipment that will most likely be in a place where maintaining GPS lock will not be possible.

I am curious where you think the GPS 1PPS output will come from without any GPS signal/lock? I don’t know about the MediaTek MT3333 chip that’s used by the Pi Hat but I know U-Blox modules have a stationary mode where they will output a PPS with only 1 satellite (usually it requires 4) locked on. I recommend putting a GPS antenna on the roof of whatever building you’re in to get signal and run a cable down to your receiver. This is very common in data centers and other places where accurate time is required but GPS signals won’t reach. If you are underground, you will not be able to use GPS to do your high resolution time-keeping.

I think there are high end NTP/Time sync equipments which have a atomic oscillators and can output 1PPS and 10 MHz over coax even the GPS signal is lost.

I bought a Raspberry Pi GPS/RTC Expansion Board (HAB-GPSPI-NAN). I use NTP and my accuracy is about 20 us. A little bit of cold air and the offset for PPS source jumps. Just opened my peerstats with NTP Plotter and max offset for is about -5 ms. Clearly Raspberry Pi 3 Model B requires some kind of temperature controlled environment.

Could somebody recommended another SBC which is compatiple with HAB-GPSPI-NAN and has more accurate time?

My Pi 3B is synchronized to within 0.5 microseconds. You should be able to get much better than 20us. What is the output of ‘ntpq -pn’ or ‘chronyc sources’?

Hey, great write-up. I’ve followed the instructions with the Adafruit Ultimate GPS, wired it up, installed packages etc, but when I get to checking the PPS is working, the ‘pps-core’ module doesn’t show up on the output of lsmod, all I get is:

pps_gpio 16384 0

Any ideas what pps_core isn’t there?

I’ve followed the instructions above (also disabled bluetooth to be able to get to the serial port), but I’m not getting PPS data. The results of lsmod|grep pps only show ‘pps_gpio’ and not pps_core.

After disabling bluetooth, pps_ldisc appeared, but still no pps_core. I also don’t seem to have any pps devices /dev

Any ideas?

Pi4b are hard to get. Any advice on using an Orange Pi 3 LTS? It has a separate UART for serial communication, so no need for disabling the terminal serial / getty functions. I will be using the adafruit ultimate GPS board, not hat.

I haven’t tried it yet. I briefly looked into the Orange Pi devices a few months ago but never ordered one. Yes the Pi 4B are hard to get but you can back up a generation or two and still get great results.

Well, I got lucky and got a Pi4b, 8 Gb for the official price, So I abandoned the OP3 LTS.
I’m stuck at getting PPS core to load even though I edited the config file. I get
[email protected]:~ $ lsmod | grep pps
pps_gpio 16384 0
with no core load. What did I miss?

Try ppstest anyways – I am not 100% sure the pps core shows up as loaded anymore.

Also – I’m a bit jealous that you got a Pi 4 8GB for official price, nice score!

OK. That problem is resolved. GPS data is good. /dev/pps0 shows valid data. I’m now stuck at getting chrony to see the PPS data as valid and use it in the chrony console. Funny that you checked it at Engineering Radio! I have followed Paul for a while now, and was surprised to see you there.

Remember kids, TX from gps module (T as transmit which sends data) goes to RX on pi (R as receive) and vice versa! Just a reminder from a stupid guy! 😉

Are you able to provide a bit of a guide on how to determine the delay to use in the chrony config file when using GPS and PPS.

Leave a Reply

Your email address will not be published. Required fields are marked *