The findings, in brief
For people who want the whole thing in ninety seconds.
What we built
Section titled “What we built”A GPS-disciplined Stratum 1 NTP server: Raspberry Pi 4 + BerryGPS-IMU v4
(u-blox CAM-M8C), PPS on GPIO18, gpsd + chrony. Final state: RMS offset
199 ns, root delay ~1 ns, survives a cold power cut unattended. About $130 of
parts, replacing an appliance that costs $1,500–$10,000.
What the guides get wrong on a Pi 4
Section titled “What the guides get wrong on a Pi 4”| Claim | Reality |
|---|---|
| ”Use PTP for real precision” | Impossible. ethtool -T eth0 → PTP Hardware Clock: none. No hardware timestamping exists on this NIC. |
| ”Isolate the PPS IRQ on a dedicated core” | Not permitted. GPIO IRQs demux through pinctrl-bcm2835 and reject smp_affinity. |
| ”Install PREEMPT_RT” | Made jitter 3× worse until patched — it threads the handler that takes the timestamp. |
| ”Raise the GPS baud rate” | Irrelevant. PPS offset measured −1 ns at 9600 vs 115200. Identical. NMEA only labels the second. |
What actually helped
Section titled “What actually helped”| Change | RMS offset |
|---|---|
| Baseline | 823 ns |
chrony filter 10 + prefer on the PPS refclock | 440 ns |
PREEMPT_RT + IRQF_NO_THREAD patch | 199 ns |
What we broke, and how we found it
Section titled “What we broke, and how we found it”Two failures that a reboot will never reveal. Only a cold power cut exposes them, which is why you must actually pull the plug:
- gpsd was being started by the dashboard. It’s socket-activated; chrony reads its shared memory, never its socket, so nothing else triggered it. The monitoring page was load-bearing for the time server.
- Pinning gpsd’s baud turned a module quirk into an outage. The CAM-M8 keeps
config in supercap-backed RAM and reverts to 9600 on power loss. With gpsd
pinned to 115200 it came back talking to a module that wasn’t listening: no
GPS at all. Use
GPSD_OPTIONS="-n"and let it auto-probe.
And the instrument was bending the measurement
Section titled “And the instrument was bending the measurement”Our own dashboard cost 36% more PPS jitter by forking chronyc four times a
second onto CPU 0 — the one core the PPS interrupt is welded to and cannot be
moved from. Batching it to one process and pinning it off CPU 0 erased the
penalty entirely. → The observer effect