Introduire le temps

C’est bien beau d’allumer une LED, mais si elle ne fait rien d’autre, ce n’est pas très utile. Autant la brancher directement sur une pile (avec une résistance tout de même ! :P ). Alors voyons comment rendre intéressante cette LED en la faisant clignoter ! Ce que ne sait pas faire une pile… Pour cela il va nous falloir introduire la notion de temps. Eh bien devinez quoi ? Il existe une fonction toute prête là encore ! Je ne vous en dis pas plus, passons à la pratique !

Comment faire ?

Trouver la commande…

Je vous laisse cherche un peu par vous-même, cela vous entrainera ! :pirate: … Pour ceux qui ont fait l’effort de chercher et n’ont pas trouvé (à cause de l’anglais ?), je vous donne la fonction qui va bien : on va utiliser : delay(). Petite description de la fonction : elle va servir à mettre en pause le programme pendant un temps prédéterminé.

Utiliser la commande

La fonction admet un paramètre qui est le temps pendant lequel on veut mettre en pause le programme. Ce temps doit être donné en millisecondes. C’est-à-dire que si vous voulez arrêter le programme pendant une seconde, il va falloir donner à la fonction ce même temps, écrit en millisecondes, soit 1000ms. La fonction est simple à utiliser :

// on fait une pause du programme pendant 1000ms, soit 1 seconde
delay(1000);

Rien de plus simple donc. Pour 20 secondes de pause, il aurait fallu écrire :

// on fait une pause du programme pendant 20000ms, soit 20 secondes
delay(20000);
Mettre en pratique : faire clignoter une LED

Du coup, si on veut faire clignoter notre LED, on peut utiliser cette fonction. Voyons un peu le schéma de principe du clignotement d’une LED :

Schéma de principe du clignotement
Schéma de principe du clignotement

Vous le voyez, la LED s’allume. Puis, on fait intervenir la fonction delay(), qui va mettre le programme en pause pendant un certain temps. Ensuite, on éteint la LED. On met en pause le programme. Puis on revient au début du programme. On recommence et ainsi de suite. C’est cette suite de commandes qui forme le processus faisant clignoter la LED.

Dorénavant, prenez l’habitude de faire ce genre de schéma lorsque vous faites un programme. Cela aide grandement la réflexion, croyez moi ! ;) C’est le principe de perdre du temps pour en gagner. Autrement dit : l'organisation !

Maintenant, il faut que l’on traduise ce schéma, portant le nom d'organigramme, en code.

Il suffit pour cela de remplacer les phrases dans chaque cadre par une ligne de code. Par exemple, "on allume la LED", va être traduit par l’instruction que l’on a vue dans le chapitre précédent :

digitalWrite(led_rouge, LOW);   // allume la LED

Ensuite, on traduit le cadre suivant, ce qui donne :

// fait une pause de 1 seconde (= 1000ms)
delay(1000);

Puis, on traduit la ligne suivante :

// éteint la LED
digitalWrite(led_rouge, HIGH);

Enfin, la dernière ligne est identique à la deuxième, soit :

// fait une pause de 1 seconde
delay(1000);

On se retrouve avec le code suivant :

// allume la LED
digitalWrite(led_rouge, LOW);
// fait une pause de 1 seconde
delay(1000);
// éteint la LED
digitalWrite(led_rouge, HIGH);
// fait une pause de 1 seconde
delay(1000);

La fonction qui va boucler à l’infini le code précédent est la fonction loop(). On inscrit donc le code précédent dans cette fonction :

void loop()
{
    // allume la LED
    digitalWrite(led_rouge, LOW);
    // fait une pause de 1 seconde
    delay(1000);
    // éteint la LED
    digitalWrite(led_rouge, HIGH);
    // fait une pause de 1 seconde
    delay(1000);
}

Et on n’oublie pas de définir la broche utilisée par la LED, ainsi que d’initialiser cette broche en tant que sortie. Cette fois, le code est terminé !

// définition de la broche 2 de la carte en tant que variable
const int led_rouge = 2;

