Ardu? No!Les boutons ≫ Un bouton par broche

Un bouton par broche

Un seul bouton

Ce montage est décrit en long et en large un peu partout.

Un bouton sur une broche

Il ne faut pas que l'entrée soit en haute impédance (INPUT) sinon quand le bouton n'est pas appuyé, l'état de l'entrée n'est pas définie et on peut lire aussi bien HIGH que LOW. On mettra donc la broche en INPUT_PULLUP qui rajoute une résistance entre l'entrée et le VCC (environ 35kΩ pour une Uno). Quand le bouton n'est pas appuyé, on va lire HIGH en entrée et on aura LOW en cas d'appui. Pour certains il y aurait une gymnastique d'esprit (appuyé = 0, non appuyé = 1) mais on finit vite par s'y habituer.

On peut aussi mettre le bouton entre l'entrée et l'alimentation positive. Dans ce cas, on a appuyé = HIGH, ce qui semble plus compréhensible, mais les AVR ne disposent pas de résistances de pull-down (résistance entre la broche et la masse). D'autre part, il est mieux de mettre une masse sur un bouton qu'une alimentation. Si nous travaillons avec une AVR, il vaut mieux utiliser le montage avec une borne à la masse.

Que se soit pull-up ou pull-down, la valeur de la résistance se détermine de la même façon. La plus simple et la plus utilisée est de mettre celle qui est dans le microcontrôleur, elle est gratuite. Sinon, mettez une 10kΩ. On peut toutefois calculer les limites max et min de cette valeur, mais cela n'a pas un grand intérêt.

Pour la valeur max et en 5V, il faut que l'on garantisse d'avoir avec une pull-up au moins 70% de VCC pour garantie un HIGH. Je ne retrouve pas la consommation d'une entrée en INPUT, je vais la supposer de 1µA max.

Calcul de Rmax pour la pullup

La tension aux bornes de la résistance UR doit être de 1,5V maxi. La valeur R de la résistance doit satisfaire à:
UR = Rmax.I < 1,5V soit Rmax < 1,5V / 1µA = 1,5MΩ. Toutefois avec une telle résistance, on n'est pas bien protégé contre les perturbations.

Pour la valeur min, c'est lorsque le bouton est fermé que l'on peut faire le calcul. Quand ce dernier est fermé, il va passer un courant qui ne sert à rien dans la résistance.

Calcul de Rmin pour la pullup

La valeur maximale de ce courant dépend de l'interrupteur et de l'alimentation. En théorie, on doit pouvoir faire passer 1A ou plus (ce qui ferait une résistance de valeur Rmin = 5V/1A soit 5Ω), mais dans la pratique si la carte consomme 20mA, ce serait bien que le bouton consomme nettement moins, par exemple 1mA. Dans ce cas la résistance minimale serait de Rmin = 5V / 1mA soit max = 5kΩ.

Bilan de ce calcul: on peut prendre un peu n'importe quoi. 10kΩ par défaut, on peut descendre à 5kΩ si le milieu est perturbé ou si la ligne est longue. Dans une Uno, la résistance est de l'ordre de 35kΩ.

Plusieurs bouton

On répète plusieurs fois le schéma "un seul bouton".

Exemple de programme pour un seul bouton

const byte BOUTON = 2; // Pin2 -- bouton -- GND

byte etat; // Valeur lue
byte ancienEtat = LOW; // Permettra de ne pas trop écrire

void setup()
{
  Serial.begin(115200); // Régler aussi la console à 115200 bauds!
  pinMode(BOUTON, INPUT_PULLUP);
}

void loop()
{
  etat=digitalRead(BOUTON); // Test du bouton
  if (etat != ancienEtat) // Si on a un changement
  {
    if (etat == LOW) Serial.println("Bouton appuyé"); // Affichage du nouvel état
    else Serial.println("Bouton relâché");
    ancienEtat = etat; // Mémorisation du nouvel état
    delay(50); // Anti-rebond
  }
}

Une touche est appuyée avec un seul bouton

