Serveur NTP Stratum 1 autonome sur Raspberry Pi : chrony avec DCF77 et GPS PPS en 2026
Sur Raspberry Pi OS Bookworm/Trixie, les anciens tutoriels de serveur NTP échouent en raison de libgpiod v2, du passage à chrony et des changements dans les puces GPIO. Une implémentation en C++20 utilise un récepteur DCF77 RC8000 sur GPIO 17 et un module GPS ublox NEO-M8T avec PPS sur GPIO 18. Le serveur atteint une précision <1 µs sans internet, avec vérification indépendante des sources de temps.
Problèmes des implémentations obsolètes
libgpiod v2 casse le code GPIO
libgpiod v2 a supprimé l'API v1 sans compatibilité. L'ancien code avec gpiod_chip_open_by_name et gpiod_chip_get_line ne compile pas :
// libgpiod v1 — NE COMPILE PAS avec 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");
La nouvelle API nécessite gpiod_line_settings_new() et gpiod_line_config_new() :
// libgpiod v2 — approche correcte
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()));
Un avantage clé est GPIOD_LINE_CLOCK_REALTIME : les événements reçoivent des horodatages du noyau au moment de l'interruption, réduisant le jitter de 40 ms à <5 ms.
Transition de ntpd à chrony
ntpd a été remplacé par chrony dans Bookworm. Comparaison :
| Paramètre | ntpd | chrony |
|-----------|------|--------|
| Convergence | minutes | secondes (makestep 0.5 3) |
| Perte de signal | dégradation | maintien (local stratum 10) |
| PPS | correctifs | natif refclock PPS |
| Précision PPS | ~100 µs | <1 µs |
Les deux prennent en charge NTP SHM pour l'échange de données via la mémoire partagée.
Puce GPIO sur RPi 5
gpiochip0 est obsolète. Sur RPi 5/Trixie, utilisez gpiochip4 (vérifiez avec gpiodetect).
Architecture système
Deux sources de temps :
- GPS NEO-M8T : PPS (/dev/pps0) + NMEA via gpsd (NTP SHM #0)
- DCF77 RC8000 : GPIO 17 → décodeur → NTP SHM #2
- chrony : stratum 1 avec préférence PPS
- Moniteur web : chronyc + journald sur :8080
Configuration 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 sert de secours et de détecteur de spoofing GPS (écart >plusieurs secondes).
Décodeur DCF77
Horodatage noyau
GPIOD_LINE_CLOCK_REALTIME fournit un horodatage d'interruption, pas d'espace utilisateur. Écart type dans SHM : <5 ms.
NTP SHM pour 64 bits
Structure avec remplissage :
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;
Protocole DCF77
59 bits/minute en BCD avec parité :
- 100 ms HAUT → bit 0
- 200 ms HAUT → bit 1
- Pause >1,5 s → marqueur de minute
Validation : 3 groupes de parité + timegm(). Amorçage : 2 trames avec une différence de 60 s.
Configuration NEO-M8T
- Firmware TIM : RAIM, faible jitter PPS
- dynModel=Stationary : jitter 5–10 ns
- antCableDelay=50 ns : compensation de câble
- 4 constellations : 20–30 satellites
- lpMode=Continuous : pas d'économie d'énergie
Moniteur web et déploiement
dcf77_web : HTTP :8080, chronyc en JSON, interface en binaire. API : /api/status, /api/prefer (localhost uniquement).
Construction avec vcpkg + CMake :
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j$(nproc)
Déploiement Ansible avec durcissement systemd.
Résultats
chronyc tracking :
- Stratum : 1
- Temps système : +42 ns
- Décalage RMS : 51 ns
- Dispersion racine : 15 ns
DCF77 : ~3 ms.
Points clés
- libgpiod v2 nécessite un nouveau modèle objet avec
GPIOD_LINE_CLOCK_REALTIME - chrony surpasse ntpd en précision PPS (<1 µs) et maintien
- NTP SHM pour 64 bits nécessite un remplissage explicite pour
int64_t - NEO-M8T TIM + Stationary : jitter PPS 5–10 ns
- DCF77 valide le GPS contre le spoofing
— Editorial Team
Aucun commentaire pour le moment.