DIego Yourself
 Accueil

Plan :


Niveau : Débutant

Comment gérer les boutons ?

20.09.2016

1. Un seul bouton : principes de base

Détecter l'appui sur un bouton avec votre Arduino UNO est une question qui est très souvent posée dans les forums dédiés. Cela peut sembler une tâche simple au premier abord, mais en pratique vous pouvez rapidement vous retrouver confronté à des problèmes "étranges" comme la détection de plusieurs appuis "fantômes" alors que vous n'en avez fait qu'un seul, voir aucun ; ou d'autres comportements erratiques selon ce que vous souhaitez faire... et ce sans parvenir à comprendre ce qui vous arrive malgré des heures passées dessus...
Dans ce tutoriel vous allez apprendre non seulement à gérer l'appui d'un bouton, mais aussi l'appui de plusieurs boutons distincts avec votre Arduino.

Connaissances pré-requises :

Vous l'aurez compris, pour "faire de l'Arduino" cela requiert donc non-seulement de s'intéresser à l'électronique mais aussi à la programmation, qui est tout aussi importante pour que vos montages fonctionnent correctement. C'est un aspect que malheureusement beaucoup négligent bien trop souvent, alors s'il vous plaît, ne commencez pas en commettant cette très mauvaise erreur vous aussi !

Matériel nécessaire :

Pour réaliser ce tutoriel il vous faudra disposer des éléments suivants :

Branchement "naïf" :

Commençons en faisant ce branchement "naïf", qui est le premier branchement que nous avons "naturellement" envie de faire, disons en "toute innocence" (donc méconnaissance) de ce qui se passe. À l'aide de votre plaque d'essais, faites le montage suivant :

Mauvais montage d'un bouton
Schéma logique

Mauvais montage d'un bouton
Schéma de montage

J'ai choisi au hasard l'entrée numéro 8, mais cela va fonctionner avec n'importe laquelle des entrées digitales entre 2 et 13 de votre carte Arduino (évitez la 0 et la 1 si vous utilisez, comme dans ce tutoriel, la communication série). Si vous choisissez une entrée différente, veillez à modifier votre code source pour que ce soit la bonne entrée (celle que vous avez choisi) qui est utilisée.

Puis, téléversez le code suivant dans votre carte Arduino :

// entrée digitale où le bouton est branché
#define PIN_ENTREE_BOUTON 8
 
// Variable globale contenant l'état du bouton,
// initialisée avec le bouton non-appuyé (donc relâché)
bool boutonAppuye = false;
 
void setup()
{
// Configurer la PIN où est branché le bouton en tant qu'une entrée
pinMode(PIN_ENTREE_BOUTON, INPUT);
 
// Configuration de la connexion série
Serial.begin(115200);
}
 
void loop()
{
// lire l'état de l'entrée dans une nouvelle variable temporaire
bool etatBouton = digitalRead(PIN_ENTREE_BOUTON);
 
// Si l'état du bouton a changé (son état actuel est différent de l'état précédent)
if ( etatBouton != boutonAppuye )
{
// mettre à jour l'état courant du bouton
boutonAppuye = etatBouton;
 
// Comme l'état vient de changer, nous allons afficher sur la console le nouvel état :
 
// Si le bouton est appuyé
if ( boutonAppuye )
{
// afficher l'état appuyé
Serial.println(Appuye);
}
// sinon, le bouton est donc forcément relâché
else
{
// afficher l'état relâché
Serial.println(Relache);
}
}
 
// Remarque : si l'état du bouton n'a pas changé il n'y a donc rien à faire
}
Fichier à télécharger : tuto_bouton1.ino

Comme vous pouvez le voir, il s'agit d'un code très simple qui affiche l'état du bouton (appuyé ou relâché) sur la console série dès que l'état de l'entrée associée change.

Pensez à ouvrir la console série pour pouvoir "voir" ce qui se passe dans votre Arduino.

Alors ?

Le montage n'est pas difficile ni défectueux, vous n'appuyez pas sur le bouton et pourtant vous voyez défiler des appuis et relâchements intempestifs sur votre écran !
Quelque soit le cas, essayez quand même d'appuyer sur le bouton et regardez ce qui se passe...

