DCF 77

31 mars 2024

Le filtrage passe-bas est une solution possible, mais pas optimale.

J'ai préféré caler la phase de mon horloge locale à quartz, suffisamment précise, sur le signal DCF, ce qui élimine les problèmes de décalages temporels variables suivant les conditions de réception.

Améliorer le filtre passe-bas ?

Le filtre passe-bas présente tout de même quelques défauts : les créneaux ne font plus exactement 100 ou 200 ms, mais surtout nous avons un décalage de phase et du jitter (= les fronts des créneaux ne sont pas régulier dans le temps).

dcf77 jitter
Après filtrage, non seulement les signaux sont décalés et pas régulièrement,
mais en plus, la largeur des signaux, c'est n'importe quoi, mais pas 100 et 200 ms.

C'est là que l'on peut utiliser la modulation de phase pseudo-aléatoire. Sauf que c'est vraiment compliqué à faire, il faut un récepteur adéquat, et en plus une horloge de référence très précise (à défaut d'être à l'heure puisque c'est ce que l'on cherche).

Udo Klein propose plutôt une détection de phase directement sur la modulation d'amplitude. Nous aurions une horloge déjà parfaitement régulière, nous n'aurions plus que la phase à retrouver une bonne fois pour toutes.

Sauf que notre horloge locale (pourtant à quartz) n'est pas super-précise. Mais suffisamment pour commencer à détecter une différence de phase et nous recaler, ce qu'on appelle une boucle à verrouillage de phase (dénomination un poil abusive par rapport à une vraie PLL Phase-Locked Loop classique, c'est plutôt un asservissement de phase).

Convolution

Avant de pouvoir détecter la phase, il faut pouvoir identifier la forme du signal dans les données de réception. Ce signal est parfaitement défini au départ, mais peut subir des déformations et du bruit peut se superposer à l'arrivée.

noyau de corrélation
C'est le signal que l'on souhaite reconnaitre.

A chaque seconde, sauf la 59, on aura en moyenne :

  • forcément 1 sur le premier créneau de 100 ms
  • 1/2 en moyenne sur le second créneau, les bits de données
  • puis 0 entre 200 et 800 ms

Une convolution peut être effectuée pour détecter cette forme de signal et retrouver la phase.

Cela se réalise simplement en échantillonnant le signal par exemple toutes les 10 ms, en accumulant sur plusieurs trames (ici une trame fait une seconde), mais avec une sorte de ruse :

  • signal à 1: on incrémente, mais avec une limitation
  • signal à 0: on décrémente jusqu'à zéro minimum

C'est le phase_binning() chez Udo, et cela a pour effet de limiter l'effet des "vieux échantillons", le filtre sera plus réactif si la limitation est basse, ce qui sera un avantage si l'horloge se décale beaucoup, mais le bruit sera moins filtré.

Ensuite on effectue une sommation (intégrale) des valeurs sur une seconde, multipliée par le noyau, en essayant toutes les positions entre le début et la fin de la trame.

En s'arrangeant pour décaler les valeurs afin de manipuler uniquement des entiers positifs, Udo Klein effectue 100 sommes en décalant toutes les 10 ms :

  • les 10 premières valeurs, soit 0 à 100 ms, sont multipliées par 2
  • et les 10 valeurs suivantes, soit 100 à 200ms, sont multipliées par 1
  • les 80 dernières sont multipliées par 0 (=ignorées)

On reconnait les valeurs du noyau, décalées.

Si la phase est bien calée sur le début du signal, alors la somme sera maximale. L'algorithme est simple puisqu'il suffit de détecter un maximum parmi les 100 tentatives de sommation.

Je vous laisse découvrir le détail sur le site d'Udo, qui pousse le vice jusqu'à réaliser le même genre de boucle avec les données reçues, minutes, heures et date. Et il n'utilise pas le filtre passe-bas exponentiel dans son code final, parce que ce filtre ajoute des déphasages aléatoires incompatibles avec la détection de phase.

Mais ceci dit, je trouve curieux que la sommation n'utilise pas du tout les données entre 200 et 1000 ms, supposées être zéro, alors que les données entre 100 et 200 ms, variables, sont simplement la moitié du signal à 1 entre 0 et 100 ms.

Je me serai attendu à ce que l'on donne un poids important aux données des 800 ms qui doivent être à zéro. Genre "si c'est zéro, alors on somme 2", le même poids que pour les données entre 0 et 100.

Mais bon, on détectera le pic, c'est ce qu'on cherche.

Horloge locale

Une fois que le signal sur une seconde est identifié, alors on connait sa position temporelle par rapport à notre horloge locale, alors on peut la recaler en enlevant ou ajoutant des millisecondes.

Sauf que si notre horloge locale est pourrie, alors elle va se redécaler aussi sec.

Mauvaise horloge locale

J'ai vérifié la période de mon horloge locale par rapport à la période atomique, quand je reçois le signal DCF sans bavure (ça arrive) :

période horloge
Période DCF "atomique" et période de l'horloge locale. Une différence de 3%.

J'ai constaté un écart de 3%, et on voit l'horloge locale se décaler gentiment par rapport à la référence, ce qui parait gênant pour la détection de phase : tout va bouger.

Cette histoire-là me surprend car les kits MSP430 sont livrés avec un quartz à 32768 Hz qui vaut exactement 215, un élément fort commode pour réaliser une horloge car on divise facilement cette fréquence pour obtenir 1 Hz.

La précision devrait être de l'ordre de 5 à 30 ppm : 3% d'écart, c'est juste n'importe quoi.

On est prévenu par Udo Klein : ça va mal marcher. Voici le résultat de la détection de phase par convolution telle que décrite ci-dessus :

détection de phase
Ce n'est pas si mal. Mais bon, on n'est pas bien calé, et ça parait instable.

Je suis surpris que cela marche finalement pas trop mal, car j'accumule le signal sur plusieurs secondes, et avec la différence de période entre le signal DCF et mon horloge locale, le signal finira par être réparti sur toute la trame.

Sauf que ce n'est pas une simple accumulation, lorsque l'on a zéro, on a une décrémentation qui nettoie l'ensemble, ce qui aide beaucoup, sinon nous aurions n'importe quoi comme résultat.

Bonne horloge locale

Bon, j'avais un bug dans la programmation de mon timer, 3%, c'était carrément impossible comme dérive...

période horloge
Période DCF "atomique" et période de l'horloge locale. C'est nettement mieux.
On teste plutôt la précision de l'horloge de l'oscilloscope.

Et voici l'extraction de phase sur un signal brut, de bonne qualité :

détection de phase
Cette fois, la phase est calée sur le signal.

On notera que le résultat du calcul de phase sur l'oscilloscope est fait sur les données de la précédente seconde, et donc quand on voit le signal un poil en avance, c'est un leurre. Mais bon, l'écart est assez faible.

Les résultats sont assez remarquables, la détection de phase se passe nettement mieux. Le bug sur l'horloge locale est rassurant sur l'efficacité de la détection.

Sauf que le signal DCF est particulièrement bon ici, que se passe-t-il quand la réception se dégrade ?

détection de phase
On arrive encore à extraire la phase raisonnablement bien.
détection de phase
Jusqu'à un certain point, évidemment. Ici, rien à en tirer.

Lorsque le signal revient, il suffit de quelques secondes pour retrouver correctement la phase.

Verrouillage de phase

Une fois que la position temporelle du signal DCF est connue par rapport à l'horloge locale, on peut alors décaler l'heure locale des quelques centaines de millisecondes requises.

Si la reconnaissance du signal est faite régulièrement, on pourra recaler l'heure locale chaque fois que nécessaire.

Ici, la détection du signal est faite au niveau de la seconde, mais on pourra également se recaler au niveau de la minute en identifiant l'absence de signal au niveau de la seconde 59, et à un niveau supérieur ce sera également les minutes, heures, jours, mois, ce qu'Udo Klein a proposé dans son superfiltre mais qui devient alors une usine à gaz.

Sauf que les calculs de convolution peuvent être longs si on souhaite une précision élevée. Avec 100 échantillons par seconde, on atteint la dizaine de millisecondes, ce qui s'avère largement suffisant pour l'usage qu'on veut en faire par rapport aux 100 ms de largeur à détecter.

De cette manière, l'extraction des bits devient facile car on ne regarde que le signal entre 0 et 100 ms pour détecter la seconde 59 et vérifier la cohérence des signaux, puis entre 100 et 200 ms pour lire le bit d'information.

De plus, j'ai aussi voulu voir ce qui se passe si on utilise le signal normalement à zéro entre 200 et 1000 ms, qui est ignoré par l'algorithme d'Udo Klein.

Noyau de convolution complet

Cette fois, je vais exploiter le fait que l'on doit avoir zéro entre 200 et 1000 ms, ce qui n'est pas forcément une bonne idée car si le signal est bloqué à zéro, le résultat de la convolution sera quand même élevé. Ceci dit, avec l'algorithme précédent, si le signal est bloqué à l'état haut, le résultat est du même tonneau.

Algorithme

L'algorithme est plus "naturel" dans le sens où nous allons vraiment exploiter le signal comme initialement défini, avec des nombres négatifs. Cela parait moins malin par rapport à l'usage de nombre toujours positifs, surtout que je vais devoir utiliser des entiers plus grands (16 bits au lieu de 8) mais le MSP430 est un microcontrôleur 16 bits, alors autant s'en servir.

noyau de corrélation
Positif et négatif.

On commence par une accumulation de données sur plusieurs secondes pour réduire le bruit, mais pas trop longue pour avoir une détection réactive, surtout que je n'observe pas vraiment plein de petits pics supplémentaires, mais des gros créneaux désordonnés.

Comme j'utilise une horloge avec 512 échantillons sur 1000 ms c'est le max en utilisant le watchdog, j'utilise 64 paquets (bins) où j'aurai 8 échantillons, au lieu de 128 où j'en avais 4. Dans l'intervalle -MAX et +MAX :

  • Incrémenter le paquet si on a majoritairement l'état haut,
  • sinon décrémenter

MAX donne une valeur maximale, ce qui limite la longueur d'accumulation. En pratique, MAX vaut entre 4 (à affiner suivant le bruit que l'on observe).