// fonction d'initialisation de la carte
void setup()
{
    // initialisation de la broche 2 comme étant une sortie
    pinMode(led_rouge, OUTPUT);
}

void loop()
{
    // allume la LED
    digitalWrite(led_rouge, LOW);
    // fait une pause de 1 seconde
    delay(1000);
    // éteint la LED
    digitalWrite(led_rouge, HIGH);
    // fait une pause de 1 seconde
    delay(1000);
}

Vous n’avez plus qu’à charger le code dans la carte et admirer mon votre travail ! La LED clignote ! Libre à vous de changer le temps de clignotement : vous pouvez par exemple éteindre la LED pendant 40ms et l’allumer pendant 600ms :

// définition de la broche 2 de la carte en tant que variable
const int led_rouge = 2;

// fonction d'initialisation de la carte
void setup()
{
    // initialisation de la broche 2 comme étant une sortie
    pinMode(led_rouge, OUTPUT);
}

void loop()
{
    // allume la LED
    digitalWrite(led_rouge, LOW);
    // fait une pause de 600 ms
    delay(600);
    // éteint la LED
    digitalWrite(led_rouge, HIGH);
    // fait une pause de 40 ms
    delay(40);
}

Et hop, une petite vidéo d’illustration !

Voici aussi un exemple intégré dans le simulateur interactif pour que vous puissiez tester sans matériel :

