Dès vos premières applications, vous avez pu constater que la gestion du temps revient très fréquemment dans la programmation des diverses applications qui nous concernent. Cela va de choses simples comme les animations lumineuses à base de DEL comme dans « Chenillard de DEL » ou « Enseigne de magasin » ou bien l’envoi d’une impulsion de durée déterminée à un moteur d’aiguillage électromagnétique, par exemple, à des choses plus complexes comme la détection du début et de la fin du passage d’un train avec un capteur infrarouge en distinguant les intervalles entre les wagons qui génèrent des impulsions courtes et la fin du passage qui génère une impulsion d’une longueur qui dépend du moment où un autre train va se présenter.
Nous avons déjà vu dans l’exemple de programme à la fin de « La programmation, qu’est ce que c’est » le clignotement d’une DEL. Pour rappel, la fonction loop()
intiale était comme ceci :
void loop() {
digitalWrite(13, HIGH);
digitalWrite(13, LOW);
}
La broche 13 correspondant à la DEL qui se trouve sur toutes les cartes Arduino. Dans ce cas, l’exécution est si rapide que nos yeux ne voient pas le clignotement de la DEL [1].
Supposons une animation de travaux avec un feu qui flashe toutes les secondes. Comment incorporer ce laps de temps dans le programme précédent ?.
En première approche, nous cherchons à faire des choses simples et nous allons naturellement nous tourner vers la fonction delay(...)
.
La fonction delay(...)
Cette première fonction est simple. Comme son nom l’indique, elle ajoute un délai pendant lequel la carte arduino marque une pause dans l’exécution du code. Entre parenthèses, unique argument de la fonction, se place le temps de la pause dont l’unité est la milliseconde. Ce qui ajouté au programme donne :
void loop() {
digitalWrite(13, HIGH);
delay(20);
digitalWrite(13, LOW);
delay(980);
}
et ainsi la DEL émet un flash de 20ms toutes les secondes.
Si nous voulons une pause plus précise que la milliseconde, il existe la fonction delayMicroseconds(...)
. Entre parenthèses se place le temps en microsecondes.
L’inconvénient majeur de ces deux fonctions est que pendant le temps spécifié, la carte est bloquée, elle ne peut pas continuer l’exécution du programme. Or, par exemple, de nombreuses tâches nécessitent une lecture fréquente des broches programmées en entrée : détection de train, lecture d’un bouton poussoir.
Nous allons prendre un exemple de la vie de tous les jours pour illustrer le propos. Imaginez que vous avez à mener de front deux tâches : faire cuire un œuf à la coque et guetter le facteur pour lui remettre une lettre qui ne vous est pas destiné. Si vous restez planté devant la pendule pour minuter l’œuf à la coque, vous raterez le facteur. C’est ce que l’on fait en utilisant delay(...)
, on attend devant la pendule et on ne fait rien d’autre. Si vous restez planté devant la fenêtre à guetter le facteur, votre œuf sera trop cuit.
L’autre solution consiste à jeter régulièrement un œil à la pendule tout en guettant le facteur. Si le temps de cuisson de l’œuf est atteint, on le sort de l’eau. De cette manière il est possible de faire deux choses presque en même temps.
Nous allons donc voir maintenant cette autre fonction qui permet de jeter un œil à la pendule.
La fonction millis()
La fonction millis()
renvoie une date [2] qui est le nombre de millisecondes qui se sont écoulées depuis la mise sous tension ou le dernier reset. Contrairement à delay(...)
, cette fonction n’est pas bloquante [3], elle permet juste de jeter un œil à la pendule. Il faut ensuite utiliser la date pour décider de ce que le programme doit faire.
Reprenons l’exemple simple de la DEL clignotante. Utilisons tout d’abord une variable pour mémoriser l’état de la DEL et une variable pour mémoriser la date du dernier changement d’état de la DEL. Comme millis()
renvoie une valeur de type unsigned long [4], cette date est un unsigned long
initialisé à 0 :
byte etatDEL = HIGH;
unsigned long dateDernierChangement = 0;
Ensuite, dans loop()
, nous allons jeter un œil à la pendule en appelant la fonction millis()
et stocker son résultat dans une variable dateCourante
. Ensuite, nous allons calculer l’intervalle de temps en soustrayant dateDernierChangement
à dateCourante
puis, selon l’état de la DEL et l’intervalle de temps, décider si on doit l’allumer ou l’éteindre. Enfin, si un changement d’état de la DEL a eu lieu, il faut mémoriser dans dateDernierChangement
la date de l’événement de manière à pouvoir calculer le prochain intervalle :
void loop()
{
unsigned long dateCourante = millis();
unsigned long intervalle = dateCourante - dateDernierChangement;
if (etatDEL == HIGH && intervalle > 20) {
// extinction de la DEL car elle est allumee (HIGH) et 20ms se sont écoulées
etatDEL = LOW;
digitalWrite(13, etatDEL);
dateDernierChangement = dateCourante;
}
else if (etatDEL == LOW && intervalle > 980) {
// allumage de la DEL car elle est éteinte (LOW) et 980ms se sont écoulées
etatDEL = HIGH;
digitalWrite(13, etatDEL);
dateDernierChangement = dateCourante;
}
}
Pour l’instant, le progrès n’est pas spectaculaire. On a écrit plus de code mais le programme fait exactement la même chose. Ajoutons maintenant une seconde DEL avec un cycle d’allumage différent. On va gérer simultanément les deux DEL. On appelle cela du multiplexage. Cette seconde DEL est connectée sur la broche 12 et s’allume lorsque cette broche est HIGH. Elle va être allumée pendant 100ms et éteinte pendant 200ms. Pour savoir comment raccorder une DEL, reportez vous à « Fonctionnement et pilotage d’une DEL ».
Nous allons avoir besoin de deux fois plus de variables puisque nous avons maintenant deux DEL. Voici les déclarations de ces variables dont les noms sont suffixés par le numéro de broche de la DEL concernée.
byte etatDEL13 = HIGH;
byte etatDEL12 = HIGH;
unsigned long dateDernierChangement13 = 0;
unsigned long dateDernierChangement12 = 0;
Pour clarifier le programme, nous allons créer deux fonctions. La première va gérer la DEL de la broche 13 et la seconde va gérer la DEL de la broche 12.
Voici pour commencer la fonction qui gère la DEL de la broche 13, c’est un simple copier-coller du code qui est dans loop()
ci-dessus :
void flashDEL13()
{
unsigned long dateCourante = millis();
unsigned long intervalle = dateCourante - dateDernierChangement13;
if (etatDEL13 == HIGH && intervalle > 20) {
// extinction de la DEL car elle est allumée (HIGH) et 20ms se sont écoulées
etatDEL13 = LOW;
digitalWrite(13, etatDEL13);
dateDernierChangement13 = dateCourante;
}
else if (etatDEL13 == LOW && intervalle > 980) {
// allumage de la DEL car elle est éteinte (LOW) et 980ms se sont écoulées
etatDEL13 = HIGH;
digitalWrite(13, etatDEL13);
dateDernierChangement13 = dateCourante;
}
}
La seconde fonction, flashDEL12()
est similaire. Seules changent les intervalles, les variables de mémorisation de l’état de la DEL et de la date du dernier changement et, bien sûr, la broche :
void flashDEL12()
{
unsigned long dateCourante = millis();
unsigned long intervalle = dateCourante - dateDernierChangement12;
if (etatDEL12 == HIGH && intervalle > 100) {
// extinction de la DEL car elle est allumée (HIGH) et 100ms se sont écoulées
etatDEL12 = LOW;
digitalWrite(12, etatDEL12);
dateDernierChangement12 = dateCourante;
}
else if (etatDEL12 == LOW && intervalle > 200) {
// allumage de la DEL car elle est éteinte (LOW) et 200ms se sont écoulées
etatDEL12 = HIGH;
digitalWrite(12, etatDEL12);
dateDernierChangement12 = dateCourante;
}
}
Enfin, dans loop()
, nous allons appeler les deux fonctions que nous venons de voir.
void loop()
{
flashDEL13();
flashDEL12();
}
Voici une vidéo montrant l’exécution de ce programme
Voici également le programme à télécharger :
Le programme n’est pas d’une grande élégance. Les deux fonctions ne diffèrent que par les variables utilisées. On pourrait tout à fait ajouter les variables utilisées en argument pour n’avoir qu’une fonction mais une solution encore plus élégante est d’utiliser des objets. Nous verrons ultérieurement comment s’y prendre.
Pour terminer, il existe également une fonction micros()
qui donne la date en microsecondes. Cette fonction renvoie également une donnée de type unsigned long.