Introduction
Nous allons maintenant ajouter une carte fille à la carte de TP. Cette carte fille est un afficheur sur une matrice de 8X8 LEDs RGB. Il est basé sur le circuit intégré DM163, qui permet de faire varier l'intensité de chaque couleur des LEDs de façon "très simple".
Principe du fonctionnement de la matrice de LED
La matrice de LED comportent 8 rangées de 8 colonnes de LED RVB (Rouge Vert Bleu - on dit aussi RGB en anglais). Chaque LED RGB est composée en fait de 3 LED mises dans un même chip : une rouge, une verte et une bleue.
Comme indiqué dans le schéma ci-dessous, les LED sont reliées entre elles ("multiplexées") de façon à réduire le nombre de broches de la matrice à 32 :
- Les anodes ("le côté +") des LED d'une même rangée sont reliées ensemble : 8 broches, reliées à VCC
- Les cathodes ("le côté -") des LED d'une même couleur et d'une même colonne sont reliées ensemble : 3*8 = 24 broches, reliées au contrôleur de LED DM163.

Pour allumer une certaine LED, il suffit de mettre la broche de sa ligne à VCC, et la broche de sa colonne à 0.
Mais le multiplexage pose un problème : il est impossible d'allumer en même temps deux LED situées sur des lignes et colonnes différentes. Par exemple, si on veut alllumer la led verte la plus en haut à gauche et la bleue en bas à droite, on obtiendra en fait ceci :

On va donc utiliser un processus de persistence rétinienne, et allumer les LED rangée par rangée. En cyclant suffisamment vite, on aura l'impression que toutes les rangées sont pilotées en même temps alors qu'en fait elles le seront qu'un huitième du temps.
Le contrôleur de LED
Le contrôleur, dont vous trouverez la documentation ci-dessous, permet de faire varier l'intensité lumineuse de 24 LEDs, en utilisant une modulation appelée PWM. Il est relié aux broches "colonnes" de la matrice de LED. Les broches "lignes" sont directement sur des GPIO sur processeur. Conséquence, pour allumer la LED en haut à gauche en rouge, on fera ceci :
- on mettra la GPIO correspondant à la ligne 0 à VCC, et celles aux autres lignes à GND.
- on programmera le DM163 pour qu'il fasse passer du courant sur la LED rouge de la colonne 0 (broche 28 de la matrice), et pas de courant sur les autres broches de colonne.
Comment contrôler le DM163 ?
Le DM163 est un double registre à décalage :
- le premier, appelé BANK0 qui stocke 6 bits par LED. Il contient donc 24*6 = 144 bits.
- le deuxième, appelé BANK1 qui stocke 8 bits par LED. Il contient donc 24*8 = 192 bits.
L'intensité passant dans chaque LED sera proportionnelle à (BANK0/64) * (BANK1/256).
Nous n'utiliserons que le BANK1, de façon à avoir 256 degrés d'intensité par LED, ce qui nous donne 16 millions de couleurs par point de la matrice.
Conséquence : pour pouvoir utiliser le BANK1, il faudra auparavant mettre tous les registres de BANK0 à 1 (si on les met à zéro, les LED seront éteintes quelle que soit ce qui est stocké dans BANK1).
Le protocole de communication avec le DM163 est simple. C'est un protocole série, où :
- on commence par sélectionner le registre à décalage qu'on veut mettre à jour à l'aide du signal
SB: 0 pour BANK0, 1 pour BANK1 - on envoie sur
SDAle bit de poids fort de la dernière LED (led23[7] si on met à jour le BANK1, led23[5] si on met à jour le BANK0), - puis on fait un pulse positif sur
SCK(front montant puis front descendant) - et on recommence en 2 jusqu'à ce que tous les bits aient été envoyés
- enfin, on fait un pulse négatif sur
LAT, ce qui transfère le contenu du registre à décalage dans le BANK choisi. Les sorties du DM163 sont alors mises à jour instantanément.

