Does this site look plain?

This site uses advanced css techniques

Table of Contents
  1. Notes and caveats
  2. Parts List
  3. Setting up the OS
  4. Understanding GPS data
  5. Satellite Fixes
  6. Linux / Ultimate GPS Hat
  7. Install/config GPSD
  8. Feeding NTPD
  9. Things to watch for
  10. References

Raspberry Pi 3 with my GPS hat I've been playing with the Raspberry Pi for a while now — what a delight! — and have finally managed to turn one of them into a time server using the Adafruit GPS hat. It took much longer than it should have, with resources scattered all over the internet, and it was quite frustrating.

I hope to provide guidance to save others some time, though I wish to be clear that I am documenting my setup: it may not be optimal, but it works. This is also not intended to be a tutoral for first-time Raspberry Pi users, or for those who are not comfortable working with hardware and doing a bit of light soldering.


Important notes and caveats

Parts List

There are many sources for these parts, but the coolest by far is the awesome adafruit, founded by the lovely Lady Ada: she's the EE I wish I were. So many cool parts, projects, kits - it's a one-stop shop for all things Raspberry Pi.

Raspberry Pi 3 - Model B Raspberry Pi 3; image from adafruit
This is the current Rasperry Pi 3 with the 40-pin GPIO header and Ethernet and the usual Pi experience. I have bought probably half a dozen of these fun boards. So far.
This item is meant to include the power supply and the micro SD card holding the operating system. It's assumed you know how to use the Raspberry Pi before attempting this project.
I use good-quality SanDisk or Samsung micro SD cards; for a GPS time server, 16GB is plenty large. 8GB would probably work, 4GB too small, but larger cards are often useful even if you don't think you need all the space: logs often grow faster than you think, plus having plenty of free space means the flash card's wear leveling algorithms will be able to do a better job keeping things fast and clean.

Adafruit Ultimate GPS Hat for Raspberry Pi Raspberry Pi GPS Hat; image from adafruit
This is Raspberry Pi "hat" that sits directly on top of the GPIO headers, including the GPS chip (with integrated passive antenna), small breadboarding area, plus a tiny uFL connector for an external GPS antenna.
Note #1: this product requires light soldering (attaching the 40-pin header)
Note #2: The adafruit site claims boldly that this Hat does not work with the Pi 3, but it's been working great for me, though with a bit of special sauce (hence this paper).

SMA to uFL adapter cable uFL to SMA pigtail; image from adadruit
This snippet of a cable has a tiny uFL connector on one end for attaching to the Ultimate GPS Hat, and the other end with the larger SMA connector going to the GPS antenna. As the uFL connector is tiny and fragile, I recommend getting more than one as a spare in case of breakage.

Active GPS Antenna
Active GPS Antenna; image from adafruit GPS works with very, very weak radio signals, and having a line of sight visibility to the sky is important for good reception, and this active antenna claims to achieve 28db of gain; this is substantial and I presume it uses the 3.3 volts DC supplied by the pi to the antenna.
And the 5 meter cable also allows placement near a window, so the Raspberry Pi itself can be place where it's convenient, and the built-in magnet makes it easier to make it stay where you want it to.
CR1220 coin-type battery (optional)
The Ultimate GPS Hat includes a slot for this 3v lithium coin cell for maintaining the realtime clock when power is removed: having some idea for the correct time at power up makes for much faster GPS synchronization at bootup.
Raspberry Pi Hat Enclosure (optional)
There are many, many options for cases for your Raspberry Pi project, but I liked this case that's designed for units with a hat: there's a clear cover, and it all fits well with a bit of modding of the case to make room for the two antennae.
You can use any case you like.

Setting up the OS

I typically use the NOOBS mechanism for setting up my Raspberry Pi units, and these instructions assume 2.1.0 with the full configuration. I do not typically use the X11 GUI, but this is likely the most common variant so I'm leaving it in the instructions.

