Pi OS 12 GPIO - BYO-NTP/recipes GitHub Wiki

date server os gnss daemon 🗣️
2025-06 Raspberry Pi 4
Raspberry Pi 5
Pi OS
12
6M
U7
M8Q
M8T
chrony
NTPsec
ntp
discuss

1. Install OS

Install Pi OS 12 (Bookworm) per the instructions for Pi 4 or Pi 5.

2. Configure the GNSS

Follow the instructions for your GNSS on the Assemble Pi GPIO page.

3. Configure the UART

Liberate the serial port from the console:

raspi-config nonint do_serial_hw 0
raspi-config nonint do_serial_cons 1
systemctl disable --now [email protected]
systemctl mask [email protected]

Symlink the TTY at /dev/ttyAMA0 to /dev/gps0 where the NTP daemons expect it. Enable low_latency on the serial port:

apt install -y setserial
cat > /etc/udev/rules.d/10-gps.rules <<EO_UDEV
# symlink /dev/ttyAMA0 to /dev/gps0
KERNEL=="ttyAMA0", SYMLINK+="gps0", GROUP="dialout", MODE="0660"

# configure the serial port for low latency.
KERNEL=="ttyAMA0", RUN+="/bin/setserial /dev/ttyAMA0 low_latency"
EO_UDEV
udevadm control --reload-rules
udevadm trigger

4. Configure PPS

apt install -y pps-tools
grep -q pps-gpio /boot/firmware/config.txt || cat >> /boot/firmware/config.txt <<EOWD

# PPS from GPIO connected GNSS
dtoverlay=pps-gpio,gpiopin=18

EOWD

Reboot the Pi:

reboot

5. Verify UART & PPS

Verify that the serial port is found by the OS:

dmesg | grep -i uart
[    0.048259] Serial: AMBA PL011 UART driver
[    0.049274] 107d001000.serial: ttyAMA10 at MMIO 0x107d001000 (irq = 16, base_baud = 0) is a PL011 rev3
[    3.155154] pl011-axi 1f00030000.serial: cts_event_workaround enabled
[    3.165578] 1f00030000.serial: ttyAMA0 at MMIO 0x1f00030000 (irq = 126, base_baud = 0) is a PL011 AXI

In the output we see two serial ports. This is a Pi 5 so ttyAMA10 is the dedicated console port and ttyAMA0 is the GPIO serial port 0 that the GNSS is wired to.

Configure the serial port and read some data from /dev/gps0: (Control-C to disconnect):

stty -F /dev/gps0 cs8 clocal -cstopb -parenb -echo raw 115200
cat /dev/gps0
$GNGGA,225430.00,4746.15203,N,12219.54384,W,1,12,99.99,128.2,M,-18.6,M,,*49
$GNZDA,225430.00,09,07,2025,00,00*71
$GNGGA,225431.00,4746.15203,N,12219.54384,W,1,12,99.99,128.2,M,-18.6,M,,*48
$GNZDA,225431.00,09,07,2025,00,00*70
$GNGGA,225432.00,4746.15203,N,12219.54384,W,1,12,99.99,128.2,M,-18.6,M,,*4B
$GNZDA,225432.00,09,07,2025,00,00*73
^C

It works!

Verify that the PPS module loaded:

lsmod | grep pps
pps_gpio               12288  0

Test that PPS pulses are present (Control-C to cancel):

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 1752101802.415352718, sequence: 224 - clear  0.000000000, sequence: 0
source 0 - assert 1752101803.415361180, sequence: 225 - clear  0.000000000, sequence: 0
source 0 - assert 1752101804.415369716, sequence: 226 - clear  0.000000000, sequence: 0
^C

Yay, that works too.

Note: it can take up to 20 minutes for the GNSS to get a fix and start outputting PPS signals. If the board has a PPS LED, it will blink every second when it has a fix. If necessary, move the GPS unit to where it has a clear view of the entire sky.

6. Install a NTP daemon

Offsets

The latency of each GNSS requires a custom offset in milliseconds. Export the GNSS specific offset from the table that matches your Pi & GNSS:

Pi GNSS NMEA gpsd PPS
M8T 0.1 0.12
M8Q 0.1 0.06
pi4 7M 0.11
pi5 7M 0.06
6M 0.07 .12
export GNSS_OFFSET_NMEA=""

Each section heading is a link with many more details about installing, configuring, and verifying that particular NTP daemon.

export NTP_REFCLOCKS=$(cat <<EO_CHRONY
refclock SHM 0         refid NMEA precision 4e-2 poll 2 offset $GNSS_OFFSET_NMEA
refclock PPS /dev/pps0 refid PPS  precision 4e-8 poll 2 lock NMEA trust
EO_CHRONY
)
curl -sS https://byo-ntp.github.io/tools/chrony/install.sh | sh
export NTP_REFCLOCKS=$(cat <<EO_NTPSEC
refclock nmea refid NMEA minpoll 3 maxpoll 4 time2 $GNSS_OFFSET_NMEA baud 115200 prefer
refclock pps  refid PPS  minpoll 1 maxpoll 2
EO_NTPSEC
)
curl -sS https://byo-ntp.github.io/tools/ntpsec/install.sh | sh

or with gpsd:

export NTP_REFCLOCKS=$(cat <<EO_NTPSEC_GPSD
refclock shm unit 0 refid NMEA minpoll 3 maxpoll 4 time1 $GNSS_OFFSET_NMEA
refclock shm unit 2 refid PPS  minpoll 1 maxpoll 2 prefer
EO_NTPSEC_GPSD
)
curl -sS https://byo-ntp.github.io/tools/ntpsec/install.sh | sh

Note, if the GNSS doesn't maintain a solid 3D fix, setting minpoll 4 maxpoll 6 can greatly reduce the inaccuracies when the fix is lost.

export NTP_REFCLOCKS=$(cat <<EO_NTP
server 127.127.20.0 minpoll 3 maxpoll 6 mode 80 prefer
fudge  127.127.20.0 refid NMEA time2 $GNSS_OFFSET_NMEA minjitter 0.02

server 127.127.22.0 minpoll 3 maxpoll 4 prefer
fudge  127.127.22.0 refid PPS minjitter 0.005
EO_NTP
)
curl -sS https://byo-ntp.github.io/tools/ntp/install.sh | sh

Observe

  • watch the status in near real time:
    • printf '\e[8;9;80t'; ssh -t pi5 watch -n2 chronyc sources
    • printf '\e[8;9;80t'; ssh -t pi5 watch -n2 ntpq -c peer
  • Measure the offset
  • Gather statistics with telegraf + influxdb + grafana or similar.

Reference

⚠️ **GitHub.com Fallback** ⚠️