Hello les gens !
Pas évidant de trouver un peu de temps libre pour ce troisième chapitre (travaux, boulot, etc...) mais étant donné qu'on est tous plus ou moins confiné en ce moment, j'en profite pour me replonger dans le sujet
Voici dans ce troisième chapitre consacré à la programmation !
Après y avoir réfléchit, je me suis dis que faire tout un tutoriel de programmation ne serait pas forcément la meilleurs idée étant donné qu'il y a beaucoup à dire et que l'on en trouve déjà plein sur le net, qui seront surement bien mieux expliqué que ce que je pourrais faire.
Du coup, je vais assumer que si vous vous lancez là dedans, vous avez un minimum de connaissance en programmation (ou que vous êtes assez débrouillard pour comprendre et adapter des copier/coller).
Bref, ce chapitre sera donc organisé sous forme d'exemples pour présenter différentes animations que l'on peut faire sur ces LEDs.
A noter que l'Arduino se programme en C/C++ mais si vous avez déjà des bases dans un langage (java, python, visual basic, etc...), vous ne devrez pas être trop perdu étant donné que l'on retrouve généralement les mêmes mots clefs (if, else, for, while, etc...)
Dans tous les cas, je vous conseille d'aller faire un tour sur
le site de Eskimon qui explique comment prendre en main l'Arduino en partant de zéro, quitte à passer un peu vite certains chapitres si vous avez les bases.
Pour votre premier programme Arduino, vous aurez besoin de l'IDE Arduino (
téléchargeable ici) ainsi que la lib NeoPixel (
téléchargeable ici).
L'ajout de cette lib se fait dans le menu "Croquis", puis "Inclure une bibliothèque" puis "Ajouter la bibliothèque ZIP".
A noter qu'il y a aussi la lib FastLED qui est plutôt bien fournie et propose déjà de nombreuses animations, libre à chacun de tester et de se faire son opinion.
Maintenant que cela est fait, parlons un peu de la lib NeoPixel.
Celle-ci permet de gérer des rubans LEDs au travers de la classe Adafruit_NeoPixel qui aura besoin de connaitre : le nombre de LEDs du ruban, la pin de l'Arduino reliée au ruban, le protocole de communication qui dépend de la référence des LEDs de votre ruban.
Une fois une instance créée, nous utiliserons principalement les fonctions setPixelColor et show qui permettent respectivement de changer la couleur d'une LED et de mettre à jour tout le ruban.
A noter que setPixelColor ne fait qu’enregistrer la couleur dans la mémoire interne de l'Arduino (donc extrêmement rapide) tandis que show communique avec le ruban pour le mettre à jour (donc beaucoup plus lent).
Il convient donc d'appeler setPixelColor pour toutes les LEDs que l'on souhaite modifier puis de faire un appel à show pour mettre à jour tout ça.
Pour ce qui est des couleurs, vu le nombre d'artistes numériques ici, je ne vais probablement rien vous apprendre en annonçant que chaque couleur peut être définit comme un composante de rouge, de vert et de bleu, dont la valeur varie entre 0 et 255.
A noter tout de même que le ruban n'étant pas sur un arrière plan noir, il est impossible d'afficher la couleur noir et les couleurs sombres.
Par exemple, la couleur (32,32,32) correspondant à un gris très sombre sur un écran sera en réalité un blanc très peu lumineux sur le ruban.
Citation:
EDIT: Suite à l'intervention de Pr. Théodose dans le message qui suit, j'ajoute cette quote pour annoncer qu'il existe un simulateur en ligne !
L'auteur du simulateur m'a d'ailleurs gentiment partagé un lien pour configurer correctement celui-ci avec le bon nombre de LEDs.
Je vous invite donc à lire ce message pour pouvoir tester le code qui va suivre :
viewtopic.php?f=4&t=11369&p=294775#p294775Maintenant, place au premier exemple pour mettre cela en pratique :
+ [spoiler]
Code:
#include <Arduino.h>
#include <stdint.h>
#include <Adafruit_NeoPixel.h>
#define PIN_PIXELS 6
#define NB_PIXELS 60
Adafruit_NeoPixel pixels ( NB_PIXELS, PIN_PIXELS, (NEO_GRB+NEO_KHZ800) );
void setup ( void ) {
Serial.begin(115200);
Serial.println(">Start:");
pixels.begin();
}
void loop ( void ) {
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 255, 0, 0);
}
pixels.show();
delay(1000);
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 0, 0, 0);
}
pixels.show();
delay(1000);
}
Vous remarquerez qu'il n'y a pas de fonction main, c'est une typicité d'Arduino à laquelle on finit par s'y habituer.
Il faut en effet définir une fonction setup (pour l'initialisation) qui sera automatiquement appelée au début du programme puis une fonction loop qui sera appelée en boucle par la suite.
Quelques explications :
- lignes 8 et 10, on définit la pin connectée au ruban ainsi que le nombre de LEDs
- ligne 14, on construit l'instance de Adafruit_NeoPixel évoquée plus haut (j'utilise des LED WS2812B donc le protocole est (NEO_GRB+NEO_KHZ800))
- ligne 18, on définit la fonction setup qui initialise le Serial (la console de debug Arduino) ainsi que notre ruban LED
- ligne 29, on définit la fonction loop qui vient faire clignoter le ruban
Pour ce clignotement:
- on commence par mettre toutes les LED en rouge (255,0,0) avec la première boucle for suivie d'un appel à show pour envoyer ce rouge au ruban
- puis une attente d'une seconde (fonction delay d'Arduino)
- on repasse toutes les LED sur OFF (0,0,0) et le show associé
- et à nouveau attente d'une seconde
Le tout se répétant indéfiniment car la fonction loop est appelée en boucle
Bon, ça clignote, c'est joli, mais on ne va pas aller loin avec ça...
D'autant plus que la fonction delay est bloquante, ce qui signifie qu'elle met en pause l'exécution du programme pendant en certain temps.
C'est pratique pour ce clignotement mais si on doit aussi gérer l'animation d'un second ruban ou faire une action quand on clique sur un bouton, ça va poser problème car pendant que le programme est en pause, il ne test pas l'état du bouton.
Du coup, je vous propose d'oublier la fonction delay et de la remplacer par un peu de mathématique avec la fonction millis.
Cette fonction millis retourne ne nombre de millis secondes depuis le lancement du programme, ce qui est très pratique.
Voici comment faire la même chose avec millis :
+ [spoiler]
Code:
void loop ( void ) {
uint32_t time = millis();
if ( ((time/1000)%2) == 0 ) {
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 255, 0, 0);
}
} else {
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 0, 0, 0);
}
}
pixels.show();
}
Toute l'astuce se trouve dans ce
if ( ((time/1000)%2) == 0 ) ligne 33.
Comme dit plus haut, millis retourne le nombre de millis secondes depuis le lancement du programme donc au premier appel de loop, ce sera très certainement 0, puis peut-être encore 0 (si le programme a été rapide), puis 1, peut-être encore 1, puis 2, etc... et ainsi ce suite, millis retourne une valeur qui augmente au fur et à mesure que le programme s’exécute.
En divisant par 1000, on obtiens donc le nombre de secondes depuis le lancement du programme : d'abord 0, puis 1, puis 2, puis 3, etc... au fur et à mesure que le programme s’exécute.
Vient ensuite le modulo 2, qui correspond au reste de la division euclidienne par 2, ce qui donnera le résultat 0 si le nombre est pair (0,2,4,etc...) ou 1 si le nombre est impair.
Du coup, c'est la boucle for qui met la couleur rouge qui est exécutée lorsque le nombre de secondes est pair tandis que ce sera OFF si le nombre est impair.
Si l'on veut faire défiler plusieurs couleurs, il suffit simplement de changer la valeur du modulo pour avoir un résultat autre que 0 ou 1.
Par exemple, avec un modulo 3, on aura comme résultat 0, 1, ou 2, ce qui pourrait permettre de faire défiler 3 couleurs :
+ [spoiler]
Code:
void loop ( void ) {
uint32_t time = millis();
uint8_t colormod = ((time/1000)%3);
if ( colormod == 0 ) {
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 255, 0, 0);
}
}
if ( colormod == 1 ) {
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 0, 255, 0);
}
}
if ( colormod == 2 ) {
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, 0, 0, 255);
}
}
pixels.show();
}
Une couleur toutes les secondes, c'est peut-être un peu trop lent, ou trop rapide, ça dépend des goûts de chacun.
Pour changer ça, c'est extrêmement simple, il suffit de changer la valeur de la division.
- Diviser par une valeur plus faible aura pour effet d'augmenter la cadence
- Diviser par une valeur plus forte aura pour effet de réduire la cadence
Bref, vous l'aurez compris, il faut faire preuve d'imagination pour trouver les bonnes formules permettant d'effectuer les bonnes actions en fonction du temps qui s'écoule.
On peut bien évidement faire du fadding en se servant de ce temps pour générer la composante d'une couleur.
Un petit exemple qui allume progressivement le ruban, puis l'éteint tout aussi progressivement :
+ [spoiler]
Code:
void loop ( void ) {
uint32_t time = millis();
uint32_t count = (time%200);
uint8_t col;
if ( count < 100 ) {
col = map(count, 0, 99, 0, 255);
} else {
col = map(count, 100, 199, 255, 0);
}
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, col, 0, 0);
}
pixels.show();
}
Avant d'entrer dans les détails, je dois vous présenter la fonction map(x, A, B, C, D), qui permet de mapper une valeur x de l'intervalle [A;B] vers l'intervalle [C;D].
Dans ce code, je commence par récupérer le temps avec un modulo 200, ce qui me permet d'avoir une valeur d'animation qui part de 0 et augmente progressivement jusqu'à 199 puis repart à 0.
Si cette valeur est inférieur à 100, on est dans la première partie de l'animation, il faut augmenter l'intensité de la couleur, donc je map cette valeur 0-99 vers intervalle 0-255 correspondant à une composante de couleur.
Pour la seconde partie, ce sera le else, avec cette fois ci une valeur de 100-199 que je map sur l'intervalle 255-0 (on notera que l'intervalle de sortie est inversé car je souhaite que la composante diminue en partant de 255 pour rejoindre 0 au fur et à mesure que le temps passe).
Et pour finir, le top du top, histoire de faire exploser le cerveau avec les mathématiques, on peut aussi intégrer la position de la LED dans le calcul.
On peut par exemple incrémenter virtuellement le temps avec l'index de la LED, de sorte qu'elle s'anime en avance par rapport à la LED précédente, ce qui aura pour effet de créer un défilement :
+ [spoiler]
Code:
void loop ( void ) {
uint32_t realTime = millis();
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
uint32_t virtualTime = (realTime + i*20);
uint32_t count = (virtualTime%200);
uint8_t col;
if ( count < 100 ) {
col = map(count, 0, 99, 0, 255);
} else {
col = map(count, 100, 199, 255, 0);
}
pixels.setPixelColor(i, col, 0, 0);
}
pixels.show();
}
Ici, il s'agit de la même animation que précédemment, sauf que les LEDs ont toutes un déphasage temporelle de 20ms les unes par rapport aux autres grâce à
(realTime + i*20).
Du coup, au lieux que tout le ruban s'éteigne puis s'allume en même temps, chaque LED est indépendante.
Quand une LED est éteinte, la LED suivante ne le sera que 20ms plus tard, et ainsi de suite.
Du coup, au fur et à mesure que le temps passe, la zone d'extinction se déplace d'une LED à l'autre, ce qui cré une animation de défilement le long du ruban.
- Si l'on veut que le défilement soit plus rapide, il suffit de faire un décalage plus faible avec par exemple (realTime + i*5)
- Si l'on veut que le défilement soit plus lent, il suffit de faire un décalage plus important avec par exemple (realTime + i*40)
- Si l'on veut que le défilement soit dans l'autre sens, il suffit de faire une soustraction plutôt qu'une addition avec par exemple (realTime - i*20)
Bref, les combinaison sont infinies, il suffit de se creuser le cerveau pour enchainer les conditions et les calculs mathématiques.
Le même exemple avec une animation qui change de sens toutes les 3 secondes, ça donnerait ça :
+ [spoiler]
Code:
void loop ( void ) {
uint32_t realTime = millis();
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
uint32_t virtualTime;
if ( ((realTime/3000)%2) == 0 ) {
virtualTime = (realTime + i*20);
} else {
virtualTime = (realTime - i*20);
}
uint32_t count = (virtualTime%200);
uint8_t col;
if ( count < 100 ) {
col = map(count, 0, 99, 0, 255);
} else {
col = map(count, 100, 199, 255, 0);
}
pixels.setPixelColor(i, col, 0, 0);
}
pixels.show();
}
Voila, je pense qu'on a fait un beau tour d'horizon des différentes animations.
Il y aura peut-être d'autres chapitres pour voir comment intégrer d'autres éléments (bouton poussoir par exemple), optimiser un peu le tout, les pièges à éviter, peut-être de nouvelles animations, etc...
Comme toujours, n'hésitez pas à me faire part de vos remarques et questions.
Avant de se quitter, un petit bonus, l'effet arc en ciel défilant grâce à la fonction ColorHSV qui permet de convertir une composante HUE (0-65535) en une couleur :
+ [spoiler]
Code:
void loop ( void ) {
uint32_t tm = millis();
for ( int i = 0 ; i < NB_PIXELS ; i++ ) {
pixels.setPixelColor(i, Adafruit_NeoPixel::ColorHSV( (tm+i*10)*50 ) );
}
pixels.show();
}
Comme quoi, ce n'est pas forcément les effets les plus Wooowwww les plus dure à implémenter.
Maintenant, vous savez pourquoi on retrouve plein d'arc en ciel défilant sur les rubans LEDs
