Matrices de LED RGB [Partie 1]

Matrices de LED RGB [Partie 1]

Les matrices de LED RGB, on en a tous vu un jour ou l’autre : elles composent les écrans géants dans les concerts, sur les plateaux TV ou dans les transports en commun sur les bornes d’information voyageur ou sur les girouettes des bus.

Détails techniques

Les différentes matrices de LED RGB se différencient par :

  • leur définition : généralement 32×16, 32×32, 64×32, 64×64…
  • leur résolution matérialisée par la distance entre les pixels : P5 pour un espacement de 5mm, P3 pour un espacement de 3mm. Ou trouve des P2.5, P3, P4, P5, P10…
  • leurs dimensions : 64×64 pixels en P3 donne 192x192mm
  • le type de LED utilisé : SMD2121, SMD3528…
  • le multiplexage : « scan line » 1/8, 1/16, 1/32 par exemple,  à un instant t seulement une partie des LED sont allumées.
Matrice P3 64×64

Dans la doc on trouve d’autres informations intéressantes :

  • La tension d’alimentation : 5V
  • La consommation max d’une matrice (elle sera différente si on affiche tout en blanc ou juste du texte par exemple) : 4A pour celle utilisée ici.

Scanline

Ce qui va se passer, c’est qu’on va s’occuper de seulement 2 lignes à chaque fois. Sur un panneau avec un scanline de 1/32, on allumera les ligne 0 et la ligne 32, puis la ligne 1 et la ligne 33 et ainsi de suite jusqu’à la ligne 31 et 63… après ça et bien on recommence. C’est la persistance rétinienne qui va faire que l’on croit voir une image entière.

 

Les LED sont allumées ou éteintes, il n’y a pas d’entre-deux, donc le panneau ne s’occupe pas de la gestion des couleurs. C’est le contrôleur qui va devoir gérer le PWM afin de générer les différentes couleurs.

Et il va falloir aller très vite… Si on veut une profondeur de couleurs de 24bits, soit 8 par canal (R, G, B) avec une fréquence de 100fps, il va falloir rafraîchir l’intégralité de la matrice 256×100=25600 fois par seconde !

La connectique

À l’arrière du panneau on trouve :

  • un connecteur d’entrée « HUB75 » (JIN)
  • un connecteur de sortie (JOUT)
  • un connecteur d’alimentation (avec VCC et GND… deux fois).

Pourquoi un connecteur de sortie ? Car ces matrices peuvent êtres chaînées en série pour faire un afficheur plus grand. La sortie de la matrice N est connectée à l’entrée de la matrice N+1. Les flèches indiquent le sens de propagation des données.

Arrière de la matrice P3 64×64

 

Pin Nom Description
1 R1 Rouge ligne 1 (allumé ou éteint)
2 G1 Vert ligne 1
3 B1 Bleu ligne 1
4 GND Masse
5 R2 Rouge ligne 2
6 G2 Vert ligne 2
7 B2 Bleu ligne 2
8 E Adresse de la ligne (MSB) (scanline 1/32 seulement)
9 A Adresse de la ligne (LSB)
10 B Adresse de la ligne
11 C Adresse de la ligne
12 D Adresse de la ligne (MSB) (scanline 1/16 et 1/32 seulement)
13 CLK Horloge
14 LATCH Transfert le buffer de données vers le buffer d’affichage.
15 OE Allume les LED de la ligne sélectionnée (actif à l’état bas)
16 GND Masse

 

Sur ma matrice, il y a une erreur de sérigraphie, il y a GND à la place de D.

Zoom sur le connecteur « HUB75 » de sortie

 

Le principe de fonctionnement

Maintenant qu’on connait la fonction de chaque broche et le principe du scanline, ça va se passer comme ça :

Lors de l’initialisation du programme :

  • OE est à l’état haut pour désactiver l’affichage : la matrice est éteinte.
  • LAT est à l’état bas : le transfert du registre à décalage dans le buffer d’affichage est bloqué.

