It's Just a Matter of Time

It’s Just a Matter of Time

 12.04.2021, last updated 20.10.2021 -  Jeremy T. Bouse -  ~15 Minutes

Let’s go back in time

Original Raspberry Pi NTP build 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 workaround.

New Raspberry Pi NTP build This was my first time soldering a 2x20 header, so it was not 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 reboot the system to reset the board routinely. 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 was not much thought that went into the construction of the build. I used the simple Official Red & White Raspberry Pi case that was not designed with the external GPS antenna in mind, so I had to let 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 did not 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 purchased a 5V power supply  as I had begun 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 get another header and reuse the jig. The final missing part to polish the finished product was getting a nylon screws and standoffs  kit. I picked the black nylon standoffs rather than the white just for personal preference and availability, ensuring that the HAT and the Raspberry Pi are securely supported more than by the header alone.

Putting together the pieces

Board assembly 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 standoff 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.

Enclosure assembly The enclosure then slides over the base plate assembly and the RF adapter cable jack is secured through the top left hole. The enclosure kit includes several rubber plugs to fill the other three holes that were not 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 will not go deep into how to perform the initial install. You need to be able to access the command line, so if you do not enable SSH before bootstrapping, you will need a monitor and keyboard to perform the initial installation. It 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 to install the smallest footprint possible from a security standpoint and just not want to install an unnecessary desktop environment.

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 pi3-disable-bt and 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 run headless.

# Additional overlays and parameters are documented /boot/overlays/README

# Enable audio (loads snd_bcm2835)
# dtparam=audio=on


# Enable DRM VC4 V3D driver on top of the dispmanx display stack


The other system-level configuration is to edit the /boot/cmdline.txt and remove all references 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 not to run tickless to reduce the jitter and offset in NTP when using GPS PPS. You will generally see the /boot/cmdline.txt will contain something that looks similar to the following:

console=serial0,115200 console=tty1 root=PARTUUID=cead1835-02 rootfstype=ext4 elevator=deadline rootwait

Which I then edited to look similar to:

console=tty1 root=PARTUUID=cead1835-02 rootfstype=ext4 elevator=deadline rootwait plymouth.ignore-serial-consoles nhoz=off
GPS udev rules

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:

KERNEL=="pps0", SYMLINK+="gpspps0"
KERNEL=="ttyAMA0", SUBSYSTEM=="tty", MODE="0777", SYMLINK+="gps0"

Now we can move to install 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 us 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
GPS udev rules

The last piece we want to set up is performing the initial serial communication settings with the GPS, so we will open up the /etc/rc.local in our editor and add the highlight lines 20-28.

# Print the IP address
_IP=$(hostname -I) || true
if [ "$_IP" ]; then
  printf "My IP address is %s\n" "$_IP"

systemctl stop ntp.service
setserial /dev/ttyAMA0 low_latency
/bin/echo -e '$PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0*29\r\n' > /dev/ttyAMA0 # diable all except GPMRC and GPGGA
/bin/echo -e '$PMTK320,0*26\r\n' > /dev/ttyAMA0 # power saving mode off
/bin/echo -e '$PMTK301,0*2C\r\n' > /dev/ttyAMA0 # No DGPS source
/bin/echo -e '$PMTK313,0*2F\r\n' > /dev/ttyAMA0 # disable SBAS satellite
/bin/echo -e '$PMTK251,115200*1F\r\n' > /dev/ttyAMA0 #set baud to 115200
stty -F /dev/ttyAMA0 raw 115200 cs8 clocal -cstopb # set serial baud to 115200
systemctl start ntp.service

exit 0

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 do not 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 that things are working. The first is to make sure that the PPS device is responding. We will do this using the ppstest utility 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 confirm we are getting the GPS data. You can do this very quickly using cat.

pi@raspberrypi:~ $ cat /dev/gps0

The lines we are looking for most are the $GPGGA and $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 great site  that you can paste the string into and see what it is telling you.

GPS udev rules

If you are getting positive results, 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 peered.

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 essential lines you will need to modify or add if missing from your own.

While getting things initially set up 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.

# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help

driftfile /var/lib/ntp/ntp.drift

# Leap seconds definition provided by tzdata
leapfile /usr/share/zoneinfo/leap-seconds.list

# Enable this if you want statistics to be logged.
#statsdir /var/log/ntpstats/

statistics loopstats peerstats clockstats
filegen loopstats file loopstats type day enable
filegen peerstats file peerstats type day enable
filegen clockstats file clockstats type day enable

# You do need to talk to an NTP server or two (or three).
#server ntp.your-provider.example

# maps to about 1000 low-stratum NTP servers.  Your server will
# pick a different set every time it starts up.  Please consider joining the
# pool: <>
pool iburst
pool iburst
pool iburst
pool iburst

# Access control configuration; see /usr/share/doc/ntp-doc/html/accopt.html for
# details.  The web page <>
# might also be helpful.
# Note that "restrict" applies to both servers and clients, so a configuration
# that might be intended to block requests from certain clients could also end
# up blocking replies from your own upstream servers.

# By default, exchange time with everybody, but do not allow configuration.
restrict -4 default kod notrap nomodify nopeer noquery limited
restrict -6 default kod notrap nomodify nopeer noquery limited

# Local users may interrogate the ntp server more closely.
restrict ::1

# Needed for adding pool entries
restrict source limited kod notrap nomodify noquery

# Clients from this (example!) subnet have unlimited access, but only if
# cryptographically authenticated.
#restrict mask notrust

# If you want to provide time to your local subnet, change the next line.
# (Again, the address is an example only.)

# If you want to listen to time broadcasts on your local subnet, de-comment the
# next lines.  Please do this only if you trust everybody on the network!
#disable auth

server mode 83 minpoll 4 maxpoll 4 prefer
fudge flag1 1 flag4 1 time2 0.350 refid GPS

tos mindist 0.002

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 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 $GPRMC and $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 obscures the GPS location, which would be stored in the clockstats statistics file and is not needed to operate. The time2 setting is the serial EOL offset and seems fine for Raspberry Pi serial communications. The tos command on line 65 is to set the minimum 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.

GPS udev rules

With the ntp.conf configuration saved, you should be ready to restart the NTP service, and after given a bit of time to stabilize 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 correctly, you expect to see refid=GPS and stratum=1 in the ntpq output. You are also looking to see an o to the left of the peer entry with the delay, offset, and 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 set up your stratum-1 NTP time server and can begin to point all your servers and workstations to it 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 correctly.

All in all, I think that the 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 pretty happy with how this project turned out, and the results have been highly more stable than my original build. I expect this device to see a long lifetime of use as I keep my timestamps accurate.