L'intégration avec le noyau de convolution est définie ainsi :

  • Entre 0 et 100ms, on ajoute le paquet. C'est le signal normalement haut.
  • Entre 200 et 1000ms, on SOUSTRAIT le paquet (coefficient -1). Si le signal est bas, négatif, il vient alors s'ajouter à l'intégration et contribue à obtenir une note élevée.

Si l'intégration est négative, c'est forcément un mauvais signal.

Résultats

En pratique l'intégrale descend en-dessous de 120 quand c'est mauvais, et atteint 227 avec un signal propre, ce qui nous donne un critère de décision.

Je n'ai pas vu de différence fondamentale entre les deux algorithmes, je trouve que les deux convergent rapidement (si on ne met pas une accumulation trop longue), et se calent suffisamment bien, un peu mieux pour le "128 bins" car le pas est plus fin, mais ça ne change pas la face du monde.

côté temps d'exécution, cet algorithme tourne dans les 212 μs, alors que l'algorithme "entiers positifs" tourne en 340 μs en utilisant plus de mémoire.

J'ai donc gardé "mon algorithme" à 64 bins et nombres positifs/négatifs.

Voici le résultat du calage de l'horloge :

fenêtres de lecture
L'horloge locale en bleu est déphasée par rapport aux signaux DCF.

