DIego Yourself
 Accueil

Plan :


Niveau : Débutant

Comment gérer les boutons ?

20.09.2016

2. Plusieurs boutons

Plein de boutons

Pour mieux appréhender cette partie du tutoriel, je vous conseille fortement d'avoir au moins lu la "Partie 2 : Techniques « avancées » du langage C " du tutoriel pour apprendre le C.

Maintenant que la gestion d'un bouton n'a plus de secrets pour vous, nous allons pouvoir passer à la vitesse supérieure. En effet, c'est bien joli de savoir détecter l'activité d'un capteur/bouton sur une entrée, mais c'est encore mieux de pouvoir en gérer plusieurs et en même temps.

Commençons par le montage, pour plus de simplicité nous allons utiliser la résistance de pull-up interne à votre Arduino UNO et commencer "simplement" en ne branchant que deux boutons, comme ceci :

Deux boutons branchés en pull-up interne
Schéma logique

Deux boutons branchés en pull-up interne
Schéma de montage

Jusque-là rien à signaler, chaque bouton a son entrée (8 et 9) et ils sont tous branchés vers la masse, comme auparavant.

C'est donc au niveau du code source que les choses vont changer, la première chose que nous avons envie de faire, c'est simplement de dupliquer le code de gestion d'un bouton, comme ceci :

// entrée digitale où le bouton est branché
#define PIN_ENTREE_BOUTON_1 8
#define PIN_ENTREE_BOUTON_2 9
 
// temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
// seront ignorés
#define TEMPS_ANTI_REBOND_MS 30
 
// Variable globale contenant l'état du bouton,
// initialisée avec le bouton non-appuyé (donc relâché)
bool boutonAppuye_1 = false;
bool boutonAppuye_2 = 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_1 = 0;
unsigned long dateAntiRebond_2 = 0;
 
// compteur de clics/appuis sur votre bouton
int compteurDeClicks_1 = 0;
int compteurDeClicks_2 = 0;
 
void setup()
{
// Configurer les PIN où sont branchés les boutons en tant qu'une entrées qui
// utilisent la résistance interne de pull-up
pinMode(PIN_ENTREE_BOUTON_1, INPUT_PULLUP);
pinMode(PIN_ENTREE_BOUTON_2, INPUT_PULLUP);
 
// Configuration de la connexion série
Serial.begin(115200);
}
 
void loop()
{
 
// GESTION DU BOUTON 1 : ------------------------------------------------------------
 
// lire l'état de l'entrée dans une nouvelle variable temporaire
bool etatSignal = !digitalRead(PIN_ENTREE_BOUTON_1);
 
// 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_1 != 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_1;
 
// 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_1 = true;
 
// arrêter l'anti-rebond
dateAntiRebond_1 = 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_1++;
 
// Afficher le compteur de clicks
Serial.print(Clicks sur le bouton 1 : );
Serial.println(compteurDeClicks_1);
}
}
// sinon, si le bouton est actuellement considéré comme relâché
else if ( !boutonAppuye_1 )
{
// activer l'anti-rebond en obtenant la date actuelle du système
dateAntiRebond_1 = 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_1 )
{
// le considérer maintenant comme relâché
boutonAppuye_1 = 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_1 = 0;
}
}
 
// GESTION DU BOUTON 2 : ------------------------------------------------------------
 
// lire l'état de l'entrée dans une nouvelle variable temporaire
etatSignal = !digitalRead(PIN_ENTREE_BOUTON_2);
 
// 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_2 != 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_2;
 
// 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_2 = true;
 
// arrêter l'anti-rebond
dateAntiRebond_2 = 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_2++;
 
// Afficher le compteur de clicks
Serial.print(Clicks sur le bouton 2 : );
Serial.println(compteurDeClicks_2);
}
}
// sinon, si le bouton est actuellement considéré comme relâché
else if ( !boutonAppuye_2 )
{
// activer l'anti-rebond en obtenant la date actuelle du système
dateAntiRebond_2 = 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_2 )
{
// le considérer maintenant comme relâché
boutonAppuye_2 = 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_2 = 0;
}
}
}
Fichier à télécharger : tuto_bouton4.ino

