Electronique et Code de bas niveau du cube led 8x8x8

CauS4TNW0AEblN4.jpg_large.jpeg

::TOC::

Mon cube led est assemblé, la carte électronique finie (et déboguée) Let's code!

Plusieurs composants ne sont pas simples à faire fonctionner avec l'arduino et à force de chercher des exemples de programmation du cube led (avec le même type de démultiplexage), j'en suis arrivé à la conclusion suivante:

Les auteurs des blogs et tutoriels ont tous utilisé le même code de bas niveau[1]. Que l'auteur du code d'origine lève la main!.

J'ai aussi remarqué que ce code est "imbuvable" et peu malléable[2],

La manipulation des ports arduino et les opérations sur les bits, sont des mondes qui me sont étrangers et pénibles à appréhender.

Par exemple, cette ligne dans le code source:

PORTB = (PORTB & 0xF8) | (0x07 & (i+1));

Heu là,... i est compris entre [0 et 7] vu les lignes précédentes. Il modifie l'état (HIGH / LOW) de certaines pins entre D8 et D13 puisqu'on parle de "PORTB". Mais lesquelles précisément et avec quels états? Je n'en sais rien. L'explication est donnée en commentaire par Khajidu

Je ne veux pas faire un bête copier collé de code que je ne comprends pas.