!(https://www.tinkercad.com/embed/eATxXUrRJGE)

https://www.youtube.com/watch?v=fyJPb3F3_ag

Faire clignoter un groupe de LED

Vous avouerez facilement que ce n’était pas bien difficile d’arriver jusque-là. Alors, à présent, accentuons la difficulté. Notre but : faire clignoter un groupe de LED.

Le matériel et les schémas

Ce groupe de LED sera composé de six LED, nommées L1, L2, L3, L4, L5 et L6. Vous aurez par conséquent besoin d’un nombre identique de résistances. Le schéma de la réalisation :

Schéma avec 6 leds
Schéma avec 6 leds
Montage avec 6 leds
Montage avec 6 leds
Le programme

Le programme est un peu plus long que le précédent, car il ne s’agit plus d’allumer une seule LED, mais six ! Voilà l’organigramme que va suivre notre programme :

Description du programme
Description du programme

Cet organigramme n’est pas très beau, mais il a le mérite d’être assez lisible. Nous allons essayer de le suivre pour créer notre programme. Traduction des six premières instructions :

digitalWrite(L1, LOW); // notez que le nom de la broche a changé
digitalWrite(L2, LOW); // et ce pour toutes les LED connectées
digitalWrite(L3, LOW); // au micro-controleur
digitalWrite(L4, LOW);
digitalWrite(L5, LOW);
digitalWrite(L6, LOW);

Ensuite, on attend 1,5 seconde :

delay(1500);

Puis on traduit les six autres instructions :

digitalWrite(L1, HIGH); // on éteint les LED
digitalWrite(L2, HIGH);
digitalWrite(L3, HIGH);
digitalWrite(L4, HIGH);
digitalWrite(L5, HIGH);
digitalWrite(L6, HIGH);

Enfin, la dernière ligne de code, disons que nous attendrons 4,32 secondes :

delay(4320);

Tous ces bouts de code sont à mettre à la suite et dans la fonction loop() pour qu’ils se répètent.

void loop()
{
    digitalWrite(L1, LOW);  // allumer les LED
    digitalWrite(L2, LOW);
    digitalWrite(L3, LOW);
    digitalWrite(L4, LOW);
    digitalWrite(L5, LOW);
    digitalWrite(L6, LOW);

    delay(1500);             // attente du programme de 1,5 secondes

    digitalWrite(L1, HIGH);   // on éteint les LED
    digitalWrite(L2, HIGH);
    digitalWrite(L3, HIGH);
    digitalWrite(L4, HIGH);
    digitalWrite(L5, HIGH);
    digitalWrite(L6, HIGH);

    delay(4320);             // attente du programme de 4,32 secondes
}
la boucle complète

Je l’ai mentionné dans un de mes commentaires entre les lignes du programme, les noms attribués aux broches sont à changer. En effet, car si on définit des noms de variables identiques, le compilateur n’aimera pas ça et vous affichera une erreur. En plus, le micro-contrôleur ne pourrait pas exécuter le programme car il ne saurait pas quelle broche mettre à l’état HAUT ou BAS. Pour définir les broches, on fait la même chose qu’à notre premier programme :

const int L1 = 2; // broche 2 du micro-contrôleur se nomme maintenant : L1
const int L2 = 3; // broche 3 du micro-contrôleur se nomme maintenant : L2
const int L3 = 4; // ...
const int L4 = 5;
const int L5 = 6;
const int L6 = 7;
Définition des broches

Maintenant que les broches utilisées sont définies, il faut dire si ce sont des entrées ou des sorties :

pinMode(L1, OUTPUT); // L1 est une broche de sortie
pinMode(L2, OUTPUT); // L2 est une broche de sortie
pinMode(L3, OUTPUT); // ...
pinMode(L4, OUTPUT);
pinMode(L5, OUTPUT);
pinMode(L6, OUTPUT);
Le programme final

Il n’est certes pas très beau, mais il fonctionne :

const int L1 = 2; // broche 2 du micro-contrôleur se nomme maintenant : L1
const int L2 = 3; // broche 3 du micro-contrôleur se nomme maintenant : L2
const int L3 = 4; // ...
const int L4 = 5;
const int L5 = 6;
const int L6 = 7;

void setup()
{
    pinMode(L1, OUTPUT); // L1 est une broche de sortie
    pinMode(L2, OUTPUT); // L2 est une broche de sortie
    pinMode(L3, OUTPUT); // ...
    pinMode(L4, OUTPUT);
    pinMode(L5, OUTPUT);
    pinMode(L6, OUTPUT);
}

void loop()
{
    // allumer les LED
    digitalWrite(L1, LOW);
    digitalWrite(L2, LOW);
    digitalWrite(L3, LOW);
    digitalWrite(L4, LOW);
    digitalWrite(L5, LOW);
    digitalWrite(L6, LOW);

    // attente du programme de 1,5 secondes
    delay(1500);

    // on éteint les LED
    digitalWrite(L1, HIGH);
    digitalWrite(L2, HIGH);
    digitalWrite(L3, HIGH);
    digitalWrite(L4, HIGH);
    digitalWrite(L5, HIGH);
    digitalWrite(L6, HIGH);

    // attente du programme de 4,32 secondes
    delay(4320);
}
Allumage puis extinction en boucle d’un groupe de leds

Voilà, vous avez en votre possession un magnifique clignotant, que vous pouvez attacher à votre vélo ! :P

Une question me chiffonne. Doit-on toujours écrire l’état d’une sortie, ou peut-on faire plus simple ?

C’est là un un point intéressant. Si je comprends bien, vous vous demandez comment faire pour remplacer l’intérieur de la fonction loop() ? C’est vrai que c’est très lourd à écrire et à lire ! Il faut en effet s’occuper de définir l’état de chaque LED. C’est rébarbatif, surtout si vous en aviez mis autant qu’il y a de broches disponibles sur la carte ! Il y a une solution pour faire ce que vous dites. Nous allons la voir dans quelques chapitres, ne soyez pas impatient ! ;) En attendant, voici une vidéo d’illustration du clignotement :

Et son illustration sur simulateur interactif :

