Acquisition GNSS/PPS : udev, gpsd#

Pourquoi#

Le câblage physique (chapitre Matériel et câblage) et le kernel isolé (chapitres précédents) ne servent à rien tant que le système d’exploitation n’expose pas proprement les deux flux du récepteur GNSS : le signal PPS via un module kernel dédié, et les trames NMEA via gpsd. Ce chapitre relie le matériel au logiciel.

Comment#

Modules kernel#

cat > /etc/modules-load.d/pps.conf <<'EOF'
pps-gpio
pps-ldisc
EOF

modprobe pps-gpio
modprobe pps-ldisc

Règles udev : noms stables#

cat > /etc/udev/rules.d/10-pps.rules <<'EOF'
KERNEL=="ttyAMA0", SYMLINK+="gps0"
KERNEL=="pps0", OWNER="root", GROUP="tty", MODE="0660", SYMLINK+="gpspps0"
EOF

udevadm control --reload-rules
udevadm trigger

Le symlink stable (SYMLINK+=) évite de dépendre de l’ordre d’énumération des devices au boot (ttyAMA0/pps0 pourraient en théorie changer de nommage). Sur la ligne pps0, OWNER="root", GROUP="tty", MODE="0660" applique le principe de moindre privilège au device : lecture/écriture réservées au propriétaire et au groupe tty (le groupe historique des périphériques série sous Linux), aucun accès pour le reste du système.

Latence basse sur le port série#

cat > /etc/systemd/system/setserial.service <<'EOF'
[Unit]
Description=Low-latency UART for GNSS NMEA
After=systemd-modules-load.service

[Service]
Type=oneshot
ExecStart=/bin/setserial /dev/ttyAMA0 low_latency
RemainAfterExit=true

[Install]
WantedBy=multi-user.target
EOF

systemctl daemon-reload
systemctl enable --now setserial.service

Sans ce réglage, le pilote série du noyau regroupe par défaut plusieurs caractères reçus avant de réveiller les processus en attente (un délai de l’ordre de quelques millisecondes, pensé pour l’efficacité sur un usage terminal classique). Le flag low_latency désactive ce comptage temporisé et livre chaque caractère reçu immédiatement, ce qui stabilise la latence de réception des trames NMEA, la compensation offset 0.125 appliquée côté chrony (chapitre Chrony) part de l’hypothèse d’un délai constant, que ce réglage aide à garantir.

Configuration de gpsd#

cat > /etc/default/gpsd <<'EOF'
DEVICES="/dev/gps0"
GPSD_OPTIONS="-n"
USBAUTO="false"
EOF

systemctl enable --now gpsd

-n démarre la collecte immédiatement, sans attendre qu’un client se connecte, indispensable puisque chronyd doit disposer des données dès le boot.

Confiner gpsd sur le Core 0#

gpsd ne traite qu’un flux série à faible débit (NMEA) : il n’a besoin ni d’isolation temps réel ni d’un cœur dédié, seulement de ne pas être préempté par les tâches généralistes moins prioritaires du système.

mkdir -p /etc/systemd/system/gpsd.service.d/
cat > /etc/systemd/system/gpsd.service.d/override.conf <<'EOF'
[Service]
# Confinement sur le Core 0 (pool général, cf. chapitre Firmware)
CPUAffinity=0
# Priorité haute : préempte les services bavards (Nice >= 0) sans toucher au RT
Nice=-10
EOF

systemctl daemon-reload
systemctl restart gpsd

Erreurs à éviter#

Le Raspberry Pi 5 expose plusieurs devices /dev/ppsN, un seul est le bon. Sur ce matériel, on trouve typiquement pps0 (GPIO du récepteur GNSS), pps1 (horloge matérielle du contrôleur réseau, utilisée par phc2sys, voir chapitre PHC) et pps2 (créé automatiquement par gpsd sur la ligne DCD du port série, généralement inactif si le récepteur n’émet pas de PPS sur cette broche). Cibler le mauvais device dans la configuration de chrony ne produira aucune erreur visible, juste une absence totale de synchronisation précise. Toujours vérifier avant de figer la configuration :

for p in /sys/class/pps/pps*; do
    printf "%-20s name=%s\n" "$p" "$(cat $p/name)"
done
  • Sans antenne raccordée, le récepteur émet quand même des données, invalides. Les trames NMEA continuent d’arriver, avec des champs vides et un statut V (Void), sourcées sur l’oscillateur interne du récepteur, non traçables UTC. Ne pas confondre “je reçois des trames” avec “j’ai un fix valide” : c’est précisément le rôle de la directive lock GPS côté chrony (chapitre Chrony) de faire cette distinction automatiquement.
  • Oublier USBAUTO="false". Si gpsd tente une détection automatique de périphériques USB en plus du device série explicite, cela peut introduire un délai de démarrage ou des conflits avec d’autres périphériques USB éventuellement branchés.

Vérifier#

# Trames NMEA brutes (sans fix : champs vides, statut V : c'est normal
# dans les premières secondes/minutes après le boot)
timeout 5 cat /dev/gps0

# Impulsions PPS, doit afficher 1 ligne par seconde
ppstest /dev/pps0

# Synthèse interactive gpsd (quitter avec 'q')
cgps -s

# Confinement effectif sur le Core 0
ps -eLo psr,ni,comm | grep gpsd

Sortie attendue de ppstest une fois le fix acquis :

trying PPS source "/dev/pps0"
found PPS source "/dev/pps0"
ok, found 1 source(s), now start fetching data...
source 0 - assert 1778011166.000219296, sequence: 186 - clear  0.000000000
source 0 - assert 1778011167.000218233, sequence: 187 - clear  0.000000000

Le champ assert est le timestamp UTC (epoch Unix + nanosecondes) du front montant de chaque impulsion, l’écart entre deux lignes consécutives doit être extrêmement proche de 1,000000000 seconde.

L’acquisition d’un premier fix GNSS après un démarrage à froid (cold start) prend généralement de quelques dizaines de secondes à quelques minutes, le temps que le récepteur télécharge les éphémérides des satellites visibles.


📚 Pour aller plus loin

Le module kernel pps-gpio attache un gestionnaire à une broche GPIO et expose le résultat à l’espace utilisateur via /dev/ppsN. À chaque front montant, l’interruption est traitée et un timestamp est enregistré dans un tampon circulaire interne, lisible via l’appel système ioctl PPS_FETCH (documentation noyau Linux, sous-système PPS).

La latence entre le front physique du GPIO et l’enregistrement du timestamp côté kernel est fondamentalement bornée par la latence d’interruption du noyau : de l’ordre de 30 à 150 µs sur un kernel standard, contre 5 à 30 µs sur un kernel PREEMPT_RT correctement configuré (cf. chapitre Noyau temps réel) : c’est ce facteur 5 à 10 qui justifie tout l’effort de configuration temps réel de ce guide.