Ardu? No!Les boutons ≫ Lecture analogique avec une table

Lecture analogique avec une table

Dans un ensemble de boutons utilisant la lecture analogique, on obtient une valeur par bouton (ou combinaison). Pour décoder l'information, il y a en gros deux méthodes:
- on utilise le fait que la répartition des valeurs est mathématique. Ceci est décrit dans la page Lecture analogique de boutons par calcul
- on identifie la lecture en parcourant une table des limites. C'est cette méthode qui est développée dans cette page.

Principe

En théorie, c'est assez simple. Le convertisseur va nous donner une valeur numérique différente pour chaque bouton. Ou plutôt quelques valeurs par bouton. Si pour un bouton on a par exemple des valeurs entre 254 et 262 et pour le bouton d'après des valeurs entre 287 et 295, on peut calculer le milieu de 262 et 287 soit 274 (arrondi entier). On dira que si on est en dessous de 274 c'est le premier bouton et ce sera le deuxième si on est au dessus.

On va donc avoir une table contenant les limites des valeurs entre les boutons. Pour avoir le numéro du bouton, on cherchera dans la table ou on se situe.

Cette méthode permet de compenser au maximum les erreurs du montage. Elle peut permettre de différentier plus de boutons que les autres méthodes, au prix de code plus important et d'un temps de calcul plus long. Il ne faut pas oublier la table des limites. Si on peu de boutons, la lecture sans table fonctionne bien, mais si on a beaucoup de boutons, la lecture avec table est plus fiable, mais pour 100 boutons, il faut une table de 200 octets.

Cette méthode fonctionne avec tous les montages d'ensemble de boutons lus avec CAN.

Méthode