Install Raspbian via NOOBS
I used NOOBS 2.1.0 with the full configuration, not minimal.
Apply all pending updates
After becoming root, run apt-get update, followed by apt-get upgrade, then reboot. This gets everything current: it's not known if this is strictly necessary, but I always do it just because.
Enable SSH for easier remote work (optional)
Those who care to work directly on the console may do so, but my R-Pi configurations always use a tiny chicklet-type keyboard with poor trackpad, so I prefer to do my work over Secure Shell from my main workstation.
Enabling SSH involves:
  1. In the X11 GUI click the little raspberry icon in the upper left
  2. Select Preferences, then Raspberry Pi Configuration
  3. Click the [Interfaces] tab
  4. Enable (*) SSH, then click OK
  5. Login over the network via SSH
Those with a full-sized keyboard, good display, and a mouse may well do all of this directly without setting up SSH.
IMPORTANT: if you enable Secure Shell, be sure you secure it or your network properly.
Customize the rest of the OS per your preference
Here you can do what you like: set up your own user account (rather than use the pi user), configure a static IP rather than rely on DHCP, etc. On my system I have full IPv6 support on a private VLAN, and I usuall disable X11.

Understanding GPS data and signals

A full discussion of the Global Positioning System is far beyond the scope of this Tech Tip, but some items are important

First, though the GPS signals are in the radio spectrum, GPS controllers typically decode them into an ASCII data stream that can be parsed to determine the location and time.

This data stream is in NMEA — National Marine Electronics Association — format which includes far more data than we care about for the purposes of accurate timekeeping. Sample data looks like:

$GPGGA,043801.000,3343.3999,N,11749.3068,W,2,07,1.25,24.0,M,-34.2,M,0000,0000*66
$GPGSA,A,3,28,19,30,11,17,13,15,,,,,,1.54,1.25,0.90*0C
$GPGSV,3,1,12,17,85,221,32,19,61,216,21,28,51,040,24,30,46,127,27*76
$GPGSV,3,2,12,13,45,255,18,46,42,149,,15,26,290,17,01,21,056,*77
$GPGSV,3,3,12,06,15,170,,11,12,044,12,07,11,133,,24,05,315,*7C
$GPRMC,043801.000,A,3343.3999,N,11749.3068,W,0.39,297.37,240117,,,D*7E
$GPVTG,297.37,T,,M,0.39,N,0.72,K,D*3F

We don't really need to understand any of the above — the gpsd software does that for us — but knowing vaguely what the data streams look like will help with troubleshooting later.

But less obvious is the second data source, the PPS (Pulse Per Second) signal, which is critical for accurate timekeeping.

The gist of PPS is that the NMEA data stream is sent to the computer ahead of the start of the next second (perhaps 500 msec early), but PPS signal marks the exact start of that second. This eliminates variations due to serial timing and other factors.

"At the tone, the time will be 3:12:07 PM.... (beep)" - PPS is that tone.

The PPS signal is delivered on one of the GPIO pins, and the Linux kernel has specific support for treating this signal as special with respect to timekeeping. The chip can deliver the PPS pulse within 10 nanoseconds of the top of the second, though it seems unlikely that Linux can actually maintain that kind of precision.

It's possible to configure an accurate GPS time source without PPS, it will still be within one second of the correct time, but those of us with more free time than good sense will dig for that extra accuracy.

The fix is in?

GPS satellites in orbit Though some GPS receivers (including, I think, the one used in the Ultimate GPS hat) can compute a rough time based on seeing just one satellite, proper GPS operation needs to see at least four satellites to obtain a proper 3D position fix.

GPS signals are extremely weak, and good visibility to the sky is important for acquisition: a good antenna located outdoors, or at least very near a window, helps a lot.

But it's not just a matter of seeing the satellite: the receiver has to know where the satellites are in space as they fly around the planet: this requires a catalog of every satellite and their orbital parameters. This catalog is known as the "almanac", and it's sent over the air every 12.5 minutes; the GPS receiver needs to have this information before it can acquire a real fix.

This means that an initial power-on of the GPS receiver must not only see all the satellites, but receive the full almanac before it can acquire a fix. This time can be many minutes.

On the test setup used to write this Tech Tip, with just a pigtail antenna (no active external GPS antenna), a single-satellite's rough UTC time was reliably obtained in about 60 seconds after power on, but a 3D fix took between 8 and 20 minutes, with the PPS signal showing up a couple of minutes later.

