How to Setup a GPS PPS NTP Time server on Raspberry Pi

The South Florida Amateur Astronomers Association is currently working on a project to coordinate multiple Radio Telescope collection points.   This would require very precise time stamps on recorded data, much more accurate than what public NTP server pools could deliver.     So we successfully built as close to a Stratum-1 time server as possible as cheaply as possible.

So we used a Raspberry Pi3b with the Adafruit GPS Hat and were able to setup a clock source based on kernel mode PPS pulses to get 1 microsecond of jitter.   That’s as low as the NTP service can register and as good as we can get without spending a LOT of money.

So for about $90 in parts, we were able to build a system with the needed time precision for this project to work.

Building the Pi

So to begin building this time source, I used the following:

  • Raspberry Pi 3b Board or Kit (MainBoard, power adapter, heatsinks, etc…)
  • Tall Pi Case (extra room to hold the GPS hat)
  • 16 GB Micro sd Card
  • AdaFruit GPS Hat (Model FBA_2324)
  • GPS Antenna 28 dB Gain
  • RF connecter Pigtail (SMA Female to to Ufl/ipx)  to connect the GPS antenna to the GPSHat
  • You need some Spacers/Standoffs to mount the GPS Hat properly.


Solder the GPS Hat to the plastic 24 pin adapter that came with the GPS Hat.   Then mount the Pi with GPS hat into your case using the Spacers/Standoffs as needed to help support the GPS Hat in place.  Attach the Pigtail to the GPS Hat and then to the GPS antenna.

Pi with GPS Hat

Pi with GPS Hat without pigtail installed


Install the OS

Once its all assembled, you need to install the OS.    I used the latest Raspberry Stretch with Desktop Image.    With a Windows workstation I used Etcher to burn the Raspberry Image onto the Micro SD Card.   Plug in your SD card to the Pi, connect Monitor, Keyboard, mouse, network cable, and power it up.

The Pi will boot into the default Desktop.    Default username=pi with password=raspberry.

We want to enable SSH access to the Pi by using the raspi config. Open a Terminal window in the Pi desktop and use:

sudo raspi-config
Select Interfacing Options
Select P2 SSH
Enable it.

Before leaving the window, use this to get your current IP address:

$ ifconfig | grep inet
        inet  netmask  broadcast
        inet  netmask is the IP to use as the SSH host’s IP. is your loopback IP, you can ignore this one for now.

So, with the Pi setup and on the network, we can SSH into the Pi and begin the config.
If you are on Windows, Putty is the tool you need to ssh into the host. You just need the IP, ID, and password. Mac/Linux users can already SSH from command line.


Configuring the PI to use the GPS Hat

NOTE: all the commands in the code boxes can be combined to work in a setup script. They can be copied/pasted directly into the ssh session with no user intervention needed.

Disable ipv6, this will take effect on next reboot

sed -i.bck '$s/$/ ipv6.disable=1/' /boot/cmdline.txt

Change hostname on the Pi. I use radiopi as the new name.

sudo hostnamectl set-hostname radiopi

Add new hostname to local hosts file (just to avoid error messages)

sudo sed -i 's/\tlocalhost/\tlocalhost radiopi/g' /etc/hosts

Run apt update and upgrade

sudo sh -c "apt-get update ; sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold""

Install additional packages

sudo sh -c " apt-get install usbmount eject gpsd gpsd-clients python-gps pps-tools -y"

Disabling the serial tty. Because the GPS hat is going to use the serial pins on the Pi, we need to disable the Serial tty otherwise there will be a conflict.

sudo sed -i 's/console=serial,115200//g' /boot/cmdline.txt

GPSD is the program used to receive the GPS data. By default, it looks for a USB based GPS module. Since ours is not USB, we disable this to speed things up.
In addition, we want to use ttyAMA0 to deliver the GPS data. Setting GPSD Options to -n ensure the GPS Daemon continues to run even if no application is using it.
Change the gpsd startup to NOT use the usb auto and to not wait for client before getting GPD fix

sed -i 's/USBAUTO="true"/USBAUTO="false"/g' /etc/default/gpsd
sed -i 's:DEVICES="":DEVICES="/dev/ttyAMA0 /dev/pps0":g' /etc/default/gpsd
sed -i 's:GPSD_OPTIONS="":GPSD_OPTIONS="-n":g' /etc/default/gpsd

Enable the GPSD daemon in systemctl so that it starts with the Pi

systemctl enable gpsd

Create symbolic links in udev rules for GPS and PPS devices so the Network Time Protocol (NTP) daemon software application can make references to them using expected names (requires reboot).