Essayez, ça fonctionne ! Vous pouvez appuyer sur les deux boutons en même temps, garder un appuyé pendant que vous appuyez sur l'autre, et toute autre manip "tordue" qui vous passe par la tête, ça fonctionne vraiment bien.

Mais alors, nous avons terminé non ?

Ça fonctionne, mais du point de vue de la programmation, le fait de dupliquer du code c'est mal. Pourquoi ? Pour un tas de bonnes raisons :

Comment faire alors ? Qu'est-ce-qu'il faut faire pour simplifier tout ça ?

En langage technique, cela s'appelle "factoriser du code", en d'autres mots, éviter de répéter du code en le rendant réutilisable.

Et comment qu'on fait ça ?

En créant une fonction. Une fonction est un bout de code qui peut être appelé avec des paramètres différents autant de fois que vous voulez.
Dans notre cas, nous pouvons factoriser ce bout de code que nous "copions-collons" à chaque fois que nous souhaitons ajouter un bouton, voici ce que ça donnerait :

// entrée digitale où le bouton est branché
#define PIN_ENTREE_BOUTON_1 8
#define PIN_ENTREE_BOUTON_2 9
 
// temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
// seront ignorés
#define TEMPS_ANTI_REBOND_MS 30
 
// Variable globale contenant l'état du bouton,
// initialisée avec le bouton non-appuyé (donc relâché)
bool boutonAppuye_1 = false;
bool boutonAppuye_2 = 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_1 = 0;
unsigned long dateAntiRebond_2 = 0;
 
// compteur de clics/appuis sur votre bouton
int compteurDeClicks_1 = 0;
int compteurDeClicks_2 = 0;
 
void EtatDuBouton(int entreeDigitale, // Paramètre : entrée digitale à lire
bool &boutonAppuye, // Paramètre : état du bouton
unsigned long &dateAntiRebond, // Paramètre : état de l'anti-rebond
int &compteurDeClicks) // Paramètre : compteur de clicks
{
// lire l'état de l'entrée dans une nouvelle variable temporaire
bool etatSignal = !digitalRead(entreeDigitale);
 
// 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 sur le bouton sur l'entree );
Serial.print(entreeDigitale);
Serial.print( : );
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;
}
}
}
 
void setup()
{
// Configurer les PIN où sont branchés les boutons en tant qu'une entrées qui
// utilisent la résistance interne de pull-up
pinMode(PIN_ENTREE_BOUTON_1, INPUT_PULLUP);
pinMode(PIN_ENTREE_BOUTON_2, INPUT_PULLUP);
 
// Configuration de la connexion série
Serial.begin(115200);
}
 
void loop()
{
// gérer le bouton 1
EtatDuBouton(PIN_ENTREE_BOUTON_1,
boutonAppuye_1,
dateAntiRebond_1,
compteurDeClicks_1);
 
// gérer le bouton 2
EtatDuBouton(PIN_ENTREE_BOUTON_2,
boutonAppuye_2,
dateAntiRebond_2,
compteurDeClicks_2);
}
Fichier à télécharger : tuto_bouton5.ino

C'est déjà beaucoup mieux : la fonction EtatDuBouton() permet donc de pouvoir réutiliser le code qu'elle contient autant de fois que vous le souhaitez.

Mais il reste encore du code à "copier-coller" pour créer les variables globales de gestion et pour appeler la fonction que nous venons de créer. Si vous avez réalisé le tutoriel à propos du langage C fortement recommandé en début de cette page, vous savez alors qu'en C vous pouvez créer vos propres types de variables. Nous allons utiliser cette technique pour créer un code source complètement factorisé :

// temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
// seront ignorés
#define TEMPS_ANTI_REBOND_MS 30
 
// Définition du nombre de boutons à gérer
#define NOMBRE_DE_BOUTONS 2
 
// structure permettant de définir les propriétés d'un bouton
struct UnBouton
{
int entreeDigitale; // Entrée digitale où le bouton est connecté
bool boutonAppuye; // Etat du bouton
unsigned long dateAntiRebond; // Anti-rebond pour le bouton courant
int compteurDeClicks; // Compteur de clicks
};
 
// Définition d'un tableau de boutons et initialisation
UnBouton tableauDeBoutons[NOMBRE_DE_BOUTONS];
 