Branchement sur la carte IoT_Node
Le driver se branche naturellement sur la carte IoT_Node. Attention de ne pas vous tromper de sens et de ne le faire que si vous êtes hors-tension sinon vous cramez tout !
La matrice de LED se branche sur la carte driver, mais le sens est un peu difficile à repérer : la broche marquée 1 sur la matrice doit être branchée sur le connecteur blue 1 du driver.
Les broches du drivers, telles que documentées dans le user guide, sont branchées ainsi sur le processeur :
| Broche du driver | Broche du processeur |
|---|---|
SB |
PC5 |
LAT |
PC4 |
RST |
PC3 |
SCK |
PB1 |
SDA |
PA4 |
C0 |
PB2 |
C1 |
PA15 |
C2 |
PA2 |
C3 |
PA7 |
C4 |
PA6 |
C5 |
PA5 |
C6 |
PB0 |
C7 |
PA3 |
Contrôle basique
Initialisation des broches
Dans un fichier matrix.c, écrivez une fonction void matrix_init() qui :
- met en marche les horloges des ports A, B et C
- configure toutes les broches reliées au driver en mode GPIO Output et haute vitesse
- positionne ces sorties à une valeur initiale acceptable :
RST:0(reset le DM163)LAT:1SB:1SCKetSDA:0C0àC7:0(éteint toutes les lignes)
- attend au moins 100ms que le DM163 soit initialisé, puis passe
RSTà l'état haut.
Contrôle des broches
Écrivez les fonctions ou macros suivantes permettant de piloter indépendamment chaque broche :
RST(x)SB(x)LAT(x)SCK(x)SDA(x)ROW0(x) à ROW7(x)
Par exemple RST(0) mettra la broche RST à 0, LAT(1) mettra la broche LAT à 1, ROW6(1) mettra C6 à 1, etc.
Génération de pulse
- Écrivez, à l'aide de la macro
SCK, une macro (ou fonction)pulse_SCKqui effectue un pulse positif (état bas, attente, état haut, attente, état bas, attente) surSCKrespectant les timings attendus par le DM163. -
Écrivez, à l'aide de la macro
LAT, une macro (ou fonction)pulse_LATqui effectue un pulse négatif (état haut, attente, état bas, attente, état haut, attente) surLATrespectant les timings attendus par le DM163.
Si vous devez faire des pauses, faites pour l'instant des boucles d'attente active à l'aide de asm volatile ("nop").
Contrôle des lignes
- Écrivez, à l'aide des macros
ROW0àROW7, une fonctionvoid deactivate_rows()qui éteint toutes les lignes. - Écrivez, à l'aide des macros
ROW0àROW7, une fonctionvoid activate_row(int row)qui active la ligne dont le numéro est passé en argument.
Contrôle du DM163
- Écrivez une fonction
void send_byte(uint8_t val, int bank)qui, à l'aide des macrosSB,pulse_SCKetSDA, envoie 8 bits consécutifs au bank spécifié par le paramètrebank(0 ou 1) du DM163 (dans l'ordre attendu par celui-ci). - Définissez le type rgb_color comme une structure représentant la couleur d'une case de la matrice :
uint8_t r;
uint8_t g;
uint8_t b;
} rgb_color;
- Écrivez, à l'aide de
send_byteetactivate_row, une fonctionvoid mat_set_row(int row, const rgb_color *val)qui :- prend en argument un tableau
valde 8 pixels - à l'aide de
send_byteenvoie ces 8 pixels au BANK1 du DM163 dans le bon ordre (B7, G7, R7, B6, G6, R6, ..., B0, G0, R0) - puis à l'aide de
activate_rowetpulse_LATactive la rangée passée en paramètre et les sorties du DM163.
- prend en argument un tableau
Initialisation du BANK0
Écrivez une fonction void init_bank0() qui à l'aide de send_byte et pulse_LAT met tous les bits du BANK0 à 1.
Faites en sorte que cette fonction soit appelée par matrix_init.
Test basique
Écrivez une fonction void test_pixels() qui teste votre affichage, par exemple en allumant successivement chaque ligne avec un dégradé de bleu, puis de vert puis de rouge. Cette fonction devra être appelée depuis le main, de façon à ce que nous n'ayons qu'à compiler (en tapant make) puis à loader votre programme pour voir votre programme de test fonctionner.
Committez tout ça avec le tag TEST_MATRIX.
Test d'affichage d'une image statique
Récupérez le fichier image.raw. Ce fichier est un binaire contenant les valeur de chaque pixels stockés au format suivant :
- octet 0 : ligne 0, LED 0, rouge
- octet 1 : ligne 0, LED 0, vert
- octet 2 : ligne 0, LED 0, bleu
- octet 3 : ligne 0, LED 1, rouge
- octet 4 : ligne 0, LED 1, vert
- octet 5 : ligne 0, LED 1, bleu
- ...
- octet 189 : ligne 7, LED 7, rouge
- octet 190 : ligne 7, LED 7, vert
- octet 191 : ligne 7, LED 7, bleu
Faites en sorte que votre main affiche automatiquement cette image, en cyclant sur les lignes suffisament vite pour que l'oeil ait l'impression d'une image statique.
Vous devriez obtenir une image ressemblant à peu près à ceci (désolé pour la qualité de la photo) :

Committez le code avec le tag TEST_STATIC_IMAGE.
Conclusion
Nous savons maintenant piloter l'afficheur. On va voir comment faire pour afficher des images animées, mais pour cela il va falloir apprendre à :
- contrôler le temps de façon plus précise qu'avec des boucles d'attente active,
- pouvoir faire plusieurs tâches en parallèle : gérer les interruptions.
C'est le but des prochaines étapes.