Building a Stratum 1 NTP Server

Use a GPS chip and Raspberry Pi to get sub-ms time synchronization at home

Why?

  • Public servers aren't accurate enough?
  • Raspberry Pi's Collecting Dust?
  • Independence from public infrastructure?
  • With several receivers you will never lose your house

Fundamentals

Network Time Protocol (NTP)

  • NTP uses UDP to synchronize time between computers.
  • Servers are prioritized by their proximity to a Stratum 0 time source (clock).
  • The best server on NTP is Stratum 1
  • The worst is Stratum 15

NTP

  • Clocks represent a time source (GPS, Atomic Clock)
  • Pool servers are typically stratum 3
  • Propagate the time across the Internet with only a few Stratum 0 sources

GNSS (Global Navigation Satellite Systems)

In order to properly locate:

  • GNSS Satellites are equipped with precise atomic clocks
  • The GNSS Receiver sees multiple satellites
  • Once receiver has seen 4 satellites it can calculate time precisely

GPS Sentences

In addition to providing satellite locations and location information, GPS chips send this precise time in each message it sends.

Open standard for this is NMEA, these are sent at 4800 baud on serial.

But that isn't good enough

Sources of Delay

Line Delay

On a serial bus at 4,800 baud it takes about 36ms for complete time data.
Not much better than public servers.

Kernel Delay

Even if we moved at 115,200 baud, we have to contend with the uncertainty of the Linux Kernel in processing and delivering to our process.

At the tone, the time will be...

Pulse Per Second

The GPS chip will provide a signal to indicate the start of a second.

  • Kernel GPIO PPS to interrupt system
  • Chrony uses this and NMEA sentence to discipline SoC oscillator
  • Fall within several hundred nS of actual time

Before Getting Started

You'll need a few parts to build a Stratum 1 server at home.

A Suitable Computer

  • This can be any computer with a serial port.
  • If you do use a serial port, you will need a level shifter, UART is on 3.3v and Serial can be 5V or more.
  • The level shifter will need to support shifting for PPS pin to DCD on serial port.
  • Raspberry Pi or other SBC with UART & GPIO that can be used with KPPS.

A GPS Chip

U-Blox NEO-6M

  • This can be found on Amazon labeled as a GT-U7 for around $15.
  • It is compatible with the US GPS system
  • Has a direct USB connection and NVRAM available for programming with U-Blox Software

U-Blox NEO-8M

  • These can be found on AliExpress for around $20
  • It is compatible with the major constellations, GPS, GLONASS, and BeiDou.
  • May or may not have NVRAM and USB available.

Other GPS dev boards

  • Must expose a PPS pin alongside UART.
  • Check compatibility with GPSD

A GPS Antena

SMA, Push on SMA, and U.FL/IPX

SMA


Common on dev boards

Push on SMA

Rarer connector

U.FL / IPX

Common on dev boards

Getting Started

Start with a fresh Raspberry Pi OS Lite Installation

Modify config.txt

[all]
              # Disable Bluetooth, it resides on the only hardware UART in older PIs
              dtoverlay=disable-bt
              # WiFi will introduce too much delay for NTP server
              dtoverlay=disable-wifi
              #Enable the Hardware UART
              enable_uart=1
              #Set up PPS-GPIO Driver
              dtoverlay=pps-gpio,gpiopin=4,assert_falling_edge=off,capture_clear=on
            

KPPS-GPIO Arguments

gpiopin: Pin Number

Which GPIO on the Pi to use.
There are several, we will use GPIO 4.

KPPS-GPIO Arguments

assert_falling_edge: on | off

Standard GPS PPS should assert on the rising edge of the signal, so this should be off. Some vendors may use the falling edge of the signal.

capture_clear: on | off

Report clear events, opposite of which edge is asserted

Modify cmdline.txt

We want to disable tickless kernel and using serial console for boot
Remove

console=serial0,115200
            

Add

nohz=off
            

Required Packages

  • chrony - NTP server
  • gpsd - GPSD is a daemon that manages GPS
  • setserial - Allow us to declare a tty low_latency
  • pps-tools - Useful for testing PPS data
  • cpufrequtils - To set CPU Governor

Nice to Have:

  • neovim - For editing config files in style
  • picocom - For checking GPS stream

Checking our PPS

If our GPS chip has a PPS LED, make sure that it is flashing first. If the GPS cannot get a lock it will not provide PPS.

Checking KPPS

hayden@myoken:~ $ 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 1725497833.000152163, sequence: 46 - clear
              1725497832.100146865, sequence: 46
              source 0 - assert 1725497833.000152163, sequence: 46 - clear
              1725497833.100149806, sequence: 47
            

Checking GPS

We can run gpsmon to test our GPS before setting up the daemon.

hayden@myoken:~ $ sudo
              gpsmon /dev/ttyAMA0
            
myoken:/dev/ttyAMA0 9600 8N1
              NMEA0183>
              ┌──────────────────────────────────────────────────────────────────────────────┐
              │Time: 2024-09-05T01:04:13.000Z Lat: 41 07.788540' N Lon: 81
                30.162470' W │
              └───────────────────────────────── Cooked TPV ─────────────────────────────────┘
              ┌──────────────────────────────────────────────────────────────────────────────┐
              │ GNTXT GNRMC GNVTG GNGGA GNGSA GPGSV GLGSV GNGLL │
              └───────────────────────────────── Sentences ──────────────────────────────────┘
              ┌───────────────────────┌─────────────────────────┌────────────────────────────┐
              │ SVID PRN Az El SN HU│Time: 010413.00 │Time: 010412.00 │
              │GP 3 3 222 16 36 Y│Latitude: 4107.78854 N │Latitude: 4107.78851 │
              │GP 4 4 271 73 38 Y│Longitude: 08130.16247 W │Longitude: 08130.16244 │
              │GP 7 7 287 13 21 Y│Speed: 0.009 │Altitude: 321.8 │
              │GP 8 8 177 19 34 Y│Course: │Quality: 2 Sats: 12 │
              │GP 9 9 308 40 38 Y│Status: A FAA:D │HDOP: 0.63 │
              │GP 16 16 47 78 36 Y│MagVar: │Geoid: -34.5 │
              │GP 26 26 53 41 35 Y└───────── RMC ───────────└─────────── GGA ────────────┘
              │GP 27 27 148 39 35 Y┌─────────────────────────┌────────────────────────────┐
              │GP 31 31 86 35 36 Y│Mode: A3 Sats: 3 4 7 8 + │UTC: RMS: │
              │SB133 46 239 22 39 Y│DOP H=0.63 V=1.01 P=1.19 │MAJ: MIN: │
              │SB135 48 235 25 44 Y│TOFF: 0.225045395 │ORI: LAT: │
              │GL 1 65 331 16 37 Y│PPS: 0.000054503 │LON: ALT: │
              └───v──── GSV ──────────└────── GSA + PPS ────────└─────────── GST ────────────┘
              ------------------------------------- PPS -------------------------------------
              (68) $GNRMC,010413.00,A,4107.78854,N,08130.16247,W,0.009,,050924,,,D*75
            

Configruing GPSD

hayden@myoken:~ $ sudo nvim
              /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/ttyAMA0 /dev/pps0"

              # Other options you want to pass to gpsd
              GPSD_OPTIONS="-n "

              # Automatically hot add/remove USB GPS devices via gpsdctl
              USBAUTO="true"
            
hayden@myoken:~ $ sudo
              systemctl enable --now gpsd
            

Check fix using cgps

hayden@myoken:~ $ cgps
            

This command will parse the data coming from GPSD. It should tell us what we need to know about the quality of the signal we are getting.

We should be looking for a 3D GPS or DGPS fix, and ideally close to 9 or more satellites.

