Retour à l'accueil

Stratum 1 NTP sur Raspberry Pi avec chrony et GPS

L'article décrit la création d'un serveur NTP Stratum 1 autonome sur Raspberry Pi utilisant chrony, libgpiod v2, DCF77 et GPS NEO-M8T. Une précision de 42 ns est obtenue. Les problèmes de migration depuis ntpd et l'API GPIO sont abordés.

NTP Stratum 1 précis sur RPi : 42 ns avec GPS PPS
Advertisement 728x90

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() :

Google AdInline article slot
// 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 |

Google AdInline article slot

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

| Convergence | minutes | secondes (makestep 0.5 3) |

| Perte de signal | dégradation | maintien (local stratum 10) |

Google AdInline article slot

| 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

Advertisement 728x90

Lire ensuite