NTP time from GPS

I found a blog post about Millisecond accurate Chrony NTP with a USB GPS for $12 USD.

Wow! I’ve had an interest in NTP since about 1994, when I had to prove that a system my (somewhat shady) employers were selling to an NSA cut-out could synchronize to an NTP server.

GT-U7 GPS receiver

I actually run the chrony NTP client on my homelab server, and on the random machines I use around the house. $12 isn’t a lot, that’s an attractive hardware thing to fool around with.

Before futzing around, my server’s chrony had settled like this:

chronyc> sources
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
^- LAX.CALTICK.NET               2  10   377   109  +1211us[+1211us] +/-   69ms
^* time.cloudflare.com           3  10   377   992   -100us[ -107us] +/- 4708us
^- clock.nyc.he.net              2  10   377  1010  +3021us[+3014us] +/-   54ms
^- haka.ruselabs.com             2   9   377    24  -4990us[-4990us] +/-   26ms

I purchased a GT-U7 GPS receiver from Amazon for $10 and change. The GT-U7 u-blox GPS receiver with a micro-USB connector. This will be relevant later.

I followed Austin’s blog post, but since I use and recommend Arch Linux, I installed gpsd and pps-tools on my server (and later my laptop) like this:

$ pacman -Ss gpsd
$ pacman -Ss pps-tools

I followed Austin’s config file mods, except that Arch Linux has /etc/chrony.conf and /etc/default/gpsd files.

I added the refclock line to /etc/chrony.conf:

pool 2.arch.pool.ntp.org iburst

refclock SHM 0 refid NMEA offset 0.000 precision 1e-3 poll 3 noselect

I changed /etc/default/gpsd to look like this:

# Default settings for gpsd.
START_DAEMON="true"
USBAUTO="true"
GPSD_OPTIONS="-n"
DEVICES="/dev/ttyACM0 /dev/pps0"
USBAUTO="true"

Yeah, USBAUTO set to “true” twice.

I plugged in the GT-U7, started gpsd like this this: systemctl start gpsd, and restarted chrony like this: systemctl restart chronyd

I got immediate success.

chronyc> sources
MS Name/IP address         Stratum Poll Reach LastRx Last sample
===============================================================================
#? NMEA                          0   3     3     4    +59ms[  +59ms] +/- 1000us
^+ edge-dfw.txryan.com           2   6    17     8    +29us[  +68us] +/-   11ms
^- 66.220.10.2                   2   6    17     9  +1384us[+1424us] +/-   29ms
^- 38.229.58.9                   2   6    17     9  +1569us[+1608us] +/-   94ms
^* time.cloudflare.com           3   6    17     9    +35us[  +74us] +/- 4670us

The NMEA line is from the GT-U7, and it’s terrible. 59 milliseconds off, where the worst actual NTP servers is 1569 microseconds (1.569 milliseconds) off. That’s a whole order of magnitude worse.

I ran out of time, I had to go make supper. The next morning, I tried to find out if 1PPS was working. No dice. gpsmon said PPS: N/A. ppscheck timed out waiting for an answer from /dev/pps0.

Then my server locked up. Nothing. No pings, no response on any network port. Even the USB keyboard and old, cheap monitor showed a login prompt, but the server didn’t respond. I had to find the power button to restart it.

A few minutes later, it locked up again.

Ian Fleming wrote that “Once is happenstance, twice is coincidence, three times is enemy action”, so I pulled the GT-U7, and held down the power button once again.

I decided that unless I got this figured out, my $10.67 GT-U7 purchase was wasted, so I moved development to my laptop. I got gpsd installed and configured, and chrony.conf fixed up. I could not get gpsmon or ppscheck to say PPS was present no matter what.

Eventually, I had chrony.conf set up like this:

refclock SHM 0 refid NMEA offset 0.000 precision 1e-3 poll 3 noselect
refclock SHM 1 refid PPS precision 1e-7

and /etc/default/gpsd like this:

# Default settings for gpsd.
START_DAEMON="true"
GPSD_OPTIONS="-n"
DEVICES="/dev/ttyACM0 /dev/pps0"
USBAUTO="true"

Still no PPS. gpsmon -a /dev/ttyACM0 did not show anything PPS related.

Other informative, but not helpful commands:

$ ipcs -m  # show the shared memory segments gpsd sets up
$ gpsctl --reset --device /dev/ttyACM0 # reset GT-U7

Look at ASCII character values for every 2-letter chunk of the IPCS shared memory segment key.

Through a combination of googling and reading PPS-related posts, and deciding that since gpsmon didn’t show any PPS-related communications with the GT-U7, it just wasn’t doing PPS, I found these 2 commands:

$ ubxtool -e PPS --device /dev/ttyACM0
$ modprobe -v pps_ktimer

The ubxtool invocation turns on PPS. It’s possible you may have to do the gpsctl --reset from above before “enabling” PPS takes effect. gpsmon -a will show PPS “packets”. The modload has subtler effects.

If you’ve started gpsd and plugged in the GT-U7 cable, It will cause /dev/pps1 and/or /dev/pps2 to appear. gpsd will have created /dev/pps0 and it will be inert. ppscheck /dev/pps1 will show KPPS entries every second after that.

Unplugging the GT-U7, restarting gpsd and then re-plugging the GT-U7 will cause /dev/pps0 to appear, gpsmon -a will show PPS-related communications, and chronyc sources will start to show a reference clock named “PPS” that varies pretty wildly.

I’ve got Arch Linux kernel 6.2.11-zen1-1-zen running. I believe the USB PPS, “KPPS” events, won’t show up without loading the pps_ktimer module. All the older “get your time from GPS” web pages were at least a bit wrong because at least the 6.2.11 kernel works differently, or maybe gpsd doesn’t load the right modules when the GT-U7 gets plugged in.

It looks like a serious investigation of PPS-over-USB shows that the whole process incurs delays, jitter and other artifacts that keep PPS-over-USB from working. I may try to set up a Raspberry Pi with wires from the GT-U7’s pins to GPIO pins, but I’m not going to go further with the all-USB experiment. Between locking up my server, which has been flawless for years, and delays and jittering, the GT-U7 won’t help my server keep better time than it already does.