Mais que se passe-t-il ?

Au premier abord nous nous imaginons que l'électronique est quelque chose de très carré et sans interférences, dans notre tête nous avons l'impression que l'appui d'un bouton correspond au schéma suivant du point de vue de l'entrée de votre Arduino :

Fonctionnement hypothétique d'un bouton

Si vous branchez un bouton directement comme ça, il y a énormément d'interférences électromagnétiques autour de votre Arduino, suffisantes pour lui faire croire que le bouton a été appuyé alors qu'en fait non. Donc en fait le signal n'est vraiment pas aussi "beau", la réalité en est toute autre.

Le vrai signal qui entre dans votre carte Arduino ressemble plutôt à ceci :

Interférences électriques d'un bouton

Toutes ces variations de tension que vous voyez (petits pics dans tous les sens) sont dues aux interférences que subissent tant votre bouton, que les fils utilisés, que votre carte Arduino elle-même : un sacré bazar ! Bazar, qui ne risque donc pas de s'améliorer avec l'incessante incrémentation des champs électromagnétiques environnants tels que le Wi-Fi, le chargeur de votre téléphone, la 4G, les objets connectés, etc...

Alors, comment fait-on pour remédier à tout ça ?

Ne vous inquiétez pas, il y a une solution, cette solution est physique (électronique) ET logicielle (le code qui va dans votre carte). Commençons par le côté physique, sans modifier ni donc téléverser un quelconque nouveau code source. Modifiez votre montage électronique de la façon suivante, avec le simple ajout d'une résistance de 10KΩ vers la masse au niveau de l'entrée de votre carte Arduino :

Montage d'un bouton en pull-down
Schéma logique

Montage d'un bouton en pull-down
Schéma de montage

Vous verrez que maintenant vous n'avez plus d'appuis intempestifs sans avoir rien fait, c'est déjà pas mal hein ! Mais si vous appuyez sur le bouton il peut arriver que la carte Arduino détecte plusieurs appuis alors que vous ne l'avez fait qu'une seule fois...

Que se passe-t-il ?

En ajoutant la résistance nous avons fait ce qui en langage technique s'appelle "une résistance de pull-down", en d'autres mots, puisque c'est du franglais, nous avons ajouté une résistance qui "tire vers le bas".
Et elle tire quoi vers le bas ?
L'électricité (tension) pardi ! En mettant une résistance de pull-down vous "stabilisez" le signal en entrée en le ramenant à 0V (la masse). Comme ça les interférences ne sont plus assez fortes pour influencer votre signal d'entrée. C'est grâce à cela que vous ne voyez plus d'appuis "fantômes" dans votre console tant que vous ne faites rien !
Et pourquoi une résistance de 10KΩ et pas d'une autre valeur ?
Simplement parce que c'est ce qui est recommandé dans la documentation technique du microcontrôleur Atmel 328P qui est en quelque sorte le "cerveau" de votre Arduino !

Le signal en entrée de votre carte ressemble donc plutôt à ceci maintenant, grâce à cette simple résistance de pull-down :

Comportement de l'appui sur un bouton

Vous voyez cependant que l'appui sur le bouton n'est pas tout à fait ce que nous attendons non plus. Ces "pics" qui sont appelés des "rebonds", provoqués par l'appui ou le relâchement de votre bouton, peuvent créer des perturbations du signal suffisantes pour faire croire à votre Arduino que vous avez appuyé, relâché puis ré-appuyé sur le bouton plusieurs fois...

Comment faire pour ne détecter qu'un seul appui alors ?

Même s'il peut y avoir des solutions électroniques pour remédier à cela, le plus simple c'est de passer cette fois par le code source à téléverser sur votre carte. Procéder ainsi rend aussi la solution ajustable pour mieux s'adapter à votre besoin sans devoir acheter des nouveaux composants. Ce qui à la longue peut être pénible, surtout lorsque vous êtes en train d'apprendre et de faire plein d'essais, mais aussi par la suite pour limiter simplement le nombre de composants électroniques à utiliser.

Sans modifier votre montage, téléversez maintenant le code suivant dans votre carte Arduino :

// entrée digitale où le bouton est branché
#define PIN_ENTREE_BOUTON 8
 
// temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
// seront ignorés
#define TEMPS_ANTI_REBOND_MS 50
 
// Variable globale contenant l'état du bouton,
// initialisée avec le bouton non-appuyé (donc relâché)
bool boutonAppuye = false;
 
// compteur de temps pour l'anti-rebond, initialisé à 0 ce qui veut
// dire qu'il n'y a pas d'anti-rebond en cours
unsigned long dateAntiRebond = 0;
 
void setup()
{
// Configurer la PIN où est branché le bouton en tant qu'une entrée
pinMode(PIN_ENTREE_BOUTON, INPUT);
 
// Configuration de la connexion série
Serial.begin(115200);
}
 
void loop()
{
// lire l'état de l'entrée dans une nouvelle variable temporaire
bool etatBouton = digitalRead(PIN_ENTREE_BOUTON);
 
// si le bouton semble avoir été appuyé
if ( etatBouton )
{
// si l'anti-rebond est déjà en train d'ignorer les rebonds
if ( dateAntiRebond != 0 )
{
// obtenir et déterminer combien de temps s'est écoulé depuis le début
// de l'anti-rebond ( = date actuelle - date de début)
unsigned long tempsEcoule = millis() - dateAntiRebond;
 
// si le temps écoulé est supérieur ou égal au temps d'anti-rebond
if ( tempsEcoule >= TEMPS_ANTI_REBOND_MS )
{
// Considérer alors que le bouton est appuyé
boutonAppuye = true;
 
// arrêter l'anti-rebond
dateAntiRebond = 0;
 
// afficher l'état appuyé sur la console
Serial.println(Appuye);
}
}
// sinon, si le bouton est actuellement considéré comme relâché (non-appuyé)
else if ( !boutonAppuye )
{
// activer l'anti-rebond en obtenant la date actuelle du système
dateAntiRebond = millis();
}
// sinon, le bouton est déjà considéré comme appuyé, il n'y a rien à faire
}
// sinon, le bouton semble avoir été relâché
else
{
// si le bouton était appuyé
if ( boutonAppuye )
{
// le considérer maintenant comme relâché
boutonAppuye = false;
 
// afficher l'état relâché sur la console
Serial.println(Relache);
}
// sinon, le bouton était déjà relâché ou l'anti-rebond était en cours
else
{
// arrêter l'anti-rebond s'il était en cours
dateAntiRebond = 0;
}
}
}
Fichier à télécharger : tuto_bouton2.ino

Faites plusieurs essais, vous verrez, maintenant c'est terminé : il n'y a plus d'appuis "fantômes", seulement vos appuis réels provoquent des affichages sur la console !

Vous pouvez utiliser ce code tel quel sur vos projets, prenez note donc que la variable boutonAppuye contient l'état courant du bouton et que pour détecter uniquement les moments où le bouton est appuyé ou relâché (respectivement le "front montant" ou le "front descendant") vous devrez placer votre code là où se trouvent les Serial.println()

Ceci dit, c'est bien de faire du copier-coller, mais ce qui est encore mieux c'est de comprendre ce que vous copiez... Pourquoi ? parce que c'est seulement ainsi que vous pourrez être en mesure de modifier le code pour l'adapter à vos besoins.

Alors, que fait tout ce code ?

Essayez de lire et comprendre le code que vous venez de téléverser. Plusieurs relectures sont souvent nécessaires. Ne vous inquiétez pas, des explications vont suivre...
Le principe est relativement simple, il s'agit d'ignorer les rebonds tant que le signal n'est pas stable. Cela porte un nom, c'est "l'anti-rebond" en français ou encore "debounce" en anglais.

Comment faisons-nous cela ?
Sur le schéma suivant vous voyez le résultat de ce principe logique appliqué par notre code source, la courbe verte est celle de l'état du bouton tel que le code le considère (donc l'état de la variable boutonAppuye) :

Bouton avec anti-rebond