void etatDuBouton(UnBouton *boutonATraiter)
{
// lire l'état de l'entrée dans une nouvelle variable temporaire
bool etatSignal = !digitalRead(boutonATraiter->entreeDigitale);
 
// 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 ( boutonATraiter->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() - boutonATraiter->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é
boutonATraiter->boutonAppuye = true;
 
// arrêter l'anti-rebond
boutonATraiter->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
boutonATraiter->compteurDeClicks++;
 
// Afficher le compteur de clicks
Serial.print(Clicks sur le bouton sur l'entree );
Serial.print(boutonATraiter->entreeDigitale);
Serial.print( : );
Serial.println(boutonATraiter->compteurDeClicks);
}
}
// sinon, si le bouton est actuellement considéré comme relâché
else if ( !boutonATraiter->boutonAppuye )
{
// activer l'anti-rebond en obtenant la date actuelle du système
boutonATraiter->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 ( boutonATraiter->boutonAppuye )
{
// le considérer maintenant comme relâché
boutonATraiter->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
boutonATraiter->dateAntiRebond = 0;
}
}
}
 
void setup()
{
// Initialiser le tableau des boutons avec les valeurs par défaut
for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
{
tableauDeBoutons[i].boutonAppuye = false;
tableauDeBoutons[i].dateAntiRebond = 0;
tableauDeBoutons[i].compteurDeClicks = 0;
}
 
// Initialiser les numéros spécifiques des entrées utilisées
tableauDeBoutons[0].entreeDigitale = 8;
tableauDeBoutons[1].entreeDigitale = 9;
 
// Initialiser les PIN de tous les boutons à gérer
for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
{
pinMode( tableauDeBoutons[i].entreeDigitale, INPUT_PULLUP );
}
 
// Configuration de la connexion série
Serial.begin(115200);
}
 
void loop()
{
// gérer tous les boutons
for ( int i=0; i < NOMBRE_DE_BOUTONS; i++ )
{
etatDuBouton( &(tableauDeBoutons[i]) );
}
}
Fichier à télécharger : tuto_bouton6.ino

Et si j'ai 14 boutons à gérer, que dois-je faire alors ?

Maintenant c'est devenu donc très simple : il suffit de changer la valeur de la constante NOMBRE_DE_BOUTONS et lui attribuer la valeur correspondant au nombre de boutons que vous souhaitez gérer, en l'occurrence 14. Sans oublier d'initialiser le numéro de l'entrée digitale à utiliser pour chaque bouton dans la fonction setup().
Et voilà ! Simple non !

OK, c'est mignon de compter les clicks, mais comment faire pour faire autre chose d'un peu plus utile avec tous mes boutons ?

En effet, jusqu'à maintenant nous faisons le même traitement quelque soit le bouton qui est appuyé. Mais vous avez peut-être envie d'allumer et éteindre une LED avec un bouton et régler une température de consigne pour un thermostat avec d'autres boutons, gérer un capteur, etc... Les traitements sur une entrée digitale peuvent être très différents, alors oui, j'y viens !

A partir de là il y a plusieurs solutions possibles, toutes aussi bonnes les unes que les autres car elles peuvent dépendre de votre besoin particulier, de votre expérience et de votre appréciation personnelle. Mais pour commencer je vous propose une solution envisageable, parmi tant d'autres, qui serait celle de faire en sorte que la fonction etatDuBouton() ne fasse pas le traitement associé à un bouton directement mais plutôt qu'elle renvoie un état permettant d'identifier le moment où un traitement est à faire.

Un bouton à quatre états !

Avant de commencer à recoder tout ça il faut se rendre compte qu'un bouton a non pas deux états mais quatre états qui nous intéressent (si si !) : évidemment un bouton peut être appuyé ou relâché, ce qui nous fait déjà deux états ; mais nous pouvons être intéressés par connaître uniquement le moment où il change d'état. Il devient pratique de considérer les transitions comme des états supplémentaires pour pouvoir les gérer au même titre : le moment où vous appuyez et le moment ou vous relâchez le bouton. Ça nous fait quatre états !

C'est quoi la différence entre "appuyé" et "le moment où il est appuyé" ?

Le schéma suivant tente d'illustrer ces quatre différents états, la courbe est carrée sans les rebonds car nous prendrons en compte l'état du bouton après avoir "filtré" son état réel avec notre anti-rebond. Nous disons qu'il s'agit de son état logique par opposition à son état réel, qui comme vous le savez déjà, est très bruité :

