Afficheur à LED

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.

(version large ici)

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 :

(version large ici)

 

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ù :

  1. 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
  2. on envoie sur SDA le 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),
  3. puis on fait un pulse positif sur SCK (front montant puis front descendant)
  4. et on recommence en 2 jusqu'à ce que tous les bits aient été envoyés
  5. 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 :

  1. met en marche les horloges des ports A, B et C
  2. configure toutes les broches reliées au driver en mode GPIO Output et haute vitesse
  3. positionne ces sorties à une valeur initiale acceptable : 
    • RST : 0 (reset le DM163)
    • LAT : 1
    • SB : 1
    • SCK et SDA : 0
    • C0 à C7 : 0 (éteint toutes les lignes)
  4. 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

  1. Écrivez, à l'aide de la macro SCK, une macro (ou fonction) pulse_SCK qui effectue un pulse positif (état bas, attente, état haut, attente, état bas, attente) sur SCK respectant les timings attendus par le DM163.
  2. Écrivez, à l'aide de la macro LAT, une macro (ou fonction) pulse_LAT qui effectue un pulse négatif (état haut, attente, état bas, attente, état haut, attente) sur LAT respectant 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

  1. Écrivez, à l'aide des macros ROW0 à ROW7, une fonction void deactivate_rows() qui éteint toutes les lignes.
  2. Écrivez, à l'aide des macros ROW0 à ROW7, une fonction void 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 macros SB, pulse_SCK et SDA, envoie 8 bits consécutifs au bank spécifié par le paramètre bank (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 :
typedef struct {
  uint8_t r;
  uint8_t g;
  uint8_t b;
} rgb_color;
  • Écrivez, à l'aide de send_byte et activate_row, une fonction void mat_set_row(int row, const rgb_color *val) qui :
    • prend en argument un tableau val de 8 pixels
    • à l'aide de send_byte envoie 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_row et pulse_LAT active la rangée passée en paramètre et les sorties du DM163.

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.