C'est un grand classique car c'est presque le plus simple. Je dis presque car il faut que les appels ne soient pas trop espacés sinon on manquerait des appuis, ou un délai trop important interviendrait entre l'appui et l'action que cela engendre. On appelle l'instruction ou la fonction qui lit l'état du ou des boutons.

Voici par exemple un programme qui allume une led avec un poussoir, mais avec une fonction va et vient (un appui allume la led, un appui l'éteint):

const byte BOUTON = 2; // Pin2 -- bouton -- GND
const unsigned long BOUNCE = 20; // 20ms pour attendre la fin des rebonds

byte etat = HIGH; // Permettra de voir les changements
unsigned long tempsDuDernierAppui; // Pour l'anti-rebond

void setup()
{
  pinMode(BOUTON, INPUT_PULLUP); // Un appui, j'allume, un appui j'éteins
  pinMode(LED_BUILTIN, OUTPUT); // Led qui va s'allumer ou éteindre
}

void loop()
{
  if ((digitalRead(BOUTON) != etat) && (millis() - tempsDuDernierAppui > BOUNCE)) // Si on a un changement et hors rebonds
  {
    etat = etat == LOW ? HIGH : LOW; // On mémorise le nouvel état
    tempsDuDernierAppui = millis(); // Départ pour l'anti-rebond
    if (etat == LOW) digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ==LOW ? HIGH : LOW); // Change la led si on appuie sur le bouton
  }
}

L'instruction qui lit l'état du bouton digitalRead(BOUTON) est très courte. Dans ce cas il est impossible de faire mieux lors d'un appel régulier.

Une touche est appuyée avec plusieurs boutons

Par contre si on a plusieurs boutons, câblés sur un même port, la lecture globale du port permet avec une comparaison de savoir si un des boutons est appuyé ou pas. Avec une Uno, le port D se trouve sur les broches 0 à 7. En réservant les broches 0 et 1 pour la programmation et le dialogue, on peut mettre 6 boutons sur les sorties 2 à 7 (PD2 à PD7). La lecture globale de PORDD nous dira si un bouton a changé d'état ou pas. Si c'est le cas, on a perdu un peu de temps, mais si ce n'est pas le cas, on en gagne beaucoup. Voici le même programme si on utilise 6 boutons (explications après):

const byte BOUTON2 = 2; // Pin2 -- bouton -- GND
const byte BOUTON3 = 3; // Pin3 -- bouton -- GND
const byte BOUTON4 = 4; // Pin4 -- bouton -- GND
const byte BOUTON5 = 5; // Pin5 -- bouton -- GND
const byte BOUTON6 = 6; // Pin6 -- bouton -- GND
const byte BOUTON7 = 7; // Pin7 -- bouton -- GND
const unsigned long BOUNCE = 20; // 20ms pour attendre la fin des rebonds

byte pinD; // Lecture mémorisée (pour avoir la même valeur pour une loop())
byte etat = 0b11111100; // Permettra de voir les changements sur un des 6 boutons
byte etat2 = 0b00000100; // Pour le bouton 2, "HIGH" sur le bit de poids 2 
unsigned long tempsDuDernierAppui2; // Pour l'anti-rebond du bouton 2

void setup()
{
  pinMode(BOUTON2, INPUT_PULLUP); // Un appui, j'allume, un appui j'éteins
  pinMode(BOUTON3, INPUT_PULLUP); // D'autres boutons, d'autres applications non détaillées ici
  pinMode(BOUTON4, INPUT_PULLUP);
  pinMode(BOUTON5, INPUT_PULLUP);
  pinMode(BOUTON6, INPUT_PULLUP);
  pinMode(BOUTON7, INPUT_PULLUP);
  pinMode(LED_BUILTIN, OUTPUT); // Led qui va s'allumer ou éteindre
}

