"Cube" 3D 3x3x7 à LED sur Arduino - partie 3 : Le code

Comme prévu, voici la troisième et dernière partie, le code Arduino de la guirlande 3D. A noter qu'il est possible de télécharger l'intégralité du code en fin d'article, avec d'avantage de commentaires pour une meilleure compréhension.

Donc, pour démarrer, la déclaration des constantes et variables :

// CONSTANTES
const int fps = 20;
const int milli_fps = 1000 / fps; // durée d'une frame en milliseconde
const int etages = 7;
const int colonnes = 9;
const int cote = sqrt(colonnes);
const int persiste = 2; //  délai d'affichage en millisecondes
const int delai_defaut = 1; // calcul la frame de l'animation toutes les x frames (à toutes les frames par défaut)
const char colonne[] = {5, 6, 10, 4, 8, 7, 9, 2, 3}; // défini les 9 PINS colonnes (+)
const char etage[] = {A1, A2, A3, A5, A4, 0, 1}; // défini les 7 PINS étages (-)
const int serpent_tour[8] = {0,1,2,5,8,7,6,3};
// VARIABLES
unsigned long last_millis = millis(); // sert à définir à quelle frame nous sommes
unsigned long programme_millis = millis(); // gère le temps pour permettre de changer d'animation
int programme_count = 0; // numéro en cours du programme
int programme_delai = 10; // durée en seconde d'une animation
int frame = 0;
int niveau = 0;
int led = 0;
bool grille[etages][colonnes] = { {0,0,0, 0,0,0, 0,0,0} };
// VARIABLES EFFETS (correspond au variables pouvant être utilisées par les différentes fonctions)
int var_init = 0; // initialisation de l'effet
int var_tmp = 0;
int var_rnd = 0;
int var_etage = 0;
int var_1 = 0;
int var_2 = 0;
int var_3 = 0;
int var_y1 = 0;
int var_y2 = 0;
int var_y3 = 0;
int var_colonne = 0;
int var_frame = 0;
int var_delai = 1;
int var_delai_count = 0;
int var_array[5];
int var_array8[8];
int var_array9[9];
int var_colonnes[cote];
int var_etages[cote];
int var_direction;


Les 3 fonctions principales qui seront utilisées pour modifier l'état des LED :

// ------------------------------------- etage (traite l'état des étages)
void etage_maj (int num = -1, bool etat = 0) { // étage à dés/activer (defaut rien), etat (defaut désactive)
    if (num > -1) // s'il y a un étage à activer
        digitalWrite(etage[num], etat == 1 ? LOW : HIGH); // dés/active l'étage
    else { // sinon
        for (int e = 0; e < etages; e++) { // désactive tous les étages
            digitalWrite(etage[e], HIGH);
            }
        }
    };
// ------------------------------------- de même pour les colonne
void colonne_maj (int num = -1, bool etat = 0) {
    if (num > -1)
            digitalWrite(colonne[num], etat == 1 ? HIGH : LOW); // inverse
    else {
        for (int c = 0; c < colonnes; c++) {
            digitalWrite(colonne[c], LOW);
            }
        }
    };
// ------------------------------------- active les leds pour afficher la frame, traitement par ETAGE
void affiche_frame () {
for (int e = 0; e < etages; e++) { // sur 7 étages (0-6)
    etage_maj(e, 1); // allumer l'étage e
    for (int c = 0; c < colonnes; c++) { // pour les 9 colonnes
        if (grille[e][c] == 1) { // si l'une est à activer
            colonne_maj(c, 1); // allume la colonne c
            }
        }
        delay(persiste); // pause persistance rétinienne
        etage_maj(e); // eteindre l'étage e
        for (int c = 0; c < colonnes; c++) { // désactive toutes les colonnes
                colonne_maj(c);
            }
        }
    };


Le Setup qui se contente d'initialiser et désactiver l'état de toutes les LED :

void setup () {
    for (int e = 0; e < etages; e++) {
        pinMode(etage[e], OUTPUT); // initialise chaque étage (-) 0 à 6
        etage_maj(e); // désactive
        }
    for (int c = 0; c < colonnes; c++) {
        pinMode(colonne[c], INPUT); // initialise chaque colonne (+) 0 à 8
        colonne_maj(c); // désactive
        }
    }


La boucle qui va activer telle ou telle fonction. Il s'agit en gros de la programmation des différentes animations :