The Ultimate GPS Pi Hat has a small red LED to indicate whether it has a proper fix or not:

Note: the LED only determines the 3D fix; you can't tell from looking at the board whether the PPS signal is being delivered or not without adding a bit of hardware (another LED).

Though you'll get the NMEA messages from the unit over serial whether it has a fix or not, there won't be a PPS. This means that without a good GPS fix, you cannot test whether you have much of this working or not.

The small CR1220 coin cell on the Ultimate GPS Hat board can power the realtime clock inside the GPS chip, as well as maintain the almanac while powered off: though it won't be tracking the satellites when the power is off, it makes a huge difference in time-to-first-fix: typically less than 60 seconds.

ProTip: Make sure your GPS board obtains a solid fix before troubleshooting the GPS-to-Pi interface.

Configure Linux for the the Ultimate GPS Hat

As noted in the previous section, there are two data streams coming from the Ultimate GPS hat, and we have to configure them both. Due to changes between the Raspberry Pi 2 and 3, instructions differ substantially between the two, and this is definitely more complicated on the Pi3 than the Pi2.

Serial port

The Ultimate GPS hat delivers its data over a serial port at 9600 bps, which uses pins 14 and 15 on the GPIO header.

On the Raspberry Pi 2, this went directly to the hardware UART and was available to Linux on /dev/ttyAMA0. Assuming there was no serial console using it, GPS works great here.

But on the Pi 3, the high-performance hardware UART is used by the Bluetooth subsystem, and the serial port supported on GPIO pins 14+15 is emulated much more weakly and is available on /dev/ttyS0. This is pretty much a software UART, and I don't like it.

Fortunately, it's possible to shuffle around the hardware in software (!) to take back the hardware UART for our purposes, and I am so grateful to Jon Watkins for his outstanding article on this exact subject: I'd not have figured this out otherwise. It's very well written and I'm just going to cobble his instructions.

Disable the console getty programs
This refers to the serial console, which is generally useful, but as we prefer to use the UART for our GPS Hat, we have to disable the consoles. This is done in several steps.
# systemctl stop serial-getty@ttyAMA0.service
# systemctl disable serial-getty@ttyAMA0.service
This prevents the serial console programs from starting, but does not keep the Linux kernel from attempting to use it. This must be disabled by editing /boot/cmdline.txt and removing the serial portions shown in strikeout text:
dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p7 ...
Disable Bluetooth and steal the hardware UART
I didn't care about Bluetooth so was happy to disable it entirely and use the /dev/ttyAMA0 serial port for the GPS, and this is easy to do with the Device Tree Overlay facility: these allow easy tailoring of low-level device behavior with a simple config file.
Edit /boot/config.txt and add the lines:
# Use the /dev/ttyAMA0 UART for user applications (GPS), not Bluetooth
dtoverlay=pi3-disable-bt
Those wishing to actually use Bluetooth with the software UART can do so, though with reduced efficiency, with:
# Use software UART for Bluetooth
enable_uart=1
dtoverlay=pi3-miniuart-bt
All changes to /boot/config.txt require a reboot to make it so. My configs do not use Bluetooth at all.
Though not strictly necessary, we can also disable the hciuart service that nominally attempts to talk to the UART; this may prevent some warnings in the logfiles:
# systemctl disable hciuart

If this has been done correctly (and after a reboot), we expect to see NMEA data on that serial port, which you can see directly with:

$ cat /dev/ttyAMA0
$GPGGA,052731.000,3343.3943,N,11749.3064,W,2,04,3.93,24.8,M,-34.2,M,0000,0000*65

$GPGSA,A,3,13,17,28,19,,,,,,,,,4.05,3.93,0.99*0C

$GPRMC,052731.000,A,3343.3943,N,11749.3064,W,0.84,313.46,240117,,,D*74
...

If you see the above, your serial port is configured correctly and it's seeing GPS data.

Do not move to the next step until you can see serial data.

PPS (Pulse Per Second)

Linux has special kernel support for Pulse Per Second input via a GPIO pin to help synchronize the time: it associates a hyper-precise kernel timestamp with the rising edge of the PPS signal and makes it available to the application; it can then get highly accurate timestamps.

