Après avoir réussi à faire marcher l'écran LCD1602-RGB de WaveShare avec un Yocto-I2C, on s'est demandé si on pouvait utiliser le système de job intégré du Yocto-I2C pour lire les valeurs d'un capteur I2C et les afficher directement sur l'écran sans l'intervention d'un host USB. En théorie, c'est faisable, en pratique, c'est plutôt compliqué, mais intéressant.
Pour faire l'expérience, on a récupéré le capteur MCP9804 d'un vieux Yocto-Temperature qui traînait par là et on l'a branché en parallèle avec l'écran.
Les branchements
On aimerait donc créer un job qui:
- Lise la température mesurée par le capteur MCP9804.
- Mappe cette valeur sur le GenericSensor1 du Yocto-I2C.
- Affiche la valeur sur l'écran.
- Change la couleur de l'écran en fonction d'un seuil de température prédéfini.
Le code
Sachant que les variables d'un même job sont communes à toutes les tâches de ce job, on va créer une petite machine à état contrôlée par la variable $state. Chaque tâche correspondra à un état distinct et elles commenceront toutes par un assert qui vérifiera que la variable $state a la valeur attendue.
Etat 0: Initialisation
On vérifie que la variable $state n'est pas définie, et par précaution, on vide le buffer du Yocto-I2C avec l'instruction reset.
assert: !isset($state) reset
Ensuite, on initialise quelques constantes: la couleur de l'écran quand la température est basse, la couleur de l'écran quand la température est haute et le seuil qui définit la limite entre les deux couleur.
compute: $ColorLow=0x00FF00 compute: $ColorHi=0xFF0000 compute: $ColorTheshold=25
Puis on initialise l'écran comme expliqué dans l'article précédent et on profite pour écrire "Temperature" aux coordonnées (0,0) et "°C" aux coordonnées (14,1). Pour chaque commande I2C, on vérifie que l'écran a bien répondu avec une commande expect.
writeVar: {S}C00000{P}{S}C008FF{P} expect: 60:{A}{A}{A} 60:{A}{A}{A} writeLine: {S}7C8028{P}{S}7C800C{P}{S}7C8014{P}{S}7C8080{P}{S}7C4054656D7065726174757265{P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A} 3E:{A}{A}{A} 3E:{A}{A}{A} 3E:{A}{A}{A}{A}{A}{A}{A}{A}{A}{A}{A}{A}{A} writeLine: {S}7C80CE{P}{S}7C40DF43{P} expect": 3E:{A}{A}{A} 3E:{A}{A}{A}{A}
Et enfin, on mets à jour la variable $state avec la valeur 1.
Etat 1: Lecture du capteur
Pour lire le capteur MCP9804, il faut faire pointer le pointeur de registre du capteur sur l'adresse 5 et lire deux bytes sous la forme d'un entier 16 bits (WORD) qu'on stocke dans la variable $TRAW.
assert: $state==1 writeLine: {S}3E05{R}3Fxx{A}xx{N}{P} expect: 1F:{A}{A} 1F:{A}($TRAW:WORD)
La valeur absolue de la température est encodée en 16èmes de degré Celsius sur les bits 11..0 de $TRAW. Le signe est stocké dans le bit 12. On calcule une variable $SIGN qui contient, en fonction du bit de signe, le code ASCII du "-" ou de l'espace. On filtre les 13 bits de poids faible et si le résultat est plus grand que 0x0fff, on a affaire à un nombre négatif, on effectue alors l'extension de signe avec l'opération $TRAW-0x2000.
compute: $SIGN=0x20+(($TRAW&0x1000)>>12)*13 compute: $TRAW=($TRAW & 0x1FFF) compute: $state=2 assert: $TRAW > 0x0FFF compute: $TRAW = $TRAW-0x2000
Etat 2: Affectation de la température au genericsensor1
Rien d'extraordinaire, ici si ce n'est qu'il faire diviser $TRAW par 16 puisqu'il s'agit de seizièmes de degrés.
assert: $state==2 compute: $1 = $TRAW/16 compute: $state=3
Etat 3: Affichage de la température
C'est le gros morceau de ce job, on aimerait que la température soit affichée de manière un peu jolie. C'est-à-dire:
- En bas à droite de l'écran juste à gauche des caractères "°C"
- Justifié à droite
- Sans zéro inutiles à gauche
- Le signe juste à gauche du premier digit
Organisation de l'affichage
Le problème, c'est que les jobs du Yocto-I2C n'offrent pas de structure de contrôle telle que les boucles for ou while ou les if..then..else. On ne dispose que de la commande assert qui stoppe la tâche en cours si l'expression donnée en paramètre est fausse.
Dans la mesure où le capteur mesure des températures entre -40 et +125°C, on sait qu'on aura besoin de 4 digits au maximum. Après avoir calculé la variable $T comme floor(fabs($TRAW/1.6)), c'est-à-dire la valeur absolue de la température en dixièmes de degrés Celsius, on peut calculer le code ASCII de chaque digit en utilisant les formules suivantes:
Digit0 | 48+floor(10*frac($T /10)) |
Digit1 | 48+floor(10*frac( floor($T/10)/10)) |
Digit2 | 48+floor(10*frac( floor($T/100)/10)) |
Digit3 | 48+floor(10*frac( floor($T/1000)/10)) |
On sait que les Digit0, Digit1 et le point décimal seront affichés quoi qu'il arrive, on peut donc écrire du code assez linéaire. On en profite aussi pour mémoriser dans la variable $PADADDR l'adresse du prochain digit, on en aura besoin plus tard.
assert: $state==3 compute: $T = floor(fabs($TRAW)/1.6) compute: $DIGIT0 = 48+ floor(10*frac($T /10)) writeVar: {S}7C80CD{P}{S}7C40($DIGIT0:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A} writeLine: {S}7C80CC{P}{S}7C402E{P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A} compute: $DIGIT1 = 48+ floor(10*frac( floor($T/10) /10)) compute: $PADADDR=0xCA writeVar: {S}7C80CB{P}{S}7C40($DIGIT1:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A} compute: $state=4
Si $T n'est pas plus grand que 99 (9.9°C) on interrompt la tâche, sinon on affiche le digit 2 et on affecte 0xC9 à $PADADDR
assert: $T>99 compute: $DIGIT2 = 48+ floor(10*frac( floor($T/100) /10)) compute: $PADADDR=0xC9 writeVar: {S}7C80CA{P}{S}7C40($DIGIT2:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A}
Même principe pour le digit 3
assert: $T>999 compute: $DIGIT3 = 48+ floor(10*frac( floor($T/1000) /10)) compute: $PADADDR=0xC8 writeVar: {S}7C80C9{P}{S}7C40($DIGIT3:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A}
Etat 4: Padding
Il faut ensuite compléter les digits restants. C'est important pour afficher le signe et surtout pour effacer les éventuels digits de mesures précédentes, par exemple si la température passe 10.0 à 9.9 °C.
On écrit donc à l'adresse $PADADDR la variable $SIGN calculée à l'état 1. On mets ensuite la valeur de $SIGN à 0x20 (code ASCII de l'espace), on décrémente le pointeur $PADADDR et on arrête la tâche si $PADADDR n'a pas atteint 0xC8.
assert: $state==4 compute: $state=5 writeVar: {S}7C80($PADADDR:BYTE){P}{S}7C40($SIGN:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A} compute: $SIGN=0x20 compute: $PADADDR=$PADADDR-1 assert: $PADADDR>0xC8
On recommence encore deux fois pour être sûr que tous les digits sont écrasés
writeVar: {S}7C80($PADADDR:BYTE){P}{S}7C40($SIGN:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A} compute: $PADADDR=$PADADDR-1 assert: $PADADDR>0xC8 writeVar: {S}7C80($PADADDR:BYTE){P}{S}7C40($SIGN:BYTE){P} expect: 3E:{A}{A}{A} 3E:{A}{A}{A}
Etat 5: Calculer la couleur du rétro-éclairage
Comparé à l'affichage de la température, calculer la couleur du rétro éclairage en fonction de la variable $ColorTheshold, qu'on a définie dans la tâche 0, est assez simple, mais on est quand même obligé de le faire en deux étapes (toujours pas de if..then..else). On affecte une variable $color à $ColorLow si $T est plus petit que $ColorTheshold*10, et $ColorHi sinon.
assert: $state==5 compute: $state=6 compute: $color=$ColorLow assert: $T>$ColorTheshold*10 compute: $color=$ColorHi
Etat 6: Affecter la couleur
Rien d'extraordinaire: on extrait les composantes Rouge, Verte et Bleue de la variable $color et on les envoie au contrôleur de LED de l'écran en une seule requête I2C.
assert: $state==6 compute: $R=($color >>16) &0xff compute: $G=($color >>8) &0xff compute: $B= $color & 0xff writeVar: {S}C082($B:BYTE)($G:BYTE)($R:BYTE){P} expect: 60:{A}{A}{A}{A}{A} compute: $state=1
Et voilà, on a maintenant la température qui s'affiche sur notre écran et le rétro éclairage qui passe du vert au rouge quand la température dépasse la valeur de 25°C définie par la variable $ColorTheshold.
Ça marche!
Et si on défini notre job comme le "startup job" du Yocto-I2C, on se retrouve avec un afficheur de température vraiment indépendant: on peut l'alimenter avec un simple chargeur USB et ça marchera quand même.
Ça fonctionne aussi avec un simple chargeur USB
Conclusion
Bien évidement, ce job est plus un exercice de style qu'un truc vraiment utile. Mais il montre que, malgré ses limitations, le système de jobs du Yocto-I2C permet des faire des choses assez évoluées. On aurait pu tricher et créer une nouvelle instruction permettant de transformer un nombre en une suite de codes ASCII, mais avec les différentes options pour tenir compte des formats d'affichage les plus courants, cela aura pris beaucoup de place dans le firmware du Yocto-I2C pour un usage plutôt spécifique. Enfin, si vous souhaitez essayer vous-même, vous pouvez télécharger le job ici.