void loop () {
    if (millis() - last_millis >= milli_fps) {
        if (millis() - programme_millis >= programme_delai * 1000) {
            programme_millis = millis();
            programme_count++;
            var_init = 0; // réinitialise la variable pour indiquer à la fonction si une nouvelle fonction commence ou si une fonction continue d'être traitée
            }
        switch(programme_count) { // programmation à moduler en fonction des envies
            case 0:
                programme_count = 7;
            break;
            case 7:
                programme_delai = 5; // change la durée d'affichage de l'animation en fonction de cette dernière. Peut être fixe ou aléatoire
                anim_etage(1, 1); // inverse
            break;
            case 8:
                programme_delai = 15;
                anim_sin(3);
            break;
            case 9:
                programme_delai = 5;
                anim_etage(2);
            break;
            case 10:                
                programme_delai = 10;
                anim_point_aleatoire(3, 2); // nombre de point alétoires
            break;
            case 11:
                programme_delai = 5;
                anim_point_aleatoire(1, 5);    
            break;
            case 12:
                programme_delai = 10;
                anim_point_aleatoire(10, 18);    
            break;
            case 13:
                programme_delai = 7;
                anim_empli_rand(1, 0);
            break;
            case 14:
                programme_delai = 2;
                anim_vide_rand(1);
            break;
            case 15:
                anim_empli_rand(1, 0); // 0 = ne vide pas le contenu au départ
            break;
            case 16:
                anim_vide_rand(1);
            break;
            case 17:
                anim_empli_rand(1, 0);
            break;
            case 18:
                programme_delai = 7;
                anim_vide_rand(1);
            break;
            case 19:
                programme_count++;
            break;
            case 20:
                programme_delai = 10;
                anim_rand_haut_bas(3);
            break;
            case 21:
                anim_point_aleatoire(1, 5);    
            break;
            case 22:
                programme_delai = 5;
                anim_tourne(3, 3); //    3 colonnes
            break;
            case 23:
                programme_count++;
            break;
            case 24:
                programme_delai = 10;
                anim_pluie(2); // de bas en haut                
            break;
            case 25:
                programme_delai = 15;
                anim_points_haut_bas(1);
            break;
            case 26:
                programme_delai = 5;
                anim_clignote_plein_vide(10);
            break;
            default:
                programme_delai = 10;
                programme_count = 0;
            }
        var_frame++; // frame suivante
        if (var_frame >= fps) // dernière frame
            var_frame = 0; // revient à la première
        last_millis = millis(); // raffraichit time pour la prochaine frame
        }
    affiche_frame(); // à toutes les boucles, active, désactive sans cesse les leds de la frame (array grille)
    }


Enfin, les différentes fonctions d'animations avec une petite description pour chacune. La variable delai indique le temps d'affichage d'une frame de l'animation en fonction des frames à afficher par seconde (par défaut à 2, soit 5 par seconde)


Fait clignoter l'ensemble des LED, jour, nuit, jour, nuit... :

void anim_clignote_plein_vide (int delai = delai_defaut) {
    
    if (var_init == 0) {
        var_init = 1;
        var_delai = delai;
        var_delai_count = 0;
        var_1 = 0;
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = {0};
                }
            }
        }
        
    if (var_delai_count >= var_delai) {
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = var_1 == 1 ? 1 : 0; // active ou desactive
                }
            }
        var_1 = var_1 == 1 ? 0 : 1; // change var plein -> vide, vide -> plein pour next
        var_delai_count = 0; // remet à zéro, fin du code joué toutes les x frames
        }
    
    var_delai_count++;
    };


Eclaire une étage à la fois de haut en bas, ou inversement :

void anim_etage (int delai = delai_defaut, bool inverse = 0) {
    
    if (var_init == 0) {
        var_init = 1;
        var_etage = 0;
        if (inverse == 1)
            var_etage = colonnes-1;
        var_delai = delai;
        var_delai_count = 0;
        }
        
    if (var_delai_count >= var_delai) {
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = 0; // désactive
                }
            }
        for (int c = 0; c < colonnes; c++) {
            grille[var_etage][c] = 1; // active toutes
            }
        if (inverse == 1)
            var_etage--;
        else
            var_etage++; // étage suivant (vers le bas)
        
        if (inverse == 0 && var_etage >= etages) {
            var_etage = 0; // boucle l'animation
            }
        else if (inverse == 1 && var_etage < 0) {
            var_etage = colonnes-1;
            }
        var_delai_count = 0;
        }
    var_delai_count++;

    };


Neuf points se déplacent aléatoirement et tour à tour de haut en bas :

void anim_points_haut_bas (int delai = delai_defaut) {

    if (var_init == 0) {
        var_init = 1;
        var_etage = var_colonne = var_direction = 0;
        var_delai = delai;
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = 0;
                }
            }
        for (int c = 0; c < colonnes; c++) {
            var_1 = random(0, 2);
            var_1 = var_1 == 1 ? etages-1 : 0; // si 1, etage max
            var_array9[c] = var_1; // renseigne l'étage de la colonne 0 (haut) ou 6 (bas)
            grille[var_1][c] = 1; // met à jour la grille
            }
        }
        
    if (var_delai_count >= var_delai) {
        if (var_direction == 0) { // 0 = pas de déplacement de point en cours
            var_colonne = random(0, colonnes); // choisi une des 9 colonnes
            var_etage = var_array9[var_colonne]; // récupère son étage
            var_direction = var_etage == 0 ? 1 : -1; // 1 descend, -1 monte en conséquence
            }
        else { // sinon, déplace -1 bas ou +1 haut
            grille[var_etage][var_colonne] = 0; // eteint la précédente
            if (var_etage+var_direction >= 0 && var_etage+var_direction <= etages-1) { // s'il peut encore se déplacer
                var_etage += var_direction; // passe à 'étage suivant
                var_array9[var_colonne] = var_etage; // change l'étage du point fini d'etre déplacé 0 haut ou 6 bas
                }
            else // sinon, arrivé tout en bas/haut
                var_direction = 0; // remet à zéro pour passer à un autre point
            grille[var_etage][var_colonne] = 1; // allume la suivante (ou rallume la derniere si au bout)
            }
        var_delai_count = 0;
        }
    
    var_delai_count++;
    };


Trois points se suivent et se déplacent alétoirement le long des colonnes extérieures à la manière du fameux jeu Nokia Snake :

void anim_serpent (int delai = delai_defaut) {

    if (var_init == 0) {
        var_init = 1;
        var_delai = delai;
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = 0;
                }
            }
        var_tmp = random(0, colonnes); // un point au hasard
        var_3 = var_2 = var_1 = var_tmp;
        var_y3 = var_y2 = var_y1 = random(0, etages);        
        var_direction = random(1, 5);
        }

    if (var_delai_count >= var_delai) {
        var_rnd = random(0, 4); // 1 fois sur 4
        if (var_rnd == 0) {
            var_tmp = random(1, 5); // direction random
            if ((var_tmp - 2 != var_direction) || (var_tmp + 2 != var_direction)) { // vérifie qu'il ne s'agit pas d'un demi-tour
                var_direction = var_tmp;
                }
            }
        grille[var_y3][serpent_tour[var_3]] = grille[var_y2][serpent_tour[var_2]] = grille[var_y1][serpent_tour[var_1]] = 0;
        var_3 = var_2;
        var_2 = var_1;
        var_y3 = var_y2;
        var_y2 = var_y1;
        if (var_direction == 1) {
            var_tmp--;
            if (var_tmp < 0)
                var_tmp = 7;
            var_1 = var_tmp;
            }
        else if (var_direction == 3) {
            var_tmp++;
            if (var_tmp > 7)
                var_tmp = 0;
            var_1 = var_tmp;
            }
        else if (var_direction == 2) { // étage haut
            var_y1--;
            if (var_y1 < 0)
                var_y1 = etages - 1;
            }
        else if (var_direction == 4) { // étage bas
            var_y1++;
            if (var_y1 >= etages)
                var_y1 = 0;
            }
        grille[var_y3][serpent_tour[var_3]] = grille[var_y2][serpent_tour[var_2]] = grille[var_y1][serpent_tour[var_1]] = 1;
        var_delai_count = 0;
        }
    
    var_delai_count++;
    };


Des points apparaissent alétoirement et tombent tour à tour comme une pluie :

void anim_pluie (int delai = delai_defaut, bool inverse = 0) {
    
    if (var_init == 0) {
        var_init = 1;
        var_etage = var_colonne = var_direction = 0;
        var_delai = delai;
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = 0;
                var_array9[c] = 0;
                if (inverse == 1)
                    var_array9[c] = etages-1;
                }
            }
        }
        
    if (var_delai_count >= var_delai) {
        if (var_direction == 0) {
            var_colonne = random(0, colonnes);
            var_etage = var_array9[var_colonne];
            var_direction = 1;
            if (inverse == 1)
                var_direction = -1;    
            grille[var_etage][var_colonne] = 1;
            }
        else {
                grille[var_etage][var_colonne] = 0;    
            if (var_etage+var_direction >= 0 && var_etage+var_direction <= etages-1) {
                var_etage += var_direction;
                grille[var_etage][var_colonne] = 1;    
                }
            else
                var_direction = 0;
            }
        var_delai_count = 0;
        }
    
    var_delai_count++;
    };


Une à plusieurs colonnes extérieurs s'allument en tournant :

void anim_tourne (int delai = delai_defaut, int longueur = 1) {
    
    if (var_init == 0) {
        var_init = 1;
        var_delai = delai;
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = 0;
                }
            }
        var_1 = 0; // premiere sequence relative à serpent_tour
        var_2 = 1;
        var_3 = 2;
        }
    
    if (var_delai_count >= var_delai) {
        for (int e = 0; e < etages; e++) {
            grille[e][serpent_tour[var_1]] = 0;
            if (longueur > 1)
                grille[e][serpent_tour[var_2]] = 0;
            else if (longueur > 2)
                grille[e][serpent_tour[var_3]] = 0;
            }
        var_1++;
        if (var_1 > 7) // si dépasse array8
            var_1 = 0;
        var_2++;
        if (var_2 > 7)
            var_2 = 0;
        var_3++;
        if (var_3 > 7)
            var_3 = 0;
        for (int e = 0; e < etages; e++) {
            grille[e][serpent_tour[var_1]] = 1;
            if (longueur > 1)
                grille[e][serpent_tour[var_2]] = 1;
            else if (longueur > 2)
                grille[e][serpent_tour[var_3]] = 1;
            }        
        var_delai_count = 0;
        }
    
    var_delai_count++;
    };


Allume des LED aléatoirement :

void anim_empli_rand (int delai = delai_defaut, bool vide = 1) {
    
    if (var_init == 0) {
        var_init = 1;
        var_delai = delai;
        if (vide == 1) {
            for (int e = 0; e < etages; e++) {
                for (int c = 0; c < colonnes; c++) {
                    grille[e][c] = 0;
                    }
                }
            }
        }
    
    if (var_delai_count >= var_delai) {
        grille[random(0, etages)][random(0, colonnes)] = 1;
        var_delai_count = 0;
        }
    
    var_delai_count++;
    };


Eteint des LED aléatoirement :

void anim_vide_rand (int delai = delai_defaut) {
    
    if (var_delai_count >= var_delai) {
        grille[random(0, etages)][random(0, colonnes)] = var_delai_count = 0;
        }
        
    var_delai_count++;
    };


Effet de pluie :

void anim_rand_haut_bas (int delai = delai_defaut) {
    
    if (var_init == 0) {
        var_init = 1;
        var_etage = var_colonne = var_direction = 0;
        var_delai = delai;
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = var_array9[c] = 0;
                }
            }
        }

    if (var_delai_count >= var_delai) {
        for (int e = etages-1; e >= 0; e--) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = e == 0 ? 0 : grille[e-1][c]; // déplace vers le bas
                }
            }
        grille[0][random(0, colonnes)] = 1;
        var_delai_count = 0;
        }
    var_delai_count++;
    
    };


Effet de vague diagonale défilante :

void anim_sin (int delai = delai_defaut) {
    
    if (var_init == 0) {
        var_init = 1;
        var_etage = var_colonne = var_direction = 0;
        var_delai = delai;
        
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = var_array9[c] = 0;
                }
            }
        var_1 = 0; // etapes
        }
    
    if (var_delai_count >= var_delai) {
        for (int e = etages-1; e >= 0; e--) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = e == 0 ? 0 : grille[e-1][c];
                }
            }
        for (int c = 0; c < colonnes; c++) {
                grille[0][c] = 0;
                }
        switch(var_1) {
            case 0: case 8:
                grille[0][0] = 1;
            break;
            case 1: case 7:
                grille[0][1] = grille[0][3] = 1;
            break;
            case 2: case 6:
                grille[0][2] = grille[0][4] = grille[0][6] = 1;
            break;
            case 3: case 5:
                grille[0][5] = grille[0][7] = 1;
            break;
            case 4:
                grille[0][8] = 1;
            break;
            default:
                var_1 = 0;
            }
        var_1++;
        if (var_1 >= 8)
            var_1 = 0;
            
        var_delai_count = 0;
        }
    var_delai_count++;
    };


Allume et éteint une ou plusieurs LED aléatoirement :

void anim_point_aleatoire (int delai = delai_defaut, int nombre = 1) {
    
    if (var_init == 0) {
        var_init = 1;
        var_delai = delai;
        }
    
    if (var_delai_count >= var_delai) {
        for (int e = 0; e < etages; e++) {
            for (int c = 0; c < colonnes; c++) {
                grille[e][c] = 0;
                }
            }
        for (int e = 0; e < nombre; e++) {
            grille[random(0, etages)][random(0, colonnes)] = 1;
            }
        var_delai_count = 0;
        }
    
    var_delai_count++;
    }


Beaucoup d'autres animations sont possibles (tetris, lettrage, effets de scintillement en jouant sur la durée d'affichage etc), mais c'est déjà une base suffisante pour commencer à égayer vos apéros !

Télécharger le code commenté


Et pour conclure, une vidéo en situation réelle avec le programmation en Loop, qui permet de visualiser le comportement des différentes fonctions :

Voila pour ce premier projet qui a été mené à terme et sans trop d'accroc.

Pour le prochain, je souhaite m'attaquer à un coin pusher, dit pousse-pièce ou cascade ou plus vulgairement : une machine à sous de forain, mais de comptoir et toujours original.
Projet qui fera certainement l'objet de plusieurs articles s'il se concrétise... à suivre !

 

Partie 1 : Le prototype
Partie 2 : Le premier modèle
Partie 3 : Le code (vous êtes ici)