Un bouton à quatre états

Les états ETAT_RELACHE et ETAT_APPUYE sont donc assez triviaux, ils correspondent à comment est votre bouton. Les transitions TRANSITION_APPUI et TRANSITION_RELACHEMENT correspondent quant à elles, uniquement au moment où le bouton change d'état. Ces transitions s'appellent respectivement "front montant" (parce que la courbe d'état "monte" à ce moment-là) et "front descendant" (vous aurez compris pourquoi...).

Et ça sert à quoi en fin de compte ?

En programmation vous pouvez avoir besoin de faire deux types d'actions :

  1. Les états vous permettent de faire des actions tant que le bouton est dans un état donné ;
  2. Les transitions vous permettent de faire des actions ponctuellement (une seule fois) lorsqu'elles se produisent.

Tout cela peut encore vous sembler un peu abstrait, c'est compréhensible. Pour illustrer cette solution, nous allons mettre en place deux traitements différents en modifiant un peu notre code :

Attendez un peu, je branche comment la LED qui doit s'allumer ?

Nul besoin d'un montage particulier. Nous avons de la chance, en bonus la sortie numérique numéro 13 de votre carte Arduino est directement câblée sur une petite LED CMS (Composant Monté/soudé en Surface) visible sur le dessus de votre circuit. Vous pouvez l'utiliser ainsi à votre guise, comme nous allons faire ici pour tester la gestion de nos boutons.

// Sortie utilisée pour allumer la LED
#define SORTIE_LED 13
 
// temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
// seront ignorés
#define TEMPS_ANTI_REBOND_MS 30
 
// Définition du nombre de boutons à gérer
#define NOMBRE_DE_BOUTONS 2
 
// type énuméré permettant de définir plusieurs états pour le bouton
enum EtatBouton
{
ETAT_RELACHE, // Le bouton est relâché
ETAT_APPUYE, // Le bouton est appuyé
TRANSITION_APPUI, // Le bouton est en train de passer de l'état relâché à appuyé
TRANSITION_RELACHEMENT, // Le bouton est en train de passer de l'état appuyé à relâché
};
 
// structure permettant de définir les propriétés d'un bouton
struct UnBouton
{
int entreeDigitale; // Entrée digitale où le bouton est connecté
bool boutonAppuye; // Etat du bouton
unsigned long dateAntiRebond; // Anti-rebond pour le bouton courant
};
 
// Définition d'un tableau de boutons et initialisation
UnBouton tableauDeBoutons[NOMBRE_DE_BOUTONS];
 
// Compteur de clics
int compteurDeClicks = 0;
 
EtatBouton etatDuBouton(UnBouton *boutonATraiter)
{
// valeur qui sera renvoyée comme résultat de cette fonction
EtatBouton etat = ETAT_RELACHE;
 
// lire l'état de l'entrée dans une nouvelle variable temporaire
bool etatSignal = !digitalRead(boutonATraiter->entreeDigitale);
 
// 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 ( boutonATraiter->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() - boutonATraiter->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é
boutonATraiter->boutonAppuye = true;
 
// arrêter l'anti-rebond
boutonATraiter->dateAntiRebond = 0;
 
// renvoyer l'état correspondant à la transition de relâché vers appuyé
etat = TRANSITION_APPUI;
}
}
// sinon, si le bouton est actuellement considéré comme relâché
else if ( !boutonATraiter->boutonAppuye )
{
// activer l'anti-rebond en obtenant la date actuelle du système
boutonATraiter->dateAntiRebond = millis();
}
// sinon, le bouton est déjà considéré comme appuyé
else
{
// renvoyer l'état appuyé
etat = ETAT_APPUYE;
}
}
// sinon, le bouton semble avoir été relâché
else
{
// si le bouton était appuyé
if ( boutonATraiter->boutonAppuye )
{
// le considérer maintenant comme relâché
boutonATraiter->boutonAppuye = false;
 
// renvoyer l'état correspondant à la transition de appuyé vers relâché
etat = TRANSITION_RELACHEMENT;
}
// 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
boutonATraiter->dateAntiRebond = 0;
}
}
 
