Aller au contenu principal

Lire le monde : boutons et capteurs

Ce que ce chapitre apporte

  • Lire l'état d'une entrée numérique, et expliquer pourquoi une entrée non reliée rend n'importe quoi.
  • Choisir entre une résistance de tirage externe et INPUT_PULLUP, et prévoir la valeur lue au repos et à l'appui.
  • Détecter un appui plutôt qu'un état, et compter les appuis sans être trompé par les rebonds.
  • Convertir une lecture analogique en tension, et une tension en lecture.
  • Ramener une lecture à une autre échelle avec map, en connaissant ses arrondis et ses limites.

Écrire sur une broche est facile : la carte impose 5 V ou 0 V, et la LED obéit. Lire est plus délicat, parce que le monde physique ne se présente pas proprement. Une entrée que rien ne relie flotte et rend n'importe quoi, un bouton qui se ferme rebondit pendant quelques millisecondes, un capteur délivre une tension continue qu'il faut ramener à un nombre. Ce chapitre traite ces trois difficultés, dans l'ordre où elles surviennent.

Une entrée numérique

digitalRead(broche) rend HIGH ou LOW selon la tension présente sur la broche. Sur l'Uno alimentée en 5 V, une tension supérieure à environ 3 V est lue HIGH, une tension inférieure à environ 1,5 V est lue LOW.

Définition

Une entrée est flottante quand rien ne la relie à une tension définie. Elle se comporte alors comme une antenne : les parasites, la proximité d'une main, le voisinage d'un fil suffisent à faire changer sa lecture.

Le montage naïf, un bouton entre la broche et le 5 V, produit exactement cette situation. Bouton enfoncé, la broche est à 5 V et lit HIGH. Bouton relâché, elle n'est plus reliée à rien, et sa lecture est indéfinie : elle peut rester haute, retomber, osciller.

La résistance de tirage

La solution consiste à relier la broche, au repos, à une tension connue, à travers une résistance assez forte pour que le bouton l'emporte quand il est enfoncé.

MontageAu reposBouton enfoncéConfiguration
Résistance de 10 kΩ vers GND, bouton vers 5VLOWHIGHpinMode(2, INPUT)
Résistance interne vers 5 V, bouton vers GNDHIGHLOWpinMode(2, INPUT_PULLUP)

Le second montage est le plus employé : il n'ajoute aucun composant, la résistance étant intégrée au microcontrôleur. Il a une contrepartie, qui trompe systématiquement la première fois : la logique est inversée. Un bouton enfoncé se lit LOW.

Arduino
const int BOUTON = 2;
const int LED = 13;
void setup() {
pinMode(BOUTON, INPUT_PULLUP); // bouton entre la broche 2 et GND
pinMode(LED, OUTPUT);
}
void loop() {
if (digitalRead(BOUTON) == LOW) { // LOW signifie enfoncé
digitalWrite(LED, HIGH);
} else {
digitalWrite(LED, LOW);
}
}

Un état n'est pas un appui

Compter les appuis semble simple : incrémenter un compteur quand le bouton est enfoncé. Mais loop() s'exécute des milliers de fois par seconde, et un appui humain dure un dixième de seconde. Tester l'état fait donc compter des centaines d'appuis pour une seule pression.

Ce qu'il faut détecter, c'est le changement d'état : le moment où la lecture passe de relâché à enfoncé. On garde pour cela la lecture précédente, et l'on compare.

Arduino
const int BOUTON = 2;
int precedent = HIGH; // INPUT_PULLUP : relâché se lit HIGH
int appuis = 0;
void setup() {
Serial.begin(9600);
pinMode(BOUTON, INPUT_PULLUP);
}
void loop() {
int etat = digitalRead(BOUTON);
if (etat == LOW && precedent == HIGH) { // on vient d'enfoncer le bouton
appuis++;
Serial.println(appuis);
}
precedent = etat;
}

Les rebonds

Ce programme compte encore trop, et la raison est mécanique. Quand deux lames métalliques se touchent, elles rebondissent pendant quelques millisecondes : le contact s'établit, se rompt, s'établit de nouveau. Chaque rebond est un vrai changement d'état, que le programme compte comme un appui.