Le code ignore donc les rebonds tant que 50ms ("Temps de l'anti-rebond" sur le schéma) ne se sont pas écoulées sans rebond. En d'autres mots, le code attend que le signal soit stable pendant au moins 50ms pour considérer que le bouton est appuyé.
Pour une meilleure réactivité du bouton, seulement les rebonds lors de l'appui du bouton sont ignorés/masqués de la sorte et comme vous le constatez à l'utilisation, c'est suffisant. L'état relâché est donc considéré comme l'état "par défaut" du bouton, à priori "plus stable" que l'état appuyé. Les rebonds du relâchement sont ainsi automatiquement ignorés, ne menant pas sur un état "appuyé" stable. N'oubliez pas que l'objectif c'est de se rapprocher autant que possible du moment réel où l'action d'appui ou relâchement a vraiment été effectuée.
C'est, certes, une logique un peu particulière à saisir au premier abord, mais elle est utilisée ainsi en électronique car elle s'avère très efficace par sa simplicité de mise en œuvre.

Concernant le code en lui-même, il faut remarquer que la variable dateAntiRebond sert à deux choses, ce qui peut être déroutant si vous n'êtes pas familier avec le principe :

  1. Lorsqu'elle vaut 0 (zéro), cela a une signification spécifique : l'anti-rebond n'est pas en train d'ignorer les rebonds. Si vous préférez, cela veut dire que votre bouton est considéré comme dans un état stable ne nécessitant aucune analyse particulière ;
  2. Lorsqu'elle vaut autre chose que 0 ( != 0 ) : elle contient la "date" du moment où l'anti-rebond a été activé. En d'autres mots, vous êtes en train d'attendre que 50ms d'un signal stable se soient écoulées avant de considérer le bouton comme appuyé.

C'est quoi cette histoire de date ?

Cette "date", obtenue par l'appel de la fonction millis(), n'est rien d'autre que le nombre de millisecondes qui se sont écoulées depuis le dernier reset ou allumage de votre carte Arduino. Le schéma suivant tente de vous démontrer le principe utilisé. La valeur de T=54989ms est prise au hasard pour démontrer le principe via un exemple concret, pour qu'il soit plus parlant. La fonction millis() renvoie tout simplement la valeur de T au moment où elle est appelée :

Principe du timer en utilisant la fonction millis() sur Arduino

C'est un principe général important et utile pour gérer le temps avec votre carte Arduino, nous allons le réutiliser dans la suite du tutoriel pour gérer plusieurs boutons simultanément.

Maintenant, faites quelques essais en modifiant votre code, notamment la valeur de la constante TEMPS_ANTI_REBOND_MS : mettez des valeurs plus grandes, jusqu'à 1000ms (1 seconde) ou plus petites, jusqu'à 0ms. Vérifiez le comportement du bouton après téléversement.

Pourquoi 50ms ?

La valeur du temps de l'anti-rebond est déterminée selon l'utilisation qui en est faite de votre bouton. Pour l'utilisation par un être humain, une valeur entre 20 et 50 ms est tout à fait raisonnable et pratique. Cependant pour d'autres utilisations cette valeur peut s'avérer trop élevée si la fréquence d'appui sur le bouton est supérieure à 50Hz ( 50 fois par seconde ou plus), ce qui peut arriver lorsque vous utilisez des entrées digitales sur le même principe pour utiliser des capteurs ou détecteurs branchés sur votre Arduino.

Exercices :

Il est temps maintenant de mettre un petit peu en pratique ce que vous avez appris dans cette première partie :

  1. Affichez un "compteur de clics" sur votre console série : comptez le nombre de fois que vous avez appuyé sur votre bouton et affichez ce compteur à chaque appui sur votre bouton.
  2. Indépendamment de la gestion de l'appui sur un bouton et sans utiliser la fonction delay(), affichez un message sur la console toutes les secondes sur le même principe utilisé ici pour l'anti-rebond, en utilisant la fonction millis() pour mesurer le temps.
  3. De manière équivalente à ce qui est expliqué concernant la résistance de "pull-down", vous pouvez aussi utiliser une résistance positionnée en mode "pull-up", qui "tire vers le haut" la tension de votre bouton à l'état de repos. Le montage dans ce cas, serait le suivant :
    Montage d'un bouton en pull-up
    Schéma logique

    Montage d'un bouton en pull-up
    Schéma de montage
    Modifiez votre code source pour prendre en compte cette nouvelle logique de fonctionnement afin de détecter correctement l'appui sur votre bouton.