Install pps-tools
These are a standard package, though not installed by default; they are added with:
# apt-get install pps-tools
These tools are definitely needed.
Enable PPS support in the kernel
We have to tell the Linux kernel that PPS support is desired, and which GPIO pin to use for it. As with the UART configuration, this is done in /boot/config.txt by adding this to the bottom:
# enable GPS PPS
dtoverlay=pps-gpio,gpiopin=4
NOTE: different GPS hats use different GPIO pins for PPS; check your documentation to see whether it's using GPIO #4 or GPIO #18 and edit above as needed.
Test the PPS support
With this done, the special device /dev/pps0 is available to poll the PPS signal, and it can be checked out with the ppstest program:
# 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 1485236391.009463473, sequence: 148 - clear  0.000000000, sequence: 0
source 0 - assert 1485236392.009462426, sequence: 149 - clear  0.000000000, sequence: 0
source 0 - assert 1485236393.009459816, sequence: 150 - clear  0.000000000, sequence: 0
source 0 - assert 1485236394.009457259, sequence: 151 - clear  0.000000000, sequence: 0
source 0 - assert 1485236395.009456680, sequence: 152 - clear  0.000000000, sequence: 0
...
^C
This should show one line every second; if it does, your Ultimate GPS hat is delivering PPS to the Linux kernel properly, and you have all the GPS inputs required: now we have to use them.

Disable NTP support in DHCP

DHCP on many networks delivers time server config to their clients, allowing them to synchronize time properly along with the rest of the network. This is a good thing except for NTP time servers themselves, and disabling this behavior took much longer than it should have.

Failing to make these updates will find your /etc/ntp.conf either edited or ignored; it was maddening.

Remove ntp-servers from /etc/dhcp/dhclient.conf
...
#supersede domain-name "fugue.com home.vix.com";
#prepend domain-name-servers 127.0.0.1;
request subnet-mask, broadcast-address, time-offset, routers,
        domain-name, domain-name-servers, domain-search, host-name,
        dhcp6.name-servers, dhcp6.domain-search,
        netbios-name-servers, netbios-scope, interface-mtu,
        rfc3442-classless-static-routes , ntp-servers;
#require subnet-mask, domain-name-servers;
#timeout 60;
...
Remove or comment out "ntp-servers" from /etc/dhcp/dhclient.conf
...
# A list of options to request from the DHCP server.
option domain_name_servers, domain_name, domain_search, host_name
option classless_static_routes
# Most distributions have NTP support.
# option ntp_servers
# Respect the network MTU.
...
Delete these related files:
  • /etc/dhcp/dhclient-exit-hooks.d/ntp
  • /lib/dhcpcd/dhcpcd-hooks/50-ntp.conf
  • /var/lib/ntp/ntp.conf.dhcp (might not exist)

It probably requires reboot for this to take effect. At this point, you will be able to configure NTP support entirely without interference from the rest of the system.

Install and configure GPSD

GPSD logo There are a number of services that can decode and interpret the NMEA data coming from your Ultimate GPS hat, but the most popular by a fair measure is the open source GPSD.

This package is available in the usual apt-get repository, but as of this writing, the version found there (3.11) didn't work right for me — permissions issue with the PPS device, I think — and the only way I got this to work was to build it myself using version 3.16. I don't know if 3.11 has actual problems, or if I just didn't know what I was doing.

NOTE: these instructions are just to show what I did, not a tutorial.

I'll note that this is the first software package I've ever used that employed the scons build tool, replacing autoconf and make; this was a learning experience for me in part because it's built around python, not m4.


# apt-get install scons libncurses5-dev python-dev 
("scons" is the new "make")

# cd /home 
(you may want to put this somewhere else)

# wget http://download-mirror.savannah.gnu.org/releases/gpsd/gpsd-3.16.tar.gz 
(file downloads)

# tar -xzvf gpsd-3.16.tar.gz 
(file extracts)

# cd gpsd-1.16 

# scons 
(lots of build steps)

# scons check 
(runs many tests)

# scons install 
(installs stuff mostly to /usr/local/sbin/ and /usr/local/share/)

