Getting accurate time GPS PPS over USB
2023 Sep 07 - Brian Kloppenborg
Since returning to astronomy as the Executive Director of the AAVSO in September 2022, I’ve been participating in more public outreach events and attending star parties in different states. Whenever I can, I take along my telescope so it can continue collecting data on my science projects while I’m taking with the local crowd.
Unfortunately, moving my telescope around presents some minor challenges. In order for the telescope to point at astronomical sources correctly, it needs to know its position within a few km and its time within a few seconds. That’s good enough for most of my science cases, but I also track satellites. In this situation, you must know your position within 10s of meters and the current time to within 1 millisecond (ms). That level of accuracy is pretty easy to achieve at home using Google maps and some network time software. Unfortunately, it is very difficult to achieve when you go places where cellular service is spotty. This is where position, navigation, and timing (PNT) services provided by global navigation satellite systems (GNSS), like the Global Positioning System (GPS), fit in.
What can you get from GNSS/GPS?
Most consumer-grade GNSS receivers provide PNT data through NEMA messages. These message encode GPS Fixed Data (GGA messages, which include time), Geographic position (GGL messages), information about active satellites (GSA messages), information about the satellites in view of the receiver (GSV messages), and other data. One technical item to note is that most GNSS providers to not report UTC. Instead, their concept of time is a consecutive number of seconds since the start date of their GNSS system (called the “epoch”). For example, the US GPS system counts consecutive seconds since January, 5, 1980. Unfortunately, these seconds are encoded in a 10-bit number which rolls over every few decades.
The data provided by GNSS is very accurate. Current GNSS receivers report their position to within 10 meters. Unfortunately, the time information provided by GGA messages can lag the start of a second by 10 - 100 milliseconds. To compensate for this delta, some GNSS receivers also provide a pulse per second (PPS) signal to mark the start of a second. On an oscilloscope, this signal looks like a square wave with abruptly rising/falling edges. Most receivers generate this signal within picoseconds to nanoseconds of the true start of a second, so they are exceptionally accurate.
How does one normally get the PPS signal?
While I would normally describe a topic like this in detail, I’m only going to present a summary this time around. If you want to dig into this further, I would recommend reading the GPSD HowTo which is much more comprehensive than what I present here.
The most common way to receive PPS signals from a consumer-grade GNSS device is by way of a RS-232 serial port, DB-25 parallel port, or GPIO pin. In this setup the PPS signal is routed to a pin that provides a hardware interrupt signal to the underlying operating system. The interrupt is timestamped by the operating system and passed off to specialized software that combines the PPS signal, GGA time, and leap second information to yield a time in UTC.
In the case of RS-232, the PPS signal is typically connected to the
Data Carrier Detect (DCD) pin; however, it can also appear on the
Ring Indicator (RI), Data Set Ready (DSR), and Clear to Send (CTS) pins.
On Linux, the
ppscheck application looks at all of these pins for the signal
Oddly, the version 3.22 of
gpsd on my computer only seems to monitor DCD.
It is also possible to receive the PPS signal on a Network Interface Card (NIC),
like the Intel I210 card
however, none of the computer I’m targeting have a PCIe slot so I’ll ignore
this option for now.
What other time standards are there and what accuracy can be achieved?
Beyond PNT data provided by GNSS, there are a variety of other ways to get accurate time on a computer. These include:
- Network Time Protocol (NTP) over a WAN or LAN.
chronytime synchronization daemon on Linux
- Precision Time Protocol (PTP)
- GPS Atomic Clocks
- Linux Kernel PPS (KPPS)
- Hardware latching PPS (HPPS)
As you might imagine, the accuracy (and price) of these sources varies quite dramatically. PTP, which requires specialized networking hardware, can sync time to within 1.5 nanoseconds (ns) whereas NTP doesn’t do much better than 1 millisecond (ms). The chrony daemon on my laptop uses NTP servers to sync my clock to within 1.1 seconds of UTC.
|Source||Error (ms)||Error (ns)|
|PTP LAN ||1.5E-6||1.5E+0|
|GPS Atomic Clock ||5.0E-5||5.0E+1|
|chrony local HW ||7.0E-5||7.0E+1|
|chrony local SW ||2.0E-3||2.0E+3|
|chrony PPS ||1.0E-2||1.0E+5|
|NTP LAN ||1.0E+0||1.0E+6|
|chrony WAN (me)||1.1E+0||1.1E+6|
|chrony WAN ||2.0E+0||2.0E+6|
|NTP WAN ||1.0E+1||1.0E+7|
RS-232 serial? Seriously? Surely USB can do better!
Unfortunately, there are very few computers that have the interfaces required
to receive the PPS signal. Outside of business desktop computers, I haven’t
seen a RS-232 serial port or DB-25 parallel port on a computer for about a decade.
Sometimes you can find an unpopulated header on a motherboard of a laptop, but
none of my computers have one. Single board computers, like the Raspberry Pi,
often have GPIO ports, but these are
ARM devices and the orbital propagation
software I use is
x86_64 only. So I had to look for other alternatives.
The most obvious solution is to use a USB to RS-232 serial converter; however, there are some technical aspects of USB which must be taken into consideration. As discussed above, the arrival of a PPS signal at a real RS-232 serial port will result in a hardware interrupt that is timestamped against the current system clock. This typically reduces the accuracy of the PPS signal from 10-30 nanoseconds to 1,000 nanoseconds.
Unlike RS-232 which has hardware interrupts, USB is a polled bus. As shown in the table below, this means that a PPS signal can only be received once every 0.125 - 1 millisecond. This will introduce jitter in the PPS signal which needs to be compensated for in whatever receiving software is used.
In my situation, time with an accuracy of 1 millisecond is fine, so a jitter of 0.125 milliseconds is acceptable.
|USB Version||Linux Driver||Name||Bandwidth||Frame Time (ms)|
|USB 1.0||UHCI||Low Speed||1.5 Mbps||1 ms|
|USB 1.1||OHCI||Full Speed||12 Mbps||1 ms|
|USB 2.0||EHCI||High Speed||480 Mbps||0.125 ms|
|USB 3.0||XHCI||SS / Gen 1||5 Gbps||0.125 ms|
|USB 3.1||XHCI||SS+ / Gen 2||10 Gbps||0.125 ms|
|USB 3.2||XHCI||USB 3.2 / Gen 2x2||20 Gbps||0.125 ms|
|USB 4.0||TBD||USB4 / Gen 3x2||40 Gbps||?|
- UHCI Specification Section 1.2.1 specifies 1 ms between frames.
- EHCI Specification Section 2.3.4 specifies 0.125 ms between frames.
- The XHCI Requriements Specification in the defintion of Interval pg. 36 specifies 0.125 ms and 1 ms between frames; however, a comment in the Linux kernel suggests USB 3.0 may have 250 ns intervals between interrupts
For my initial experiment, I purchased a FTDI Chip C232HD-DDHSP-0 USB to UART Serial Cable Adapter for $41.70 and a Goouuu Tech GT-U7 GPS / GNSS receiver with PPS output for $14.99. I wired it up as specified in the table below and placed it on my patio with a clear view of the sky.
The setup is wired up as follows:
|UART Pin||Color||GT-7 Pin|
Setting up the software was fairly straightforward. After connecting the USB
adapter to my computer I first verified that
gpsmon could communicate
with the receiver:
sudo gpsmon /dev/ttyUSB1
Next I edited the
gpsd configuration file to use that device and started
brian@epislon:~$ cat /etc/default/gpsd # Devices gpsd should collect to at boot time. # They need to be read/writeable, either by user gpsd or the group dialout. DEVICES="/dev/ttyUSB1" # Other options you want to pass to gpsd GPSD_OPTIONS="-n" # Automatically hot add/remove USB GPS devices via gpsdctl USBAUTO="true"
and then restarted the service to load the new configuration file
sudo systemctl restart gpsd
Lastly I edited the
chrony configuration file to read the output from
gpsd using shared memory (
SHM). I initially configured the offset for
the GPS time signal to be zero to allow
chrony track how far off it is
from other time sources
brian@epislon:~$ tail /etc/chrony/chrony.conf # one second, but only in the first three clock updates. makestep 1 3 # Get TAI-UTC offset and leap seconds from the system tz database. # This directive must be commented out when using time sources serving # leap-smeared time. leapsectz right/UTC refclock SHM 0 refid GPS precision 1e-1 offset 0.0 refclock SHM 1 refid PPS precision 1e-7
Then I restarted
chrony to load the new configuration file:
sudo systemctl restart chrony
and verified that both GPS and PPS were picked up. After a few minutes, both time sources were identified and reporting data:
brian@epislon:~$ chronyc sources MS Name/IP address Stratum Poll Reach LastRx Last sample =============================================================================== #x GPS 0 4 377 23 +116ms[ +116ms] +/- 100ms #* PPS 0 4 37 23 -31us[ -31us] +/- 4273ns ^- prod-ntp-4.ntp4.ps5.cano> 2 10 377 226 -2956us[-2960us] +/- 65ms ^- prod-ntp-3.ntp1.ps5.cano> 2 10 377 620 -2610us[-2621us] +/- 67ms ^- prod-ntp-5.ntp4.ps5.cano> 2 10 377 776 -5930us[-5941us] +/- 70ms ^- prod-ntp-5.ntp4.ps5.cano> 2 10 377 812 -2791us[-2802us] +/- 67ms ^- ntp06.cymru.com 2 10 377 747 -6047us[-6057us] +/- 186ms ^- shaka.ruselabs.com 2 10 377 841 -5754us[-5765us] +/- 37ms ^- 2607:f298:5:101d:f816:3e> 2 10 377 488 -936us[ -944us] +/- 37ms ^- 2604:a880:400:d0::83:2002 2 10 377 29 -4112us[-4113us] +/- 33ms
I then let
chrony run in this configuration for several hours to derive
experimental offsets. As can be seen in the output of
below, the GPS source is off by about 112 +/- 2 milliseconds. This is larger
than I would have anticipated, but it appears stable enough that it can be
calibrated out. The PPS signal has an offset of -0.001 +/- 10 microseconds.
This is much smaller than I would have anticipated given the USB 2.0 polling
interval is 124 microseconds.
brian@epislon:~$ chronyc sourcestats Name/IP Address NP NR Span Frequency Freq Skew Offset Std Dev ============================================================================== GPS 7 5 94 -25.843 180.399 +112ms 2204us PPS 64 43 109m -0.000 0.002 -1ns 10us prod-ntp-4.ntp1.ps5.cano> 54 26 475m +0.030 0.012 -1922us 247us prod-ntp-3.ntp1.ps5.cano> 55 26 486m +0.004 0.013 -2527us 258us prod-ntp-5.ntp4.ps5.cano> 55 29 483m -0.002 0.063 -4508us 1234us prod-ntp-5.ntp1.ps5.cano> 23 12 378m -0.006 0.022 -2024us 184us ntp06.cymru.com 28 10 440m +0.060 0.022 -3816us 241us shaka.ruselabs.com 15 8 240m -0.088 0.324 -5822us 1228us 2607:f298:5:101d:f816:3e> 52 23 471m +0.018 0.014 +750us 267us 2604:a880:400:d0::83:2002 47 27 384m -0.001 0.043 -3341us 568us
Lastly, I applied the 112 millsecond offset to the GPS timesource to the
chrony configuration file and restarted the service.
brian@epislon:~$ tail /etc/chrony/chrony.conf # one second, but only in the first three clock updates. makestep 1 3 # Get TAI-UTC offset and leap seconds from the system tz database. # This directive must be commented out when using time sources serving # leap-smeared time. leapsectz right/UTC refclock SHM 0 refid GPS precision 1e-1 offset 0.112 refclock SHM 1 refid PPS precision 1e-7