void loop()
{
  pinD = PIND & 0b11111100; // Valeurs binaires des 6 boutons
  if (pinD != etat) // On a un changement sur un des 6 boutons
  { // Ne sera fait que si on a un changement
    etat = pinD; // Prend alors la valeur lue

    // Pour le bouton 2:
    if (((etat & 0b00000100) != etat2) && (millis() - tempsDuDernierAppui2 > BOUNCE)) // Si on a un changement et hors rebonds
    {
      etat2 = etat & 0b00000100; // On mémorise le nouvel état
      tempsDuDernierAppui2 = millis(); // Départ pour l'anti-rebond
      if (etat2 == 0) digitalWrite(LED_BUILTIN, digitalRead(LED_BUILTIN) ==LOW ? HIGH : LOW); // Change la led si on appuie sur le bouton 2
    }
    // Pour le bouton 3
    /* Code pour la gestion d'un changement pour ce bouton */

    // Pour le bouton 4
    /* Code pour la gestion d'un changement pour ce bouton */

    // Pour le bouton 5
    /* Code pour la gestion d'un changement pour ce bouton */

    // Pour le bouton 6
    /* Code pour la gestion d'un changement pour ce bouton */

    // Pour le bouton 7
    /* Code pour la gestion d'un changement pour ce bouton */
  }
}

Il ne faut utiliser dans la boucle qu'une seule fois la lecture via PIND, il faut donc la mémoriser. Si on la lisait deux fois, à cause des rebonds, on pourrait avoir 2 lectures différentes.

Le bouton 2 est branché sur le port D à la position 2 (c'est pareil pour les chiffres 3 à 7). Pour la lecture:

pinD = PIND & 0b11111100; // Valeurs binaires des 6 boutons

On masque les bits PD0 et PD1 qui servent à la liaison série. Ainsi, les 6 premiers bits indiquent chacun l'état d'un bouton et les deux derniers seront toujours à 0. Si on avait mis les boutons sur les broches A0 à A5 (PORT C), on n'aurait pas eu besoin des masques.

etat & 0b00000100 isole le bit 2, c'est à dire retourne sur le poids 22 un bit à 1 si le bouton n'est pas appuyé et un bit à 0 si il l'est. Et c'est cette valeur que l'on va garder pour les prochaines fois.

Une touche est appuyée, bilan temporel

Voici le code utilisé si aucun bouton n'est appuyé (temps t):

  pinD = PIND & 0b11111100; // Valeurs binaires des 6 boutons
  if (pinD != etat) // On a un changement sur un des 6 boutons
  {
  }

Voici maintenant une idée code exécuté si que l'on ferait si on n'utilisait pas l'idée "un bouton est-il appuyé?" (temps T):

    // Pour le bouton 2:
    if (((etat & 0b00000100) != etat2) && (millis() - tempsDuDernierAppui2 > BOUNCE)) // Si on a un changement et hors rebonds
    {
    }

    // Pour le bouton 3:
    if (((etat & 0b00000100) != etat3) && (millis() - tempsDuDernierAppui3 > BOUNCE))
    {
    }

    // Pour le bouton 4:
    if (((etat & 0b00000100) != etat4) && (millis() - tempsDuDernierAppui4 > BOUNCE))
    {
    }

    // Pour le bouton 5:
    if (((etat & 0b00000100) != etat5) && (millis() - tempsDuDernierAppui5 > BOUNCE)) 
    {
    }

    // Pour le bouton 6:
    if (((etat & 0b00000100) != etat6) && (millis() - tempsDuDernierAppui6 > BOUNCE))
    {
    }

    // Pour le bouton 7:
    if (((etat & 0b00000100) != etat7) && (millis() - tempsDuDernierAppui7 > BOUNCE))
    {
    }

On peut comprendre que c'est nettement plus rapide de faire systématiquement que le premier code et rarement les deux ensemble.

Bilan

- pas de composants supplémentaires
- lecture poly (on a l'état de tous les boutons)
- lecture simple (juste un digitalRead())
- il y a une multitude de bibliothèques pour ce type de montage
- l'utilisation d'interruption est possible (très simple avec 2 boutons et une Uno).
MAIS
- pour N boutons, il faut N sorties
- on ne peut pas utiliser les claviers pré-câblés

Montage idéal pour quelques boutons si on a assez de broches disponibles. Très courant.