The above installs the software, but unfortunately there's a raft of extra configuration required that doesn't seem to be handled by the gpsd source distribution. My knowledge of systemd is limited, so it's possible I just did this all wrong.

The instructions in the gpsd file "build.txt" suggests installing with "scons udev-install", but this appears to hook into the udev code to start/invoke gpsd only when a USB-based GPS device is added. Since we're using the hardware serial port, we want GPSD running all the time, and the udev stuff looks spurious.

Create /etc/default/gpsd
This file contains the default parameters for the service when it starts, and this includes the required device names for the serial port and PPS device names. gpsd will definitely not work right without this.
# /etc/default/gpsd
#
# Default settings for the gpsd init script and the hotplug wrapper.

# Start the gpsd daemon automatically at boot time
START_DAEMON="true"

# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="false"

# Devices gpsd should collect to at boot time.
# They need to be read/writeable, either by user gpsd or the group dialout.
DEVICES="/dev/ttyAMA0 /dev/pps0"

# Other options you want to pass to gpsd
#
# -n    don't wait for client to connect; poll GPS immediately

GPSD_OPTIONS="-n"
Key valus here are the DEVICES=, which provides the name of the serial port feeding NMEA data (/dev/ttyAMA0) and the pulse-per-second (/dev/pps0); they must appear in that order.
Also important is the -n value in GPSD_OPTIONS: this tells GPSD to start talking to the GPS device on startup and not to wait for the first client to attach. Since we have a fulltime connection via the hardware serial port, there's no need not to start talking with the GPS unit immediately.
Configure the systemctl scripts/handlers
I'm much more familiar with the init.d type startup scripts — going back decades — than I am with systemd, but the latter is the new world order, so it's probably best to go with the flow.
Under the gpsd source directory systemd/ are three files, but we only want two of them: gpsd.service and gpsd.socket. You'll want to inspect both files to be sure that parameters inside those files match what you're actually using (the location of the executable, for instance), and then copy them to the /lib/systemd/system/ directory.
The file gpsdctl@.service need not be copied; this is related to the auto-hotplug support where GPS devices can come and go, but for a hardwired connection we don't need this.
Now we must get systemctl to read those files, then we need to enable and start the service:
# systemctl daemon-reload

# systemctl enable gpsd

# systemctl start gpsd
Check status
Assuming the "start" operation succeeded, check the status more generally via systemctl:
# systemctl status gpsd
* gpsd.service - GPS (Global Positioning System) Daemon
   Loaded: loaded (/etc/systemd/system/gpsd.service; enabled)
   Active: active (running) since Fri 2018-03-09 19:52:51 UTC; 3s ago
 Main PID: 24956 (gpsd)
   CGroup: /system.slice/gpsd.service
           +-24956 /usr/local/sbin/gpsd -N -n -b /dev/ttyAMA0 /dev/pps0

Mar 09 19:52:54 gpsclock.unixwiz.lan gpsd[24956]: gpsd:PROG: PPS:/dev/pps0 Assert cycle: 1985535384, duration:  999999 @ (null)
Mar 09 19:52:54 gpsclock.unixwiz.lan gpsd[24956]: gpsd:PROG: NTP: ntpshm_put(/dev/pps0 pps)  1520625174.000000000 @  1520625173.999978052
...
Here we're looking for the status is enabled and active and running, possibly with a few snippets of logfiles after.

At this point, if all is well, the gpsd service will be running and syncing with the GPS device. That doesn't necessarily mean that the GPS module has a fix on the satellites; that you can tell by looking at the Ultimate GPS hat for a brief red LED flash every 15 seconds. A LED that's flashing one second on/one second off has not yet acquired a fix.

Now for the moment of truth: we're going to launch the gpsmon program to see what the daemon is doing:


+------------------------------------------------------------------------------+
|Time: 2017-01-24T14:04:17.000Z Lat:  33 43' 23.556" N Lon: 117 49' 18.353" W  |
+--------------------------------- Cooked TPV ---------------------------------+
+------------------------------------------------------------------------------+
| GPGGA GPGSA GPRMC GPZDA GPGSV                                                |
+--------------------------------- Sentences ----------------------------------+
+------------------++----------------------------++----------------------------+
|Ch PRN  Az El S/N ||Time:      140417.000       ||Time:      140417.000       |
| 0  18 305 73  28 ||Latitude:     3343.3926 N   ||Latitude:  3343.3926        |
| 1  21  22 68  32 ||Longitude:   11749.3059 W   ||Longitude: 11749.3059       |
| 2  10 257 45  22 ||Speed:     0.19             ||Altitude:  16.2             |
| 3 133 147 44   0 ||Course:    309.52           ||Quality:   2   Sats: 06     |
| 4  15  62 40  17 ||Status:    A       FAA: D   ||HDOP:      1.33             |
| 5  29 155 32  26 ||MagVar:                     ||Geoid:     -34.2            |
| 6  20  52 29   0 |+----------- RMC ------------++----------- GGA ------------+
| 7  27 318 23  21 |+----------------------------++----------------------------+
| 8  16 277 22  16 ||Mode: A3 Sats: 18 21 10 15  ||UTC:           RMS:         |
| 9  26 248 18  18 ||DOP: H=1.33  V=0.93  P=1.62 ||MAJ:           MIN:         |
|10  13  41 15   0 ||TOFF:  0.487295282          ||ORI:           LAT:         |
|11  32 194 12   0 ||PPS:  0.001810795           ||LON:           ALT:         |
+------ GSV -------++-------- GSA + PPS ---------++----------- GST ------------+
(68) $GPGSV,4,1,13,18,73,305,28,21,68,022,32,48,47,206,,10,45,256,22*79
(68) $GPGSV,4,2,13,15,40,062,17,29,32,155,26,20,29,052,,27,23,318,21*73
(66) $GPGSV,4,3,13,16,22,277,16,26,18,248,18,13,15,041,,32,12,194,*79
(29) $GPGSV,4,4,13,24,04,115,*4C
(72) $GPRMC,140410.000,A,3343.3923,N,11749.3054,W,0.30,313.36,240117,,,D*7B
(35) $GPZDA,140410.000,24,01,2017,,*55
------------------- PPS offset: -0.001808670 ------
(82) $GPGGA,140411.000,3343.3924,N,11749.3055,W,2,06,1.33,16.2,M,-34.2,M,0000,0000*64
(54) $GPGSA,A,3,15,29,10,26,21,18,,,,,,,1.62,1.33,0.93*0C
(72) $GPRMC,140411.000,A,3343.3924,N,11749.3055,W,0.34,316.90,240117,,,D*71
(35) $GPZDA,140411.000,24,01,2017,,*54
------------------- PPS offset: -0.001808677 ------
(82) $GPGGA,140412.000,3343.3925,N,11749.3056,W,2,06,1.33,16.2,M,-34.2,M,0000,0000*65
(54) $GPGSA,A,3,15,29,10,26,21,18,,,,,,,1.62,1.33,0.93*0C
(72) $GPRMC,140412.000,A,3343.3925,N,11749.3056,W,0.19,316.43,240117,,,D*71
...

The top line summarizes the best information collected from the GPS receiver, including the time (in UTC) and the current location, and the bottom portion of the screen shows the more or less raw data from the GPS unit.

Of particular import are the lines ---- PPS offset --- because they indicate proper handling of the pulse-per-second input.

Running this on the real console likely shows proper clean line drawing characters, but for an SSH session (putty or SecureCRT) you may need to set your terminal type to VT100 (via "TERM=vt100 gpsmon") so things look right.

If the top of the gpsmon display shows:

+------------------------------------------------------------------------------+
|Time: 1980-01-08T14:14:50.092Z Lat: n/a               Lon: n/a                |
+--------------------------------- Cooked TPV ---------------------------------+

The lack of Lat/Lon indicates the GPS receiver has not obtained a satellite fix, and the time in 1980 indicates that it doesn't see one satellite to get a rough time.

Always check for the fix LED on the GPS hat itself, which should flash once every 15 seconds; if instead it's one second on/one second off, then it doesn't have a fix and there's not much that gpsmon can do for you.

Do not proceed to the next section until you get valid PPS input and valid GPS data showing up via gpsmon.

