Le temps : delay(), millis() et les événements
En une phrase : delay() met le programme en pause, alors que millis() regarde l'horloge sans s'arrêter : c'est ce qui permet de faire plusieurs choses en même temps.
Compter le temps en millisecondes¶
L'ESP32 compte le temps en millisecondes (ms) : 1 seconde = 1 000 ms.
| Durée | En millisecondes |
|---|---|
| un clignement d'œil | environ 300 ms |
un tour de loop() avec delay(10) |
un peu plus de 10 ms |
| une seconde | 1 000 ms |
| une minute | 60 000 ms |
Fréquence et période¶
Pour un phénomène qui se répète, on parle de fréquence (combien de fois par seconde, en hertz, Hz) ou de période (combien de temps dure un cycle). Les deux sont liées :
période (ms) = 1 000 ÷ fréquence (Hz)
Une LED qui clignote 2 fois par seconde (2 Hz) a une période de 1 000 ÷ 2 = 500 ms : 250 ms allumée, 250 ms éteinte.
delay() : la sieste¶
Simple et efficace… tant qu'il n'y a qu'une seule chose à faire. Pendant la sieste, l'ESP32 n'écoute plus rien : ni la manette, ni les boutons.
Le problème, en chiffres
Le programme de la mission 0 passe 1 000 ms par tour de loop() dans deux delay(500). S'il devait aussi lire la manette, il ne la regarderait qu'une fois par seconde : appuie sur ✕ pendant un quart de seconde au mauvais moment, et l'appui est tout simplement raté.
millis() : l'horloge¶
millis() donne le nombre de millisecondes écoulées depuis le démarrage de la carte. Au lieu de dormir, on compare l'heure :
unsigned long dernierChangement = 0;
bool allumee = false;
void loop() {
if (millis() - dernierChangement >= 500) { // 500 ms sont passées ?
dernierChangement = millis();
allumee = !allumee; // on inverse
digitalWrite(LED, allumee ? HIGH : LOW);
}
// ici, le programme continue : il peut lire la manette en même temps !
}
C'est comme vérifier sa montre plutôt que de faire une sieste : « est-ce que c'est l'heure ? Non ? Alors je fais autre chose et je regarde de nouveau dans un instant. »
delay() |
millis() |
|
|---|---|---|
| Ce qu'il fait | met en pause | donne l'heure |
| Pendant l'attente | le programme dort | le programme continue |
| Idéal pour | les premiers essais, les petits programmes | tout ce qui réagit à la manette |
delay() : pendant les zones grises, le programme dort et rate tout. En bas, millis() : le programme vérifie l'heure en permanence et reste attentif.La notation condition ? A : B
allumee ? HIGH : LOW se lit « si allumee est vrai, alors HIGH, sinon LOW ». C'est un if miniature, pratique dans une seule ligne.
Plusieurs chronos en même temps¶
Le grand avantage de millis() : chaque tâche peut avoir son propre chrono. Voir la mise en pratique plus bas, avec deux LEDs à des rythmes différents.
Le temps qui avance plus ou moins vite¶
Dans le banc d'essai de la mission 1, les scénarios ne lisent pas millis() directement : ils lisent une horloge maison, qui avance plus ou moins vite selon la vitesse choisie avec les flèches ↑ ↓. C'est comme ça qu'on accélère un clignotement sans toucher à son code.
Une horloge qui fait le tour
millis() est rangé dans un unsigned long, qui ne dépasse pas 4 294 967 295. Ça fait 4 294 967 295 ms ÷ 1 000 ÷ 60 ÷ 60 ÷ 24 ≈ 49,7 jours. Au-delà, l'horloge repasse à 0. Écrire millis() - debut >= duree (et pas millis() >= debut + duree) évite tout souci ce jour-là : la soustraction reste juste même quand l'horloge a fait le tour.
Les événements : réagir au bon moment¶
Un bouton appuyé une demi-seconde est vu 50 fois par un loop() qui tourne toutes les 10 ms. Pour ne réagir qu'une fois, on compare avec ce qu'on avait vu au tour précédent :
bool avant = false;
void loop() {
bool maintenant = manette->a();
if (maintenant && !avant) { // appuyé maintenant, pas appuyé avant :
Serial.println("✕ !"); // il VIENT d'être enfoncé
}
avant = maintenant;
}
avant |
maintenant |
Ce qui vient de se passer |
|---|---|---|
| non | non | rien, le bouton est au repos |
| non | oui | le bouton vient d'être enfoncé ← l'événement |
| oui | oui | il est toujours tenu |
| oui | non | il vient d'être relâché (un autre événement !) |
C'est exactement ce que fait la fonction vientDAppuyer du banc d'essai de la mission 1.
Et les vrais boutons ?
Un bouton-poussoir branché sur une broche « rebondit » : au moment de l'appui, le contact s'ouvre et se ferme plusieurs fois en quelques millisecondes. Il faut alors ignorer les changements trop rapprochés (moins de 20 ms environ) : c'est l'anti-rebond. La manette, elle, fait ce travail toute seule.
Les étapes : une petite machine à états¶
Pour un enchaînement (« boum, attendre, boum, attendre… ») sans delay(), on note à quelle étape on en est, et l'heure de départ. À chaque tour de loop(), on regarde si c'est l'heure de passer à l'étape suivante. C'est la technique du battement de cœur de la mission 1 : le programme garde une variable etapeCoeur et un tableau des moments où vibrer.
Mise en pratique : deux LEDs, deux rythmes¶
Sur le montage de la piste d'atterrissage, fais clignoter D18 toutes les 300 ms et D19 toutes les 700 ms, en même temps. Impossible avec delay(), facile avec deux chronos :
const int LED_A = 18, LED_B = 19;
unsigned long chronoA = 0, chronoB = 0;
bool etatA = false, etatB = false;
void setup() {
pinMode(LED_A, OUTPUT);
pinMode(LED_B, OUTPUT);
}
void loop() {
unsigned long maintenant = millis();
if (maintenant - chronoA >= 300) { // la LED A a son propre chrono
chronoA = maintenant;
etatA = !etatA;
digitalWrite(LED_A, etatA);
}
if (maintenant - chronoB >= 700) { // la LED B aussi
chronoB = maintenant;
etatB = !etatB;
digitalWrite(LED_B, etatB);
}
}
Regarde bien : de temps en temps, les deux LEDs changent en même temps. Tous les combien ? Le plus petit nombre divisible à la fois par 300 et par 700 : 2 100 ms.
Exercices¶
1. Une LED doit clignoter 5 fois par seconde. Combien de temps allumée, combien éteinte ?
Période : 1 000 ÷ 5 = 200 ms, donc 100 ms allumée, 100 ms éteinte.
2. millis() vaut 12 500. Depuis combien de secondes la carte est-elle allumée ?
12 500 ÷ 1 000 = 12,5 secondes.
3. Un loop() contient delay(20). Combien de fois par seconde lit-il la manette, au maximum ?
1 000 ÷ 20 = 50 fois par seconde (un peu moins en vrai, car le reste du code prend aussi un peu de temps).
4. Modifie la mise en pratique pour ajouter une 3ᵉ LED sur D21, qui clignote toutes les 1 100 ms.
Il suffit d'ajouter une variable chronoC, une variable etatC, un pinMode(21, OUTPUT) et un troisième bloc if (maintenant - chronoC >= 1100) { … }. Avec plus de LEDs, un tableau de chronos et une boucle for seraient encore mieux : voir tableaux, boucles et fonctions.
À retenir
- 1 s = 1 000 ms ; période = 1 000 ÷ fréquence.
delay()= pause : tout s'arrête.millis()= horloge : on vérifie l'heure et on continue.- Toujours écrire
millis() - debut >= duree. - Un événement, c'est un changement : comparer avec le tour d'avant.
Où ça sert dans l'atelier¶
- Mission 0 · Écrire le programme : le clignotement avec
delay(). - Mission 1 · Comment il est construit : scénarios avec
millis(), boutons avecvientDAppuyer. - Mission 1 · Défi 2 · La manette qui bat comme un cœur : des vibrations au bon moment, sans bloquer.
Pour aller plus loin¶
- 🇫🇷 Introduire le temps, cours Arduino d'Eskimon :
delay(),millis()et un chenillard. - 🇬🇧 Blink Without Delay, l'exemple officiel Arduino.
- 🇬🇧 La référence officielle : delay() et millis().