PNG time
        server logo


UPDATE: My only GPS-based time server is now running on a tiny Raspberry Pi Zero 2 W computer!  Here is the pinout of that board:
JPG rpi zero w 2

Here are the connections required between the Raspberry Pi Zero 2 W board and the u-blox NEO-6M GPS Module (note that the TXD pin of the GPS board connects to the UART RX of the Pi, and the RXD pin of the GPS board connects to the UART TX of the Pi):

PNG
                timeserver circuit

Here is the current 'ntpviz' local clock time/frequency offsets:
PNG local click time frequency offsets
Here is the current 'ntpviz' local clock time offset histogram:
PNG local offset histogram


Here is some Linux 'ntpmon' time statistics when it is connected to the GPS satellites:
    remote           refid      st t when poll reach   delay   offset   jitter
 LOCAL(0)        .LOCL.          10 l    -   64    0   0.0000   0.0000   0.0019
oPPS(0)          .PPS.            0 l    7   16  377   0.0000  -0.0098   0.0007
*ip253.ip-142-4- 192.168.10.254   2 u   34   64  377  42.2103  10.5883   0.7589
+time.cloudflare 10.72.8.222      3 u   29   64  377  17.0231   2.2827   0.7449
+xpr-254.gta.igs .GNSS.           1 u    5   64  377  38.9150  -0.2130   0.3767
+ntp2.torix.ca   .PTP0.           1 u   20   64  377  31.7691   1.2909   0.3605
ntpd ntpsec-1.2.0 2021-06-17T05:15:04Z         Updated: 2024-03-26T09:45:21 (8)
 lstint avgint rstr r m v  count    score   drop rport remote address
      0   1.60    0 . 6 2 163993    1.352      0 41493 localhost
      6     66   40 . 4 4   1717    0.052      0   123 216.58.112.254 (xpr-254.gta.igs.net)
     21     66   40 . 4 4   1713    0.052      0   123 ntp2.torix.ca
     30     66   40 . 4 4   1714    0.052      0   123 time.cloudflare.com
     35     66   40 . 4 4   1715    0.052      0   123 142.4.192.253 (ip253.ip-142-4-192.net)

Here is some Linux 'cgps' position tracking statistics when connected to the GPS satellites:
┌───────────────────────────────────────────┐┌──────────────────Seen 16/Used 10┐
Time:        2024-03-27T16:17:56.000Z (18)││GNSS   PRN  Elev   Azim   SNR Use│
│ Latitude:         xx.18374174 N           ││GP  2    2  37.0  105.0  12.0  Y │
│ Longitude:        xx.12526860 W           ││GP  7    7  47.0  150.0  28.0  Y │
│ Alt (HAE, MSL):    300.345,    326.047 m  ││GP  8    8  28.0   52.0  26.0  Y │
│ Speed:             0.28 km/h              ││GP 13   13  36.0  288.0  31.0  Y │
│ Track (true, var):   224.5,   3.6     deg ││GP 14   14  67.0  284.0  21.0  Y │
│ Climb:            -5.88 m/min             ││GP 15   15  16.0  315.0  26.0  Y │
│ Status:         3D FIX (9 secs)           ││GP 17   17  36.0  208.0  30.0  Y │
│ Long Err  (XDOP, EPX):  0.53, +/-  8.0 m  ││GP 21   21  37.0   84.0  29.0  Y │
│ Lat Err   (YDOP, EPY):  0.88, +/- 13.2 m  ││GP 22   22  46.0  273.0  27.0  Y │
│ Alt Err   (VDOP, EPV):  1.93, +/- 44.8 m  ││GP 30   30  75.0  199.0  24.0  Y │
│ 2D Err    (HDOP, CEP):  1.03, +/- 20.3 m  ││GP  1    1   n/a    0.0   0.0  N │
│ 3D Err    (PDOP, SEP):  2.19, +/- 42.2 m  ││GP 10   10   0.0   28.0   0.0  N │
│ Time Err  (TDOP):       1.28              ││GP 19   19   9.0  214.0   0.0  N │
│ Geo Err   (GDOP):       2.54              ││GP 23   23   1.0  356.0  16.0  N │
│ ECEF X, VX:    -590394.040 m   -0.030 m/s ││SB133   46  34.0  179.0   0.0  N │
│ ECEF Y, VY:   -4135253.440 m    0.000 m/s ││SB138   51  33.0  192.0   0.0  N │
│ ECEF Z, VZ:    4804167.050 m    0.090 m/s ││                                 │
│ Speed Err (EPS):       +/-  3.7 km/h      ││                                 │
│ Track Err (EPD):        n/a               ││                                 │
│ Time offset:            0.357281442 s     ││                                 │
│ Grid Square:            EN09we44          ││                                 │
└───────────────────────────────────────────┘└─────────────────────────────────┘