Feeding NTPD

The final part of this project configures the NTP (Network Time Protocol) daemon to consume the GPS time, possibly correlate it with other sources, and make accurate time available to the local machine and the rest of the network.

Many have configured NTP to check in with time servers on the internet, there are many references on the internet about this; this is not a general tutorial about NTP. We're here only to hook into GPS.

Disclaimer: there is a lot of confusing stuff surrounding ntpd, and I'm not at all any kind of expert; I waffled my way through most of this, and still don't fully get most of the intricacies. Some of this information might be wrong.

The question is: how does NTP get the precise time from GPSD?, and the answer is via shared memory. NTPD supports multiple kinds of time drivers, and one of them is a set of shared memory segments that feeds accurate time to all callers.

Note that GPSD also provides a TCP-based network service for all of the data it collects (certainly location data), but the network service is not useful for accurate time; the shared memory segment is far more precise for process-to-process communications.

The configuration is a little tricky and is not at all intuititive, so we'll do checking in steps.

First is the tool ntpshmmon, which monitors shared memory as if it were a client, and reports the counters found:

# ntpshmmon
ntpshmmon version 1
#      Name   Seen@                Clock                Real               L Prec
sample NTP0 1485442395.871672922 1485442395.484197563 1485442395.000000000 0  -1
sample NTP2 1485442395.871817243 1485442395.006707787 1485442395.000000000 0 -20
sample NTP0 1485442396.371839022 1485442395.484197563 1485442395.000000000 0  -1
sample NTP2 1485442396.371890219 1485442396.006709260 1485442396.000000000 0 -20
sample NTP0 1485442396.872924176 1485442396.452044456 1485442396.000000000 0  -1
sample NTP2 1485442396.872965791 1485442396.006709260 1485442396.000000000 0 -20
sample NTP0 1485442397.373009183 1485442396.452044456 1485442396.000000000 0  -1
sample NTP2 1485442397.373049704 1485442397.006707661 1485442397.000000000 0 -20
...
^C to interrupt

The key values here are NTP0 (which represents the time), and NTP2, which represents the PPS; I believe the "Prec" (precision) column is what identifies this but am not sure. These NTP# values are key for the next step.

In /etc/ntp.conf we often find a list of servers

/etc/ntp.conf
...
# pool.ntp.org 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: 
server 0.debian.pool.ntp.org iburst
server 1.debian.pool.ntp.org iburst
server 2.debian.pool.ntp.org iburst
server 3.debian.pool.ntp.org iburst
...

Even though GPS provides highly accurate time, it's a best practice to have multiple time sources and let ntpd sort it out: if the GPS antenna gets disloged or blocked or something, we want at least some semblance of accurate time rather than just freewheeling with no real time source. So, comment out all but the first two server lines, giving us two outside sources.

We're also adding in some fake server lines that hook into the shared memory portion. This is done with "servers" using IP addresses in the 127.127.t.u range, where "t" is the clock driver type and "x" is the unit number within that type. The type numbers are hardcoded in the program with several dozen driver numbers assigned.

A small sample of drivers:

We only care about 127.127.28.u, which is the Shared Memory driver supported by gpsd, but these are not real IP addresses; don't bother trying to ping them.

/etc/ntp.conf
add at the bottom of the file  ...

# GPS PPS reference
server 127.127.28.2 prefer
fudge  127.127.28.2 refid PPS

# get time from SHM from gpsd; this seems working
server 127.127.28.0
fudge  127.127.28.0 refid GPS

The key values here are the final octets: the number represents the NTP# tag in shared memory, so 127.127.28.0 refers to NTP0 (the time source) and 127.127.28.2 represents NTP2 (the PPS signal). The refid tags are actually free text (1-4 chars) and can be anything you like; these show up in the "peers" display so you can tell where the time source got its data from. If you don't include a refid, it will show as .SHM., the tag for the driver.

A reliable source has suggested that putting the PPS server+fudge stanzas first, followed by GPS, and then only preferring PPS, will yield a more reliable clock.

Once the servers have been updated in ntpd, restart the service and keep an eye on the peers:

# systemctl restart ntp

# ntpq -p

