Several weeks ago I decided to build my own stratum 1 NTP server using a Raspberry Pi 2B. Just like many other hobby projects, this doesn't really serve a purpose, other than learning something new. There are many articles about similar setup on the internet (see references below), these are just my own notes.
To build a stratum 1 NTP server, we need a reference clock. These are typically very expensive cesium chips. A much cheaper solution is to use a GPS receiver. These GPS receivers will receive the current time from GPS satellites which contain cesium chips.
The setup is made up of the following components:
I used the expansion board from Hab Supplies because it does not require any soldering. It contains a Ublox MAX-M8Q module connected via serial to the RPi. The PPS output from the module is connected to GPIO18 (pin 12).
Assembling the hardware is easy, just follow the instructions provided. Once powered on, a red led on the expansion board lights up. Place the antenna in a location with a clear view of the sky and after a while, the timepulse led should start blinking. This could take several minutes.
Some might wonder what kind of data we can get from the GPS module and how we can use it to determine the exact time. There are two signals, the NMEA sentences and the PPS signal.
The GPS module is connected via serial (9600 baud) to the RPi. Every second it sends NMEA sentences. This are simple lines of text values in a comma separated format. The first column indicates the type of data that follows. The most important ones are $GPRMC, $GPGLL, $GPGGA. These all indicate the current position, but more important in this case, they also contain the current time. The downside is that this time information isn't very accurate, it could easily be 100ms off and there is quite a bit of jitter as well. In practice, you get a more accurate time using sources on the internet. So this information is mainly useful when you do not want to depend on internet connectivity.
The PPS signal is the most important for accurate time keeping. It does not provide the current time but instead its a signal that sends one pulse every second. This pulse indicates the start of the second with a high accuracy. The signal is simply a voltage being applied on a GPIO pin for 100 milliseconds, after which it goes back to 0 for 900ms. Visually this looks like this:
In case of the Ublox module, the rising edge indicates the start of the second. Combined with a coarse grained time (accurate to ~0.4 seconds) obtained from the NMEA sentences on the serial port, or another time source such as an NTP server on the internet, this results in a very accurate clock.
You need a kernel with PPSAPI support. Luckily this is included in the current raspbian kernels so this does not require any recompiling. Install the operating system as usual. I used "MINIbian" instead of the official distribution because I don't like installing more packages than needed. Whichever one you choose, start by installing all the latest updates first and assign a static IP to the machine. If you want to use DHCP, beware that some additional configuration may be required to prevent it from overwriting the NTP configuration file.
First of all, change the kernel parameters so that GPIO18 is used as PPS source and disable the serial console:
- Add the following line to /boot/config.txt:
- Edit /boot/cmdline.txt and remove the following two items:
After making these changes, reboot the Pi. During boot (or in dmesg) you should now see the following:
pps_core: LinuxPPS API ver. 1 registered
pps_core: Software ver. 5.3.6 - Copyright 2005-2007 Rodolfo Giometti
pps pps0: new PPS source pps.-1
pps pps0: Registered IRQ 466 as PPS source
Don't worry about PPS source pps.-1, this output is normal on the Raspberry Pi 2.
Install the "pps-tools" package via apt-get and run
ppstest /dev/pps0. You should see one new line per second of output, similar to this:
trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1424290504.449416110, sequence: 81 - clear 0.000000000, sequence: 0
source 0 - assert 1424290505.449422255, sequence: 82 - clear 0.000000000, sequence: 0
source 0 - assert 1424290506.449430223, sequence: 83 - clear 0.000000000, sequence: 0
The ntp package included in raspbian does not support the ATOM (PPS) reference clock so next you need to recompile the ntp package. Many articles build NTP from source, but I prefer rebuilding the raspbian package. This allows me to compile the package on one RPi and easily copy it to another.
- Add the following line to /etc/apt/sources.list:
deb-src http://mirrordirector.raspbian.org/raspbian/ wheezy main contrib non-free rpi
- Install the dependencies required to build the ntp package from source:
apt-get build-dep ntp
- Download the source code:
apt-get source ntp
- Go into the directory that was just created and add
--enable-ATOM to the configure parameters in the debian/rules file
- Increase the version number in debian/changelog. Change from 4.2.6.p5+dfsg-2+deb7u3 to 4.2.6.p5+dfsg-2+deb7u4~pps1 or similar.
- Recompile the NTP package (this will take a while):
- Once compiled, install the new ntp package:
dpkg -i ntp_4.2.6.p5+dfsg-2+deb7u4~pps1_armhf.deb
Now that NTP is installed, add the following udev rules which create the symlink required so that ntpd can use the GPS module. Create a new file /etc/udev/rules.d/09-pps.rules with the following contents and restart the RPi:
KERNEL=="pps0", OWNER="root", GROUP="tty", MODE="0777", SYMLINK+="gpspps0"
Now that PPS is working and can be used by NTPd, all that is left is configuring NTPd to use the PPS source. There are several ways in which this can be configured, either using the NMEA driver with PPS support enabled, or using the ATOM driver with a preferred peer.
The easiest and most reliable if there is no internet access is to use the NMEA driver (20) with flag1 (PPS) enabled. The configuration required is:
# NMEA /dev/gps0, /dev/gpspps0. Mode 17: 9600 baud (16) and process $GPMRC
server 127.127.20.0 mode 17 minpoll 4 maxpoll 4 prefer
fudge 127.127.20.0 time2 0.115
fudge 127.127.20.0 flag1 1 # enable PPS signals processing
fudge 127.127.20.0 flag2 0 # capture PPS pulse on rising edge
fudge 127.127.20.0 flag3 0 # Do not use kernel clock dicipline - ntp comment claims this one is more accurate, and it does not appear to work?
fudge 127.127.20.0 refid GPS
fudge 127.127.20.0 stratum 15
Restart ntpd after making the configuration changes. After a little while, check the status using
ntpq -p and the result should be something like this:
remote refid st t when poll reach delay offset jitter
oGPS_NMEA(0) .GPS. 0 l 6 16 377 0.000 -0.198 0.017
Note the o in front, this means that the PPS signal is used.
Not sure if these make a noticeable difference, but the other changes I made to this device are:
- Set the CPU scaling governor to
- Set the
low_latency flag on the serial port using setserial.
- I have several different configuration other than the one mentioned above, using different drivers (NMEA only, ATOM+NMEA, ATOM+GPSd SHM). The plan is to compare the different options and see if there is a big difference. So far it seems the NMEA only setup performs better when there is no other network-based clock source available.
- I wrote some C code to put the GPS module in "stationary" dynamic mode. Need to compare this over time to see if it makes a noticeable difference.