Here is a Linux 'inxi' hardware report from this board:
System:    Host: raspberrypi Kernel: 5.10.63-v7+ armv7l bits: 32 Console: tty 0 Distro: Raspbian GNU/Linux 11 (bullseye)
Machine:   Type: ARM Device System: Raspberry Pi Zero 2 Rev 1.0 details: BCM2835 rev: 902120 serial: 00000000655a270f
Memory:    RAM: total: 491.6 MiB used: 188.4 MiB (38.3%) gpu: 64 MiB
           RAM Report: unknown-error: Unknown dmidecode error. Unable to generate data.
CPU:       Info: Quad Core model: ARMv7 v7l variant: cortex-a53 bits: 32 type: MCP
           Speed: 600 MHz min/max: 600/1000 MHz Core speeds (MHz): 1: 600 2: 600 3: 600 4: 600
Graphics:  Device-1: bcm2835-hdmi driver: vc4_hdmi v: N/A
           Device-2: bcm2835-vc4 driver: vc4_drm v: N/A
           Display: server: No display server data found. Headless machine? tty: 238x55
           Message: Advanced graphics data unavailable in console for root.
Audio:     Device-1: bcm2835-hdmi driver: vc4_hdmi
           Device-2: bcm2835-audio driver: bcm2835_audio
           Sound Server: ALSA v: k5.10.63-v7+
Network:   Message: No ARM data found for this feature.
           IF-ID-1: wlan0 state: up mac: e4:5f:01:5b:3c:55
Drives:    Local Storage: total: 14.67 GiB used: 3.86 GiB (26.3%)
           ID-1: /dev/mmcblk0 vendor: Transcend model: USDU1 size: 14.67 GiB
Partition: ID-1: / size: 14.16 GiB used: 3.82 GiB (27.0%) fs: ext4 dev: /dev/mmcblk0p2
           ID-2: /boot size: 252 MiB used: 48.1 MiB (19.1%) fs: vfat dev: /dev/mmcblk0p1
Swap:      ID-1: swap-1 type: file size: 100 MiB used: 9.8 MiB (9.8%) file: /var/swap
Sensors:   System Temperatures: cpu: 42.4 C mobo: N/A
           Fan Speeds (RPM): N/A
Info:      Processes: 117 Uptime: 3d 23h 49m Init: systemd runlevel: 5 Shell: Bash inxi: 3.3.01

Below is my older boards and general information about setting this up:

JPG rpi 2 model
                b
JPG
                ublox neo-6m gps


PNG rpi 2 model b pinout

The primary reason that I had originally purchased my first Raspberry Pi 2 Model B SBC (Single Board Computer) was to operate my Flight Tracking Station.

With the prices of GPS modules dropping to extremely affordable levels (about $4 CDN), I decided to swap out the U-Blox NEO-6M GPS module for one that had a 5th pin -- a PPS (Pulse-Per-Second) contact that I could use to implement a high-accuracy NTP (Network Time Protocol)  stratum 1 time server!  In other words, using the primary (stratum 0) time clock sources available on GPS satellites to make my own secondary (stratum 1) shareable computer time source.

My primary source of information on how to do this came from these excellent sources:

Setting up a Stratum 1 NTP server on a Raspberry Pi
The Raspberry Pi as a Stratum-1 NTP Server
GPSD Time Service HOWTO
A Guide To GPS Network Time Synchronization

The wiring for the GPS module that I used is as follows:

NEO-6M
Raspberry Pi
VCC
3V3
GND
GND
RXD
TXD (GPIO14)
TXD
  RXD (GPIO15)
PPS
PWM0 (GPIO18)

Power (VCC/3V3) and Ground (GND) pins are basic.  The Receive-Data (RXD) and Transmit-Data (TXD) are used to receive and transmit the serial-based GPS data stream (notice that reversal when connecting the pins i.e. the GPS 'transmit' pin is connected to the Raspberry Pi 'receive' pin, and vice versa).  The magic comes from the Pulse-Per-Second (PPS) GPS pin being connected to the Raspberry Pi Pulse-Code-Modulation (PCM) pin (also acting as Pulse-Width-Modulation (PWM0)).