Réponses :

  1. Le plus simple c'est de modifier le code de gestion d'un bouton pour lui ajouter la gestion de l'incrémentation et l'affichage d'un compteur compteurDeClicks, initialisé à 0 :

    // entrée digitale où le bouton est branché
    #define PIN_ENTREE_BOUTON 8
     
    // temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
    // seront ignorés
    #define TEMPS_ANTI_REBOND_MS 50
     
    // Variable globale contenant l'état du bouton,
    // initialisée avec le bouton non-appuyé (donc relâché)
    bool boutonAppuye = false;
     
    // compteur de temps pour l'anti-rebond, initialisé à 0 ce qui veut
    // dire qu'il n'y a pas d'anti-rebond en cours
    unsigned long dateAntiRebond = 0;
     
    // compteur de clics/appuis sur votre bouton
    int compteurDeClicks = 0;
     
    void setup()
    {
    // Configurer la PIN où est branché le bouton en tant qu'une entrée
    pinMode(PIN_ENTREE_BOUTON, INPUT);
     
    // Configuration de la connexion série
    Serial.begin(115200);
    }
     
    void loop()
    {
    // lire l'état de l'entrée dans une nouvelle variable temporaire
    bool etatSignal = digitalRead(PIN_ENTREE_BOUTON);
     
    // si le bouton semble avoir été appuyé (pas de signal, ou signal = 0/false)
    if ( !etatSignal )
    {
    // si l'anti-rebond est déjà en train d'ignorer les rebonds
    if ( dateAntiRebond != 0 )
    {
    // obtenir et déterminer combien de temps s'est écoulé depuis le début
    // de l'anti-rebond ( = date actuelle - date de début)
    unsigned long tempsEcoule = millis() - dateAntiRebond;
     
    // si le temps écoulé est supérieur ou égal au temps d'anti-rebond
    if ( tempsEcoule >= TEMPS_ANTI_REBOND_MS )
    {
    // Considérer alors que le bouton est appuyé
    boutonAppuye = true;
     
    // arrêter l'anti-rebond
    dateAntiRebond = 0;
     
    // comme le bouton vient d'être considéré comme appuyé (front montant)
    // c'est le moment d'incrémenter le compteur de clicks
    compteurDeClicks++;
     
    // Afficher le compteur de clicks
    Serial.print(Clicks : );
    Serial.println(compteurDeClicks);
    }
    }
    // sinon, si le bouton est actuellement considéré comme relâché
    else if ( !boutonAppuye )
    {
    // activer l'anti-rebond en obtenant la date actuelle du système
    dateAntiRebond = millis();
    }
    // sinon, le bouton est déjà considéré comme appuyé, il n'y a rien à faire
    }
    // sinon, le bouton semble avoir été relâché
    else
    {
    // si le bouton était appuyé
    if ( boutonAppuye )
    {
    // le considérer maintenant comme relâché
    boutonAppuye = false;
    }
    // sinon, le bouton était déjà relâché ou l'anti-rebond était en cours
    else
    {
    // arrêter l'anti-rebond s'il était en cours
    dateAntiRebond = 0;
    }
    }
    }
    Fichier à télécharger : tuto_bouton3.ino
    Vous remarquerez que rien n'est affiché lorsque le bouton est relâché, c'est normal, l'exercice demande seulement d'afficher le compteur à chaque appui.
  2. Le code source suivant illustre le principe de décompte du temps en utilisant la fonction millis(), tout en affichant le même message que sur le tutoriel d'affichage sur la console :

    // Compteur
    int compteur=1;
     
    // Date pour la gestion du temps
    unsigned long date;
     
    void setup()
    {
    // Démarrage de la connexion série
    Serial.begin(115200);
     
    // Initialiser la date
    date = millis();
    }
     
    void loop()
    {
    // obtenir la date courante de la carte
    unsigned long dateCourante = millis();
     
    // si une seconde (1000ms) s'est écoulée
    if ( (dateCourante - date) >= 1000 )
    {
    // Afficher le début de la phrase sans aller à la ligne
    Serial.print(Bonjour Diego, pour la );
     
    // Afficher la valeur du compteur sans aller à la ligne
    Serial.print(compteur);
     
    // Afficher la fin de la phrase ET aller à la ligne
    Serial.println(e fois !);
     
    // incrémenter le compteur pour le prochain tour
    compteur++;
     
    // réinitialiser la date pour compter la prochaine seconde
    date = dateCourante;
    }
    }
    Fichier à télécharger : tuto_console_millis.ino
    Ce qui est important de comprendre en utilisant ce principe, dit "non bloquant", c'est que contrairement à la fonction delay() qui est "bloquante" parce qu'elle "stoppe" votre code en attendant que le temps se soit écoulé ; l'utilisation de millis() vous permet de faire autre chose pendant tout le temps que vous devez attendre. Sans cela vous ne pourriez gérer plusieurs capteurs (ou boutons) simultanément, ce qui serait trop contraignant et bien dommage, du "gaspillage de temps" en d'autres mots...
  3. La logique de ce montage est inversée, commençons par déterminer la forme qu'aurait le signal en entrée d'un tel montage :
    Signal d'un bouton en pull-up
    La résistance de pull-up force et maintient le signal d'entrée au maximum (5V sur votre Arduino UNO) et lorsque vous appuyez sur le bouton vous le ramenez à 0V (la masse). Au premier abord cela peut sembler "illogique", mais en pratique c'est plus souvent comme cela que les signaux sont traités.
    Si vous n'aviez pas réussi l'exercice, réessayez maintenant sans aller plus loin dans la lecture de la solution, cet indice peut vous avoir mis sur une piste aussi importante qu'utile !

    Le code source qui s'adapte à ce signal est en fait presque le même que pour la gestion d'un bouton monté en pull-down, la solution est très simple, il suffit de changer la ligne de lecture du signal d'entrée comme suit :

    bool etatBouton = !digitalRead(PIN_ENTREE_BOUTON);

    En inversant ainsi la gestion du signal à l'aide de l'opérateur " ! " de négation logique, vous retombez sur exactement le même schéma et traitement que lorsqu'il est monté en pull-up ! La courbe d'état de votre variable ressemble maintenant à ceci :
    Anti-rebond d'un bouton en pull-up

