12.04.2021, last updated 14.04.2021 - Jeremy T. Bouse - ~15 Minutes
Let’s go back in time
Back in August of 2016 I had built my original NTP server with a Raspberry Pi 2 that I had purchased at my local MicroCenter. I had then ordered the original Adafruit Ultimate GPS HAT from Amazon along with an RF adapter cable that I later determined was the incorrect one I needed and had to find a way to work around.
This was my first time trying to solder a 2x20 header so it wasn’t the best job and I think it may have contributed to some of the performance issues I encountered with the project. There was some general instability that would require me to routinely reboot the system to reset the board. The other factor was that the default NTP daemon package available with that version of Raspberry Pi OS, called Raspbian at the time but has since been renamed, did not support the GPS NMEA reference clock needed to use the Ultimate GPS HAT.
Besides that there wasn’t much thought that went into the construction of the build. I used the simple Official Red & White Raspberry Pi case which was not built with the external GPS antenna in mind so I had to just leave the RF adapter cable exit the case between the USB ports. While this worked, it was not “pretty” and it provided no support so any tension on the adapter cable went directly to the u.FL connector on the Ultimate GPS HAT risking potential damage. I had also made the mistake of purchasing an RP-SMA to u.FL cable rather than a SMA to u.FL cable which required me to get an additional RP-SMA to SMA adapter between the Raspberry Pi and the external GPS antenna .
So I wanted to come up with something more purpose built and look more like an appliance ready for future use. I found that Adafruit had a great enclosure kit that had the holes perfect for the RF adapter jack to be secured and looked how I thought an appliance box should. So I started the new build project with the purchase of the enclosure kit . Since I already had the Raspberry Pi board and the microSD card I didn’t need to purchase those but if not I could have gotten a new Raspberry Pi 3 . I had been making use of an old phone USB charging adapter to power my Raspberry Pi, with the new enclosure I went ahead and purchase a 5V power supply as I had began noticing that I was getting under-voltage messages in the logs.
While I purchased a new Ultimate GPS HAT due to my displeasure with the solder work and I had found that Adafruit now had a solderless header option with a jig kit . The Ultimate GPS HAT does come with a 2x20 header that requires soldering and the solderless jig kit does include a 2x20 solderless header so I got the kit and if I build another box later I can just get the header and reuse the jig. The final missing part to polish the finished product was getting a kit of nylon screws and stand-offs . I picked the black nylon stand-offs rather than the white just for personal preference and availbility which ensured that the HAT and the Raspberry Pi are securely supported more than by the header alone.
Putting together the pieces
The first step in the new build was to take the Ultimate GPS HAT board and the 2x20 solderless header from the kit. Using the installation jig from the kit I secured the header and to the HAT. I then took four 12mm long M-F hex standoffs and 4 M2.5 x 4mm screws from the screw and stand-off kit to assemble the Raspberry Pi and Ultimate GPS HAT together to the base plate of the enclosure kit . With the boards secured to the base plate I just needed to attach the RF adapter cable to the u.FL connector on the HAT to complete the assembly.
The enclosure then just slides over the base plate assembly and the RF adapter cable jack secured through the top left hole. The enclosure kit includes several rubber plugs to fill the other 3 holes that were not going to be used to help keep dust out of the enclosure. Securing the top of the case on the enclosure completes the build. With the hardware fully assembled it was time to move on to the software phase of the build.
The first step in the software phase was to install the Raspberry Pi OS on the microSD card. The easiest way to perform this is with the Raspberry Pi Imager application which will automatically download the image and write it to the microSD card then verify it. The new version 1.6 has recently added an advanced configuration option available by hitting CTRL+Shift+X which allows you to set the hostname, password, enable SSH and configure WiFi to assist in being able to bring the Raspberry Pi online and ready to be configured remotely without having to have a monitor and keyboard.
Ready for some Pi?
So there are plenty of Raspberry Pi install guides out there so I won’t go deep into how to perform
the initial install. You do need to be able to access the command line so if you do not enable SSH
before bootstrapping then you will need a monitor and keyboard to perform the initial installation.
It really does not matter if you use wifi or ethernet, though an ethernet connection will most likely
be a more stable connection. As for the Raspberry Pi OS, I choose to use the
Raspberry Pi Lite
over the Raspberry Pi Full or Desktop. The reasoning for this was simply to install the smallest
footprint possible from a security standpoint and just not wanting to install unnecessary desktop
Once the Raspberry Pi has bootstrapped and is running the first thing you want to do is edit the
/boot/config.txt. You want to comment out the line to disable
audio as shown below on line 57.
Next you want to add the dtoverlay for
pps-gpio as shown below on lines 59-60.
The GPIO used on the Ultimate GPS HAT is Pin 4, this needs to be included as it is not the default
for the pps-gpio overlay. The
enable_uart=1 on line 61 enables the GPIO serial port to use
/dev/ttyAMA0 (uart) rather than
/dev/ttyS0 (mini uart). The
gpu_mem=0 on line 62 can provide
some memory optimization leaving more RAM for the system if you are running headless.
The other system level configuration to make is to edit the
/boot/cmdline.txt and remove all
reference to the serial console. This is because the Ultimate GPS HAT will be using the serial
port to communicate. We also want to tell the kernel to not run tickless to reduce the jitter
and offset in NTP when using GPS PPS. You will generally see the
contain something that looks similar to the following:
console=serial0,115200 console=tty1 root=PARTUUID=cead1835-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
Which I then editted to look similar to:
console=tty1 root=PARTUUID=cead1835-02 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait plymouth.ignore-serial-consoles nhoz=off
The next thing to get in place is the udev rule to set up the symlinks for our GPS device. Using
your favorite editor you can create the
/etc/udev/rules.d/99-gps.rules file with the following:
Now we can move to installing the actual software. Thankfully this is all available through the
software repository and able to use
apt to install it. It is generally a good idea to make sure
you have everything up to date as well so here are the commands I executed:
sudo apt update sudo apt upgrade sudo apt install pps-tools setserial ntp ntpdate
We also want to help speed up the boot time so let’s disable and mask the serial console related systemd service units as they are not needed.
sudo systemctl disable hciuart sudo systemctl mask hciuart sudo systemctl disable serial-getty@ttyAMA0.service sudo systemctl mask serial-getty@ttyAMA0.service
Another item is to disable the restarting of NTP by the DHCP client. The easist way to perform this is to simply remove those files so you want to execute the following:
sudo rm /etc/dhcp/dhclient-exit-hooks.d/ntp sudo rm /var/lib/ntp/ntp.conf.dhcp sudo rm /lib/dhcpcd/dhcpcd-hooks/50-ntp.conf
The last piece we want to setup is performing the initial serial communication settings with the
GPS so we’ll open up the
/etc/rc.local in our editor and add the highlight lines 20-28.
At this point it is a good idea to give your Raspberry Pi a reboot to enable it to start up with this configuration in place. We don’t yet have NTP configured to use the GPS input but restarting now will bring everything up with the hardware configured and we should be ready to configure NTP.
The time is nigh
When our Raspberry Pi has come back online after being restarted and we have connected back to
it, either via monitor and keyboard or over SSH, we will want to confirm things are in fact working.
The first is to make sure that PPS device is responding, we will do this using the
we installed earlier.
pi@raspberrypi:~ $ 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 1618365982.999996770, sequence: 2337 - clear 0.000000000, sequence: 0 source 0 - assert 1618365983.999994313, sequence: 2338 - clear 0.000000000, sequence: 0 source 0 - assert 1618365984.999994148, sequence: 2339 - clear 0.000000000, sequence: 0 source 0 - assert 1618365985.999993720, sequence: 2340 - clear 0.000000000, sequence: 0
You can hit CTRL+C to cancel this after confirming it works. If you get a
Time out message then
your GPS has most likely not locked on to the satellites yet so you need to attempt repositioning
your antenna to have better access to the sky.
Next we can actually confirm we’re getting the GPS data. You can do this very easily using
pi@raspberrypi:~ $ cat /dev/gps0 $GPGGA,021742.000,2810.6866,N,08124.9390,W,1,11,0.83,16.4,M,-30.6,M,,*65 $GPGSA,A,3,12,06,23,15,02,13,05,25,29,18,20,,1.44,0.83,1.17*0D $GPRMC,021742.000,A,2810.6866,N,08124.9390,W,0.02,49.30,140421,,,A*49 $GPVTG,49.30,T,,M,0.02,N,0.04,K,A*05 $GPGGA,021743.000,2810.6866,N,08124.9390,W,1,11,0.83,16.4,M,-30.6,M,,*64 $GPGSA,A,3,12,06,23,15,02,13,05,25,29,18,20,,1.44,0.83,1.18*02 $GPRMC,021743.000,A,2810.6866,N,08124.9390,W,0.03,223.42,140421,,,A*72 $GPVTG,223.42,T,,M,0.03,N,0.05,K,A*3E
The lines we are looking for most are the
$GPRMC prefixed ones that I have
highlighted. The lines are comma-delimited and the
$GPGGA line will indicate how many
satellites you are locked onto in the 8th column. In this example we are locked on 11
satellites and I typically find my system is locked on anywhere from 6-13 satellites with
9-11 as an average. If you are curious to understand these lines I found a
that you can paste the line into and see what it is actually telling you.
If you’re getting positive results to this point it is finally time to tie it all together and get NTP using this data. At this point by default your NTP server will be running and using external NTP pool servers to get peer with.
At this point we need to edit the
/etc/ntp.conf file. Below you will find the configuration
I have in place and I have highlighted the important lines you will need to modify or add
if missing from your own.
While getting things initially setup you may wish to uncomment line 9 to allow NTP to record
the statistics. In particular the
clockstats will be very helpful to confirm that NTP is
receiving the GPS data. I would however probably turn this off once confident in the setup
as this would add additional writes to the microSD card which can lower the usefulness of the
drive and fill up the space if you do not monitor it.
On line 46 I include the
limited kod options to add more security to the configuration and
limit the changes that can be made from the peer servers.
Lines 62-63 are where we actually add the GPS as our reference clock source. The
address is the internal address for the
GPS_NMEA reference clock and will point to the
/dev/gps0 and /dev/gpspps0 devices from our udev rules. The
mode we set on line 62 tells NTP
that we are looking to process
$GPGGA data lines only and we are communicating
at 115200 baud over the serial port. The
flag1 setting on line 63 signals that we want to
enable PPS signal processing. The
flag4 setting is to obscure the GPS location which would be
stored in the
clockstats statistics file and isn’t really needed to operate. The
is the serial EOL offset and seems to be fine for Raspberry Pi serial communications. The
command on line 65 is to set the minmimum distance used but the selection and anticlockhop
algorithm. Setting this to
0.002 is just slightly more than the default of
0.001 which seems
to work excellent with the PPS signal on the Ultimate GPS HAT.
ntp.conf configuration saved you should be ready to restart the NTP service and after
given a bit of time to stablize you should be able to query the NTP server and see that it is
using the GPS data.
sudo systemctl restart ntp.service ntpq -crv -pn
If everything is working properly you are expecting to see
stratum=1 in the
ntpq output. As well you are looking to see an
o to the left of the
entry with the
jitter column values as close to
0.000 as possible.
This will indicate that your time is stable. A
reach column value of
377 indicates that
there have been no polling attempt failures.
That’s a wrap
At this point you will have successfully setup your stratum-1 NTP time server and can begin to
point all your servers and workstations to it in order to receive time updates. This is where
I would give one last
reboot test and bounce the server to ensure that everything comes back
up as expected. I always say it is not done unless it has been rebooted. So give it one more
reboot and double check that everything comes up right.
All in all, I think that total cost for this NTP server project build was around $150-160, which includes the extras that can be used with future projects. While the case and everything makes mention of a Raspberry Pi 3, I did reuse my existing Raspberry Pi 2 which does not include wifi and bluetooth. My existing Raspberry Pi 3 board is already in use with another build project which is why I did not use it and I did not think it worth buying a new one when this worked fine. The original Ultimate GPS HAT I bought did have a reported issue with the Raspberry Pi 3 but that appears to have been resolved and a new revision of the board released.
Overall I am quite happy with how this project turned out and the results have been extremely more stable than my original build. I expect this device to see a long lifetime of use as I do like to keep my timestamps accurate.