┌───────────────────────────────────────────┐┌──────────────────Seen 22/Used 17┐
              │ Time: 2024-09-05T01:35:58.000Z (18)││GNSS PRN Elev Azim SNR Use│
              │ Latitude: 41.12979270 N ││GP 7 7 23.0 295.0 39.0 Y │
              │ Longitude: 81.50268280 W ││GP 8 8 33.0 174.0 26.0 Y │
              │ Alt (HAE, MSL): 286.871, 321.331 m ││GP 9 9 51.0 296.0 44.0 Y │
              │ Speed: 0.03 km/h ││GP 16 16 63.0 45.0 37.0 Y │
              │ Track (true, var): 57.7, -8.4 deg ││GP 26 26 28.0 58.0 29.0 Y │
              │ Climb: 0.00 m/min ││GP 27 27 52.0 133.0 34.0 Y │
              │ Status: 3D DGPS FIX (9 secs) ││GP 31 31 24.0 98.0 38.0 Y │
              │ Long Err (XDOP, EPX): 0.39, +/- 1.5 m ││SB133 46 22.0 239.0 39.0 Y │
              │ Lat Err (YDOP, EPY): 0.49, +/- 1.9 m ││SB135 48 25.0 235.0 43.0 Y │
              │ Alt Err (VDOP, EPV): 1.27, +/- 1.9 m ││GL 1 65 29.0 322.0 32.0 Y │
              │ 2D Err (HDOP, CEP): 0.63, +/- 1.4 m ││GL 7 71 24.0 188.0 31.0 Y │
              │ 3D Err (PDOP, SEP): 1.42, +/- 5.3 m ││GL 8 72 57.0 243.0 40.0 Y │
              │ Time Err (TDOP): 0.74 ││GL 9 73 29.0 55.0 24.0 Y │
              │ Geo Err (GDOP): 1.60 ││GL 10 74 60.0 3.0 29.0 Y │
              │ ECEF X, VX: 710938.250 m 0.000 m/s ││GL 11 75 35.0 278.0 35.0 Y │
              │ ECEF Y, VY: -4758523.390 m 0.030 m/s ││GL 19 83 21.0 52.0 9.0 Y │
              │ ECEF Z, VZ: 4173479.720 m -0.020 m/s ││GL 20 84 15.0 116.0 8.0 Y │
              │ Speed Err (EPS): +/- 1.1 km/h ││GP 3 3 5.0 214.0 25.0 N │
              │ Track Err (EPD): n/a ││GP 4 4 70.0 221.0 26.0 N │
              │ Time offset: 1.001272567 s ││GP 18 18 2.0 52.0 0.0 N │
              │ Grid Square: EN91fd91 ││SB138 51 36.0 216.0 0.0 N │
              └───────────────────────────────────────────┘└More...──────────────────────────┘
            

Check PPS with gpsmon

We can see PPS messages and check timing with gpsmon.

hayden@myoken:~ $ gpsmon
            
tcp://localhost:2947 u-blox>
              ┌──────────────────────────┐┌─────────────────────────────────────────────────┐
              │Ch PRN Az El S/N Flag U ││ECEF Pos: │
              │ 0 3 213 4 25 1954 ││ECEF Vel: │
              │ 1 4 217 69 32 195f Y ││ │
              │ 2 7 296 24 43 195f Y ││LTP Pos: │
              │ 3 8 174 35 34 195f Y ││LTP Vel: │
              │ 4 9 294 52 31 195f Y ││ │
              │ 5 16 45 61 35 195f Y ││Time: │
              │ 6 18 51 3 0 1211 ││Time GPS: Day: │
              │ 7 26 58 27 34 195f Y ││ │
              │ 8 27 131 53 35 195f Y ││Est Pos Err m Est Vel Err m/s │
              │ 9 31 99 23 33 195f Y ││PRNs: ## PDOP: xx.x Fix 0x.. Flags 0x.. │
              │10 133 239 22 39 195f Y │└─────────────────── NAV_SOL ─────────────────────┘
              │11 135 235 25 44 195f Y │┌─────────────────────────────────────────────────┐
              │12 138 216 36 0 0701 ││DOP [H] 0.6 [V] 1.2 [P] 1.3 [T] 0.7 [G] 1.5 │
              │13 65 321 30 35 191f Y │└─────────────────── NAV_DOP ─────────────────────┘
              │14 71 188 22 26 191e Y │┌─────────────────────────────────────────────────┐
              │15 72 240 55 43 191f Y ││TOFF: 0.648505784 PPS: 0.000092637 │
              └────── NAV-SAT ───────────┘└─────────────────────────────────────────────────┘
              ------------------- PPS offset: 0.000092637 ------
            