2 ms par bit500 bauds32 bits, 64 ms en tout
lecture brute00101011111111111111111111111111état retenu00000000000000000000011111111111dernier rebondstable depuis 30 ms016 ms32 ms48 ms64 ms
Un appui vu par la broche, sur 64 ms (ici avec un montage où l'appui se lit HIGH). La lecture brute change cinq fois en douze millisecondes. L'état retenu ne change qu'une fois, lorsque la lecture n'a plus bougé pendant 30 ms.
L'anti-rebond qui oublie le relâchement
Ajouter un delay(50) juste après avoir compté un appui paraît régler le problème : les rebonds de l'appui tombent pendant la pause. Mais le bouton rebondit aussi quand on le relâche, et ces rebonds-là produisent un nouveau front, compté comme un second appui. Une simulation d'un seul appui avec rebonds donne ainsi 2 au lieu de 1.

La méthode fiable ne se fie pas au premier changement : elle n'accepte un nouvel état que s'il est resté stable pendant un délai fixé, à l'appui comme au relâchement.

Arduino
const int BOUTON = 2;
const unsigned long STABLE_MS = 30; // durée de stabilité exigée
int lectureBrute = HIGH; // dernière lecture, même instable
int etatStable = HIGH; // état accepté après stabilité
unsigned long dernierChangement = 0;
int appuis = 0;
void setup() {
Serial.begin(9600);
pinMode(BOUTON, INPUT_PULLUP);
}
void loop() {
int lecture = digitalRead(BOUTON);
if (lecture != lectureBrute) { // la lecture bouge : on relance le chronomètre
lectureBrute = lecture;
dernierChangement = millis();
}
if (millis() - dernierChangement >= STABLE_MS && lectureBrute != etatStable) {
etatStable = lectureBrute; // stable depuis 30 ms : on l'accepte
if (etatStable == LOW) { // et c'est un appui
appuis++;
Serial.println(appuis);
}
}
}

Ce programme compte un appui par pression, quels que soient les rebonds, et ignore une impulsion parasite plus courte que 30 ms. Il n'emploie aucun delay : il ne bloque jamais le reste du programme, ce que le chapitre 7 généralise.

Une entrée analogique

Un capteur de température, de lumière, un potentiomètre ne délivrent pas deux états mais une tension continue. Les broches A0 à A5 la mesurent.

Définition

analogRead(broche) convertit la tension présente, entre 0 et 5 V, en un entier de 0 à 1023. Le convertisseur travaille sur 10 bits, soit 1024 valeurs possibles.

La convention employée dans ce parcours fait correspondre 5 V à la valeur maximale :

lecture=tension×10235tension=lecture×51023\text{lecture} = \text{tension} \times \frac{1023}{5} \qquad \text{tension} = \text{lecture} \times \frac{5}{1023}

Un palier représente donc environ 4,9 mV : c'est la plus petite variation de tension que la carte puisse distinguer. Une lecture de 512 correspond à 2,50 V, une lecture de 768 à 3,75 V.

Le potentiomètre est le capteur le plus simple : ses deux extrémités vont au 5 V et à la masse, son curseur à A0, et tourner le bouton fait varier la tension du curseur de 0 à 5 V.

Changer d'échelle avec map

Il faut souvent ramener une lecture à une autre échelle : de 0 à 1023 vers 0 à 255 pour piloter une LED, ou vers un pourcentage. map fait cette règle de trois.

Arduino
void setup() {
Serial.begin(9600);
}
void loop() {
int lecture = analogRead(A0); // de 0 à 1023
int pourcent = map(lecture, 0, 1023, 0, 100); // de 0 à 100
Serial.println(pourcent);
delay(200);
}
Deux propriétés de map à connaître
map calcule en entiers et tronque : map(768, 0, 1023, 0, 255) rend 191, alors que le calcul exact donne 191,4. Elle ne borne pas non plus son résultat : une valeur hors de l'échelle de départ donne une valeur hors de l'échelle d'arrivée, map(1100, 0, 1023, 0, 255) rendant 274. Quand l'entrée peut sortir de sa plage, constrain ramène le résultat dans les bornes.

Exercices type

Un bouton relie la broche 2 à la masse, configurée en INPUT_PULLUP. Que lit digitalRead au repos, bouton enfoncé, et que lirait-elle en INPUT simple ?

Au repos, la résistance interne tire la broche vers le 5 V : HIGH. Bouton enfoncé, la broche est reliée à la masse : LOW. La logique est inversée par rapport à l'intuition.

En INPUT simple, aucune résistance ne tire la broche au repos : elle flotte, et sa lecture est indéfinie. Le programme peut sembler fonctionner un moment, puis réagir à un appui fantôme quand on approche la main.

Un programme incrémente un compteur dès que digitalRead(2) == LOW. Un appui d'un dixième de seconde fait monter le compteur de plusieurs milliers. Pourquoi, et quelle est la correction ?

Le programme compte un état, pas un appui. loop() tourne des milliers de fois pendant que le bouton est enfoncé, et chaque tour ajoute 1.

La correction compare la lecture courante à la précédente, et ne compte que le passage de HIGH à LOW. Pour ne pas être trompé par les rebonds, ce changement n'est accepté que si la lecture est restée stable une trentaine de millisecondes, à l'appui comme au relâchement.

Un potentiomètre donne une lecture de 768. Quelle tension cela représente-t-il, et que rend map(768, 0, 1023, 0, 255) ?

La tension vaut 768×5/10233,75768 \times 5 / 1023 \approx 3{,}75 V.

map calcule 768×255/1023=191,4768 \times 255 / 1023 = 191{,}4 et tronque : elle rend 191. Diviser la lecture par 4, un raccourci fréquent, rendrait 192 : les deux méthodes diffèrent d'une unité, ce qui est sans conséquence pour une luminosité, mais pas pour une comparaison à un seuil exact.

Pourquoi un delay(50) placé juste après le comptage d'un appui ne suffit-il pas à supprimer les rebonds ?

La pause absorbe les rebonds qui suivent l'appui. Mais le contact rebondit aussi au relâchement : la lecture repasse brièvement à l'état enfoncé, ce qui fait un nouveau front, compté comme un nouvel appui.

Une seule pression est alors comptée deux fois. La méthode par état stable règle les deux cas à la fois, puisqu'elle attend que la lecture ait cessé de bouger avant d'accepter un changement, dans un sens comme dans l'autre.

Vérification

Vérification rapideon peut se reprendre

1.Une broche configurée en INPUT n'est reliée à rien. Que rend digitalRead ?

2.Avec INPUT_PULLUP et un bouton relié à la masse, que lit-on bouton enfoncé ?

3.Pourquoi un bouton mécanique produit-il plusieurs changements d'état pour un seul appui ?

4.Quelle tension correspond à une lecture de 512 ?

5.Combien de valeurs différentes analogRead peut-elle rendre ?

6.Que rend map(1100, 0, 1023, 0, 255) ?

La méthode

  1. Donner un état de repos à toute entrée numérique : INPUT_PULLUP avec un bouton vers la masse, ou une résistance de tirage externe.
  2. Écrire la table des états avant le code : ce qui se lit au repos, ce qui se lit à l'appui.
  3. Détecter les changements, et non les états, dès qu'il s'agit de compter ou de basculer.
  4. Exiger la stabilité d'une lecture avant de l'accepter, au lieu de s'en remettre à un delay.
  5. Convertir sur une valeur connue : 512 donne 2,50 V ; vérifier le calcul avant de brancher le capteur.
  6. Borner avec constrain tout résultat de map dont l'entrée peut sortir de sa plage.

Synthèse

  • digitalRead rend HIGH au-dessus d'environ 3 V, LOW en dessous d'environ 1,5 V.
  • Une entrée flottante rend une valeur indéfinie ; une résistance de tirage lui donne un état au repos.
  • INPUT_PULLUP : pas de composant à ajouter, bouton vers la masse, enfoncé se lit LOW.
  • Compter des appuis, c'est détecter un changement d'état, pas un état.
  • Les contacts rebondissent : un changement n'est accepté que s'il reste stable une trentaine de millisecondes, à l'appui comme au relâchement.
  • analogRead rend de 0 à 1023 pour 0 à 5 V : tension = lecture × 5 / 1023.
  • map calcule en entiers, tronque, et ne borne pas son résultat.

Mettre en pratique