Ardu? No!Les boutons ≫ Rebonds et lecture analogique

Rebonds et lecture analogique

On parle souvent de rebonds quand il s'agit de lecture digitales de boutons. Cette page va donner quelques pistes et quelques pièges quand la lecture devient analogique. Il faudrait employer le terme de stabilisation et non de rebond pour ces lectures.

Rebonds en lecture digitale

Quelques petits rappels. Supposons le montage (la résistance peut être externe ou une PULL_UP):

Un poussoir et sa pullup

La page Rebonds explique que lors de l'appui sur le bouton, on peut avoir des rebonds sur la tension ressemblants à:

Diagrame temporel d'un rebond

Si on lit le bouton une seule fois à un certain instant, on lira HIGH si c'est avant les rebonds, LOW si c'est après, et l'un ou l'autre si c'est pendant la durée des rebonds. En fait la lecture n'a pas vraiment d'importance car la lecture aurait pu être demandée 1ms avant ou après. Il n'y a pas dans ce cas à se soucier des rebonds. C'est le cas par exemple d'un bouton "appuyé ➔ la lampe est allumée, relâché ➔ la lampe est éteinte.

Dans tous les cas la valeur lue est bonne.

Par contre si on lit régulièrement le bouton pour détecter le changement d'état, cela devient problématique. C'est le cas si on veut mémoriser les appuis (j'appuie ➔ ça allume, j'appuie ➔ ça s'éteint), ou si on veut compter le nombre d'appuis. On dispose en général de deux solutions:
- on branche un condensateur en parallèle sur le bouton qui supprime les rebonds vus
- on ne lit plus le bouton pendant un certain temps après avoir vu un front pour ne tenir compte que du premier front vu lors de l'appui.

Dans tous les cas, même si on ne traite pas les rebonds, la valeur lue est bonne, même si on alterne d'une valeur à l'autre. Comment pourrait-il en être autrement vu que l'on a que des 0 et des 1?

Rebonds en lecture analogique

Les signaux analogiques vont prendre une infinité de valeurs et les tensions ne peuvent pas s'établir instantanément. On peut avoir pour un appui sur un bouton:

Etablissement de la tension

Si on échantillonne l'entrée de mesure au hasard, on peut tomber dans la zone non stabilisée, et on croira lire un autre bouton que celui qui a été pressé. Et si on fait comme en digital, on verra un changement et on retiendra la mauvaise valeur. Certaines personnes vont garder cette valeur et même empêcher une nouvelle mesure avant le temps bounce. Il en sera de même au relâchement du bouton.

Les tests suivant sont faits avec une Uno, avec une carte plus rapide ce serait sans doute pire, je risque de lire plus de valeurs intermédiaires.

Soit le montage:

Keypad analogique

Je ne vais m'intéresser qu'au bouton "1". Ce qui va se passer pour les autres boutons est similaire. Il n'y a plus qu'une seule résistance utile (les autres ne sont utilisées que pour les boutons que je n'utilise pas). Et pour la résistance de 38kΩ, j'ai pris la résistance de pull-up interne. Ainsi branché le keypad n'a besoin que de deux fils. De toutes façons la valeur de la résistance importe peu, je procède à un apprentissage pour les valeurs lues des autres boutons. Le montage pour le seul bouton qui m'intéresse est donc le même que précédemment.

Je vais lire pendant 2 secondes les valeurs du CAN pour mon bouton branché sur A0, puis afficher si la valeur numérique est vue (j'affiche "1") ou pas (j'affiche "0"). Soit le programme suivant:

// Ce programme informe sur les valeurs lues possibles
// en lecture analogique pour un ou plusieurs boutons.

boolean valeurs[1024];

void setup()
{
  // Initialisation
  Serial.begin(115200);
  pinMode(A0, INPUT_PULLUP);

  // Lecture analogique pendant 2 secondes
  Serial.println("C'est parti");
  while (millis() < 2000L) valeurs[analogRead(A0)] = true;

  // Affichage des valeurs lues
  for (word ligne=0; ligne<16; ligne++) // 1023 valeurs possibles affichées avec 16 lignes de 64 valeurs
  {
    if (ligne == 0) Serial.print("  0"); // N° de la ligne affichée à gauche
    else if (ligne == 1) Serial.print(" 64");
    else Serial.print(ligne*64);
    Serial.print(" "); // Pour aligner la table
    for (word colonne=0; colonne<64; colonne++) Serial.print(valeurs[ligne*64+colonne]); // Les valeurs
    Serial.println(); // Fin de ligne
  }
  Serial.println();
}

void loop(){}

Si je n'appuie jamais sur le bouton, je vais lire la valeur maximale. Je m'attends à 1023 uniquement. J'obtiens (en enlevant le "c'est parti"):

  0 0000000000000000000000000000000000000000000000000000000000000000
 64 0000000000000000000000000000000000000000000000000000000000000000
128 0000000000000000000000000000000000000000000000000000000000000000
192 0000000000000000000000000000000000000000000000000000000000000000
256 0000000000000000000000000000000000000000000000000000000000000000
320 0000000000000000000000000000000000000000000000000000000000000000
384 0000000000000000000000000000000000000000000000000000000000000000
448 0000000000000000000000000000000000000000000000000000000000000000
512 0000000000000000000000000000000000000000000000000000000000000000
576 0000000000000000000000000000000000000000000000000000000000000000
640 0000000000000000000000000000000000000000000000000000000000000000
704 0000000000000000000000000000000000000000000000000000000000000000
768 0000000000000000000000000000000000000000000000000000000000000000
832 0000000000000000000000000000000000000000000000000000000000000000
896 0000000000000000000000000000000000000000000000000000000000000000
960 0000000000000000000000000000000001111110000000000000000000000000

On n'a pas 1023, mais des valeurs comprises entre 993 et 998. Cela n'a pas une grande importance pour l'utilisation du keypad, les valeurs sont "apprises" en fonction du montage.

Si j'appuie en permanence sur le bouton, je n'attends que des 0:

  0 0000000000000111000000000000000000000000000000000000000000000000
 64 0000000000000000000000000000000000000000000000000000000000000000
128 0000000000000000000000000000000000000000000000000000000000000000
192 0000000000000000000000000000000000000000000000000000000000000000
256 0000000000000000000000000000000000000000000000000000000000000000
320 0000000000000000000000000000000000000000000000000000000000000000
384 0000000000000000000000000000000000000000000000000000000000000000
448 0000000000000000000000000000000000000000000000000000000000000000
512 0000000000000000000000000000000000000000000000000000000000000000
576 0000000000000000000000000000000000000000000000000000000000000000
640 0000000000000000000000000000000000000000000000000000000000000000
704 0000000000000000000000000000000000000000000000000000000000000000
768 0000000000000000000000000000000000000000000000000000000000000000
832 0000000000000000000000000000000000000000000000000000000000000000
896 0000000000000000000000000000000000000000000000000000000000000000
960 0000000000000000000000000000000000000000000000000000000000000000

En fait j'ai des valeurs entre 13 et 15.

Mais là ou cela devient intéressant c'est si j'appuie régulièrement sur le bouton (en 2 secondes de mesure, je vais appuyer une dizaine de fois). J'aurais aimé n'obtenir que des valeurs comprises dans les deux intervalles [993, 998] et [13, 15], mais la réalité me donne:

  0 0000000000000111111111111111111111111111111011111111101111110010
 64 0001001000010000100000010001000001100000001010000000010000000000
128 0000000000000000001000000000000000000000000001000000000000000000
192 0000000000000000000000000000000000000000000001000000000000000000
256 0000000000000000000000000000000000000000000000000000000000000000
320 0000000000000000000000000000000000000000000000000000000000000000
384 0000000000000000000000000000000000000000000000000000000000000000
448 0000000000000000000000000000000000000000000000000000000000000000
512 0000000000000000000000000000000000000000000000000000000000000000
576 0000000000000000000000000000000000000000000000000000000000000000
640 0000000000000000000000000000000000000000000000000000000000000000
704 0000000000000000000000000000000000000000000000000000000000000000
768 0000000000000000000000000000000000000000000000000000000000000000
832 0000000000000000000000000000000000000000000000000000000000000000
896 0000000000000000000000000000000000000000000000000000000000000000
960 0000000000000000000000000000000100000011100000000000000000000000

On voit qu'en plus de lire les "bonnes valeurs" on obtient pas mal de valeurs intermédiaires. En faisant le test sue 10 secondes, j'obtiens même:

  0 0000000000000111111111111111111111111111111111111111111111111111
 64 1111111111111111110111010101110010010100001100000000010000001111
128 0100001000000000000000000000100000000010001100100000001000000100
192 0000000101000000000000000000000000000000000000000000010000000000
256 0000000010000000000000010000110000000000000000000001000000001001
320 0000000000000001000100000000000100000000010000000000000000000000
384 0000000000000000001100000000000000000000000000000000000000000000
448 0000000000000000000000000000000000000000000000000000000000000000
512 0000000000000000000000000000000000000000000000000000000000000000
576 0000000000000000000000000000000000000000000000000000000000000000
640 0000000000000000000000000000000000000000000000000000000100000000
704 0000000000000000000000000000000000000000000000000000000100000000
768 0000000000000000000000000000000001000000000000000000100000000000
832 0000000000000000000000000000000000000000000000100000000000000000
896 0000000000000000000000010000000000000000000000000000000000000000
960 0000000000000000000000010011000111011111111110000000000000000000

En gros une lecture à la volée peut donner n'importe quelle valeur, et surtout peut donner les valeurs attendues pour les autres boutons. Nous ne sommes plus du tout comme en digital ou nous avions que des bonnes valeurs.

Le condensateur

Bien entendu, le condensateur sur l'entrée, comme en digital, s’avérerait une catastrophe car le signal remonterait plus lentement, et on aurait plus de valeurs indésirées.

Bounce

En regardant à posteriori ce qui se fait, je n'ai trouvé que l'astuce de la variable bounce qui bloque la lecture pendant un certain temps après une lecture différente de la précédente. Cela fonctionnait bien en digital, mais ici cela ne peut pas fonctionner. Si j'appuie sur mon bouton "1", je lirais la bonne valeur au bout d'un moment. Quand je le relâche, je peux trouver une nouvelle valeur avant le "repos". Le fait d'attendre me permet seulement de n'avoir qu'une seule touche fantôme au lieu de plusieurs. Mais c'est toujours une de trop. Imaginez un digit code pour un immeuble ou une fois sur 10 on ait une touche fantôme en plus!

La bibliothèque ezAnalogKeypad par exemple retourne pendant le temps bounce la même valeur qui peut donc être fausse. En essayant cette bibliothèque avec leur exemple de base, j'ai une touche fantôme au relâchement une fois sur quatre.

La moyenne

Un autre problème qu peur surgir est que la valeur lue pour une tension fixe n'est pas tout à fait constante et que l'on peut avoir des valeurs aberrantes. Certains vont donc préconiser de lire plusieurs valeurs et d'en faire la moyenne. Cela permet de diminuer très fortement le poids des valeurs aberrantes, mais ne va pas régler le problème d'une lecture fausse car effectuée au mauvais moment. Si les lectures se font rapidement, on est revenu au problème précédent, si les lectures sont plus espacées on fera une moyenne entre [993, 998] et [13, 15].

On trouve aussi la méthode de la médiane: on ne garde que la valeur centrale. J'ai aussi testé sans succès un mélange des deux méthodes en supprimant les valeurs extrêmes et en gardant la moyenne de ce qui reste. C'est mieux pour lire une valeur stabilisée en tension, mais cela ne change rien pour les appuis ou relâchement des touches.

Attente de stabilisation

Il y a certainement d'autres solutions que celle que j'utilise, merci de m'en faire part. Pour ma part, j'attends que la tension se stabilise avant d'utiliser la valeur lue. Les valeurs fantômes ne sont pas stables, sinon le tableau ci-dessus serait remplis de "1". Et donc si l'écart entre une valeur lue et la valeur suivante est trop grande, j'ignore ces mesures. Cela élimine complètement les valeurs fantômes et les valeurs aberrantes, et donne moins de valeurs dispersées. Par contre je ne garantis pas de pouvoir faire la lecture rapidement si le CAN reçoit des valeurs fortement perturbées.

Dans les exemples de lecture des pages sur les boutons, les programmes pou lire un keypad anamogiques utilise ce que j'appelle une double lecture qui permet d'attendre que la tension soit stabilisée:

do
{
  oldValeurLue = valeurLue; // Sauvegarde de la mesure précédente
  valeurLue = analogRead(A5); // Nouvelle lecture
} while (valeurLue!=oldValeurLue); // On recommence jusqu'à avoir deux lectures identiques

Mais rien ne prouve que je ne vais pas boucler éternellement. Dans la pratique, je ne lis que deux ou trois valeurs, rarement plus. Ma bibliothèque MTobjects pour lire un ensemble de boutons analogiques, une valeur est lue toutes les 16ms et est gardée si deux lectures consécutives donne la même valeur à un chouia près. Pour lire un écran sensitif, je boucle jusqu'à 15 fois sans attente.