Header Ads

Chenillard à Leds - Part 3

Introduction
Cela fait maintenant bien longtemps que j'ai publié mes deux précédents articles:
La deuxième partie de l'article offrait déjà un certain aboutissement avec ses différentes animations. Déjà bien content de moi, je me suis arrêté là.
Mais voilà, tout récemment, Flippers (un bloggeur) me demandait comment monter un variateur de vitesse sur le montage existant.
Puisque l'idée est bonne, je m'y suis attardé ce week-end.

L'approche technique
Rien de bien compliqué en l'occurence.
J'ai ré-utilisé le montage précédent et j'y ai ajouter un potentiomètre de 10KOhms.
Variateur du chenillard 
Les LEDs (voir premier article)

Changement d'animation (deuxième article)

Pour le détail des montages précédents (du code et explications diverses), je vous propose de lire les articles correspondants.

Ainsi, grâce à l'utilisation du potentiomètre (utiliser un potentiomètre linéaire et non un potentiomètre logarithmique) il est possible de lire une tension qui varie entre 0 et 5v sur la pin Analogique A1.
Dans le programme Arduino, cette tension sera traduite en une valeur numérique variant de 0 à 1024, valeur que nous obtiendrons à l'aide de l'instruction analogRead( A1 ).
Le délai de pause utilisé entre deux étape d'une animation dépendra de la tension lue sur la pin A1.
Ainsi, le temps de pause variera de 10 millisecondes à 200 millisecondes.

Une solution informatique élégante
Actuellement, il y a un temps de pause régulier entre chaque étape de l'animation.
Ce temps est actuellement de 80ms et est assuré par l'instruction:
delay( interval )
La variable "interval" contenant la valeur 80 (pour les 80 millisecondes de pause).

Idéalement, il faudrait que durant cet intervalle, exécuté régulière durant l'animation, le programme lise la valeur de l'entrée Analogique et change la valeur de la variable "interval" de façon adéquate et à la volée.

Facile à dire... mais comment faire ces modification pendant que le programme fait une pause de 80 ms... ou plus tard des pauses de 200 millisecondes?
C'est ce qui est décrit dans le point suivant.

Première étape - remplacer l'instruction delay
Pour commencer, les différentes animations seront modifiées de sorte à ne plus appeler l'instruction delay( interval ) mais la fonction intelligentDelay() crée tout spécialement pour le variateur de vitesse.
Par exemple, l'animation "OnOffAll" qui ressemble à ceci
 void OnOffAll(){
   for( int iTime=0; iTime<NbrLed; iTime++ )
     for( int iOnOff=0; iOnOff<2; iOnOff++ ) {
       for( int i=0; i<NbrLed; i++ )
         if( iOnOff == 0 ) // Allumer au premier passage  
           digitalWrite( ledPins[i], HIGH );
         else // Eteindre au deuxième passage
           digitalWrite( ledPins[i], LOW );
       if( animChanged() )
         return;
       delay(interval); 
     }
 }
sera modifiée pour appeler intelligentDelay()
 void OnOffAll(){
   for( int iTime=0; iTime<NbrLed; iTime++ )
     for( int iOnOff=0; iOnOff<2; iOnOff++ ) {
       for( int i=0; i<NbrLed; i++ )
         if( iOnOff == 0 ) // Allumer au premier passage  
           digitalWrite( ledPins[i], HIGH );
         else // Eteindre au deuxième passage
           digitalWrite( ledPins[i], LOW );
       if( animChanged() )
         return;
       intelligentDelay(); 
     }
 }
Notez le remplacement de delay par intelligentDelay.

La fonction IntelligentDelay
Voici le corps de la fonction intelligentDelay().
Dans un premier temps, elle se contente seulement d'attendre 80ms comme l'ancienne fonction delay(interval) MAIS elle fractionne le temps d'attente en intervalle de 10 millisecondes.
 void intelligentDelay() {
    
   unsigned long startMillis = millis(); 
   unsigned long currentMillis = millis();
   
   // Tant que l'on a pas atteind l'interval de pause
   while( (currentMillis - startMillis) < interval) {
     // Faire une pause de 10 milli-seconde
     delay( 10 );
     currentMillis = millis();
   }
 }
Par conséquent, la boucle sera exécutée plusieurs fois jusqu'à obtenir le temps d'attente voulu.

Deuxième étape - Rendre intelligentDelay vraiment intelligent!
Comme mentionné au point précédent, intelligentDelay() boucle tant que l'intervalle d'attente n'est pas atteind.
Ainsi donc, si la valeur de la variable Interval venait à être modifiée, le temps d'attente de intelligentDelay() s'adapterait automatiquement à le nouvelle exigence.