Pull-up, pull-down, au final ça revient au même non ?

En théorie oui en effet, cela vous donne la possibilité de choisir comment vous traitez votre signal. En pratique, lorsque vous utiliserez d'autres capteurs que des boutons, ils peuvent fonctionner d'une manière qui vous impose d'utiliser l'une ou l'autre des façons de faire. Il est donc très important de connaître les deux.

Bonus !

En bonus, votre carte Arduino a l'agréable avantage d'avoir été conçue avec une résistance en pull-up déjà en place, à l'intérieur même de votre microcontrôleur ! Pour gérer des boutons c'est super car cela nous évite d'avoir à utiliser notre propre résistance pull-up et par conséquent cela simplifie aussi nos montages !

Pour utiliser cette résistance pull-up "intérieure", il faut l'activer. Pour ce faire, il suffit de configurer la PIN d'entrée en conséquence :

pinMode(PIN_ENTREE_BOUTON, INPUT_PULLUP);

Ca y est ! Vous avez une résistance branchée en mode pull-up sur votre entrée !

Et c'est quoi le montage alors ?

Le montage devient très simple, il est très proche du montage "naïf" du début de cet article, à l'exception près que votre bouton est branché sur la masse plutôt que sur le +5V :

Bouton en mode pull-up interne
Schéma logique

Bouton en mode pull-up interne
Schéma de montage

Le code est le même que celui pour gérer un bouton en "pull-up" tel que décrit dans l'exercice 3 ci-dessus.

Bravo ! La gestion d'un bouton, ainsi que tout capteur binaire (à deux états "ON/OFF", comme un bouton) se comportant de la même façon n'a plus de secrets pour vous !

Mais savoir gérer un seul bouton c'est bien, sauf que généralement ce n'est pas assez, vous avez 14 entrées binaires, donc vous pourriez potentiellement utiliser jusqu'à 14 boutons directement branchés sur votre carte Arduino. Dans la suite de ce tutoriel, nous allons approfondir un peu la gestion en parallèle de plusieurs boutons ou capteurs binaires.

  Retourner en haut