Stabilisation thermique active#

Pourquoi#

L’oscillateur à quartz (XO, non compensé) qui cadence l’horloge du système dérive d’environ 0,1 ppm par degré Celsius. Si la température du boîtier oscille librement entre 35 et 50 °C au gré de la charge CPU, cette variation induit à elle seule environ 1,5 ppm de dérive que chrony doit compenser en continu, un bruit évitable si la température est maintenue stable. Le chapitre suivant replace ce travail dans la hiérarchie des oscillateurs (XO, TCXO, OCXO, rubidium, CSAC) et détaille comment l’ensemble des étapes de ce guide transforme ce quartz nu en pseudo-TCXO logiciel.

Comment#

Approche retenue par défaut : régulation par ventilateur#

Le chapitre Firmware a déjà mis en place un profil de ventilation PWM à 4 paliers (fan_temp0 à fan_temp3 dans config.txt), démarrant dès 40 °C. C’est l’approche passive retenue par défaut : elle stabilise la température sans consommation électrique permanente, contrairement à l’alternative ci-dessous.

Alternative optionnelle : charge CPU contrôlée (ntpheat)#

Une autre approche, plus agressive, consiste à maintenir une charge CPU artificielle et contrôlée sur le cœur 0 pour stabiliser activement la température à une consigne fixe (typiquement 57 °C), quelle que soit la charge réelle du système.

Le script ci-dessous provient de l’utilitaire ntpheat du projet NTPsec (contrib/ntpheat, écrit par Gary E. Miller, licence BSD-2-Clause), dans la copie diffusée par David Taylor sur satsignal.eu, légèrement adaptée ici :

mkdir -p /opt/ntpheat
cat > /opt/ntpheat/ntpheat.py <<'PYEOF'
#!/usr/bin/env python3
# Charge CPU contrôlée pour maintenir une consigne de température
# Origine : projet NTPsec, contrib/ntpheat (Gary E. Miller,
# licence BSD-2-Clause), via la copie satsignal.eu de David Taylor.
import argparse, hashlib, os, time
p = argparse.ArgumentParser()
p.add_argument('-c','--copies', type=int, default=1)
p.add_argument('-t','--temp',   type=float, default=57.0)
p.add_argument('-w','--wait',   type=float, default=0.001)
a = p.parse_args()
for _ in range(a.copies - 1):
    if os.fork(): break
zone = '/sys/class/thermal/thermal_zone0/temp'
m = hashlib.md5(); cnt = 0; max_cnt = a.wait * 200000; temp = 0
while True:
    delta = a.temp - temp
    if delta > 0:
        m.update(b'controlled load')
    else:
        cnt = max_cnt
        s = max(0.001, a.wait * 10.0 * -delta); time.sleep(s)
    cnt += 1
    if cnt > max_cnt:
        with open(zone) as f: temp = int(f.read())/1000.0
        cnt = 0
PYEOF
chmod +x /opt/ntpheat/ntpheat.py

cat > /etc/systemd/system/ntpheat.service <<'EOF'
[Unit]
Description=Stabilisation thermique active (quartz)

[Service]
ExecStart=/usr/bin/python3 /opt/ntpheat/ntpheat.py -c 1 -t 57
Restart=on-failure
CPUAffinity=0
Nice=19
CPUSchedulingPolicy=idle
IOSchedulingClass=idle

[Install]
WantedBy=multi-user.target
EOF

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

Erreurs à éviter#

Ne pas activer ntpheat par défaut, d’abord observer. Cette section est délibérément marquée optionnelle. Le coût est réel : environ 1 W de consommation permanente, usure thermique accrue, bruit de ventilateur constant. La recommandation par défaut de ce guide est de skip cette étape et de réévaluer après 24-48h d’observation via Munin (chapitre Munin) : l’activation ne se justifie qu’en présence de variations de fréquence corrélées à la température supérieures à 1 ppm, ou d’une dispersion racine qui reste supérieure à 20 µs malgré un PPS fonctionnel.

  • Confondre régulation passive et active. Le profil fan_temp du chapitre Firmware est une régulation réactive (le ventilateur s’active quand la température monte, redescend quand elle baisse), la température oscille toujours, mais dans une plage plus resserrée. ntpheat est une régulation active qui vise une consigne fixe en permanence, plus stable, mais avec le coût mentionné ci-dessus.
  • Oublier CPUSchedulingPolicy=idle. Sans cette directive, la charge de stabilisation thermique entrerait en compétition avec les vrais services du système pour le temps CPU, au lieu de ne consommer que les cycles autrement perdus.

Vérifier#

# Température actuelle (en millidegrés Celsius)
cat /sys/class/thermal/thermal_zone0/temp

# Historique de la dérive de fréquence, à observer sur plusieurs heures
chronyc tracking | grep Frequency

La bonne méthode de vérification pour ce chapitre n’est pas instantanée : il faut observer les graphes Munin de température et de fréquence (chapitre Munin) sur au moins 24h, en cherchant une corrélation visuelle entre les cycles jour/nuit (température ambiante) et la dérive de fréquence rapportée par chrony.