When a good GPS satellites lock has been achieved, my GPS will transmit useful time (as well as position) data (at 9600 baud) using various NMEA sentence structures.  The one that the NTP utility uses to extract time are the following:
$GPRMC,POS_UTC,POS_STAT,LAT,LAT_REF,LON,LON_REF,SPD,HDG,DATE,MAG_VAR,MAG_REF*CC<cr><lf>
$GPGLL,LAT,LAT_REF,LONG,LONG_REF,POS_UTC,POS_STAT*CC<cr><lf>
$GPGGA,POS_UTC,LAT,LAT_REF,LONG,LONG_REF,FIX_MODE,SAT_USED,HDOP,ALT,ALT_UNIT,GEO,G_UNIT,D_AGE,D_REF*CC<cr><lf>
Here are some samples of those sentences from my GPS (with position information obscured):
$GPRMC,195038.00,A,xx11.02333,N,xxx07.52859,W,0.052,,140817,,,A*61
$GPGLL,xx11.02333,N,xxx07.52866,W,195037.00,A,A*77
$GPGGA,195038.00,4911.02333,N,09807.52859,W,1,08,1.06,341.9,M,-26.1,M,,*69
Here is a sample NTP time statistics from my time server soon after it was running:
pi@raspberrypi ~ $ ntpq -p 
     remote           refid      st t when poll reach   delay   offset  jitter
==============================================================================
 LOCAL(0)        .LOCL.          10 l  45h   64    0    0.000    0.000   0.000
oGPS_NMEA(0)     .GPS.            0 l    8   16  377    0.000    0.003   0.002
*192.95.27.155   200.98.196.212   2 u   37   64  377   51.341    1.001 166.392
+kirdu.smartacti 213.251.128.249  2 u    3   64   77   42.552   10.272  99.637
+host1.hosttechn 213.251.128.249  2 u   20   64  377   38.465   10.301 161.560
+zero.gotroot.ca 30.114.5.31      2 u   65   64  376   34.556    0.719  52.451
In this sample, the GPS source is providing a time accuracy of 0.003 microseconds (3 millionths of a second) to my computer network!  This time source is good enough to join (if I chose to do so) the list of other NTP time sources used by millions of computers around the world: How do I join pool.ntp.org?

If everything is working OK, and a PPS lock has been established (sometimes tricky inside my home), this text will show the NTP statistics results:

Embedded Text Document

UPDATE: After upgrading my Raspberry Pi linux version to Raspbian/Debian 9 'Stretch', my GPS/PPS functionality stopped working -- which I have now finally fixed by doing the following -- since I found out that the '/dev/ttyAMA0' device was being used by the 'login' process, which was interferring with 'ntp' from getting at it as well:
  • Type: "sudo raspi-config"
  • Pick: "5 Interfacing Options  Configure connections to peripheral"
  • Pick: "P6 Serial Enable/Disable shell and kernel messages on the serial connection"
  • Answer 'No' for: "Would you like a login shell to be accessible over serial?"
  • Answer 'Yes' for: "Would you like the serial port hardware to be enabled?"
  • Type: "sudo reboot"

I also did the following, but was not sure whether it was necessary or helped:

systemctl stop serial-getty@ttyAMA0.service
systemctl disable serial-getty@ttyAMA0.service
systemctl mask serial-getty@ttyAMA0.service
UPDATE: I also activated the same feature on my backup server: an Orange Pi PC Plus (info here, purchased here and here).  I chose this particular model since it was the best 'bang-for-the-buck' that I could find in the Orange Pi series, was less expensive than the boards offered by the Raspberry Pi foundation, and had an 8GB eMMC flash memory module on it (to avoid eventual wear-out of removable SD cards).  A good resource comparing the various Orange Pi models is here.

PNG orange pi pc plus
PNG orange pi
                  pinout
The wiring for the GPS module that I used is as follows:

NEO-6M
Orange Pi
Pin
VCC
+3.3V
1
GND
GND
9
RXD
PA13 (UART3_TX)
8
TXD
  PA14 (UART3_RX)
10
PPS
PA6 (PWM1)
7

Currently the 'best' source of Linux distros for the Orange Pi boards comes from Armbian, rather than from the manufacturer itself (which provides almost no support, and with non-100%-functional software!).  A useful discussion forum for the Allwinner H3 boards with mainline Linux kernel is here.
  • For 'legacy' kernel, no PPS features are present, so a new kernel needs to be compiled (lots of work!):
