Powrót do strony głównej

Stratum 1 NTP na Raspberry Pi z chrony i GPS

Artykuł opisuje stworzenie autonomicznego serwera Stratum 1 NTP na Raspberry Pi z wykorzystaniem chrony, libgpiod v2, DCF77 i GPS NEO-M8T. Osiąga się dokładność 42 ns. Omówiono problemy migracji z ntpd i GPIO API.

Precyzyjny NTP Stratum 1 na RPi: 42 ns z GPS PPS
Advertisement 728x90

Autonomiczny serwer NTP Stratum 1 na Raspberry Pi: chrony z DCF77 i GPS PPS w 2026 roku

Na Raspberry Pi OS Bookworm/Trixie stare poradniki dotyczące serwerów NTP nie działają z powodu libgpiod v2, przejścia na chrony oraz zmian w układach GPIO. Implementacja w C++20 wykorzystuje odbiornik DCF77 RC8000 na GPIO 17 oraz moduł GPS ublox NEO-M8T z PPS na GPIO 18. Serwer zapewnia dokładność <1 µs bez internetu, z niezależną weryfikacją źródeł czasu.

Problemy przestarzałych implementacji

libgpiod v2 psuje kod GPIO

libgpiod v2 usunął API v1 bez zachowania kompatybilności. Stary kod z gpiod_chip_open_by_name i gpiod_chip_get_line nie kompiluje się:

// libgpiod v1 — NIE KOMPILUJE SIĘ z 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");

Nowe API wymaga gpiod_line_settings_new() i gpiod_line_config_new():

Google AdInline article slot
// libgpiod v2 — właściwe podejście
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()));

Kluczowa zaleta — GPIOD_LINE_CLOCK_REALTIME: zdarzenia otrzymują znaczniki czasu od jądra w przerwaniu, jitter spada z 40 ms do <5 ms.

Przejście z ntpd na chrony

ntpd został zastąpiony przez chrony w Bookworm. Porównanie:

| Parametr | ntpd | chrony |

Google AdInline article slot

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

| Zbieżność | minuty | sekundy (makestep 0.5 3) |

| Utrata sygnału | degradacja | holdover (local stratum 10) |

Google AdInline article slot

| PPS | łaty | natywny refclock PPS |

| Dokładność PPS | ~100 µs | <1 µs |

Oba wspierają NTP SHM do wymiany danych przez pamięć współdzieloną.

Układ GPIO na RPi 5

gpiochip0 jest przestarzały. Na RPi 5/Trixie potrzebny jest gpiochip4 (sprawdzić gpiodetect).

Architektura systemu

Dwa źródła czasu:

  • GPS NEO-M8T: PPS (/dev/pps0) + NMEA przez gpsd (NTP SHM #0)
  • DCF77 RC8000: GPIO 17 → dekoder → NTP SHM #2
  • chrony: stratum 1 z prefer PPS
  • Monitor WWW: chronyc + journald na :8080

Konfiguracja chrony:

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 służy jako zapasowe źródło i detektor spoofingu GPS (rozbieżność >kilku sekund).

Dekoder DCF77

Timestamp od jądra

GPIOD_LINE_CLOCK_REALTIME daje znacznik przerwania, a nie userspace. StdDev w SHM: <5 ms.

NTP SHM dla 64-bit

Struktura z dopełnieniem:

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;

Protokół DCF77

59 bitów/minutę w BCD z parzystością:

  • 100 ms HIGH → bit 0
  • 200 ms HIGH → bit 1
  • Pauza >1.5 s → znacznik minuty

Walidacja: 3 grupy parzystości + timegm(). Bootstrap: 2 ramki z różnicą 60 s.

Konfiguracja NEO-M8T

  • Oprogramowanie TIM: RAIM, niski jitter PPS
  • dynModel=Stationary: jitter 5–10 ns
  • antCableDelay=50 ns: kompensacja kabla
  • 4 konstelacje: 20–30 satelitów
  • lpMode=Continuous: bez oszczędzania energii

Monitor WWW i wdrożenie

dcf77_web: HTTP :8080, chronyc w JSON, UI w pliku binarnym. API: /api/status, /api/prefer (tylko localhost).

Kompilacja vcpkg + CMake:

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

Wdrożenie Ansible z hardeningiem systemd.

Wyniki

chronyc tracking:

  • Stratum: 1
  • Czas systemowy: +42 ns
  • RMS offset: 51 ns
  • Root dispersion: 15 ns

DCF77: ~3 ms.

Co jest ważne

  • libgpiod v2 wymaga nowego modelu obiektowego z GPIOD_LINE_CLOCK_REALTIME
  • chrony przewyższa ntpd w dokładności PPS (<1 µs) i holdover
  • NTP SHM na 64-bit wymaga jawnego dopełnienia pod int64_t
  • NEO-M8T TIM + Stationary: jitter PPS 5–10 ns
  • DCF77 weryfikuje GPS przed spoofingiem

— Editorial Team

Advertisement 728x90

Czytaj dalej