Il suffit (ce n'est pas si simple quand même) de lire les valeurs possibles en lisant les valeurs possibles pour tout les boutons, puis de calculer les différents milieux. Cela nécessite donc de faire un premier programme pour lire les valeurs.

Le programme suivant permet simplement de créer la table, et tant qu'à faire de donner un programme de test. Il suffit de faire le montage souhaité, de modifier éventuellement les constantes en début pour indiquer la broche qui lira les valeurs, changer éventuellement la tension de référence du convertisseur et la vitesse de la console. Le programme va demander d'appuyer sur tous les boutons (ou toutes les combinaisons dans le cas d'une lecture poly). On peut appuyer dans le désordre, cela n'a pas d'importance. Si on ne fait rien pendant plus de trois secondes le programme donne des indications sur ce qu'il a vu et donne un exemple d'application. Le programme est le suivant, il tourne sur les AVR:

// Ce programme utilise l'apprentissage pour générer un programme permettant de lire
// un ensemble de boutons à l'aide d'une broche analogique
//
//
// Il faut changer quelques constantes pour le configurer.
//
// Il faut donner la broche pour la lecture analogique
#define PIN A5 // Changer A5 en A0..A7 suivant le cas
//
// Il faut choisir la référence de tension à utiliser pour la conversion
// DEFAULT: pour Vcc
// INTERNAL: 1,1V pour Nano/Uno
// INTERNAL1V1: 1,1V pour Mega
// INTERNAL2V56: 2,56V pour Mega
// EXTERNAL: pour utiliser AREF
#define REFERENCE DEFAULT 
//
// Il faut donner le taux de transfert de la console
#define BAUDS 115200 // 115200 conseillée, mais on peut mettre 9600
// Ne pas oublier de régler la console à 115200 bauds



// *************************** Mémorisation des valeurs du CAN pour tous les boutons
byte lecture[128]; // Contiendra les valeurs possibles du CAN pour tous les boutons   

void ecrire(word valeur) // Enregitre que l'on a vu la valeur passée en argument (0 à 1023)
{
  lecture[valeur/8] = bitSet(lecture[valeur/8], valeur%8);
}

byte lire(word valeur) // true si le CAN a eu cette valeur
{
  return bitRead(lecture[valeur/8], valeur%8); 
}


// *************************** Variables globales
boolean repos; // true pour 1023, false pour 0 quand aucun bouton n'est appuyé
word valeurLue, // Résultat de la conversion puis n° de la touche
     oldValeurLue, // Mémorisation pour permettre deux lectures consécutives identiques
     tempsDepart; // Pour patienter tant qu'on appuie sur une touche; word suffit car on ne dépassera jamais 3000
int indexDebut = 0, indexFin = 0; // Pour explorer la table et lire les valeurs limites
byte nbBoutons = 0;
boolean pasPremiereVirgule = false;



void setup()
{

  // *************************** Initilalisations
  Serial.begin(BAUDS); // Régler aussi la console à BAUDS bauds!
  analogReference(REFERENCE); // Permet de choisir la référence de tension
  Serial.println(F("/*\nAppuyez sur toutes les touches, une par une\n"));
  repos = (analogRead(PIN) > 512); // Au repos on a 0 ou 1023

  // *************************** On attend qu'une touche soit appuyée
  do
  {
    oldValeurLue = valeurLue; // Sauvegarde de la mesure précédente du CAN
    valeurLue = analogRead(PIN); // Nouvelle lecture
  } while ((valeurLue!=oldValeurLue) || // Il faut lire deux valeurs identiques (double lecteure par sécurité)
     (repos && (valeurLue > 1020)) || (!repos && (valeurLue < 3))); // Et on s'écarte du repos

  // *************************** Recherche des valeurs possibles du CAN
  do // Tant qu'on appui sur des touches
  {
    do // Tant que l'on ne lit pas une bonne valeur du CAN
    {
      oldValeurLue = valeurLue; // Sauvegarde de la mesure précédente du CAN
      valeurLue = analogRead(PIN); // Nouvelle lecture
    } while (valeurLue!=oldValeurLue); // On recommence jusqu'à avoir deux lectures identiques
    ecrire(valeurLue); // On trouve une valeur, on l'enregistre

    // Gestion du timer, on sortir de la boucle si on n'appuie plus sur une touche pendant 3s
    if (repos) // Suivant qu'on ait 0 ou 1023 sans appuis
    {
      if (analogRead(PIN) < 1020) tempsDepart = millis(); // RAZ du timer tant qu'on appuie
    }
    else if (analogRead(PIN) > 5) tempsDepart = millis();
  } while ((word)millis() - tempsDepart < 3000);


  // ***************************  Affichage des valeurs lues pour contrôle
  // 1 indique une valeur vue, 0 une valeur que ne donne pas le CAN
  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(lire(ligne*64+colonne)); // Les valeurs
    Serial.println(); // Fin de ligne
  }
  Serial.println();


  // *************************** On compte le nombre de boutons. 
  // C'est le nobre d'intervalle de la table qui contionnent les 0. Les 1 indiquent un bouton
  while (indexDebut < 1024)
  {
    while (!lire(indexDebut)) indexDebut++; // On se place sur la première valeur 1
    while (lire(indexDebut) && (indexDebut < 1024)) indexDebut++; // On se place sur le premier 0 qui suit
    nbBoutons++;
  }


  // *************************** Affichagne un nombre de boutons sur la console
  Serial.print(F("\nJ'ai vu "));
  Serial.print(--nbBoutons);
  Serial.print(F(" boutons\n\n"));


  // *************************** On fait maintenent l'affichage du programme de test
  // Commentaires du départ, début commun
  Serial.print(F("Voici un exemple de programme pour gérer cet ensemble de boutons\n"
    "----------------------------------------------------------------*/\n\n"
    "// Ce programme teste un ensemble de boutons avec une lecture analogique\n\n\n"
    "#define PIN "));
  Serial.print(PIN);  
  Serial.print(F("\nconst word TABLE[] = {"));

  // Il faut donner la table des valeurs
  // Les valeurs sont par ordre décroissantes si au repos on a CAN=1023
  if (repos)
  {
    indexDebut = 1023;
    while (!lire(indexDebut)) indexDebut--; // On se place sur la dernière valeur
    while (lire(indexDebut) && (indexDebut >= 0)) indexDebut--; // On se place avant la dernière valeur
    while ((indexDebut >= 0) && (indexFin >= 0))
    {
      indexFin = indexDebut; // On cherche un intervalle sans bouton. Cela finit à indexDebut
      while (!lire(indexFin) && (indexFin >= 0)) indexFin--;  // La zone commence à indexFin + 1
      if (indexFin >= 0) // C'est un intervalle sans boutons
      {
        if (pasPremiereVirgule) Serial.print(F(", ")); // La première fois on ne met pas de virgules
        pasPremiereVirgule = true; // Les autres fois on la mettra
        Serial.print((indexDebut + indexFin + 1)/2); // Et on met le milieu de l'intervalle
      }
      indexDebut = indexFin;
      while (lire(indexDebut) && (indexDebut >= 0)) indexDebut--; // On se replace avant le bouton
    }
    Serial.print(F(", 0"));
  }
  // Les valeurs sont par ordre croissant si au repos on a CAN=0
  else
  {
    indexDebut = 0;
    while (!lire(indexDebut)) indexDebut++; // On se place sur la première valeur
    while (lire(indexDebut) && (indexDebut < 1024)) indexDebut++; // On se place après la première valeur
    while ((indexDebut < 1024) && (indexFin < 1024))
    {
      indexFin = indexDebut; // On cherche un intervalle sans bouton. Cela commence à indexDebut
      while (!lire(indexFin) && (indexFin < 1024)) indexFin++;  // La zone finit à indexFin - 1
      if (indexFin < 1024) // C'est un intervalle sans boutons
      {
        if (pasPremiereVirgule) Serial.print(F(", ")); // La première fois on ne met pas de virgules
        pasPremiereVirgule = true; // Les autres fois on la mettra
        Serial.print((indexDebut + indexFin - 1)/2); // Et on met le milieu de l'intervalle
      }
      indexDebut = indexFin;
      while (lire(indexDebut) && (indexDebut < 1024)) indexDebut++; // On se replace après le bouton
    }
    Serial.print(F(", 1024"));
  }

  // Suite du programme
  Serial.print(F("}; // Valeurs des seuils de comparaisons\n\n"
    "void setup()\n"
    "{\n"
    "  Serial.begin("));
  Serial.print(BAUDS);
  Serial.print(F("); // Régler aussi la console à "));
  Serial.print(BAUDS);
  Serial.print(F(" bauds!\n"));
  if (REFERENCE != DEFAULT)
  {
    Serial.print(F("  analogReference("));
    Serial.print(REFERENCE);
    Serial.print(F(");\n"));
  }
  Serial.print(F("  Serial.println(\"Appuyez sur des touches\");\n"
    "}\n\n"
    "word valeurLue, oldValeurLue; // Double lecture du convertisseur\n"
    "byte index; // Index dans la table pour comparaison (= N° de la touche)\n"
    "byte oldTouche; // Permet de ne pas tenir compte deux fois de suite la même touche\n\n"

    "void loop()\n"
    "{\n"
    "  do // Lecture du convertisseur\n"
    "  {\n"
    "    oldValeurLue = valeurLue; // Sauvegarde de la mesure précédente\n"
    "    valeurLue = analogRead(PIN); // Nouvelle lecture\n"
    "  } while (valeurLue!=oldValeurLue); // On recommence jusqu'à avoir deux lectures du CAN identiques\n\n"

    "  // Recherche du n° de la touche\n"
    "  index = 0; // Parcours de la table\n"
    "  while (TABLE[index] "));
  if (repos) Serial.print(">"); else Serial.print("<");
  Serial.print(F(" valeurLue) index++;\n"
    "  // index=0 si pas de touche appuyée sinon N° de la touche\n\n\n"
    

    "  // Affichage\n"
    "  if (index!=oldTouche) // Si changement\n"
    "  {\n"
    "    if (index!=0) // C'est une nouvelle touche\n"
    "    {\n"
    "      Serial.print(F(\"Bouton \"));\n"
    "      Serial.print(index);\n"
    "      Serial.print(F(\" appuyé \"));\n"
    "    }\n"
    "    else Serial.println(F(\"et relâché\"));\n"
    "    oldTouche=index; // Mémorisation pour ne pas afficher plusieurs fois\n"
    "  }\n"
	"}"));
}


void loop()
{
}

Analyse du résultat

Quand on arrête d'appuyer sur les touches, le programme affiche sur la console tout d'abord une table et le nombre de boutons. Dans la suite, j'ai utilisé un keypad analogique de 20 touches avec une tension de référence du CAN de 1,1V et avec une Uno. Le montage est le suivant:

Il est calculé pour une référence à 1,1V. J'ai donc change la ligne 16:

#define REFERENCE DEFAULT

par

#define REFERENCE INTERNAL

Le début du résultat est:

  0 1000000000000000000000000000000000000000000000000000000001100000
 64 0000000000000000000000000000000000000000000000000000110000000000
128 0000000000000000000000000000000000000000000000110000000000000000
192 0000000000000000000000000000000000000001100000000000000000000000
256 0000000000000000000000000000000011100000000000000000000000000000
320 0000000000000000000000111000000000000000000000000000000000000000
384 0000000000011100000000000000000000000000000000000000000000000011
448 1000000000000000000000000000000000000000000000011111000000000000
512 0000000000000000000000000000000000011110000000000000000000000000
576 0000000000000000001111000000000000000000000000000000000000000000
640 0111100000000000000000000000000000000000000000011110000000000000
704 0000000000000000000000000001111000000000000000000000000000000000
768 0000000011110000000000000000000000000000000000000011100000000000
832 0000000000000000000000000001111000000000000000000000000000000000
896 0000111100000000000000000000000000000000001111100000000000000000
960 0000000000000000000000000000000000000000000000000000000000000001

La grille indique ce que l'on a vu pour les valeurs en sortie du CAN entre 0 et 1023. Un "1" indique que si en appuyant sur les boutons, on obtient la valeur considérée, un "0" indique que l'on n'obtient pas cette valeur. En regardant le tableau, on voit que l'on peut avoir 0, 57, 58, 116, 117... 1023. "0" correspond à un appui sur le bouton n°0, 57 et 58 correspond à un appui sur le bouton n°1... et 1023 est obtenu si aucun bouton n'est appuyé.

On observera que l'appui sur un bouton donne de 1 à 4 valeurs possibles. Si tout étati parfait, on aurait des paquets de un seul "1". On peut voir de plus que les paquets de 1 sont très espacés, ce qui indique que l'on pourait presque mettre 4 fois plus de boutons (ce qui ferait un clavier de 100 boutons!).

Si pour un bouton, on obtenait 0000101110000, cela s'ignifierait que l'on n'a pas appuyé suffisament longtemps sur la touche.

On remarquera aussi que les valeurs utilisent quasiment tout l'espace ce qui fait que l'on peut se permettre plus de bruit et d'incertitudes.

Avec le même montage, mais en passant la référence de tension de 5V, on aurait:

  0 1000000000011000000000001100000000000100000000000110000000000011
 64 0000000001100000000001100000000011000000000010000000000110000000
128 0111000000001000000000100000000110000000011100000011000000001100
192 0000111000000110000000000000000000000000000000000000000000000000
256 0000000000000000000000000000000000000000000000000000000000000000
320 0000000000000000000000000000000000000000000000000000000000000000
384 0000000000000000000000000000000000000000000000000000000000000000
448 0000000000000000000000000000000000000000000000000000000000000000
512 0000000000000000000000000000000000000000000000000000000000000000
576 0000000000000000000000000000000000000000000000000000000000000000
640 0000000000000000000000000000000000000000000000000000000000000000
704 0000000000000000000000000000000000000000000000000000000000000000
768 0000000000000000000000000000000000000000000000000000000000000000
832 0000000000000000000000000000000000000000000000000000000000000000
896 0000000000000000000000000000000000000000000000000000000000000000
960 0000000000000000000000000000000000000000000000000000000000000001

Les valeur sont plus tassées, l'espace entre les valeurs est plus petit. on n'utilise pas toute la plage disponible.

Cette représentation permet de se rendre compte de la qualité de la réalisation.

Suit ensuite le nombre de boutons vus:

J'ai vu 20 boutons

J'ai bien un keypas de 20 touches, tout va bien. Si j'avais eu moins de 20 boutons, ce serait sans doute un oubli d'un appui, ou suffisament de dispersion qui ferait une seule suite de 1 pour deux boutons différents (trop de bruits ou trop e boutons). Si j'avais eu plus de 20 boutons, c'est que j'aurais une suite genre 000000101100000 pour un bouton. Soit je n'aurais pas appuyé assez longtemps, soit le montage est trop perturbé.

Le programme résultat

Suit enfin un programme de test:

// Ce programme teste un ensemble de boutons avec une lecture analogique


#define PIN 19
const word TABLE[] = {982, 920, 881, 839, 798, 755, 710, 665, 619, 572, 523, 471, 421, 369, 316, 260, 203, 145, 87, 28, 0}; // Valeurs des seuils de comparaisons

void setup()
{
  Serial.begin(115200); // Régler aussi la console à 115200 bauds!
  analogReference(3);
  Serial.println("Appuyez sur des touches");
}

word valeurLue, oldValeurLue; // Double lecture du convertisseur
byte index; // Index dans la table pour comparaison (= N° de la touche)
byte oldTouche; // Permet de ne pas tenir compte deux fois de suite la même touche

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

  // Recherche du n° de la touche
  index = 0; // Parcours de la table
  while (TABLE[index] > valeurLue) index++;
  // index=0 si pas de touche appuyée sinon N° de la touche


  // Affichage
  if (index!=oldTouche) // Si changement
  {
    if (index!=0) // C'est une nouvelle touche
    {
      Serial.print(F("Bouton "));
      Serial.print(index);
      Serial.print(F(" appuyé "));
    }
    else Serial.println(F("et relâché"));
    oldTouche=index; // Mémorisation pour ne pas afficher plusieurs fois
  }
}

En copiant ce programme depuis la console vers l'IDE d'arduino, en l'exécutant, on va obtenir si on appui sur le bouton n°4:

Bouton 4 appuyé

Et si on le relâche:

Bouton 4 appuyé et relâché

Notes

On a vu dans le tableau résultat que les premières valeurs sont 0, 57, 58, 116, 117... La limite entre entre les deux premiers boutons est donc de (0+57)/2 soit 28 (28,5 arrondi à 28). La limite suivante est (116+58)/2 soit 87. Ce sont ces valeurs que l'on trouve en fin de "const word TABLE[]".

Le logiciel calcule la position de repos tout seul. index vaut 0 si aucun bouton n'est appuyé, le programme est donc légèrement différent suivant la valeur de repos, notament le sens du parcours de la table.