Zurück zur Startseite

Stratum 1 NTP auf Raspberry Pi mit chrony und GPS

Der Artikel beschreibt die Erstellung eines autonomen Stratum 1 NTP-Servers auf Raspberry Pi mit chrony, libgpiod v2, DCF77 und GPS NEO-M8T. Eine Genauigkeit von 42 ns wird erreicht. Migrationsprobleme von ntpd und GPIO API werden angesprochen.

Präziser NTP Stratum 1 auf RPi: 42 ns mit GPS PPS
Advertisement 728x90

Eigenständiger Stratum-1-NTP-Server auf Raspberry Pi: chrony mit DCF77 und GPS PPS im Jahr 2026

Auf Raspberry Pi OS Bookworm/Trixie scheitern alte NTP-Server-Tutorials aufgrund von libgpiod v2, dem Wechsel zu chrony und Änderungen bei GPIO-Chips. Eine C++20-Implementierung nutzt einen DCF77-Empfänger RC8000 an GPIO 17 und ein ublox NEO-M8T-GPS-Modul mit PPS an GPIO 18. Der Server erreicht eine Genauigkeit von <1 µs ohne Internet, mit unabhängiger Zeitquellenverifizierung.

Probleme mit veralteten Implementierungen

libgpiod v2 macht GPIO-Code unbrauchbar

libgpiod v2 hat die v1-API ohne Kompatibilität entfernt. Alter Code mit gpiod_chip_open_by_name und gpiod_chip_get_line lässt sich nicht kompilieren:

// libgpiod v1 — KOMPILIERT NICHT mit v2
struct gpiod_chip *chip = gpiod_chip_open_by_name("gpiochip0");
struct gpiod_line *line = gpiod_chip_get_line(chip, 17);
gpiod_line_request_both_edges_events(line, "dcf77");

Die neue API erfordert gpiod_line_settings_new() und gpiod_line_config_new():

Google AdInline article slot
// libgpiod v2 — korrekter Ansatz
const SettingsPtr settings(gpiod_line_settings_new());
gpiod_line_settings_set_edge_detection(settings.get(), GPIOD_LINE_EDGE_BOTH);
gpiod_line_settings_set_event_clock(settings.get(), GPIOD_LINE_CLOCK_REALTIME);

const LineCfgPtr line_cfg(gpiod_line_config_new());
gpiod_line_config_add_line_settings(line_cfg.get(), &offset, 1, settings.get());

const RequestPtr request(gpiod_chip_request_lines(chip.get(), req_cfg.get(), line_cfg.get()));

Ein entscheidender Vorteil ist GPIOD_LINE_CLOCK_REALTIME: Ereignisse erhalten Zeitstempel vom Kernel zum Interrupt-Zeitpunkt, was Jitter von 40 ms auf <5 ms reduziert.

Übergang von ntpd zu chrony

ntpd wurde in Bookworm durch chrony ersetzt. Vergleich:

| Parameter | ntpd | chrony |

Google AdInline article slot

|-----------|------|--------|

| Konvergenz | Minuten | Sekunden (makestep 0.5 3) |

| Signalverlust | Verschlechterung | Haltebetrieb (local stratum 10) |

Google AdInline article slot

| PPS | Patches | native refclock PPS |

| PPS-Genauigkeit | ~100 µs | <1 µs |

Beide unterstützen NTP SHM für Datenaustausch über Shared Memory.

GPIO-Chip auf RPi 5

gpiochip0 ist veraltet. Auf RPi 5/Trixie verwenden Sie gpiochip4 (prüfen mit gpiodetect).

Systemarchitektur

Zwei Zeitquellen:

  • GPS NEO-M8T: PPS (/dev/pps0) + NMEA über gpsd (NTP SHM #0)
  • DCF77 RC8000: GPIO 17 → Decoder → NTP SHM #2
  • chrony: Stratum 1 mit prefer PPS
  • Web-Monitor: chronyc + journald auf :8080

chrony-Konfiguration:

refclock PPS /dev/pps0 refid PPS precision 1e-9 poll 0 dpoll -2 lock NMEA prefer
refclock SHM 0 refid NMEA precision 1e-3 poll 0 dpoll -2 offset 0.0 delay 0.2 noselect
refclock SHM 2 refid DCF7 precision 1e-2 offset 0.0 delay 0.008 poll 6 dpoll 5
local stratum 10

DCF77 dient als Fallback und GPS-Spoofing-Detektor (Abweichung >mehrere Sekunden).

DCF77-Decoder

Kernel-Zeitstempel

GPIOD_LINE_CLOCK_REALTIME liefert einen Interrupt-Zeitstempel, nicht aus dem Userspace. StdDev in SHM: <5 ms.

NTP SHM für 64-Bit

Struktur mit Padding:

struct NtpShmTime {
    int32_t mode;
    int32_t count;
    int64_t clockTimeStampSec;
    int32_t clockTimeStampUSec;
    int32_t _pad;
    int64_t receiveTimeStampSec;
    int32_t receiveTimeStampUSec;
    int32_t leap;
    int32_t precision;
    int32_t nsamples;
    int32_t valid;
};
volatile NtpShmTime *shm_ = nullptr;

DCF77-Protokoll

59 Bits/Minute in BCD mit Parität:

  • 100 ms HIGH → Bit 0
  • 200 ms HIGH → Bit 1
  • Pause >1,5 s → Minutenmarker

Validierung: 3 Paritätsgruppen + timegm(). Bootstrap: 2 Frames mit 60 s Differenz.

NEO-M8T-Konfiguration

  • TIM-Firmware: RAIM, niedriger PPS-Jitter
  • dynModel=Stationary: Jitter 5–10 ns
  • antCableDelay=50 ns: Kabelkompensation
  • 4 Konstellationen: 20–30 Satelliten
  • lpMode=Continuous: kein Energiesparmodus

Web-Monitor und Deployment

dcf77_web: HTTP :8080, chronyc in JSON, UI im Binärformat. API: /api/status, /api/prefer (nur localhost).

Build mit vcpkg + CMake:

cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)

Ansible-Deployment mit systemd-Härtung.

Ergebnisse

chronyc tracking:

  • Stratum: 1
  • Systemzeit: +42 ns
  • RMS-Offset: 51 ns
  • Root-Dispersion: 15 ns

DCF77: ~3 ms.

Wichtigste Erkenntnisse

  • libgpiod v2 erfordert ein neues Objektmodell mit GPIOD_LINE_CLOCK_REALTIME
  • chrony übertrifft ntpd in PPS-Genauigkeit (<1 µs) und Haltebetrieb
  • NTP SHM für 64-Bit benötigt explizites Padding für int64_t
  • NEO-M8T TIM + Stationary: PPS-Jitter 5–10 ns
  • DCF77 validiert GPS gegen Spoofing

— Editorial Team

Advertisement 728x90

Weiterlesen