This post shows how to use a GPS receiver with a Raspberry Pi to build a stratum 1 NTP server. I am showing how to solder and use the GPS module (especially with its PPS pin) and listing all Linux commands to set up and check the receiver and its NTP part, which is IPv6-only in my case. Some more hints to increase the performance of the server round things off. In summary this is a nice “do it yourself” project with a working stratum 1 NTP server at really low costs. Great. However, keep in mind that you should not rely on such projects in enterprise environments that are more focused on reliability and availability (which is not the case on self soldered modules and many config file edits).
At the time of writing (Nov 2018) I am using a Raspberry Pi 1 B (yes, the old one), kernel 4.14.71+ and Raspbian GNU/Linux 9 (stretch). I installed a few relevant packages and gave it a static IPv6 address. Legacy IP (IPv4) is not used at all, only IPv6.
1) Soldering the GPS Module
It’s all about the GPS module “GY-GPS6MV2”, though I am quite sure that there are some other GPS modules with a PPS pin out there. Google for it and get one from China for a couple of Euros. ;) I soldered the five cables directly to the Pi, while I used a small Pigtail from the Hirose connector (the one on the GPS module) to SMA female (not RP-SMA!) to mount it on the housing of the Pi. This SMA connector is commonly used by GPS antennas for cars. In fact I did not use the small GPS antenna that was delivered with the GPS module at all, but a slightly more robust one that can be used outside as well.
Following is the assignment of the five cables from the GPS module to the Raspberry Pi. You need to look at the product details of the module to find the PPS pin. I used a ribbon cable, refer to the photos below:
- GPS module VCC, white: Pi pin 1, +3.3 V
- GPS module GND, black: Pi pin 6, GND
- GPS module RX, grey: Pi pin 8, TXD0
- GPS module TX, purple: Pi pin 10, RXD0
- GPS module PPS, blue: Pi pin 12, GPIO18
This is what it looked like on my desk:
When you’re finished you can just power on the Pi again. If you have a good GPS signal your GPS module will blink blue once a second:
2) Getting started with mere GPS
The first step is to check whether the GPS module is running and whether the Pi receives the generic NMEA 0183 sentences. You have to disable the serial login shell first while keeping the hardware port enabled:
sudo raspi-config, navigate to “5 Interface Options” -> “P6 Serial” and:
- Would you like a login shell to be accessible over serial? –> No
- Would you like the serial port hardware to be enabled? –> Yes
followed by a reboot. After that you can use “screen” to get some output:
sudo screen /dev/ttyAMA0 9600. You can close it with
Ctrl+a \ y. The output should be like:
$GPRMC,132628.00,A,5123.20905,N,00842.80522,E,0.416,,081118,,,A*7F $GPVTG,,T,,M,0.416,N,0.771,K,A*21 $GPGGA,132628.00,5123.20905,N,00842.80522,E,1,09,1.50,235.3,M,47.4,M,,*5C $GPGSA,A,3,23,16,22,03,09,02,26,06,07,,,,2.34,1.50,1.79*07 $GPGSV,3,1,12,02,30,308,21,03,28,110,39,06,58,254,27,07,30,173,21*7D $GPGSV,3,2,12,09,87,242,12,16,08,076,21,19,10,239,,22,08,115,39*71 $GPGSV,3,3,12,23,62,066,47,26,11,050,22,30,03,191,20,39,13,117,*79 $GPGLL,5123.20905,N,00842.80522,E,132628.00,A,A*64 $GPRMC,132629.00,A,5123.20896,N,00842.80502,E,0.130,,081118,,,A*76 $GPVTG,,T,,M,0.130,N,0.241,K,A*26 $GPGGA,132629.00,5123.20896,N,00842.80502,E,1,09,1.50,235.3,M,47.4,M,,*54 $GPGSA,A,3,23,16,22,03,09,02,26,06,07,,,,2.34,1.50,1.79*07 $GPGSV,3,1,12,02,30,308,22,03,28,110,39,06,58,254,27,07,30,173,22*7D $GPGSV,3,2,12,09,87,242,12,16,08,076,19,19,10,239,,22,08,115,39*7A $GPGSV,3,3,12,23,62,066,47,26,11,050,22,30,03,191,20,39,13,117,*79 $GPGLL,5123.20896,N,00842.80502,E,132629.00,A,A*6C $GPRMC,132630.00,A,5123.20904,N,00842.80489,E,0.192,,081118,,,A*7E $GPVTG,,T,,M,0.192,N,0.355,K,A*2A $GPGGA,132630.00,5123.20904,N,00842.80489,E,1,09,1.50,235.1,M,47.4,M,,*56 $GPGSA,A,3,23,16,22,03,09,02,26,06,07,,,,2.34,1.50,1.79*07 $GPGSV,3,1,12,02,30,308,22,03,28,110,39,06,58,254,27,07,30,173,23*7C $GPGSV,3,2,12,09,87,242,12,16,08,076,19,19,10,239,,22,08,115,39*7A $GPGSV,3,3,12,23,62,066,47,26,11,050,21,30,03,191,20,39,13,117,*7A $GPGLL,5123.20904,N,00842.80489,E,132630.00,A,A*6C
Now you need to install the gpsd daemon which needs to run in the background at startup:
sudo apt-get install gpsd gpsd-clients sudo nano /etc/default/gpsd # adjust the following values to: START_DAEMON="true" USBAUTO="false" DEVICES="/dev/ttyAMA0" GPSD_OPTIONS="-n" # the following symlink is needed to have gpsd started after boot correctly # ref: https://www.raspberrypi.org/forums/viewtopic.php?f=45&t=53644#p1048128 sudo ln -s /lib/systemd/system/gpsd.service /etc/systemd/system/multi-user.target.wants/ sudo reboot
After the reboot, check that the daemon is running:
pi@ntp2-gps:~ $ sudo service gpsd status ● gpsd.service - GPS (Global Positioning System) Daemon Loaded: loaded (/lib/systemd/system/gpsd.service; indirect; vendor preset: enabled) Active: active (running) since Thu 2018-11-08 14:39:31 CET; 20h ago Main PID: 768 (gpsd) CGroup: /system.slice/gpsd.service └─768 /usr/sbin/gpsd -N -n /dev/ttyAMA0 Nov 08 14:39:31 ntp2-gps systemd[1]: Started GPS (Global Positioning System) Daemon.
With tools such as
cgps -sor
gpsmonyou get user friendly information from your GPS module such as:
┌──────────────────────────┐┌─────────────────────────────────────────────────┐ │Ch PRN Az El S/N Flag U ││ECEF Pos: +4012339.72m +611236.24m +4812371.38m │ │ 0 2 305 33 23 040f Y ││ECEF Vel: +0.23m/s +0.21m/s +0.07m/s │ │ 1 3 113 24 42 070f Y ││ │ │ 2 4 0 165 0 0110 ││LTP Pos: 50.108056321° 8.682167572° 211.70m │ │ 3 6 246 57 20 040e ││LTP Vel: 0.23m/s 131.7° 0.22m/s │ │ 4 7 172 34 34 070f Y ││ │ │ 5 9 44 88 30 060f Y ││Time: 45 15:57:10.00 │ │ 6 16 73 10 29 050f Y ││Time GPS: 2026+394543.000 Day: 4 │ │ 7 19 236 6 20 040f Y ││ │ │ 8 22 116 5 37 070e ││Est Pos Err 7.21m Est Vel Err 0.00m/s │ │ 9 23 66 59 49 070f Y ││PRNs: 9 PDOP: 1.4 Fix 0x03 Flags 0xdf │ │10 26 46 12 23 040f Y │└─────────────────── NAV_SOL ─────────────────────┘ │11 29 353 1 0 0104 │┌─────────────────────────────────────────────────┐ │12 30 191 6 36 070f Y ││DOP [H] 0.9 [V] 1.1 [P] 1.4 [T] 0.6 [G] 1.5 │ │13 123 151 28 41 061c │└─────────────────── NAV_DOP ─────────────────────┘ │14 124 163 31 0 0110 │┌─────────────────────────────────────────────────┐ │15 126 117 13 0 0014 ││TOFF: 0.156538000 PPS: │ └────── NAV_SVINFO ────────┘└─────────────────────────────────────────────────┘
Good so far. Your GPS is up and running. ;D
3) Leveraging PPS
You now have to activate the pulse per second functionality:
sudo apt-get install pps-tools sudo nano /boot/config.txt # add the following line: dtoverlay=pps-gpio sudo nano /etc/modules # add the following line: pps-gpio sudo reboot
After the reboot, check that the pps module is loaded, which is the case if “lsmod” lists some drivers such as this:
pi@ntp2-gps:~ $ lsmod | grep pps pps_ldisc 2413 2 pps_gpio 3089 1 pps_core 8606 4 pps_gpio,pps_ldisc
and try to get a tick every second (cancel it with Ctrl+c as always):
pi@ntp2-gps:~ $ 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 1541684326.001410675, sequence: 79 - clear 0.000000000, sequence: 0 source 0 - assert 1541684327.001418675, sequence: 80 - clear 0.000000000, sequence: 0 source 0 - assert 1541684328.001424675, sequence: 81 - clear 0.000000000, sequence: 0 source 0 - assert 1541684329.001432675, sequence: 82 - clear 0.000000000, sequence: 0 source 0 - assert 1541684330.001437675, sequence: 83 - clear 0.000000000, sequence: 0 source 0 - assert 1541684331.001445675, sequence: 84 - clear 0.000000000, sequence: 0 ^C
Good again.
4) Configuring NTP
I have first installed NTP via
sudo apt-get install ntpto have all those dependencies and startup scripts in place, but built it from source afterwards as well to run the server in the latest version:
sudo nano /etc/apt/sources.list # uncomment the line with "deb-src" deb-src http://raspbian.raspberrypi.org/raspbian/ stretch main contrib non-free rpi sudo apt-get update sudo apt-get -y build-dep ntp # download the latest ntp: http://ntp.org/downloads.html # in this example it was 4.2.8p12 wget http://www.eecis.udel.edu/~ntp/ntp_spool/ntp4/ntp-4.2/ntp-4.2.8p12.tar.gz cd ntp-4.2.8p12/ # note: the following step takes some time: ./configure --prefix=/usr # note: the following "make" takes even longer, especially on an old Pi 1 B make sudo service ntp stop sudo make install # to disable updating ntp via apt-get, echo the following: echo "ntp hold" | sudo dpkg --set-selections sudo service ntp start
(Fun fact: Since I am running my NTP server via IPv6-only meanwhile the NTP download page is IPv4-only, I had to download and copy the ntp package from another machine to the Raspberry Pi. Sigh. Yes, I know, DNS64/NAT64 would solve the problem.)
You have just installed the latest version of NTP, while “holding” the ntp package within dpkg to not overriding/downgrading it with future “apt-get update”s. You can verify this with the following: (Note the “h” in the very first column which indicates that the ntp package is on hold.)
pi@ntp2-gps:~ $ dpkg -l | grep ntp hi ntp 1:4.2.8p10+dfsg-3+deb9u2 armhf Network Time Protocol daemon and utility programs
Similarly you can verify the running ntp version with:
pi@ntp2-gps:~ $ ntpq --version ntpq 4.2.8p12@1.3728-o Thu Nov 8 11:55:00 UTC 2018 (1)
Now you need to add the reference clock (GPS + PPS) to NTP: “Drivers have addresses in the form 127.127.t.u, where t is the driver type and u is a unit number in the range 0-3 to distinguish multiple instances of the same driver”, Reference Clock Support. In fact we have to use two drivers, one for the GPS to get the rough time (driver 28, SHM, shared memory driver which uses the gpsd daemon) and another one for the PPS (driver 22).
There are a few things to consider. First, concerning the PPS: “While this driver can discipline the time and frequency relative to the PPS source, it cannot number the seconds. For this purpose an auxiliary source is required, ordinarily a radio clock operated as a primary reference (stratum 1) source; however, another NTP time server can be used as well. For this purpose, the auxiliary source should be specified as the prefer peer, as described in the Mitigation Rules and the prefer Keyword page”, PPS Clock Discipline. And about the SHM and its “flag1”: “Skip the difference limit check if set. Useful for systems where the RTC backup cannot keep the time over long periods without power and the SHM clock must be able to force long-distance initial jumps. Check the difference limit if cleared (default)”, Shared Memory Driver.
In the end I used these lines for my two drivers in the
sudo nano /etc/ntp.conffile:
# pps-gpio /dev/pps0 server 127.127.22.0 minpoll 4 maxpoll 4 fudge 127.127.22.0 refid PPS # gpsd clock via shm server 127.127.28.0 minpoll 4 maxpoll 4 prefer fudge 127.127.28.0 time1 +0.130 refid GPS flag1 1
in conjunction with some more (in my case: IPv6 capable) NTP servers:
#http://support.ntp.org/bin/view/Servers/PublicTimeServer000388 server ntp.probe-networks.de prefer #http://support.ntp.org/bin/view/Servers/PublicTimeServer001352 server time.hueske-edv.de prefer #http://support.ntp.org/bin/view/Servers/PublicTimeServer000840 server ntp2.301-moved.de prefer #http://support.ntp.org/bin/view/Servers/PublicTimeServer001363 server ntp.fanlin.de prefer
(Note that I used “fudge 127.127.28.0 time1 +0.130 …” to adjust the GPS time according to the other NTP servers. You can try some higher/lower values to have the offset of your SHM compared to the offsets of those other NTP servers very small.)
Finally, it should give you something like this:
pi@ntp2-gps:~ $ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== oPPS(0) .PPS. 0 l 9 16 377 0.000 0.002 0.004 *SHM(0) .GPS. 0 l 11 16 377 0.000 -2.163 1.915 ptbtime2.ptb.de .PTB. 1 u 1 64 377 10.318 0.258 0.112 +ns2.probe-netwo 124.216.164.14 2 u 22 64 377 3.591 -0.137 0.308 +2a01:4f8:201:41 192.53.103.108 2 u 14 64 377 16.534 -4.055 0.094 +2a02:a00:1009:6 193.190.230.65 2 u 65 64 377 6.683 -0.072 0.124 +2001:4ba0:ffa4: 130.149.17.21 2 u 18 64 377 6.266 -2.095 0.117
Note the ° symbol (= PPS peer when the prefer peer is valid) before the PPS, as well as the * symbol (= system peer) before the SHM; refer to Peer Status Word. These symbols must be present. Otherwise your instance is not working correctly, which would show an x instead.
Congratulations! You have your own NTP server on a Raspberry Pi with GPS and PPS up and running. Go for it! :D
5) Enhanced PPS Performance
[UPDATE Nov. 2018] Please note that I was not able to reproduce this behaviour on a fresh Raspbian stretch installation in 2018 anymore. Independent of the below mentioned settings the jitter of the PPS was about 4 µs. The following listings are from an NTP server with Raspbian jessie, kernel 4.4.26+, in 2016/2017.[/UPDATE]
I came across the very detailed page from David Taylor in which he has some thoughts about reducing the (already very good) jitter for the PPS. Section “Enhanced PPS performance” on his page.
I opened the file
sudo nano /boot/cmdline.txtand added
nohz=offat the end of this single line. Of course with a space before it. Followed by a
sudo reboot.
The jitter of the PPS driver decreased from about 0.005 ms = 5 µs to about 2 µs. Wow!
# BEFORE pi@ntp2:~ $ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== oPPS(0) .PPS. 0 l 15 16 377 0.000 -0.005 0.005 *SHM(0) .GPS. 0 l 14 16 377 0.000 -1.734 1.173 name3.glorb.com 216.218.254.202 2 u 8 64 377 175.817 -2.305 0.220 2001:19f0:6c01: 36.224.68.195 2 u 60 64 377 2.282 0.936 0.078 isis.uni-paderb 192.53.103.108 2 u 13 64 377 9.797 1.354 6.588 # AFTER pi@ntp2:~ $ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== oPPS(0) .PPS. 0 l 4 16 377 0.000 -0.001 0.002 *SHM(0) .GPS. 0 l 7 16 377 0.000 -0.166 1.897 ntp5.netcompart 193.79.237.14 2 u 25 64 377 6.924 1.350 2.424 headnode.szeged 0.67.4.139 2 u 20 64 377 22.045 0.309 0.220 ftp.icm.edu.pl 210.100.177.101 2 u 61 64 377 19.681 0.261 0.146
6) Reducing Ethernet Latency
And even one more hint from David Taylor about reducing the Ethernet latency on a Pi: Adding another option to the single line in
sudo nano /boot/cmdline.txtthat states:
smsc95xx.turbo_mode=N(reboot needed) indeed reduces the “delay” as shown in the “ntpq -p” output again. I used two Raspberry Pis connected via a single switch, first time without the option, second with the option set on both Pis. The delay between those NTP servers decreased from about 0.916 ms to 0.632 ms. Nice.
# BEFORE pi@ntp1-dcf77:~ $ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== GENERIC(0) .DCFa. 0 l - 64 0 0.000 0.000 0.000 *2003:de:2016:33 .PPS. 1 s 28 64 377 0.916 1.125 0.759 ptbtime2.ptb.de .PTB. 1 u 26 64 377 9.782 1.081 0.419 # AFTER pi@ntp1-dcf77:~ $ ntpq -p remote refid st t when poll reach delay offset jitter ============================================================================== GENERIC(0) .DCFa. 0 l - 64 0 0.000 0.000 0.000 *2003:de:2016:33 .PPS. 1 s 24 64 377 0.632 0.120 0.737 ptbtime2.ptb.de .PTB. 1 u 35 64 377 9.444 1.592 0.554
That’s it for now. Wow! If you have any suggestions, please write a comment.
Links
As always here are some links for further reading:
- (de) raspberry.tips – GPS Modul mit dem Raspberry Pi – Ortung und Navigation
- (de) raspberry.tips – Raspberry Pi Uhrzeit über GPS beziehen – Zeitserver
- David Taylor – Quick start NTP on the Raspberry Pi
- David Taylor – The Raspberry Pi as a Stratum-1 NTP Server
- Frillip’s Blog – Setting up a Stratum 1 NTP server on a Raspberry Pi
- NTP – PPS Clock Discipline (driver 22)
- NTP – Shared Memory Driver (driver 28)
Featured image “Kompass_.jpg” by Rolf Schmitz is licensed under CC BY-SA 2.0.