Les gros créneaux en bas est l'horloge locale, de largeur 1 seconde. Ce qu'on veut, c'est que le début du créneau, montée ou descente, soit calé sur les créneaux du signal DCF.

fenêtres de lecture
Et nous avons maintenant verrouillé la phase !

Cela prend quelques secondes pour se caler. Puis on vérifie sur quelques secondes que ça se passe bien avant de passer à la détection des bits, à commencer celui de la seconde 59, avec un signal notoirement absent.

Détection des bits

C'est maintenant que l'on va voir si les efforts précédents portent leurs fruits.

Avec deux fenêtres

On va observer le signal dans deux fenêtres bien alignées pour avoir les valeurs entre 0 et 100 ms, puis 100 et 200 ms. Mais en réalité, les signaux ne sont pas si propres et calés, on voit bien qu'ils sont plus ou moins larges, et de plus pollués par du bruit. On peut facilement visualiser les fenêtres en les sortant sur une broche du MSP430 afin de vérifier :

fenêtres de lecture
Fenêtres de lecture. Heureusement que c'est calé en phase !
Ici, c'est facile de décider de la valeur du bit, mais le dernier commence à être ambigu.

J'ai choisi deux fenêtres de largeur 64 ms (32 échantillons), démarrant à 0 ms et à 135ms, autrement dit le début du signal fixe, et la fin du bit de données.

fenêtres de lecture
L'absence de signal indiquant la seconde 59 est facile à voir.
Mais ce n'est pas toujours le cas.

On vérifie la présence du premier créneau haut sur les 100 premières millisecondes, et on lit la donnée sur les 100 suivantes, une absence signifiant que nous avons affaire au bit 59, obligatoirement suivi d'un zéro.

fenêtres de lecture
Du bruit. Rien à faire.

Avec une seule fenêtre

Finalement, j'ai trouvé plus simple d'échantillonner le signal sur une seule fenêtre entre 0 et 200 ms. On comptabilise la quantité de signal haut, puis on décide :

  • si la quantité est inférieure à 20ms, on considère qu'il n'y a rien, ce qui est correct uniquement dans le cas de la seconde 59.
  • si la quantité est inférieure à un seuil, entre 140 et 160 ms, alors on a un bit 0
  • sinon, c'est un bit 1

Recaler l'horloge

Vu le quartz que j'utilise, c'est difficile de savoir à l'avance combien de temps le système restera en phase correctement pour lire les données. Ceci dit, on peut contrôler la phase en permanence, et recaler si on constate trop de données incohérentes sur une durée trop longue.

Généralement, les horloges DCF du commerce ne se recalent qu'une fois toutes les 24h, et souvent la nuit où la réception est meilleure.

Comme la consommation électrique ne sera pas un problème (je vais utiliser beaucoup de LED, et par conséquent une alimentation sur le secteur), je serai en permanence à l'écoute des signaux, ça me permettra de me rendre compte de la qualité du signal au cours du temps.


Nous avons à présent tous les outils basiques pour recevoir et décoder le signal DCF77.

Passons à la réalisation pratique d'une horloge.