Dans cet article on va expliquer le fonctionnement de certains composants électroniques (car j'ai eu du mal à trouver des explications simples), puis on va recoder plus simplement[3] les fonctions de base du cube led.

Résumé du fonctionnement du cube led

Il s'agit du cube led décrit par Dylan Collaud sur son blog. En suivant les liens dans son article, vous trouverez les plans pour le circuit etc... Il s'est basé sur celui proposé par le site instructables , mais avec un arduino.

8x8x8 leds. Les cathodes (+) des leds sont reliées entre elles par colonnes. Il faut gérer 64 colonnes. Les anodes (-) sont mises en commun par étage de led[4]. Il faut gérer 8 étages.

Ce qui fait un total de 72 sorties à gérer. Il va falloir démultiplexer.

Le circuit de démultiplexage comprend:

  • 8 flip flop octal 74hc574 (non ce ne sont pas des registres à décalages [5]),
  • un IC SN74HC138N qui sert à contrôler la pin clock de chaque flip flop,
  • 8 transistors.

L'impossibilité de faire certaines combinaisons de leds

On peut allumer chaque led individuellement sans problème.

Prenons mon cube 4x4x4[6], ce sera plus simple pour les gribouillages sur les photos.

IMG_20160218_180312.jpg

Pour allumer la 3ème led vers la droite, 2ème rangée en allant vers l'arrière, 2ème étage:

  • Faire passer le courant dans la 7ème colonne
  • activer l'interrupteur le transistor qui permet de mettre à la masse le 2ème étage de leds

cub1.png

Pour allumer aussi la 1ère led e, 1ère rangée en allant vers l'arrière, 3ème étage:

  • Faire passer le courant dans la 1ère colonne
  • activer l'interrupteur le transistor qui permet de mettre à la masse le 3ème étage de leds.

Résultat:

cub2.png

Zut, deux leds supplémentaires se sont allumées.

La solution pour pallier ce problème est de jouer sur la persistance rétinienne. On affiche le premier étage de led, on l’éteint, on allume le deuxième etc.... Si c'est fait assez rapidement l’œil n'y verra que du feu.

Je vous ai expliqué la contrainte de démultiplexage, de la nécessité de compliquer le code en gérant étage par étage, et de devoir aller très vite pour l'effet de persistance rétinienne.

Entrons donc dans le vif du sujet, le fonctionnement du bouzin, avec ces contraintes que j'ai eu du mal à maîtriser.



Composants

Bascule octale flip flop 8 bits 74hc574

74hc574.jpg

Ce composant va mémoriser l'état des signaux en entrée (D0 à D7) et va les reproduire sur ses sorties (Q0 à Q7), jusqu' ce qu'on lui demande d'enregistrer à nouveau les signaux en entrée.

La doc est par là http://www.nxp.com/documents/data_sheet/74HC_HCT574.pdf

Branchements

74HC574E-pinning.png

74HC574E-pin-description.png

  • OE (output enable)-> vers une sortie digitale de l'arduino. dans mon cas D11. (Toutes les pins OE des 8 flip flop sont reliées à D11)
  • D0 ~ D7 -> Ce sont les entrées, Reliées aux sorties de l'arduino A0,A1,A2,A3,A4,A5,D12,D13, toutes en mode "output"
  • CP (clock pin) -> Reliées aux sorties de l'IC. Évidemment Vous reliez dans l'ordre: la sortie 0 de l'IC sur la clock pin du 74HC574 qui gère la première ligne de led; la sortie 1 sur le 74HC574 qui gère la deuxième ligne de leds etc...
  • Q0 ~ Q7 -> ce sont les sorties, vers une résistance puis la colonne de leds.
  • Vcc vers le 5v de l'arduino et GND vers le GND. Ça parait tellement évident que sur certains schémas électroniques, ils omettent de le préciser. Mais, si si, il faut.

Utilisation

74HC574E-table.png

Pour "enregistrer" un nouvel état des sorties:

  • Mettre les entrées D0 à D7 dans le mode qu'on souhaite, celui que le flip flop doit mémoriser.
  • Mettre la pin OE à disable, c'est à dire: HIGH (logique inversée)
  • Faire passer la clock pin de LOW vers HIGH (c'est à ce moment là, lors de la transition de l'état LOW vers HIGH que le flip flop enregistre)
  • Quand on aura fini d'enregistrer le(s) flip flop Remettre OE à enable (LOW) pour qu'il(s) génèrent les signaux demandés.

En code arduino, ça donne:

digitalWrite(pinOE, HIGH);//Output Enabled off
//lines
PORTB=ICtable[0];//l'IC va mettre la clock pin du premier FlipFlop à LOW

//ecrire le bus,(là on va allumer une led sur 2)
digitalWrite(A0,LOW);
digitalWrite(A1,HIGH);
digitalWrite(A2,LOW);
digitalWrite(A3,HIGH);
digitalWrite(A4,LOW);
digitalWrite(A5,HIGH);
digitalWrite(12,LOW);
digitalWrite(13,HIGH);

PORTB=ICtable[1];//On passe au flip flop suivant, et cette manip va faire passer la clock pin
		  //  du flip flop 0 de l'état LOW à HIGH. C'est à ce moment qu'il va enregistrer.

//OE ON
digitalWrite(pinOE, LOW);

IC d'adressage 74HCT138N

SN74HC13N.jpg

Il sert à définir lequel des 8 flip flop est en cours d'écriture dans le cas du cube led.

Voici un premier extrait de sa documentation qui explique comment le raccorder.

Branchement

SN74HC13N-fiche.jpg

  • A0 A1 et A2 c'est la data, -> vers des pins digitales de l'arduino D8, D9 et D10, c'est une bonne idée.
  • E1, E2 (actifs si LOW) et GND vers la masse
  • E3 et VCC vers le 5V de l'arduino
  • Y0 à Y7 vers les PINS Clock des 74hc574

Utilisation

En fonction des états (HIGH / LOW) que vous enverrez dans les entrées A0~A2, une (et une seule) des sorties Y0~Y7 sera à l'état "LOW". Voici le tableau, fait à partir de la doc :

InputOutputput
A0 A1 A2 Y0 Y1 Y2 Y3 Y4 Y5 Y6 Y7
LOW LOW LOW LOW HIGH HIGH HIGH HIGH HIGH HIGH HIGH
HIGH LOW LOW HIGH LOW HIGH HIGH HIGH HIGH HIGH HIGH
LOW HIGH LOW HIGH HIGH LOW HIGH HIGH HIGH HIGH HIGH
HIGH HIGH LOW HIGH HIGH HIGH LOW HIGH HIGH HIGH HIGH
LOW LOW HIGH HIGH HIGH HIGH HIGH LOW HIGH HIGH HIGH
HIGH LOW HIGH HIGH HIGH HIGH HIGH HIGH LOW HIGH HIGH
LOW HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW HIGH
HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH HIGH LOW

En une function arduino ça donne:

const int ICPins[]={8,9,10}; 

const boolean ICtable[8][3]=
{
  {false,   false,	false},
  {true,    false,	false},
  {false,   true, 	false},
  {true,    true,       false},
  {false,   false,	true},
  {true,    false,	true},
  {false,   true,	true},
  {true,    true,  	true}
}

void selectIC(int ICPinLow){
    for (int i=0; i<3; i++){
        digitalWrite(ICPins[i], ICtable[ICPinLow][i]); // Arduino shows  true as HIGH and false as LOW 
    }
}

Sauf que dans le cas du cube led[7], le délai minime de traitement des digitalWrite par l'arduino va poser problème. J'ai mis plusieurs d'heures avant de comprendre ce problème.

Je vous explique: Si l'IC a la valeur 1. D8 true D9 false d10 false Pour passer 2. {false, true, false}, les commandes sont les suivantes

digitalWrite(D8, LOW); // -> LOW, LOW, LOW l'IC prend la valeur 0
digitalWrite(D9, HIGH); // -> LOW, HIGH, LOW l'IC prend la valeur 2
digitalWrite(D10, LOW); //-> D10 était déja LOW l'IC reste à la valeur 2

Pour passer de la valeur 1 à 2 sur l'IC, il va y avoir une valeur intermédiaire de 0. Et lorsque la pin clock de la bascule flip flop n°0 va enregistrer un passage de LOW vers HIGH, elle va enregistrer la configuration qui était destiné à la bascule 2. Il faut donc trouver un moyen de changer l'état de plusieurs pins simultanément.

La seule technique est la manipulation de ports. Le code précédent est remplacé par:

const byte ICtable[9]={
 B00000000,
 B00000001,
 B00000010,
 B00000011,
 B00000100,
 B00000101,
 B00000110,
 B00000111,
 B00000000 //une façon de boucler sur 0
};

//Pour choisir la sortie du 74HC13N faire:
PORTB=ICtable[ligne];
//avec "ligne" étant le numéro de la pin de l'IC qui doit être LOW

Voila, pas le choix de faire un peu d'écriture dans les registres de l'ATMEGA, ça m'embête car ça rend le code moins facile à appréhender, et un peu plus rigide. Ah j'oubliais, l'explication:

PORTB=B00000110;
       ||||||||
       |||||||+-> D8 LOW
       ||||||+->D9 HIGH
       |||||+->D10 HIGH
       ||||+->D11 LOW mais on s'en fout, c'est la pin output enable, 
       ||||                   elle doit etre low au moment où enregistre une valeur
       ||||                   sur le flipflop
       ||||
       |||+-> D12 LOW (idem, OSEF, c'est un étage de led, doit etre off à ce moment)
       ||+-> D13 LOW idem
       ++->bits inutiles

Transistors

NPN222.jpg

Non, je plaisantais avec ce titre, je ne vais pas vous expliquer le fonctionnement des transistors quand même... Si?

Transistor_NPN_symbol.png

Je m'en sers comme d'un télerupteur. Envoyez du courant dans la base, le circuit est fermé, sinon il est ouvert.

Le code

Comme je l'écrivais en introduction, l'objectif était de simplifier le code qu'on peut trouver partout sur le net. J'ai donc utilisé au minimum le "port manipulation". Et j'ai supprimé l'interruption arduino pour afficher le cube.

Vu que le code du cube c'est dans le style en pseudo code:

Led[1] -> allume;
Led[2] -> éteind;
delay(200);
Led[2] -> allume;
delay(87);
etc...

on change l'état des leds, on applique un délai et ainsi de suite,

Autant remplacer l'instruction delay par une instruction qui affiche le cube pendant une durée donnée. De cette manière, l'arduino n'a plus à compter son nombre de cycles pour gérer l'interruption (optimisation)

/*
* Low level code for cube led 8x8x8
* By Gnieark https://blog-du-grouik.tinad.fr February 2016
* GNU GPL V2
*/

#define pinOE 11
boolean cube[8][8][8];

const int layersPins[]={A0,A1,A2,A3,A4,A5,12,13};
const int busPins[]={0,1,2,3,4,5,6,7};

// IC Pins are "hard coded". here just for info,
const int ICPins[]={8,9,10}; 
//  If you change IC pin configuration, 
//  You'll have to modify const ICtable[9]

//see SN74HC138 specs 
//http://www.ti.com/lit/ds/symlink/sn54hc138.pdf page 2
const byte ICtable[9]={
 B00000000,
 B00000001,
 B00000010,
 B00000011,
 B00000100,
 B00000101,
 B00000110,
 B00000111,
 B00000000 //une façon de boucler sur 0
};

void writeCube(int tdelay){
    static int currentLayer=0;
    unsigned long startedAt = millis();
    while(millis() - startedAt < tdelay){ 

        delay(1);// wait a bit on previous layer
        digitalWrite(layersPins[currentLayer],LOW);//turn off current Layer

        //change currentLayer
        if(currentLayer == 8)
            currentLayer=0;
        else
            currentLayer++;  

        digitalWrite(pinOE, HIGH);//Output Enabled off
        //lines
        PORTB=ICtable[0];//set IC
        for(int ligne=0;ligne < 8; ligne++){
            //write BUS for this leds'line
            for (int led=0;led <8;led++){
                digitalWrite(busPins[led], cube[currentLayer][ligne][led]); 
            }
            PORTB=ICtable[ligne +1]; //74HC574 is writen when clock pin goes LOW to HIGH. SO go to the next value for IC.
        }
        //OE ON
        digitalWrite(pinOE, LOW);
        //layer on
        digitalWrite(layersPins[currentLayer],HIGH);
    }
}
void fillCube(){
 for (int i=0;i<8;i++){
  for(int j=0;j<8;j++){
   for(int k=0;k<8;k++){
       cube[i][j][k]=true;
   }
  }
 }
}
void lowCube(){
 for (int i=0;i<8;i++){
  for(int j=0;j<8;j++){
   for(int k=0;k<8;k++){
       cube[i][j][k]=false;
   }
  }
 }
}

void setup(){
    for(int pin=0; pin<8; pin++){
        pinMode(layersPins[pin], OUTPUT);
        pinMode(busPins[pin], OUTPUT);
    }
    for(int pin=0;pin<3;pin++){
     pinMode(ICPins[pin],OUTPUT);   
    }
    pinMode(pinOE, OUTPUT);
    digitalWrite(pinOE, LOW);
    lowCube();  
}

void loop(){    
 for (int i=0;i<8;i++){
  for(int j=0;j<8;j++){
   for(int k=0;k<8;k++){
       cube[i][j][k]=HIGH;
       writeCube(100);
       cube[i][j][k]=LOW;
   }
  }
 }
}

Je mettrai les évolutions de mon code sur ce dépot github. https://github.com/gnieark/8x8x8

Améliorations prévues du cube led.

J'ai deux pistes incompatibles.

J'utilise un arduino nano, et au final il me reste les pins A6 et A7 libres (spécifiques au nano).

  • Je peux les utiliser pour ajouter deux manettes et développer une sorte de snake avec le cube led.
  • Je peux aussi les utiliser pour récupérer les pins D0 et D1 qui actuellement utilisées bloquent l'utilisation du Serial et empêchent donc de piloter le cube depuis le PC.

Notes

[1] Par "bas niveau" il faut comprendre les fonctions du programme qui servent à définir quelles leds sont allumées. Ce sont les fonctions qui sont entre l'arduino et le programme qui génère les animations avec les leds quoi.

[2] Si vous vous êtes planté sur un mappage de pins et que vous souhaitez corriger ça dans le code et pas à coup de pompe à dessouder; bon courage!

[3] C'est prétentieux... disons de façon plus accessible pour un développeur amateur.

[4] J'ai une chance sur deux d'avoir inversé "cathode" et "anode"

[5] La temps d'écriture sur les registres à décalages pose peut être problème sur un cube led.

[6] Il m'avait servi pour tester la méthode avant d'attaquer les 512 leds. PS, en 4x4x4, un arduino mega a assez de sorties pour tout gérer sans avoir à démultiplexer.

[7] Dans un autre programme, cette function peut fonctionner sans poser de soucis

Commentaires

1. Le lundi, février 22 2016, 19:38 par Khajidu

En ce qui concerne cette ligne, qui est effectivement complexe à comprendre :

PORTB = (PORTB & 0xF8) | (0x07 & (i+1));

Ca ressemble à une opération bit à bit sur les chiffres en binaire des nombres en question.

PORTB & 0xF8

est une opération "et" entre le nombre PORTB et F8 (qui est l'écriture hexadécimale de 248 équivalente à l'écriture binaire 11111000). En gros, elle met les 3 derniers chiffres (et tous les chiffres avant les 8 derniers) de PORTB à 0 et on obtient le plus grand multiple de 8 en dessous, modulo 256.

 

0x07 & (i+1)

est également une opération "et". Elle consiste en fait à ne garder que les 3 derniers chiffres de i+1 (7 en hexadécimal (et en décimal, donc) s'écrit 00000111) et à remettre les autres à 0, ce qui nous donne i+1 modulo 8.

La ligne fait une opération "ou" entre les deux nombres précédemment décrits, et nous donne pour finir un nombre de 8 chiffres en binaire, les 5 premiers étant ceux correspondants de PORTB, et les 3 derniers étant ceux de i+1.

2. Le mercredi, février 24 2016, 09:42 par Gnieark

Merci pour cette impressionnante réponse!

My Friends, You Bow To No One.

 

TblBTz0.gif

TblBTz0.gif, fév. 2016

 

Page top