Ensuite on boucle pour rafraîchir la matrice, pendant qu’on affiche la ligne N (et N+32 en scanline 1/32) on transfert les données de la ligne N+1 (et N+1+32) :

  1. On sélectionne les lignes à afficher avec les broches A à E pour du scanline 1/32, ou de A à D pour du 1/16 (la table de vérité qui suit est pour du scanline 1/32).
  2. LAT est à l’état bas : le transfert du registre à décalage dans le buffer d’affichage est bloqué.
  3. OE est à l’état bas pour activer l’affichage : les 2 lignes sélectionnées s’allument.
  4. On envoie les données de la ligne suivante dans le registre à décalage. On applique les valeurs RGB du dernier pixel de la ligne 1 suivante sur R1, G1 et B1 et celles du dernier pixel de la ligne 2 suivante sur R2, G2 et B2. Pourquoi du dernier pixel ? On a à faire ici à un registre à décalage, la première donnée qui entre va aller « tout au fond » au fur et à mesure que les nouvelles données qui entrent vont venir les pousser à chaque tick d’horloge.
  5. On fait avancer l’horloge (état haut puis retour à l’état bas).
  6. On répète les étapes 4 et 5 jusqu’au premier pixel des deux lignes actives… 64 fois pour la matrice 64×64 de l’exemple.
  7. Tout le registre est rempli…
  8. On met OE à l’état haut pour éteindre la matrice.
  9. On met LAT à l’état haut pour transférer le registre à décalage dans le buffer d’affichage. À la prochaine itération c’est cette ligne qui va s’afficher.
  10. On recommence à l’étape 1 avec le couple de lignes suivant.

Si on a chaîné deux matrices pour former un écran de 128×64, on va répéter les étapes 4 et 5 128 fois car la première matrice va continuer de décaler les données dans la seconde… on enverra en premier le dernier pixel de la 2ème matrice.

En bref, on va convertir 6 signaux de données séries (R1, G1, B1, R2, G2, B2) en autant de données parallèles qu’il y a de LED sur les deux lignes à afficher, c’est un peu le principe élémentaire du registre à décalage.

 

E D C B A Ligne 1 Ligne 2
0 0 0 0 0 0 32
0 0 0 0 1 1 33
0 0 0 1 0 2 34
0 0 0 1 1 3 35
0 0 1 0 0 4 36
0 0 1 0 1 5 37
0 0 1 1 0 6 38
0 0 1 1 1 7 39
0 1 0 0 0 8 40
0 1 0 0 1 9 41
0 1 0 1 0 10 42
0 1 0 1 1 11 43
0 1 1 0 0 12 44
0 1 1 0 1 13 45
0 1 1 1 0 14 46
0 1 1 1 1 15 47
1 0 0 0 0 16 48
1 0 0 0 1 17 49
1 0 0 1 0 18 50
1 0 0 1 1 19 51
1 0 1 0 0 20 52
1 0 1 0 1 21 53
1 0 1 1 0 22 54
1 0 1 1 1 23 55
1 1 0 0 0 24 56
1 1 0 0 1 25 57
1 1 0 1 0 26 58
1 1 0 1 1 27 59
1 1 1 0 0 28 60
1 1 1 0 1 29 61
1 1 1 1 0 30 62
1 1 1 1 1 31 63

 

Connexion à l’Arduino Nano

Le gros connecteur d’alimentation de la matrice doit être branché sur une alimentation stabilisée de 5V pouvant fournir l’intensité nécessaire au fonctionnement de la matrice. Ne surtout pas l’alimenter via le 5V de l’arduino, ça risquerait de le griller…

Pin Matrice Nom Matrice Pin Arduino
1 R1 8
2 G1 9
3 B1 10
4 GND GND
5 R2 11
6 G2 12
7 B2 13
8 E A4
9 A A0
10 B A1
11 C A2
12 D A3
13 CLK 5
14 LATCH 6
15 OE 7
16 GND GND

 

Programme de test