Autre avantage stratégique de IntelligentDelay() est qu'il ne reste pas inactif longtemps (il ne fait que de très courtes pauses n'excédent jamais 10ms à chaque tour de boucle).
C'est un avantage crucial dans le cas qui nous concerne!
En effet, toutes les 10 millisecondes, le programme à l'opportunité de lire la valeur analogique sur le port A0 et de calculer une nouvelle valeur pour la variable "interval" :-)
La version finale de intelligentDelay() ressemblera donc à:
 void intelligentDelay() {
    
   unsigned long startMillis = millis(); 
   unsigned long currentMillis = millis();
   
   // Tant que l'on a pas atteind l'interval de pause
   while( (currentMillis - startMillis) < interval) {
     // Faire une pause de 10 milli-seconde
     delay( 10 );
     currentMillis = millis();
     // Lecture du port analogique
     int potValue = analogRead( potPin ); // 0-1024
     // Calcul du nouvel interval entre deux étapes d'une animation
     //  transforme la valeur de 0 à 1024 --> en interval de 10ms à 200ms (0.2 sec)
     interval = map( potValue, 0, 1024, 10, 200 ); 
   }
 }
Ainsi donc, si l'on modifie la valeur du potentiomètre durant les pauses de l'animation (ce qui représente la principale activité du programme), cette modification sera immédiatement prise en compte.
En effet, la variable interval intervient dans la boucle d'attente d'intelligentDelay(), la prise en compte est donc instantanée!

Résultat en image