echo KERNEL==\"ttyAMA0\", SUBSYSTEM==\"tty\", DRIVER==\"\", OWNER==\"root\", GROUP==\"tty\", MODE==\"0777\", SYMLINK+=\"gps0\" >> /etc/udev/rules.d/09-pps.rules

Add pps-gpio to boot/config. The AdaFruit GPSHat that I have uses gpio4. Other devices may use different gpio pins.

echo dtoverlay=pps-gpio,gpiopin=4 >> /boot/config.txt 

Include gpio in startup modules

echo pps-gpio >> /etc/modules

If DHCP client hooks exist, it can set NTP from DHCP and we don’t want that to happen since we want to use our custom configs. Remove ntp DHCP hook (so that it doesn’t override ntp settings)

if [ -f  "/etc/dhcp/dhclient-exit-hooks.d/ntp" ]; then mv /etc/dhcp/dhclient-exit-hooks.d/ntp /etc/dhcp/dhclient-exit-hooks.d/xxxntp-orig; else echo "NTP exit hook does not exist"; fi

When the Pi starts up, GPSD seems to need a ‘kick’ to start sending data. Even though the daemon starts automatically, it still seems to need this to start the flow of data.
gpsd seems to need a kickstart to get going. Adding this to rc.local sends 1 nmea packet through the gps client and exits. gpsd will continue to work

sed -i '$ s/exit 0/gpspipe -r -n 1 \&/g' /etc/rc.local ; echo exit 0 >> /etc/rc.local

At this point, we should have the GPSD software installed and can run a few tests to make sure everything is working.
The GPS Hat has an LED that will blink rapidly while a GPS fix is being taken. It will then blink red once every 15 seconds indicating that it has a GPS fix.

You can check that the GPSHat is sending data over the tty port:

cat /dev/ttyAMA0

GPS data should be seen with

cgps -s

PPS port can be checked with:

ppstest /dev/pps0

Configuring NTP to use the PPS as Time Source

At this point, since we confirmed that we are getting data off the GPS Hat in into our Serial ttyAMA0 port, we can start the NTP install.
Install ntp

apt-get install ntp -y

Add following to ntp.conf to include GPS and PPS Fudging

cat << EOF >> /etc/ntp.conf
#Note that this config works without an internet source.
#PPS Kernel mode
server minpoll 4 maxpoll 4 true
fudge flag3 1 refid PPS

# GPS Serial data Reference
server minpoll 4 maxpoll 4 iburst prefer
fudge flag1 1 time1 0.500 refid GPS stratum 1

# Shared Memory 2 source
server minpoll 4 maxpoll 4
fudge flag1 1 refid SHM2

# Fix False tickers
tos mindist 0.5

Comment out the ntp server lines for pool 0, 1, 2, 3. We only want data from the PPS pulses as a time source so we can automatically remove the other pool hosts.

sed -i 's/pool 0./# pool 0./g' /etc/ntp.conf
sed -i 's/pool 1./# pool 1./g' /etc/ntp.conf
sed -i 's/pool 2./# pool 2./g' /etc/ntp.conf
sed -i 's/pool 3./# pool 3./g' /etc/ntp.conf

Comment out the following lines in ntp.conf, we dont need it.

sed -i 's/restrict ::1/# restrict ::1/g' /etc/ntp.conf
sed -i 's/restrict -6/# restrict -6/g' /etc/ntp.conf

Verify GPS on console and the NTP expected link for the PPS kernel mode line device:

TESTGPS=$(ls /dev/gps*); if [ "$TESTGPS" == "/dev/gps0" ]; then echo 'GPS Mapping to Console is present'; else echo 'GPS NOT mapped to console port'; fi
TESTPPS=$(ls /dev/pps*); if [ "$TESTPPS" == "/dev/pps0" ]; then echo 'NTP PPS kern mode interrupt line device present'; else echo 'NTP PPS Device NOT present'; fi

NOTE: I have a copy of my working ntp.conf at the bottom of the article.

With NTP configured, I remove the NTP application but leave the configs.
I download the latest ntp Application and do a custom compile because we want to enable the
PPS and ATOM clock options so that we can read the PPS signals properly.

# Compile NTP with PPS kernel input
sudo service ntp stop
sudo apt-get remove ntp
sudo apt-get update
sudo apt-get install libcap-dev
# Get the latest stable
tar -xvf ntp-4.2.8p10.tar.gz
cd ntp-4.2.8p10
# config/build it
./configure --prefix=/usr --enable-all-clocks --enable-parse-clocks \
--enable-SHM --enable-debugging --sysconfdir=/var/lib/ntp --with-sntp=no \
--with-lineeditlibs=edit --without-ntpsnmpd --disable-local-libopts \
--disable-dependency-tracking --enable-linuxcaps --enable-pps --enable-ATOM && make && make install
sudo make install
sudo service ntp start

