I. L’attente active▲
La première fonction que l'on utilise s'appelle delay
().
Comme son nom l'indique, cette fonction crée un délai dans le code, c'est-à-dire que le microcontrôleur ne va rien faire pendant un certain temps : on arrête le programme. Pour cela on indique pendant combien de temps attendre en millisecondes, en paramètre de la fonction, sous forme d'un nombre de type unsigned
long
.
Par exemple pour faire clignoter une LED il faut l'allumer, attendre un moment dans cet état, l'éteindre, attendre un moment dans cet état puis recommencer. On pourrait donc coder cela et utiliser la loop() pour boucler.
const
byte
brocheLed =
3
; // le N° de broche de la LED
void
setup() {
pinMode
(brocheLed, OUTPUT
); // on met la broche en sortie
}
void
loop()
{
digitalWrite
(brocheLed, HIGH
); // allume la LED
delay
(1000
); // bloquer pendant 1000 millisecondes (1s)
digitalWrite
(brocheLed, LOW
); // éteint la LED
delay
(1000
); // bloquer pendant 1000 millisecondes (1s)
}
La fonction delay
() ne vous permet pas d'attendre moins d'une milliseconde. Votre Arduino fonctionne assez vite, avec une fréquence en MHz (16 MHz pour un UNO) et donc 1 milliseconde, c'est long pour le microcontrôleur : le temps d'effectuer 16 000 instructions de base sur un UNO !
Si vous souhaitez avoir un temps de blocage du code inférieur à la milliseconde, il existe la fonction delayMicroseconds(). Cette fonction a un rôle similaire et prend en paramètre le temps de blocage en microsecondes (en millièmes de milliseconde, soit en millionièmes de seconde). Le paramètre est de type unsigned
int
, mais il ne faut pas dépasser 16 383 µs et il ne faut pas non plus descendre sous 3 µs.
II. Mesurer le temps qui passe▲
Une autre caractéristique du temps, c'est qu'il s'écoule inexorablement. Votre code pourrait avoir à mesurer ce temps qui passe. Par exemple, vous avez une voiture qui fait des tours de circuit et vous voulez mesurer le temps au tour.
Votre Arduino est configuré pour capturer ce temps qui passe depuis qu'il a été démarré (à froid – première alimentation ou à chaud après un reset).
Cette fonction qui vous dit depuis combien de temps votre programme fonctionne s'appelle millis(). La valeur retournée est de type unsigned
long
, ça veut dire que le compteur peut aller jusqu'à 4 294 967 295, ce qui représente 49 jours 17 heures 2 minutes 47 secondes et 295 millisecondes. Si votre Arduino est donc allumé depuis plus de 50 jours environ, le compteur recommencera à 0.
Pour mesurer combien de temps s'est écoulé entre deux moments, vous pourriez mémoriser dans une variable la valeur de millis
() au début puis la valeur de millis
() à la fin, et la différence c'est la durée en millisecondes.
unsigned
long
debut;
unsigned
long
fin;
unsigned
long
duree;
void
setup() {
Serial
.begin(115200
); // configuration de la voie série à 115200 bauds
}
void
loop() {
debut =
millis
();
... // ici vous effectuez un traitement assez long
fin =
millis
();
duree =
fin -
debut; // calcul de la durée
Serial
.print(F("Durée de traitement : "
));
Serial
.print(duree);
}
La granularité de la mesure sera de l'ordre de la milliseconde. Si vous souhaitez obtenir une mesure plus précise, il existe la fonction micros() qui vous retourne le nombre de microsecondes écoulées depuis le lancement du programme. C'est aussi un unsigned
long
, mais comme on compte en microsecondes, la valeur revient à 0 mille fois plus vite, au bout de 1 heure, 11 minutes, 34 secondes et 967 millisecondes et 295 microsecondes. Sur les Arduino à 16 Mhz la résolution de la valeur retournée par micros
() est de 4 microsecondes. C'est la durée minimale que vous serez capable de mesurer en utilisant cette approche.
III. Mesure du temps dans un cas particulier▲
Si le temps que vous souhaitez mesurer est le temps pendant lequel une broche prend une valeur donnée (HIGH
ou LOW
), il existe la fonction pulseIn() qui va faire cela pour vous. Elle fonctionne bien pour une durée dans un état donné entre 10 microsecondes et 3 minutes. La fonction dispose d'un paramètre optionnel qui indique au bout de combien de temps abandonner (timeout) si les fronts attendus ne se sont pas produits.
Par exemple, imaginez que vous souhaitiez mesurer le temps pendant lequel un bouton est appuyé, vous pourriez avoir le code suivant :
const
byte
brocheBouton =
2
;
unsigned
long
duree;
void
setup() {
Serial
.begin(115200
); // configuration de la voie série à 115200 bauds
pinMode
(brocheBouton, INPUT_PULLUP
); // broche en entrée, activation du pullup (pin HIGH par défaut)
}
void
loop() {
duree =
pulseIn
(brocheBouton, LOW
); // mesure de la durée LOW, après une transition HIGH -> LOW
Serial
.print(duree); // affichage du résultat
Serial
.println(F(" µs"
));
}
IV. Gestion asynchrone▲
Comme nous l'avons dit précédemment, lorsque vous utilisez la fonction delay
() ou delayMicroseconds
() tout le programme s’arrête. Ce n'est pas toujours un souci, si le programme n'a rien d'autre à effectuer, mais dans certains cas c'est gênant par exemple si vous souhaitez continuer à surveiller une entrée et répondre rapidement.
Par exemple, nous voulons faire clignoter une LED à une certaine fréquence, sauf si un bouton est appuyé. On pourrait modifier le programme précédent de clignotement ainsi :
const
byte
brocheLed =
3
; // le N° de broche de la LED
const
byte
brocheBouton =
2
; // le N° de broche du bouton
void
setup() {
pinMode
(brocheLed, OUTPUT
); // on met la broche en sortie
pinMode
(brocheBouton, INPUT_PULLUP
); // broche en entrée, activation du pullup (pin HIGH par défaut)
}
void
loop()
{
if
(digitalRead
(brocheBouton) ==
HIGH
) {
// si le bouton n'est pas appuyé
digitalWrite
(brocheLed, HIGH
); // allume la LED
delay
(1000
); // bloquer pendant 1000 millisecondes (1s)
digitalWrite
(brocheLed, LOW
); // éteint la LED
delay
(1000
); // bloquer pendant 1000 millisecondes (1s)
}
}
Le souci cependant c'est que si on appuie sur le bouton juste après avoir allumé la LED, rien ne va se passer et si on ne le tient pas appuyé plus de 2 secondes, le code ne verra pas la broche à LOW
lors du prochain tour de boucle : notre programme n'est pas réactif, l'expérience utilisateur est mauvaise.
Pour corriger cela, il faut écrire un programme qui ne va pas bloquer le microcontrôleur.
On va donc commencer par écrire un clignotement qui ne bloque pas, sans delay
(). Il suffit de mémoriser le moment de dernier changement d'état de la LED (debut) et laisser la LED dans cet état jusqu'à ce qu'une certaine durée se soit écoulée. À ce moment, on inverse à nouveau l'état de la LED, on note ce nouveau moment et on recommence.
const
byte
brocheLed =
3
; // le N° de broche de la LED
unsigned
long
debut;
void
inverserLed() {
// on inverse l'état de la broche
if
(digitalRead
(brocheLed) ==
HIGH
) {
digitalWrite
(brocheLed, LOW
); // éteint la LED
}
else
{
digitalWrite
(brocheLed, HIGH
); // allume la LED
}
// on mémorise le moment de changement d'état
debut =
millis
();
}
void
setup() {
pinMode
(brocheLed, OUTPUT
); // on met la broche en sortie
inverserLed(); // on active la LED
}
void
loop()
{
unsigned
long
maintenant =
millis
();
if
(maintenant -
debut >=
1000
) {
// si plus d'une seconde s'est écoulée depuis le début du changement d'état
inverserLed(); // alors on inverse l'état
}
}
Tout le secret est donc dans le test :
if
(maintenant -
debut >=
1000
)
que l'on répète dans la boucle. Le calcul dit : « si la durée écoulée entre le début et maintenant dépasse 1000 millisecondes alors… ».
Vous remarquez qu'il n'y a pas de else
. Donc si le temps n'est pas écoulé, on ne fait rien et la boucle va juste reboucler et on va continuer à tester si c'est le bon moment. Ça ne change donc pas trop du code précédent, on clignote, mais la grande différence, c'est que l'on peut donc effectuer d'autres traitements si le temps n'est pas écoulé.
Par exemple, on pourrait dire « si le bouton est appuyé, éteindre la LED et ne plus clignoter ».
const
byte
brocheLed =
13
; // le N° de broche de la LED
const
byte
brocheBouton =
2
; // le N° de broche du bouton
unsigned
long
debut;
void
inverserLed() {
// on inverse l'état de la broche
if
(digitalRead
(brocheLed) ==
HIGH
) {
digitalWrite
(brocheLed, LOW
); // éteint la LED
}
else
{
digitalWrite
(brocheLed, HIGH
); // allume la LED
}
// on mémorise le moment de changement d'état
debut =
millis
();
}
void
setup() {
pinMode
(brocheLed, OUTPUT
); // on met la broche en sortie
pinMode
(brocheBouton, INPUT_PULLUP
); // broche en entrée, activation du pullup (pin HIGH par défaut)
inverserLed(); // on active la LED
}
void
loop()
{
if
(digitalRead
(brocheBouton) ==
HIGH
) {
// si le bouton n'est pas appuyé on regarde s'il faut clignoter
unsigned
long
maintenant =
millis
();
if
(maintenant -
debut >=
1000
) {
// si plus d'une seconde s'est écoulée depuis le début du changement d'état
inverserLed(); // alors on inverse l'état
}
}
else
{
// le bouton est appuyé
digitalWrite
(brocheLed, LOW
); // on s'assure que la LED est éteinte
}
}
Dans ce code, on va regarder si c'est le moment de clignoter seulement si le bouton n'est pas appuyé, sinon on éteint la LED. Comme nous n'avons plus de delay
(), le code ne bloque jamais et donc notre programme sera maintenant très réactif : dès que vous pressez le bouton, la LED s'éteint et dès que vous le relâchez, elle se remet à clignoter.
Une erreur fréquente est de prendre un int
ou unsigned
int
comme type de variable pour représenter le temps (debut, maintenant). Sur un UNO ou MEGA un int
ne peut pas aller au-delà de 32 767 et un unsigned
int
au-delà de 65 535 – soit environ 33 ou 66 secondes. Donc si vous testez votre code pendant quelques secondes, vous n'allez rien déceler, mais en production, très vite votre code aura un comportement anormal. Il est donc important de toujours prendre un unsigned
long
(que l'on note aussi uint32_t
pour unsigned
int
sur 32 bits) pour représenter le temps.
Vous remarquerez que le test sur la durée est écrit sous la forme maintenant -
debut >=
1000
. On voit souvent des codes avec maintenant >=
debut +
1000
et c'est mathématiquement similaire dans le cas général. Cependant, il faut retenir la première formulation et bannir la seconde si votre code doit tourner longtemps. En effet, quand vous approcherez des ~50 jours fatidiques, le unsigned
long
qui permet de stocker debut va s'approcher de sa valeur maximale. Rajouter 1000 à cette valeur va donc faire déborder ce qui est représentable et on se retrouvera avec une valeur proche de zéro.
La valeur de millis
() (maintenant) sera elle très grande (proche des 50 jours) et donc le test maintenant >=
debut +
1000
sera tout de suite vrai, sans avoir attendu les 1000 ms. En prenant l'approche par soustraction, même lorsque maintenant a débordé et retourné à 0 et que debut est proche de 50 jours, maintenant - debut représentera bien la bonne durée, et le test sera valable.
V. Pour aller plus loin▲
Nous avons décrit la gestion du temps au niveau du microcontrôleur, mais pas forcément ce que vous dit votre montre : « Samedi 13 mars 2021 à 20h30 ».
Votre Arduino n'a pas de notion du jour et de l'heure, il sait juste depuis quand il a été allumé.
Si vous avez besoin de gérer cela, il vous faudra un composant externe, une horloge, appelée RTC (Real Time Clock). Celles que l'on trouve le plus fréquemment sont les DS1307 et DS3231/DS3232. Si vous avez le choix, laissez de côté la DS1307 (elle dérive trop vite et ne sera pas précise sur plusieurs semaines/mois) et préférez une DS3231 (ou DS3232).
Il existe des alternatives : si votre Arduino dispose d'une connexion à Internet, vous pouvez interroger un serveur de temps (serveur NTP) ou si un GPS est connecté sur votre montage, il est généralement possible d'obtenir l'heure grâce aux satellites GPS.
Et si vous avez encore des questions, n’hésitez pas à ouvrir une discussion dans le forum Arduino.