bus1_bits est une image XBM à afficher sur la matrice. Dans ce test on affiche uniquement une image monochrome en utilisant le canal rouge. On se rend vite compte que l’arduino nano, bien qu’assez rapide pour afficher l’image sur une seule matrice manque cruellement de RAM avec ses 2Ko pour pouvoir afficher des images avec plus de couleurs.

Le programme utilise la lib Fast GPIO : https://github.com/pololu/fastgpio-arduino

Le Pin 4 qui change d’état sert à mesurer la fréquence de rafraîchissement avec l’oscilloscope. Pas question de faire de Serial.print ça prendrait beaucoup trop de temps. On a une fréquence de 4.73kHz, ce qui signifie qu’on affiche une ligne différente 4730 fois par seconde.

#include <FastGPIO.h>
#include <stdint.h>

//Pin declaration
static const uint8_t PIN_R1 = 8; // R1
static const uint8_t PIN_G1 = 9; // G1
static const uint8_t PIN_B1 = 10; // B1

static const uint8_t PIN_R2 = 11; // R2
static const uint8_t PIN_G2 = 12; // G2
static const uint8_t PIN_B2 = 13; // B2

static const uint8_t PIN_A = A0; // A
static const uint8_t PIN_B = A1; // B
static const uint8_t PIN_C = A2; // C
static const uint8_t PIN_D = A3; // D
static const uint8_t PIN_E = A4; // D

static const uint8_t PIN_CLK = 5; // CLK
static const uint8_t PIN_LAT = 6; // LAT
static const uint8_t PIN_OE = 7; // OE

static uint8_t displayLineIndex = 31;
static uint8_t dataLineIndex = 0;

static unsigned char bus1_bits[] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x81, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xff, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x1c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00,
0x00, 0x38, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x1e,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0x81, 0x03,
0x00, 0x00, 0x00, 0x00, 0x00, 0xe0, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0xff, 0x0c,
0x7c, 0x74, 0x3c, 0x78, 0x1e, 0xf0, 0xff, 0x0f, 0xc0, 0xcc, 0x60, 0x44,
0x26, 0xf0, 0xff, 0x0f, 0x80, 0x84, 0x40, 0x42, 0x22, 0x30, 0x00, 0x0e,
0xfc, 0x84, 0x40, 0x42, 0x1e, 0x30, 0x00, 0x0c, 0x84, 0x84, 0x40, 0x42,
0x02, 0x30, 0x00, 0x0c, 0xcc, 0x44, 0x20, 0x66, 0x12, 0x38, 0x00, 0x1c,
0x78, 0x3c, 0x3c, 0x5c, 0x0c, 0x38, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x40,
0x00, 0x38, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x40, 0x00, 0x38, 0x00, 0x1e,
0x00, 0x04, 0x00, 0x40, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x30, 0x00, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0xc0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00,
0x00, 0xc0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x0f,
0xf0, 0x10, 0x78, 0x7e, 0x1f, 0xc0, 0xff, 0x0f, 0x18, 0x10, 0x04, 0x30,
0x04, 0xc0, 0x01, 0x0e, 0x08, 0xfc, 0x05, 0x18, 0x04, 0xc0, 0x01, 0x0e,
0x08, 0x10, 0x05, 0x0c, 0x04, 0xc0, 0x01, 0x0e, 0xf0, 0x90, 0x38, 0x04,
0x04, 0xc0, 0x01, 0x0e, 0x80, 0xd0, 0x04, 0x02, 0x04, 0xc0, 0x01, 0x0e,
0x80, 0x50, 0x04, 0x02, 0x14, 0xc0, 0x01, 0x0e, 0x80, 0x30, 0x04, 0x42,
0x0c, 0xc0, 0x01, 0x0e, 0xf8, 0x30, 0x78, 0x3c, 0x04, 0xc0, 0xff, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x01, 0x0e, 0x00, 0x00, 0x00, 0x00,
0x00, 0xc0, 0xff, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x0f,
0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } ;