ORANGE PI PLUS H3 with GPS/PPS – — The Black Magic Boxes
  • For 'mainline' kernel, PPS feature is already present, but needs to be activated:

Add to the '/boot/armbianEnv.txt' file, specifically entries for 'overlays' to activate 'pps-gpio' and 'uart3' (if that is the pins you have decided to use), and 'param_pps_pin=PA6' (again, if that is the pin you have opted to use for the PPS signal):

verbosity=1
logo=disabled
console=both
disp_mode=1920x1080p60
overlay_prefix=sun8i-h3
rootdev=UUID=7f3d2ddb-0231-45dd-857c-81bc36aba60e
rootfstype=ext4
overlays=pps-gpio uart3
param_pps_pin=PA6
usbstoragequirks=0x2537:0x1066:u,0x2537:0x1068:u

After rebooting, you should see 'pps' entries in 'dmesg':

[    8.277867] pps pps0: new PPS source pps@0.-1
[    8.277928] pps pps0: Registered IRQ 64 as PPS source

You should also be able to run 'ppstest' on the new device:

darren@orangepipcplus:/boot$ 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 1524664406.998850031, sequence: 1743 - clear  0.000000000, sequence: 0
source 0 - assert 1524664407.998835994, sequence: 1744 - clear  0.000000000, sequence: 0
source 0 - assert 1524664408.998835038, sequence: 1745 - clear  0.000000000, sequence: 0
source 0 - assert 1524664409.998825706, sequence: 1746 - clear  0.000000000, sequence: 0
source 0 - assert 1524664410.998834955, sequence: 1747 - clear  0.000000000, sequence: 0
source 0 - assert 1524664411.998817203, sequence: 1748 - clear  0.000000000, sequence: 0
source 0 - assert 1524664412.998810533, sequence: 1749 - clear  0.000000000, sequence: 0
source 0 - assert 1524664413.998828901, sequence: 1750 - clear  0.000000000, sequence: 0
...
Here is what my NTP configuration file looks like, which also supports the running of a parallel GPSD process:
# /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help

# Local
server 127.127.1.0 
fudge 127.127.1.0 stratum 10

# GPSD
server 127.127.28.0 mode 17 minpoll 4 maxpoll 4 iburst true prefer 
fudge 127.127.28.0 flag1 1 refid GPS

# GPS with PPS enabled
server 127.127.22.0 mode 17 minpoll 4 maxpoll 4 iburst true prefer 
fudge 127.127.22.0 flag1 1 refid PPS
#server 127.127.20.0 mode 17 minpoll 4 maxpoll 4 iburst true prefer 
#fudge 127.127.20.0 flag1 1 refid GPS

# Internet time servers for sanity
server 0.pool.ntp.org iburst prefer 
server 1.pool.ntp.org iburst 
server 2.pool.ntp.org iburst 
server 3.pool.ntp.org iburst

# By default, exchange time with everybody, but don't allow configuration.
restrict default kod nomodify notrap nopeer noquery 
restrict -6 default kod nomodify notrap nopeer noquery

# Local users may interrogate the ntp server more closely.
restrict 127.0.0.1 
restrict -6 ::1

# Drift file etc.
driftfile /var/lib/ntp/ntp.drift 

# stats
enable stats
statistics loopstats
#statistics loopstats peerstats clockstats
statsdir /var/log/ntp/
#filegen peerstats file peers type day link enable
#filegen loopstats file loops type day link enable
filegen loopstats file loopstats type day link enable

The default 'ntp' app from the respositories doesn't appear to support PPS properly (without patching), so I got and compiled the 'NTPsec' fork, which has the bonus of providing the 'ntpmon' app.  Some instructions for compiling NTPsec are here, but basically they are:

cd $HOME/Downloads
git clone --depth 1 https://gitlab.com/NTPsec/ntpsec.git
cd ntpsec
sudo ./buildprep
./waf configure
./waf build
sudo ./waf install

In order for the NTP logging to work properly, the directory '/var/log/ntp' needs to be created for every reboot, and with the correct (open) permissions.  Add the following to '/etc/rc.local':

mkdir /var/log/ntp
chmod 777 /var/log/ntp

After that is set up, I start the NTP process this way:

sudo service ntp start

For GPSD, here is what the '/etc/default/gpsd' configuration file looks like:

# 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="true"

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

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

The way to start the process is thus:

sudo service gpsd start
If everything is working OK, and a PPS lock has been established (sometimes tricky inside my home), this text will show the NTP statistics results: