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

hardware - juillet 2018

0Comme 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.

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 la 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 !