Connecting Chrony & GPSD

  • Traditionally, an NTP SHM driver is used, GPSD and the NTP server communicate over a section of shared memory. Chrony also has support for socket driven communications. These are more secure than Shared Memory (SHM) drivers.

  • Best practice is to use socket (SOCK) based clocks rather than shared memory (SHM) clocks. This requires that Chrony start first, then GPSD

Configure Chrony

Add the following to chrony.conf above ntp pools

refclock SOCK
              /run/chrony.ttyAMA0.sock refid NMEA precision 1e-3
              refclock SOCK /run/chrony.pps0.sock refid PPS precision 1e-7
            

Replace the existing makestep with the following to allow Chrony to step the clock aggressively

makestep 0.1 3
            

Configure Chrony

To allow Chrony to act as a server to your LAN (e.x. 192.168.1.0/24)

allow 192.168.1.0/24
            

To allow Chrony to act as a server for anyone

allow
            

It is a bad idea to expose your NTP Server to the Internet

Configure Chrony

To tune your clock error and offset, you can enable statistics.

log tracking measurements
              statistics
            
  • This will track measurements into /var/log/chrony/

  • This is a fixed width table you can load into Excel to average out your error and offset to improve accuracy of the clock.

Start Chrony

First we need to stop GPSD

hayden@myoken:~ $ sudo
              systemctl stop gpsd
            

Then we need to enable and start chrony

hayden@myoken:~ $ sudo
              systemctl enable --now chrony
            

Then start GPSD again

hayden@myoken:~ $ sudo
              systemctl start gpsd
            

Check Synchronization

Use the chronyc command to query Chrony daemon

hayden@myoken:~ $ chronyc
              sources
              MS Name/IP address Stratum Poll Reach LastRx Last sample
              ===============================================================================
              #- NMEA 0 4 377 20 -200ns[ -302ns] +/- 1000us
              #* PPS 0 4 377 20 -200ns[ -302ns] +/- 144ns
            

We can see that our PPS is the preferred clock, and is coming within several hundred nanoseconds of our Ref Clock

Check Synchronization

hayden@myoken:~ $ chronyc
              tracking
              Reference ID : 50505300 (PPS)
              Stratum : 1
              Ref time (UTC) : Thu Sep 05 04:23:57 2024
              System time : 0.000000267 seconds fast of NTP time
              Last offset : +0.000000229 seconds
              RMS offset : 0.000003989 seconds
              Frequency : 6.911 ppm fast
              Residual freq : +0.000 ppm
              Skew : 0.008 ppm
              Root delay : 0.000000001 seconds
              Root dispersion : 0.000009981 seconds
              Update interval : 16.0 seconds
              Leap status : Normal
            

Additional Tuning

Set serial as low latency during startup, can be done with cron or systemd

hayden@myoken:~ $ sudo
              setserial /dev/ttyAMA0 low_latency
            

Set the CPU Governor to Performance
Edit /etc/default/cpu_governor

 CPU_DEFAULT_GOVERNOR="performance"
            

Additional Tuning

  • Use the statistics generated over a period of days to get your average offset on the NMEA clock to further tune the clock

  • Use the u-Blox u-center application to set stationary / time mode. Update referesh rate.