// renvoyer l'état du bouton
return etat;
}
 
void setup()
{
// Initialiser le tableau des boutons avec les valeurs par défaut
for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
{
tableauDeBoutons[i].boutonAppuye = false;
tableauDeBoutons[i].dateAntiRebond = 0;
}
 
// Initialiser les numéros spécifiques des entrées utilisées
tableauDeBoutons[0].entreeDigitale = 8;
tableauDeBoutons[1].entreeDigitale = 9;
 
// Initialiser les PIN de tous les boutons à gérer
for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
{
pinMode( tableauDeBoutons[i].entreeDigitale, INPUT_PULLUP );
}
 
// Initialiser la sortie pour allumer la LED
pinMode( SORTIE_LED, OUTPUT);
 
// Configuration de la connexion série
Serial.begin(115200);
}
 
void loop()
{
// gérer tous les boutons
for ( int i=0; i < NOMBRE_DE_BOUTONS; i++ )
{
// traiter le bouton et récupérer son état actuel
EtatBouton etat = etatDuBouton( &(tableauDeBoutons[i]) );
 
// selon l'état du bouton
switch ( etat )
{
case ETAT_RELACHE :
{
// si c'est le bouton à l'indice 1 du tableau (bouton 2) qui est relâché
if ( i == 1)
{
// Eteindre la LED
digitalWrite(SORTIE_LED, LOW);
}
}
break;
 
case ETAT_APPUYE :
{
// si c'est le bouton à l'indice 1 du tableau (bouton 2) qui est appuyé
if ( i == 1)
{
// Allumer la LED
digitalWrite(SORTIE_LED, HIGH);
}
}
break;
 
case TRANSITION_APPUI :
{
// Si c'est le bouton à l'indice 0 du tableau (bouton 1) qui est appuyé
if ( i == 0 )
{
// incrémenter le compteur de clicks
compteurDeClicks++;
 
// Afficher le compteur sur la console
Serial.print(Clicks sur le bouton );
Serial.print(i);
Serial.print( de l'entree );
Serial.print(tableauDeBoutons[i].entreeDigitale);
Serial.print( : );
Serial.println(compteurDeClicks);
}
}
break;
 
case TRANSITION_RELACHEMENT :
{
; // Ne rien faire pour le moment
}
break;
 
default :
{
; // Ne rien faire, le bouton est forcément dans un des 4 états
}
}
}
}
Fichier à télécharger : tuto_bouton7.ino

Ouah ! Il y a plein nouveaux trucs là ! Qu'est-ce que nous avons fait ?

En effet, il y a quelques éléments nouveaux, tant de syntaxe comme de traitement.
Commençons dans l'ordre : le type énuméré (enum) EtatBouton n'est rien d'autre qu'une définition de constantes en quelque sorte "à la chaîne" : c'est un moyen simple de définir des constantes avec des valeurs différentes et qui se suivent. Ainsi ETAT_RELACHE vaut 0 (ça commence toujours à zéro), ETAT_APPUYE vaut 1 et ainsi de suite jusqu'à TRANSITION_RELACHEMENT qui vaut par conséquent 3.
En d'autres mots, au final cela revient presque au même que d'avoir fait ceci :

#define ETAT_RELACHE 0
#define ETAT_APPUYE 1
#define TRANSITION_APPUI 2
#define TRANSITION_RELACHEMENT 3

Ça aurait été plus long à taper. Aussi, comme ce n'est pas la valeur des constantes qui est importante mais surtout le fait qu'elles aient des valeurs différentes, un type énuméré se prête donc mieux dans ce cas. Cela a d'autres avantages pratiques comme le fait de pouvoir utiliser EtatBouton comme un nouveau type pour créer des variables limitées uniquement aux quatre valeurs/constantes définies.

Poursuivons avec la fonction etatDuBouton() qui maintenant ne se contente plus de seulement gérer l'anti-rebond mais aussi de renvoyer (return tout à la fin) l'état du bouton, y compris les transitions bien entendu !
Si vous regardez de plus près, même si la fonction ressemble à celle du code précédent, il n'y a plus la gestion du compteur de clicks (qui a aussi disparu de la structure UnBouton) mais uniquement de quoi identifier dans quel état se trouve le bouton.

C'est dans la fonction loop() que tout se joue maintenant :

Ce dernier code source peut donc déjà vous servir pour bon nombre de projets impliquant l'utilisation de boutons !

Exercices :

Maintenant que vous savez gérer plusieurs boutons en même temps, vérifiez que vous avez bien compris les principes évoqués dans cette deuxième partie du tutoriel.

  1. Mettons que vous avez quatre boutons à gérer en mode pull-up, avec la résistance interne de votre carte Arduino, branchés sur les entrées 8, 9, 10 et 11 de votre carte. Voici ce que vous voulez faire avec vos boutons :

    • Sur le premier bouton (entrée numéro 8) vous souhaitez incrémenter un compteur interne de 1 et afficher sur la console son état ;
    • Sur le deuxième bouton (entrée numéro 9) vous souhaitez incrémenter le compteur par tranches de 10 et afficher sur la console son état ;
    • Sur le troisième bouton (entrée numéro 10) vous souhaitez incrémenter le compteur par tranches de 100 et afficher sur la console son état ;
    • Sur le quatrième bouton (entrée numéro 11) vous souhaitez incrémenter le compteur par tranches de 1000 et afficher sur la console son état ;
    • Sur l'appui simultané des deux premiers boutons (8 et 9) vous souhaitez réinitialiser le compteur à 0.
  2. Des contraintes liées à un montage particulier vous forcent à utiliser certains boutons en mode pull-up et d'autres en mode pull-down. Modifiez le code pour que ces deux modes puissent être gérés sans devoir faire un "copier-coller" de la fonction etatDuBouton().

Réponses :

  1. La difficulté majeure dans cet exercice réside dans la gestion des appuis simultanés. Voici le code source de la solution que je vous propose :

    // Sortie utilisée pour allumer la LED
    #define SORTIE_LED 13
     
    // temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
    // seront ignorés
    #define TEMPS_ANTI_REBOND_MS 30
     
    // Définition du nombre de boutons à gérer
    #define NOMBRE_DE_BOUTONS 4
     
    // type énuméré permettant de définir plusieurs états pour le bouton
    enum EtatBouton
    {
    ETAT_RELACHE, // Le bouton est relâché
    ETAT_APPUYE, // Le bouton est appuyé
    TRANSITION_APPUI, // Le bouton est en train de passer de l'état relâché à appuyé
    TRANSITION_RELACHEMENT, // Le bouton est en train de passer de l'état appuyé à relâché
    };
     
    // structure permettant de définir les propriétés d'un bouton
    struct UnBouton
    {
    int entreeDigitale; // Entrée digitale où le bouton est connecté
    bool boutonAppuye; // Etat du bouton
    unsigned long dateAntiRebond; // Anti-rebond pour le bouton courant
    };
     
    // Définition d'un tableau de boutons et initialisation
    UnBouton tableauDeBoutons[NOMBRE_DE_BOUTONS];
     
    // Compteur
    int compteur = 0;
     
    EtatBouton etatDuBouton(UnBouton *boutonATraiter)
    {...} // Pas de changements dans le code de cette fonction
     
    void setup()
    {
    // Initialiser le tableau des boutons avec les valeurs par défaut
    for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
    {
    tableauDeBoutons[i].boutonAppuye = false;
    tableauDeBoutons[i].dateAntiRebond = 0;
    }
     
    // Initialiser les numéros spécifiques des entrées utilisées
    tableauDeBoutons[0].entreeDigitale = 8;
    tableauDeBoutons[1].entreeDigitale = 9;
    tableauDeBoutons[2].entreeDigitale = 10;
    tableauDeBoutons[3].entreeDigitale = 11;
     
    // Initialiser les PIN de tous les boutons à gérer
    for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
    {
    pinMode( tableauDeBoutons[i].entreeDigitale, INPUT_PULLUP );
    }
     
    // Initialiser la sortie pour allumer la LED
    pinMode( SORTIE_LED, OUTPUT);
     
    // Configuration de la connexion série
    Serial.begin(115200);
    }
     
    void loop()
    {
    // gérer tous les boutons
    for ( int i=0; i < NOMBRE_DE_BOUTONS; i++ )
    {
    // traiter le bouton et récupérer son état actuel
    EtatBouton etat = etatDuBouton( &(tableauDeBoutons[i]) );
     
    // selon l'état du bouton
    switch ( etat )
    {
    case ETAT_RELACHE :
    {
    ; // Ne rien faire pour le moment
    }
    break;
     
    case ETAT_APPUYE :
    {
    ; // Ne rien faire pour le moment
    }
    break;
     
    case TRANSITION_APPUI :
    {
    // selon le bouton qui est appuyé, d'après l'entrée où il est branché
    switch(i)
    {
    // Premier bouton
    case 0 :
    {
    // si le 2e bouton est déjà appuyé
    // (détection des appuis simultanés)
    if ( tableauDeBoutons[1].boutonAppuye )
    {
    // réinitialiser le compteur
    compteur = 0;
    }
    // sinon : pas d'appui simultané avec le 2e bouton
    else
    {
    // incrémenter le compteur de 1
    compteur++;
    }
    }
    break;
     
    // Deuxième bouton
    case 1 :
    {
    // si le 1er bouton est déjà appuyé
    // (détection des appuis simultanés)
    if ( tableauDeBoutons[0].boutonAppuye )
    {
    // réinitialiser le compteur
    compteur = 0;
    }
    // sinon : pas d'appui simultané avec le 1er bouton
    else
    {
    // incrémenter le compteur de 10
    compteur += 10;
    }
    }
    break;
     
    // Troisième bouton
    case 2 :
    {
    // incrémenter le compteur de 100
    compteur += 100;
    }
    break;
     
    // Quatrième bouton
    case 3 :
    {
    // incrémenter le compteur de 1000
    compteur += 1000;
    }
    break;
     
    default :
    {
    ; // Pas d'autre bouton : rien à faire
    }
    }
     
    // après avoir modifié le compteur, afficher sa valeur sur la console
    Serial.print(compteur : );
    Serial.println(compteur);
    }
    break;
     
    case TRANSITION_RELACHEMENT :
    {
    ; // Ne rien faire pour le moment
    }
    break;
     
    default :
    {
    ; // Ne rien faire, le bouton est forcément dans un des 4 états
    }
    }
    }
    }
    Fichier à télécharger : tuto_bouton8.ino

    L'incrémentation du compteur selon le bouton ne devrait pas vous poser de problème particulier. Par contre pour la détection des appuis simultanés c'est peut-être une toute autre histoire : l'astuce réside en l'utilisation du tableau de structures qui contient l'état des autres boutons. Vous pouvez ainsi savoir dans quel état est un autre bouton si nécessaire.
    Il est important de noter que le traitement est à faire au niveau des deux boutons concernés car même si vous pouvez avoir l'impression d'appuyer dessus "simultanément", en pratique et à quelques millisecondes près il y en a toujours un qui est appuyé avant l'autre. Comme il n'y a aucun moyen de savoir lequel, alors il vaut mieux détecter dans les deux sens.
    Pour mieux comprendre cela, enlevez le traitement des appuis simultanés d'un des deux boutons (en ne laissant que l'incrémentation) et regardez ce qui se passe : vous devez appuyer simultanément, mais dans un ordre particulier pour que ça fonctionne.

  2. Au premier abord cela peut sembler compliqué, mais je vous donne un indice avant de regarder la réponse : il faudrait modifier la structure UnBouton en ajoutant un nouveau champ.

    Vous séchez toujours ?
    Voici le code qui répond à ce besoin, en considérant que le bouton sur l'entrée 8 (index 0) est le bouton en pull-up et le bouton sur l'entrée 9 en pull-down :

    // Sortie utilisée pour allumer la LED
    #define SORTIE_LED 13
     
    // temps "d'anti-rebond", en millisecondes, pendant lequel les rebonds
    // seront ignorés
    #define TEMPS_ANTI_REBOND_MS 30
     
    // Définition du nombre de boutons à gérer
    #define NOMBRE_DE_BOUTONS 2
     
    // type énuméré permettant de définir plusieurs états pour le bouton
    enum EtatBouton
    {
    ETAT_RELACHE, // Le bouton est relâché
    ETAT_APPUYE, // Le bouton est appuyé
    TRANSITION_APPUI, // Le bouton est en train de passer de l'état relâché à appuyé
    TRANSITION_RELACHEMENT, // Le bouton est en train de passer de l'état appuyé à relâché
    };
     
    // structure permettant de définir les propriétés d'un bouton
    struct UnBouton
    {
    int entreeDigitale; // Entrée digitale où le bouton est connecté
    bool entreePullUp; // Vaut vrai/true si l'entrée doit être gérée en mode pull-up
    bool boutonAppuye; // Etat du bouton
    unsigned long dateAntiRebond; // Anti-rebond pour le bouton courant
    };
     
    // Définition d'un tableau de boutons et initialisation
    UnBouton tableauDeBoutons[NOMBRE_DE_BOUTONS];
     
    // Compteur de clics
    int compteurDeClicks = 0;
     
    EtatBouton etatDuBouton(UnBouton *boutonATraiter)
    {
    // valeur qui sera renvoyée comme résultat de cette fonction
    EtatBouton etat = ETAT_RELACHE;
     
    // variable locale contenant l'état du signal à traiter
    bool etatSignal;
     
    // si le bouton est en mode pull-up
    if ( boutonATraiter->entreePullUp )
    {
    // lire l'état de l'entrée comme une pull-up
    etatSignal = !digitalRead(boutonATraiter->entreeDigitale);
    }
    // sinon, le bouton est en mode pull-down
    else
    {
    // lire l'état de l'entrée comme une pull-down
    etatSignal = digitalRead(boutonATraiter->entreeDigitale);
    }
     
    // si le bouton semble avoir été appuyé (pas de signal, ou signal = 0/false)
    if ( etatSignal )
    {...} // Pas de changement
    // sinon, le bouton semble avoir été relâché
    else
    {...} // Pas de changement
     
    // renvoyer l'état du bouton
    return etat;
    }
     
    void setup()
    {
    // Initialiser le tableau des boutons avec les valeurs par défaut
    for ( int i = 0; i < NOMBRE_DE_BOUTONS; i++ )
    {
    tableauDeBoutons[i].boutonAppuye = false;
    tableauDeBoutons[i].dateAntiRebond = 0;
    }
     
    // Initialiser les entrées utilisées de manière plus spécifique :
     
    // L'entrée digitale No 8 est en mode pull-up, il faut alors
    // l'initialiser manuellement
    tableauDeBoutons[0].entreePullUp = true;
    tableauDeBoutons[0].entreeDigitale = 8;
    pinMode( tableauDeBoutons[0].entreeDigitale, INPUT_PULLUP ); // ou INPUT selon besoin
     
    // L'entrée digitale No 9 est en mode pull-down, il faut alors
    // l'initialiser manuellement aussi
    tableauDeBoutons[1].entreePullUp = false;
    tableauDeBoutons[1].entreeDigitale = 9;
    pinMode( tableauDeBoutons[1].entreeDigitale, INPUT );
     
    // Initialiser la sortie pour allumer la LED
    pinMode( SORTIE_LED, OUTPUT);
     
    // Configuration de la connexion série
    Serial.begin(115200);
    }
     
    void loop()
    {...} // Pas de changement
    Fichier à télécharger : tuto_bouton9.ino

    Pour éviter toute confusion, rappelez-vous que c'est la façon dont vous gérez la lecture sur l'entrée digitale digitalRead() qui détermine si vous gérez votre entrée en pull-up ou pull-down. Les modes INPUT_PULLUP et INPUT déterminent uniquement si vous activez ou pas la résistance interne de pull-up, l'activer n'a donc bien entendu aucun sens si vous gérez une entrée en mode pull-down !

    La solution consiste donc à ajouter le champ bool entreePullUp; dans la structure UnBouton. Grâce à ce champ qui indique comment est branché le bouton (configuré dans la fonction setup()), nous pouvons alors déterminer comment est gérée l'entrée au moment de la lecture de son état (dans la fonction etatDuBouton(...)) pour que le reste du traitement se déroule correctement et conformément à nos attentes.

Félicitations docteur ! Vous savez maintenant gérer des boutons en entrée de votre carte Arduino !
Cette connaissance vous sera sans doute très utile non seulement pour gérer des boutons vous permettant d'interagir et/ou configurer votre carte Arduino, mais aussi de pouvoir gérer des capteurs binaires comme des capteurs de contact ou des "fin de course", etc...

Diego
  Retourner en haut
Commenter sur le forum Forum Arduino