!(https://www.tinkercad.com/embed/9RkGGvT63dd)

Réaliser un chenillard

Le but du programme

Le but du programme que nous allons créer va consister à réaliser un chenillard. Pour ceux qui ne savent pas ce qu’est un chenillard, je vous ai préparé une petite image .gif animée :

Chenillard
Chenillard

Comme on dit souvent, une image vaut mieux qu’un long discours ! :P Voilà donc ce qu’est un chenillard. Chaque LED s’allume alternativement et dans l’ordre. De la gauche vers la droite ou l’inverse, c’est au choix.

Organigramme

Comme j’en ai marre de faire des dessins avec paint.net, je vous laisse réfléchir tout seuls comme des grands à l’organigramme du programme. … Bon, aller, le voilà cet organigramme ! Attention, il n’est pas complet, mais si vous avez compris le principe, le compléter ne vous posera pas de problèmes :

Organigramme
Organigramme

À vous de jouer !

Le programme

Normalement, sa conception ne devrait pas vous poser de problèmes. Il suffit en effet de récupérer le code du programme précédent ("allumer un groupe de LED") et de le modifier en fonction de notre besoin. Ce code, je vous le donne, avec les commentaires qui vont bien :

const int L1 = 2; // broche 2 du micro-contrôleur se nomme maintenant : L1
const int L2 = 3; // broche 3 du micro-contrôleur se nomme maintenant : L2
const int L3 = 4; // ...
const int L4 = 5;
const int L5 = 6;
const int L6 = 7;

void setup()
{
    pinMode(L1, OUTPUT); // L1 est une broche de sortie
    pinMode(L2, OUTPUT); // L2 est une broche de sortie
    pinMode(L3, OUTPUT); // ...
    pinMode(L4, OUTPUT);
    pinMode(L5, OUTPUT);
    pinMode(L6, OUTPUT);
}

// on change simplement l’intérieur de la boucle pour atteindre notre objectif

void loop() // la fonction loop() exécute le code qui suit en le répétant en boucle
{
    digitalWrite(L1, LOW);   // allumer L1
    delay(1000);             // attendre 1 seconde
    digitalWrite(L1, HIGH);  // on éteint L1
    digitalWrite(L2, LOW);   // on allume L2 en même temps que l'on éteint L1
    delay(1000);             // on attend 1 seconde
    digitalWrite(L2, HIGH);  // on éteint L2 et
    digitalWrite(L3, LOW);   // on allume immédiatement L3
    delay(1000);             // ...
    digitalWrite(L3, HIGH);
    digitalWrite(L4, LOW);
    delay(1000);
    digitalWrite(L4, HIGH);
    digitalWrite(L5, LOW);
    delay(1000);
    digitalWrite(L5, HIGH);
    digitalWrite(L6, LOW);
    delay(1000);
    digitalWrite(L6, HIGH);
}
Votre premier chenillard

Vous le voyez, ce code est très lourd et n’est pas pratique. Nous verrons plus loin comment faire en sorte de l’alléger. Mais avant cela, un TP arrive… Au fait, voici un exemple de ce que vous pouvez obtenir !

En simulation ça donne ça :

!(https://www.tinkercad.com/embed/ffr2VQgC9yp)

https://www.youtube.com/watch?v=pDouLvyUxBg

Fonction millis()

Nous allons terminer ce chapitre par un point qui peut être utile, notamment dans certaines situations où l’on ne veut pas arrêter le programme. En effet, si on veut faire clignoter une LED sans arrêter l’exécution du programme, on ne peut pas utiliser la fonction delay() qui met en pause le programme durant le temps défini.

Les limites de la fonction delay()

Vous avez probablement remarqué, lorsque vous utilisez la fonction delay() tout notre programme s’arrête le temps d’attendre. Dans certains cas ce n’est pas un problème mais dans certains cas ça peut être plus gênant. Imaginons, vous êtes en train de faire avancer un robot. Vous mettez vos moteurs à une vitesse moyenne, tranquille, jusqu’à ce qu’un petit bouton sur l’avant soit appuyé (il clic lorsqu’on touche un mur par exemple). Pendant ce temps-là, vous décidez de faire des signaux en faisant clignoter vos LED. Pour faire un joli clignotement, vous allumez une LED rouge pendant une seconde puis l’éteignez pendant une autre seconde. Voilà par exemple ce qu’on pourrait faire comme code

void setup()
{
    pinMode(moteur, OUTPUT);
    pinMode(led, OUTPUT);
    pinMode(bouton, INPUT);
    // on met le moteur en marche (en admettant qu'il soit en marche à HIGH)
    digitalWrite(moteur, HIGH);
    // on allume la LED
    digitalWrite(led, LOW);
}

void loop()
{
    // si le bouton est cliqué (on rentre dans un mur)
    if(digitalRead(bouton)==HIGH)
    {
        // on arrête le moteur
        digitalWrite(moteur, LOW);
    }
    else // sinon on clignote
    {
        digitalWrite(led, HIGH);
        delay(1000);
        digitalWrite(led, LOW);
        delay(1000);
    }
}

Attention ce code n’est pas du tout rigoureux voire faux dans son écriture, il sert juste à comprendre le principe !

Maintenant imaginez. Vous roulez, tester que le bouton n’est pas appuyé, donc faites clignoter les LED (cas du else). Le temps que vous fassiez l’affichage en entier s’écoule 2 longues secondes ! Le robot a pu pendant cette éternité se prendre le mur en pleine poire et les moteurs continuent à avancer tête baissée jusqu’à fumer ! Ce n’est pas bon du tout ! Voici pourquoi la fonction millis() peut nous sauver.

Découvrons et utilisons millis()

Tout d’abord, quelques précisions à son sujet, avant d’aller s’en servir. À l’intérieur du cœur de la carte Arduino se trouve un chronomètre. Ce chrono mesure l’écoulement du temps depuis le lancement de l’application. Sa granularité (la précision de son temps) est la milliseconde. La fonction millis() nous sert à savoir quelle est la valeur courante de ce compteur. Attention, comme ce compteur est capable de mesurer une durée allant jusqu’à 50 jours, la valeur retournée doit être stockée dans une variable de type "long".

C’est bien gentil mais concrètement on l’utilise comment ?

Eh bien c’est très simple. On sait maintenant "lire l’heure". Maintenant, au lieu de dire "allume-toi pendant une seconde et ne fais surtout rien pendant ce temps", on va faire un truc du genre "Allume-toi, fais tes petites affaires, vérifie l’heure de temps en temps et si une seconde est écoulée, alors réagis !". Voici le code précédent transformé selon la nouvelle philosophie :

long temps; // variable qui stocke la mesure du temps
boolean etat_led;

void setup()
{
    pinMode(moteur, OUTPUT);
    pinMode(led, OUTPUT);
    pinMode(bouton, INPUT);
    // on met le moteur en marche
    digitalWrite(moteur, HIGH);
    // par défaut la LED sera éteinte
    etat_led = 0;
    // on éteint la LED
    digitalWrite(led, etat_led);

    // on initialise le temps
    temps = millis();
}

void loop()
{
    // si le bouton est cliqué (on rentre dans un mur)
    if(digitalRead(bouton)==HIGH)
    {
        // on arrête le moteur
        digitalWrite(moteur, LOW);
    }
    else // sinon on clignote
    {
        // on compare l'ancienne valeur du temps et la valeur sauvée
        // si la comparaison (l'un moins l'autre) dépasse 1000...
        // ...cela signifie qu'au moins une seconde s'est écoulée
        if((millis() - temps) > 1000)
        {
            etat_led = !etat_led; // on inverse l'état de la LED
            digitalWrite(led, etat_led); // on allume ou éteint
            temps = millis(); // on stocke la nouvelle heure
        }
    }
}
Clignotement avec millis

Et voilà, grâce à cette astuce plus de fonction bloquante. L’état du bouton est vérifié très fréquemment ce qui permet de s’assurer que si jamais on rentre dans un mur, on coupe les moteurs très vite. Dans ce code, tout s’effectue de manière fréquente. En effet, on ne reste jamais bloqué à attendre que le temps passe. À la place, on avance dans le programme et teste souvent la valeur du chronomètre. Si cette valeur est de 1000 itérations supérieures à la dernière valeur mesurée, alors cela signifie qu’une seconde est passée.

Attention, au if de la ligne 33 ne faites surtout pas millis() - temps == 1000. Cela signifierait que vous voulez vérifier que 1000 millisecondes EXACTEMENT se sont écoulées, ce qui est très peu probable (vous pourrez plus probablement mesurer plus ou moins mais rarement exactement)


Maintenant que vous savez maîtriser le temps, vos programmes/animations vont pouvoir posséder un peu plus de "vie" en faisant des pauses, des motifs, etc. Impressionnez-moi !