NTP conf looks like this:

driftfile /var/lib/ntp/ntp.drift
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
restrict -4 default kod notrap nomodify nopeer noquery limited
restrict source notrap nomodify noquery

#Note that this config works without an internet source.
#PPS Kernel mode
server minpoll 4 maxpoll 4 true
fudge flag3 1 refid PPS

# GPS Serial data Reference
server minpoll 4 maxpoll 4 iburst prefer
fudge flag1 1 time1 0.500 refid GPS stratum 1

# Shared Memory 2 source
server minpoll 4 maxpoll 4
fudge flag1 1 refid SHM2

# Fix False tickers
tos mindist 0.5


Verifying the Install

With the NTP config in place, you can test your time source with the following command:

$ ntpq -crv -pn

remote refid st t when poll reach delay offset jitter
o127.127.22.0 .PPS. 0 l 9 16 377 0.000 -0.020 0.001
* .GPS. 1 l 8 16 377 0.000 -17.968 19.808
+ .SHM2. 0 l 7 16 377 0.000 -0.020 0.001

The ‘o’ next to the PPS entry indicates that ntp is working and using kernel mode PPS pulses as the timesource.  This is exactly what we want.

Now the time services can take a few minutes for the ‘jitter’ to drop when you first boot up. Mine is at .001 within 5 to 10 minutes after boot. If you used a battery in the GPS hat, this should help lower the wait time to get to .001. But a 5 minute after bootup wait time is good.


Notes and Explanations

So let’s connect the dots on what’s happening here now that we have a working config.

GPSD and NTP will share memory spaces.
You can see the shared memory spaces in use like this:

 ipcs -m 

Output looks like this:
Which will display something like this
—— Shared Memory Segments ——–
key shmid owner perms bytes nattch status
0x4e545030 0 root 600 80 2
0x00000000 98305 pi 600 33554432 2 dest
0x00000000 196610 pi 600 524288 2 dest
0x00000000 229379 pi 600 393216 2 dest
0x4e545031 262148 root 600 80 1
0x4e545032 294917 root 666 80 1
0x4e545033 327686 root 666 80 1
0x4e545034 360455 root 666 80 1
0x4e545035 393224 root 666 80 1
0x4e545036 425993 root 666 80 1
0x4e545037 458762 root 666 80 1
0x47505344 491531 root 666 8928 1
Convert key column value hex to ASCII to get segment reference name
0x4e545030 is “NTP0” uses GPS in-band serial data stream (least accurate)
0x4e545031 is “NTP1” uses GPS in-band + PPS out of band (a little better accuracy)
0x4e545032 is “NTP2” same as NTP0 and used when gpsd not run as root
0x4e545033 is “NTP3” same as NTP1 and used when gpsd not run as root

The 127.127.x.x address you see in the output and that were used in the config come from GPSD drivers.
GPSD and NTP are using a shared memory resource ‘SHM’ and there are 4 shared memory spaces. These are accessed via,,, and

You’ll notice that in the ntp config we are using and as sources.  These are the GPSD provided interfaces.
GPSD also enables the PPS kernel mode driver and is accessed via
This command displays the NTP sources and stats about the sources:
remote refid st t when poll reach delay offset jitter
o127.127.22.0 .PPS. 0 l 9 16 377 0.000 -0.020 0.001
* .GPS. 1 l 8 16 377 0.000 -17.968 19.808
+ .SHM2. 0 l 7 16 377 0.000 -0.020 0.001

The 2nd column (.PPS., .GPS., and .SHM2.) are just labels configured in the ntp.conf file.

The .PPS. has an ‘o’ in front of the line indicating that the NTP service is using kernel mode PPS as the time source. This is what we want. Notice that the last column is the ‘jitter’. The smaller the number, the better. And in this output, PPS source has the smallest possible value of 1 microsecond.  It can’t get any better than that.

Now in the ntp.conf file, you notice some things that are not ordinarily seen in a normal conf.   First, I’m not using any public time source. I specifically set ‘true’ on the PPS interface

#PPS Kernel mode
server minpoll 4 maxpoll 4 true

This tells the config to use this entry as the source, no exceptions.   In a normal NTP setup, you would allow the ntp service to make the selection based on it’s selection algorithm.   Setting an entry to ‘true’ overrides ntp’s selection making process.   When using public ntp hosts, this is a bad idea, but in our case, we only want to use GPS as the source.

Now I did try various combinations in the ntp.conf using gpsd drivers and public hosts as sources. I found that the config above was the fastest and most reliable way to get the the PPS source.

Tagged , , . Bookmark the permalink.

Leave a Reply

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

Solve : *
26 + 16 =