void setup() {
// Initialisation
FastGPIO::Pin<PIN_R1>::setOutput(LOW);
FastGPIO::Pin<PIN_G1>::setOutput(LOW);
FastGPIO::Pin<PIN_B1>::setOutput(LOW);

FastGPIO::Pin<PIN_R2>::setOutput(LOW);
FastGPIO::Pin<PIN_G2>::setOutput(LOW);
FastGPIO::Pin<PIN_B2>::setOutput(LOW);

FastGPIO::Pin<PIN_A>::setOutput(LOW);
FastGPIO::Pin<PIN_B>::setOutput(LOW);
FastGPIO::Pin<PIN_C>::setOutput(LOW);
FastGPIO::Pin<PIN_D>::setOutput(LOW);
FastGPIO::Pin<PIN_E>::setOutput(LOW);

FastGPIO::Pin<PIN_CLK>::setOutput(LOW);
FastGPIO::Pin<PIN_LAT>::setOutput(LOW);
FastGPIO::Pin<PIN_OE>::setOutput(HIGH);

//Debug
FastGPIO::Pin<4>::setOutput(LOW);

}

void loop() {
refreshDisplay();
}

void refreshDisplay() {

//Set the address of the line to display
(displayLineIndex & 1) ? FastGPIO::Pin<PIN_A>::setOutputValueHigh() : FastGPIO::Pin<PIN_A>::setOutputValueLow();
(displayLineIndex & 2) ? FastGPIO::Pin<PIN_B>::setOutputValueHigh() : FastGPIO::Pin<PIN_B>::setOutputValueLow();
(displayLineIndex & 4) ? FastGPIO::Pin<PIN_C>::setOutputValueHigh() : FastGPIO::Pin<PIN_C>::setOutputValueLow();
(displayLineIndex & 8) ? FastGPIO::Pin<PIN_D>::setOutputValueHigh() : FastGPIO::Pin<PIN_D>::setOutputValueLow();
(displayLineIndex & 16) ? FastGPIO::Pin<PIN_E>::setOutputValueHigh() : FastGPIO::Pin<PIN_E>::setOutputValueLow();

//Lock register
FastGPIO::Pin<PIN_LAT>::setOutputValueLow();

//Activate display
FastGPIO::Pin<PIN_OE>::setOutputValueLow();

//Oscillo debug
FastGPIO::Pin<4>::setOutputValueHigh();

//We can send data for the next line to display...
for (uint8_t iByte = 0; iByte < 8; ++iByte) {
uint8_t byte1 = bus1_bits[dataLineIndex*8 + iByte];
uint8_t byte2 = bus1_bits[(dataLineIndex+32)*8 + iByte];

for(uint8_t iBit = 0; iBit < 8; ++iBit) {
uint8_t mask = 1 << iBit;

(byte1 & mask) ? FastGPIO::Pin<PIN_R1>::setOutputValueHigh() : FastGPIO::Pin<PIN_R1>::setOutputValueLow();
FastGPIO::Pin<PIN_G1>::setOutputValueLow();
FastGPIO::Pin<PIN_B1>::setOutputValueLow();

(byte2 & mask) ? FastGPIO::Pin<PIN_R2>::setOutputValueHigh() : FastGPIO::Pin<PIN_R2>::setOutputValueLow();
FastGPIO::Pin<PIN_G2>::setOutputValueLow();
FastGPIO::Pin<PIN_B2>::setOutputValueLow();

FastGPIO::Pin<PIN_CLK>::setOutputValueHigh();
FastGPIO::Pin<PIN_CLK>::setOutputValueLow();
}
}

//Desactivate display
FastGPIO::Pin<PIN_OE>::setOutputValueHigh();

//Oscillo debug
FastGPIO::Pin<4>::setOutputValueLow();

//Latch
FastGPIO::Pin<PIN_LAT>::setOutputValueHigh();


if (++displayLineIndex == 32) {
displayLineIndex = 0;
}

if (++dataLineIndex == 32) {
dataLineIndex = 0;
}
}

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *