Skip to main content

Project 1: GPS-based NTP server

Source: Microsecond accurate NTP with a Raspberry Pi and PPS GPS

Info

This page describes setting up a GPS module with a Pi to act as a Stratum1 NTP server. The GPS module must have a 3-5V input, with serial AND PPS outputs (5 pins minimum). Example: Adafruit Ultimate GPS Breakout.

PPS = Pulse Per Second. While the serial data from the GPS module contains the actual time & date information required for the NTP server, the PPS input is required to keep sub-millisecond accuracy. the PPS output puts out EXACTLY 1 pulse per second, +/- a few nanoseconds. The PPS software running on the Pi uses this.

Setup:

  1. Install packages:

    sudo apt install pps-tools gpsd gpsd-clients gpsd-tools chrony
    

    ('pps/gpsd' packages are for interperating GPS data, chrony is an NTP server)

  2. Add these lines to the end of /boot/config.txt

    # the next 3 lines are for GPS & PPS signals
    dtoverlay=pps-gpio,gpiopin=18
    enable_uart=1
    init_uart_baud=9600
    
  3. Add this text to the end of /etc/modules:

    pps-gpio
    

These programs and config changes enable PPS time sync inputs on GPIO pin 18 (real Pin 12), and initializes the serial COM port with a baud rate of 9600.

  1. disable system handling the COM port:
    sudo systemctl mask serial-getty@ttyS0.service
    

This prevents the system from taking control of the port. Check this command worked after rebooting: /dev/ttyS0 should be owned by root:dialout and have permissions crw-rw----.

Wire up the GPS module:

Pinout:

  1. GPS PPS to RPi pin 12 (GPIO 18)
  2. GPS VIN to RPi pin 2 or 4
  3. GPS GND to RPi pin 6
  4. GPS RX to RPi pin 8
  5. GPS TX to RPi pin 10

Picture reference (note that all the Pi pins are seqential): raspberry-pi-zero-5-1536x768.png

Check GPS functionality

The GPS device should put out either 3.3V or 5V on the antenna connector to power the antenna. With the GPS module powered, The on-board LED will light solid. The LED will start blinking once it has a GPS lock. The module spits data to the serial lines at all times, there is no bi-directional communication!

  1. Check that the pps serice is running:

     lsmod | grep pps
    

    Should return at least the service pps_core.

  2. Check for PPS pulses (after GPS has a lock):

    sudo ppstest /dev/pps0
    
    ### Output:
    trying PPS source "/dev/pps0"
    found PPS source "/dev/pps0"
    ok, found 1 source(s), now start fetching data...
    source 0 - assert 1655253832.999996389, sequence: 966 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253834.000004254, sequence: 967 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253835.000001120, sequence: 968 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253836.000000985, sequence: 969 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253836.999996852, sequence: 970 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253838.000001719, sequence: 971 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253839.000002586, sequence: 972 - clear  0.000000000, sequence: 0
    source 0 - assert 1655253840.000001453, sequence: 973 - clear  0.000000000, sequence: 0
    ### ...etc
    
    

If there is a timout, then there is likely not a good GPS lock yet.

Set up software:

  1. Edit /etc/default/gpsd:
    • change GPSD_OPTIONS=”” to GPSD_OPTIONS=”-n”
    • change START_DAEMON="false" to START_DAEMON="true"
    • change DEVICES=”” to DEVICES=”/dev/ttyS0 /dev/pps0″
  2. edit /etc/chrony/chrony.conf file, add this block of code to the top:
    ### GPS TIME SYNC INFO
    # 'delay' describes the accuracy drift of the time source, in seconds. Larger numbers deprioritizes the source. NMEA Source needs at least some delay else chrony loses sync.
    # 'offset' adjusts source arrival time forward and backward. Use to sync GPS and PPS signals (time in [brackets] in chrony sources list).
    refclock SHM 0 delay 0.1 offset 0.1165 refid NMEA
    refclock PPS /dev/pps0 refid PPS
    
    # Allow all LAN IP Ranges so NTP server is network-agnostic (can be used on any LAN)
    allow 10.0.0.0/8   
    allow 192.168.0.0/16
    allow 172.16.0.0/12
    fd00::/8
    
    ### END GPS TIME SYNC INFO
    
  3. Reboot the Pi.
  4. Check: after rebooting, run the program gpsmon, it should display a window similar to this: gps-monitor.png

This confirms that the software is decoding the GPS info properly.

  1. Check Chrony is selecting GPS as a time source; run chronyc sources
    ### Output:
    MS Name/IP address         Stratum Poll Reach LastRx Last sample               
    ===============================================================================
    #- NMEA                          0   4   377    15  +3794us[+3794us] +/-  470us
    #* PPS                           0   4   377    16   +351ns[ +515ns] +/- 3000ns
    ^- time.cloudflare.com           3   6   377    70  -2009us[-2008us] +/-   16ms
    ^- smtp.us.naz.com               2   6   377     2    -26ms[  -26ms] +/-  105ms
    ^- hc-007-ntp1.weber.edu         2   6   377     5  +3131us[+3131us] +/-   72ms
    ^- dns2.kcweb.net                2   6   377     6  +1296us[+1296us] +/-   88ms
    
    More info here under the header 'Time Sources'. TLDR: 'PPS' should have a '*' next to it, and the [bracketed] time in the 'NMEA' row should be less than ~5msec. change the offset in the chrony.conf file to adjust this value, and restart chrony with the command sudo systemctl restart chrony. A "Reach" of '377' indicates source was polled sucessfully all 8 of the last 8 tries, ups trustworthiness. Lower numbers mean polls have been missed, less reliable.

Set up other machines to use the Pi as an NTP server (Linux):

  1. Install ntp package on the machine
  2. Edit /etc/ntp.conf and add server [pi.local.ip] true to the list of servers
  3. start the ntp service (on Arch: sudo systemctl start ntpd)
  4. Check the NTP sources with ntpq -p:
    remote           refid      st t when poll reach   delay   offset  jitter
    ==============================================================================
     *192.168.1.137   .PPS.            1 u   35   64    1    1.851  +144501   0.001
     +time.walb.tech  50.205.244.21    3 u   34   64    1   82.802  +144501   0.001
     -li1187-193.memb 132.163.96.3     2 u   30   64    1  179.522  +144501   0.001
     +time-dfw.0xt.ca 68.166.61.255    2 u   33   64    1  106.817  +144501   0.001
     +LAX.CALTICK.NET 17.253.26.253    2 u   32   64    1  118.527  +144501   0.001
    
    Check back in about 20 minutes, after which one source should have a '*' next to it to indicate that server is the chosen server. Remove default NTP servers and restart the ntp service if the pi is not selected. Note: true added after the pi's IP in the config indicates it is more "trustworthy" than other sources, and is more likely to be picked.
  5. If NTP source is registered correctly, and you are ready to use NTP, enable the ntp service (on Arch: sudo systemctl enable ntpd)

Set up other machines to use the Pi as an NTP server (Windows):

  1. Install Dimension 4.
  2. add the pi as an SNTP source, set the sync time to every 5 minutes.
  3. In the advanced settings menu, force the program to only sync the selected server to force it to use the pi.