Version finale du programme
Source: Chenillard_3.pde
/*
 * Chenillard 3 pour Leds branchées sur Pin 6 -> 13 (8 leds)
 * Bouton (pull-down) sur Pin 2 permet de changer modèles d'animation
 * Potentiomètre (10k) sur la Pin A1 qui permet de changer la vitesse d'animation
 *
 * Autheur: Meurisse D. ( mchobby.be ou arduino103.blogpot.com )
 * License: CC BY-SA 2.5 (voir http://creativecommons.org/licenses/by-sa/2.5/ )
 *
 */
 
 int NbrLed = 8; // nbre de Leds dans le tableau
 int ledPins[] = { 6,7,8,9,10,11,12,13 };  // Tableau contenant les Pins pour les leds
 int potPin = A1; // Potentiomètre 10K sur port Analogique A1
 
 int pinAnimBtn = 2; // Bouton (pull-down) pour changer d'animation
 int animBtnState;   // Etat du bouton d'animation
 
 byte currentAnim = 0; // Animation actuellement affichée
                       // Change a chaque fois que le bouton est pressé
 
 int interval = 80; // Interval entre deux actions LEDS, 80 ms
 
 void setup(){
   // Activer les pins pour les leds en sortie
   for( int i=0; i<NbrLed; i++ )
     pinMode( ledPins[i], OUTPUT );
   // Activer bouton animation
   pinMode( pinAnimBtn, INPUT );
   animBtnState = digitalRead( pinAnimBtn );
   // Animation par defaut
  currentAnim = 0; 
 }
 
 void loop(){
   displayAnim();
 }
 
 // ==============================================================
 //     Controle du temps
 // ==============================================================
 
 // Effectue l'operation equivalent à
 //   delay( interval )
 // tout en lisant régulièrement l'état de l'entrée analogique A0
 //   de façon régulière (toutes les 10 ms).
 // Le l'interval d'attente sera propotionnelle à la valeur lue
 //    pour A0 (de 0 à 1024) pour (20ms à 2000ms)
 //
 void intelligentDelay() {
    
   unsigned long startMillis = millis(); 
   unsigned long currentMillis = millis();
   
   // Tant que l'on a pas atteind l'interval de pause
   while( (currentMillis - startMillis) < interval) {
     // Faire une pause de 10 milli-seconde
     delay( 10 );
     currentMillis = millis();
     // Lecture du port analogique
     int potValue = analogRead( potPin ); // 0-1024
     // Calcul du nouvel interval entre deux étapes d'une animation
     //  transforme la valeur de 0 à 1024 --> en interval de 10ms à 200ms (0.2 sec)
     interval = map( potValue, 0, 1024, 10, 200 ); 
   }
 }
 // ==============================================================
 //     Controle bouton "change animation"
 // ==============================================================
  
 int lecture1, lecture2;
 boolean resultat = false;
 
 // Vérifie si l'état du bouton a changé (enfoncé ou relaché)
 //
 boolean animBtnChanged(){
   lecture1 = digitalRead( pinAnimBtn );
   // Si pas changement état --> continuer programme
   if( lecture1 == animBtnState )
     return false;
   // Changement état bouton détecté --> deparasitage de lecture
   delay( 10 );
   lecture2 = digitalRead( pinAnimBtn );
   // Si parasite --> continuer le programme
   if( lecture1 != lecture2 )
     return false;
   // Evaluer le changement d'état
   resultat = ((lecture1 == lecture2) && (lecture1 != animBtnState) );
   // Mémoriser le nouvel état
   if( resultat )
     animBtnState = lecture1;
   return resultat; 
 }
 
 // Retourne vrai lorsque l'animation a changée
 //  (lorsque le bouton pinAnimBtn est relaché).
 // Sélectionne aussi l'animation suivante.
 boolean animChanged(){
   // si pas changement etat bouton --> continuer programme.
   if( !animBtnChanged() )
     return false;
   // Changer d'animation seulement quand le bouton est relaché.
   if( animBtnState != LOW )
     return false;
          
   // Eteind toutes les leds (comme ca, pas de problèmes 
   //    si changement en milieu d'animation)
   allLedOff();
   // Change d'animation
   nextAnim();
   return true;
 }
 
 // ==============================================================
 //     Controle du cycle des animations
 // ==============================================================
 // Change d'animation en modifiant la variable d'animation
 //
 void nextAnim(){
   // Passer à l'animation suivant
   currentAnim++;
   // Si plus d'animation, repasser à la première
   if( currentAnim > 5 )
     currentAnim = 0;
 }

 // Affiche l'animation currentAnim
 // ps: le controle du bouton "changer anim" et 
 //     changement d'animation sont fait par les 
 //     routine d'animations (via animChanged).
 void displayAnim(){
    switch( currentAnim ){
      case 0: OnOffAll();
              break;
      case 1: OnOffEachAtTheTime_LtoR();
              break;
      case 2: OnOffEachAtTheTime_RtoL();
              break;
      case 3: GraduatedOnOff_LtoR();
              break;
      case 4: GraduatedOnOff_RtoL();
              break;
      case 5: K2000();
              break;
   }
 } 
 
 // Eteind toutes les leds
 void allLedOff() {
   for( int i=0; i<NbrLed; i++ )
     digitalWrite( ledPins[i], LOW );
 }
 
 // ==============================================================
 //     Modèle d'Animation
 // ==============================================================
 
 // Allume puis éteind toutes les leds en meme temps
 // 8 fois (pcq 8 leds).
 //
 void OnOffAll(){
   for( int iTime=0; iTime<NbrLed; iTime++ )
     for( int iOnOff=0; iOnOff<2; iOnOff++ ) {
       for( int i=0; i<NbrLed; i++ )
         if( iOnOff == 0 ) // Allumer au premier passage  
           digitalWrite( ledPins[i], HIGH );
         else // Eteindre au deuxième passage
           digitalWrite( ledPins[i], LOW );
       if( animChanged() )
         return;
       intelligentDelay(); 
     }
 }
 
 // Allume puis éteind chaque led, chaque led a son tour.
 // De gauche à droite
 void OnOffEachAtTheTime_LtoR(){
   for( int i=0; i<NbrLed; i++ ){
     digitalWrite( ledPins[i], HIGH );
     intelligentDelay();
     digitalWrite( ledPins[i], LOW );
     if( animChanged() )
       return;
     intelligentDelay(); 
   }
 }

 void OnOffEachAtTheTime_RtoL(){
   for( int i=NbrLed-1; i>=0; i-- ){
     digitalWrite( ledPins[i], HIGH );
     intelligentDelay(); 
     digitalWrite( ledPins[i], LOW );
     if( animChanged() )
       return;
     intelligentDelay(); 
   }
 }
 
 // allume toutes les leds une par une puis les éteinds
 // De Gauche à Droite
 void GraduatedOnOff_LtoR(){
   for( int i=0; i<NbrLed; i++ ) {
     digitalWrite( ledPins[i], HIGH );
     if( animChanged() )
       return;
     intelligentDelay();
   }
   for( int i=0; i<NbrLed; i++ ) {
     digitalWrite( ledPins[i], LOW );
     if( animChanged() )
       return;
     intelligentDelay();  
   }
 }

 // allume toutes les leds une par une puis les éteinds
 // De Gauche à Droite
 void GraduatedOnOff_RtoL(){
   for( int i=NbrLed-1; i>=0; i-- ) {
     digitalWrite( ledPins[i], HIGH );
     if( animChanged() )
       return;
     intelligentDelay();   
   }
   for( int i=NbrLed-1; i>=0; i-- ) {
     digitalWrite( ledPins[i], LOW );
     if( animChanged() )
       return;
     intelligentDelay();   
   }
 }

 // Allume et éteind les leds à la façon K2000
 // Un aller retour complet
 void K2000(){
   for( int i=0; i<NbrLed; i++) {
      // Allume uniquement la led, la précédente et la suivante
      // éteind toutes les autres.
      for( int iBrowser=0; iBrowser<NbrLed; iBrowser++ ) 
        if( iBrowser==(i-1) || iBrowser==(i+1) || iBrowser==i )
          digitalWrite( ledPins[iBrowser], HIGH );
        else
          digitalWrite( ledPins[iBrowser], LOW );
     if( animChanged() )
       return;
      intelligentDelay(); 
   }

   for( int i=NbrLed-1; i>=0; i--) {
      // Allume uniquement la led, la précédente et la suivante
      // éteind toutes les autres.
      for( int iBrowser=0; iBrowser<NbrLed; iBrowser++ ) 
        if( iBrowser==(i-1) || iBrowser==(i+1) || iBrowser==i )
          digitalWrite( ledPins[iBrowser], HIGH );
        else
          digitalWrite( ledPins[iBrowser], LOW );
     if( animChanged() )
       return;
      intelligentDelay(); 
   }
   
   // eteind les deux dernières led
   digitalWrite( ledPins[0], LOW );
   digitalWrite( ledPins[1], LOW );
}