# systemctl restart ntp
root@raspberrypi:~# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
 PBX.cytranet.ne .STEP.          16 u  168   64    0    0.000    0.000   0.000
 SHM(0)          .GPS.            0 l    1   64    0    0.000    0.000   0.000
 SHM(2)          .PPS.            0 l    -   64    0    0.000    0.000   0.000

A key thing to look for is the character before the remote name, and these indicate how NTPD considers it. Expect these to be spaces at startup, changing to other characters as reachability and reliabilty factors are known.

As ntpd runs, it polls/queries each of the servers, either via the network connection for a "regular" ntp client (on 123/udp), or via the special kinds of drivers, and it reports reachability and tries to figure out which ones are reliable, updating the peer status word character as it goes along.

Run a few minutes after starting up, we'll see the peer information has updated:

# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+74.120.81.219   45.56.123.24     3 u    8   64   17   53.301  499.263  17.486
+SHM(0)          .GPS.            0 l   15   64   17    0.000  -65.198  14.456
*SHM(2)          .PPS.            0 l   13   64   17    0.000  523.376  31.813

Note: the first remote will vary in this paper because server 0.debian.pool.ntp.org will come up with a different pool member each time it starts; this is expected.

What we hope to see are non-zero values in the two SHM lines, indicating that ntpd is in fact able to access the shared memory segments. Seeing reach of zero is not great for either one.

Note: some Linux systems have the service as ntpd but on the Raspberry Pi (and probably on Debian proper) it's ntp.

ProTip: Getting the 127.127.X.X stuff right took a long time because GPSD actually creates more segments than this (6 by default), and prior to discovering ntpshmmon it was not clear at all which one was actually being used. I spun my wheels a lot here.

Things to watch for

One of the most difficult parts of getting this running was not knowing what to look for in the ntpq -p output: is it even working? It was very confusing.

# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
 resolver2.skyfi 216.218.192.202  2 u    1   64    1   33.927    5.140   0.000
 nx0.xplo-re.net 145.238.203.14   2 u    1   64    1  165.284    5.741   0.000
 SHM(2)          .PPS.            0 l    -   64    0    0.000    0.000   0.000
 SHM(0)          .GPS.            0 l    -   64    0    0.000    0.000   0.000

Above, there's a space at the start of every one of these potential peers, plus zeroes all the way across for the SHM drivers; ntpd is just getting started and has almost no useful knowledge about the time.


# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+resolver2.skyfi 216.218.192.202  2 u   21   64    7   33.927    5.140  28.477
*nx0.xplo-re.net 145.238.203.14   2 u   24   64    7  160.476   12.490   7.326
 SHM(2)          .PPS.            0 l    -   64    0    0.000    0.000   0.000
 SHM(0)          .GPS.            0 l    -   64    0    0.000    0.000   0.000

This time we see that ntpd has settled down on the second peer (with the * tally marker) and is using that for the time source, but we still see zeroes across all the SHM entries: this means that ntpd is not seeing gpsd, either due to misconfiguration or because gpsd is not running.


# ntpq -p
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
+resolver2.skyfi 216.218.192.202  2 u   35   64  177   33.844    5.535 187.848
+nx0.xplo-re.net 145.238.203.14   2 u   39   64  177  160.476   12.490   6.485
*SHM(2)          .PPS.            0 l   53   64    3    0.000    0.754   1.000
xSHM(0)          .GPS.            0 l   52   64    3    0.000  -546.93 113.054

This is looking much better: we see that ntpd has selected the PPS signal (third peer) as primary so we know that's working. The time difference between the PPS clock and the local linux kernel is 754 microseconds, which is pretty good. The jitter is still a full millisecond, and with more time it ought to settle down.

I'll note that the SHM/GPS peer has been excluded outright (the x tally character), most likely because the clock is so much different from the other more accurate sources. This is expected: for a clock like this, the GPS-based time arrives ahead of the PPS signal, and that means it shows up early. I expect it should hover around 500 milliseconds on average.

References

I would have never figured this all out on my own; a few resources were invaluable to me while trying to sort this all out. I'm sorry that I didn't take notes on all of them.

First published: 2018/03/13