Gestion de l’encodeur rotatif KY-040.

Diverses séquences de traitement du codeur KY-040.

Entre les contraintes imposées par l’utilisation des deux interruptions externes et celles liées au brochage du capteur rotatif « KY-040 » nous n’avons que peu d’initiatives. Seule la sortie SW reste totalement libre quand à l’affectation de la broche Arduino sur laquelle elle sera branchée. La Fig.24 concrétise le respect des aspects incontournables. Nous allons détailler les subtilités de programmation en examinant à la loupe P11_Evaluer_le_codeur_rotatif.ino pour les séquences spécifiques qui gèrent le capteur rotatif. Ces dernières constituent le « Mécano » qui sera ensuite intégré dans le projet.  Ce démonstrateur se contente de gérer un compteur évoluant entre les entiers 0 et 100 avec valeur en entrée de 50. Le bouton central recentrera le compteur à cette valeur. Le codage dédié à la gestion du KY-040 doit intégrer les aspects liés aux interruptions, mais également se charger de parer les aléas résultant des rebonds mécaniques qui affectent les contacts électriques, donc qui perturbent également les signaux disponibles sur les trois entrées X, Y et SW.

Définir les variables et les routines d’interruptions.

Disposant ci-avant d’un résumé complet de la syntaxe à respecter et des précautions à prendre, nous pouvons coder en C++ de l’IDE les actions à réaliser. Téléversez le programme démonstrateur, effectuez les divers branchements et testez son comportement. Anticipant sur la suite du projet, ce « croquis » propose le résultat du traitement sur un afficheur OLED. Vous constaterez qu’une rotation même rapide ne fait perdre aucun incrément. C’est l’avantage des interruptions, à condition toutefois que la séquence d’interruption soit courte en durée de traitement par rapport à la rapidité des événements surveillés. Le bouton central force le compteur virtuel à 50 mais génère également un BIP sonore sur D9 préparant l’avenir. Pour visualiser le fait que l’instruction delay(50); est suspendue pendant les interruptions, la LED d’ARDUINO clignote à 10Hz. Durant les rotations du capteur on constate qu’effectivement la cadence de clignotement diminue ou s’arrête. Examinons maintenant la façon dont sont traitées les séquences d’interruptions provoquées par A et B.

volatile unsigned char Position_Codeur// Compteur d’impulsions.

Cette instruction définit la variable globale Position_Codeur qui sera traitée par interruptions. Comme les valeurs entières positives sont compatibles avec le codage sur un OCTET, la variable est choisie avec le type char, dans un réflexe habituel d’optimisation en taille.
Aprés avoir déclaré les variables affectées aux interruptions, il faut déclarer les broches actives et les procédures qui y sont vectorisées. Bien que cette déclaration puisse se faire à tout emplacement et moment dans le programme, et éventuellement pointer des procédures différentes au cours de ce dernier, généralement c’est dans void setup() que sont déclarées les procédures de traitement des interruptions externes. Ce sera le cas pour notre « Sketch » :

(1) INT_active = false;
x   // Broche de l’interruption 0 (E/S D2) :
(2) attachInterrupt(0, Front_sur_A, CHANGE);
x   // Broche de l’interruption 1 (E/S D3) :
(3) attachInterrupt(1, Front_sur_B, CHANGE);

La variable INT_active rend valide par programme les interruptions. On commence en (1) par suspendre ces dernières avant d’orienter les déclenchements d’interruptions. La ligne (2) vectorise sur la procédure Front_sur_A quand un CHANGEment d’état sera détecté par INT 0. De façon totalement analogue, la ligne (3) vectorise sur la procédure Front_sur_B quand un CHANGEment d’état sera détecté par INT 1. Il reste à déterminer les traitements à effectuer au cours de ces deux procédures, sachant que durant leur exécution les TIMERs seront suspendus. Les procédures de type delay() seront alors figées, et la durée calibrée sera majorée du temps passé en IRQ.

Notez que si dans le programme développé certains délais sont critiques, et ne doivent pas être allongés, il suffit juste avant d’interdire toutes les interruptions comme explicité ci-avant. Les requêtes sont ensuite rétablies, lorsque le délai et éventuellement des instructions critiques qui le précèdent ou qui le suivent sont entièrement achevés. Quand le programme a démarré, ou sur un RESET, la variable est déclarée, les vecteurs INT 0 et INT 1 pointent sur les procédures idoine. La variable INT_active est alors positionnée à true dans la boucle de base. Tant que l’on ne tourne pas le bouton, aucun CHANGEment ne sera détecté et la boucle de base « brassera les instructions » en tâche de fond bien régulière. Dès que l’on va tourner ne serait-ce que légèrement le bouton du capteur, plusieurs transitions vont se produire alternativement sur  A ou B. C’est la première des deux qui va amorcer le processus d’analyse. La structure des deux séquences d’interruption est identique. L’une incrémente le compteur avec butée à 100, l’autre le décrémente avec limite à zéro. Supposons par exemple que ce soit A qui la première invoque void Front_sur_A() :

x       void Front_sur_A() { // Interruption quand A change d’état avant B.
(1)        if (INT_active) Delay_antirebonds(); // Attendre un peu la fin du rebond.
(2)        if (digitalRead(Codeur_A) != A_set ) { // Test pour savoir si l’état a changé.
(3)        A_set = !A_set;
(4)        if (A_set && !B_set) Position_Codeur += 1; // Incrémenter si A avant B.
(5)        if (Position_Codeur > MAX) Position_Codeur = MAX; INT_active = false; } }

En ligne (1) si INT_active est à true c’est que Front_sur_A()() arrive avant Front_sur_B() et la procédure commence par faire appel à void Delay_antirebonds() pour « gaspiller un peu de temps processeur » et donner aux contacts mécaniques le temps de se stabiliser. J’ai constaté à plusieurs reprises sur Internet, que certains programmeurs utilisent des instructions du genre :
                                                  if (INT_active) Delay(5); // Attendre un peu la fin du rebond.
Ils pensent que l’instruction va générer une temporisation de 5mS. C’est faux, car étant en routine d’interruptions ce type d’instruction est « suspendu ». Du reste, remplacez l’appel à la procédure de void Delay_antirebonds(); par delay(5000); par exemple. Le clignotement rapide devrait passer à des attentes de 5S à chaque incrément. Hors ce n’est pas du tout ce que l’on constate. Curieusement ces programmes fonctionnent … car c’est que l’appel à la procédure qui traite delay(); qui prend suffisamment de temps pour déjouer les rebonds.
Comme quoi un programme un peu « faux » peut parfois bien fonctionner !
                                                  void Delay_antirebonds() {for(byte I=0; I<3; I++);}
Ce n’est pas une raison pour « titiller le destin ». La procédure void Delay_antirebonds(); est assez « paresseuse », car elle se contente de faire trois fois … RIEN car il n’y a pas d’instruction avant le « ; » qui en termine le corps. Mais c’est bien connu, trois fois rien c’est déjà quelque chose. Le microcontrôleur doit compter jusqu’à trois, effectuer à chaque boucle une comparaison. Ces broutilles associées au temps qu’il faut pour se brancher sur cette procédure et en revenir consomme assez de temps pour que les contacts électriques soient stabilisés.
En ligne (2) on teste pour savoir si l’état de la ligne à changé. Si c’est la cas :
• En (3) on mémorise l’état actuel pour déterminer la prochaine transition,
• Si A est à l’état logique « 1 » et B est à l’état logique « 0 », en (4) on incrémente le compteur,
• Ligne (5) on commence par traiter la butée supérieure qui est placée en paramètre constant en tête du programme. Vous pouvez à convenance la modifier
x  mais n’oubliez pas que la valeur « centrale » est figée à 50 dans le « croquis ». Enfin, avant de revenir au programme appelant on positionne le booléen x  INT_active à false. Ainsi l’interruption sur Front_sur_B() sera sans effet jusqu’à ce que le programme de base réarme un autre comptage.

La procédure Front_sur_B() présente une structure totalement similaire, mis à part que c’est l’entrée Codeur_B qui est surveillée. L’action se résume alors à ne décrémenter le compteur que s’il est supérieur à zéro. L’état de l’entrée Codeur_B est mémosisé dans le booléen associé B_set.
Voyons maintenant la façon dont est pris en compte le bouton poussoir central et nous aurons en main tous les éléments logiciels pour définir la façade de maitrise du futur générateur H.F.

Examinant en détails les listages donné ci-avant, vous avez certainement un peu « tiqué » en lisant dans la ligne (4) l’expression Position_Codeur += 1; qui signifie : Prendre la valeur actuelle de Position_Codeur, ajouter 1 puis replacer le résultat dans Position_Codeur. Bien que totalement conforme à la syntaxe du C++ de l’IDE, je ne l’utilise pas souvent car je trouve personnellement que cette écriture est bien moins lisible que :
                                                 Position_Codeur = Position_Codeur + 1;
Certes, c’est plus long en texte, mais à mon sens ça ressemble plus à une phrase avec sujet, verbe et complément. C’est donc pratiquement toujours la deuxième forme qui encombrera nos exercices, mais ne jamais mentionner cette possibilité ne va pas dans le sens d’un didacticiel qui prétend ne strictement rien vous cacher. À vous de privilégier dans l’écriture celle qui emporte votre suffrage.
Autre expérience instructive, dans la deuxième ligne de la boucle de base remplacez la valeur 50 dans le délai par 5000. L’éclairement de la LED s’inverse maintenant toutes les cinq secondes. Dès qu’il change d’état, tournez rapidement le bouton sur plusieurs tours si possible. Vous constatez que la valeur affichée du compteur ne change pas. Puis dès qu’il y a poursuite du programme dans la boucle et que la LED s’inverse, les incréments sont entièrement pris en compte. C’est l’avantage intrinsèque des interruptions. Elles sont prises en compte et traitée y compris quand le microcontrôleur est « bloqué » dans une séquence courante du programme.

Le traitement spécifique associé au « bouton central » SW.

Constitué d’une charade en trois étapes, bien que n’incluant que des instructions très ordinaires en programmation Arduino, nous allons découvrir dans ces lignes une nouvelle approche matérielle très séduisante. Analysons le traitement de l’inverseur électrique SW. D’une manière totalement banale nous commençons par déclarer l’identificateur qui désignera l’entrée analogique A1 : connue dans l’IDE avec la référence 15 :

                                                 #define BP_central 15 // A1 pour lire le bouton central.

En revanche, dans void setup() la ligne digitalWrite(BP_central, HIGH); est assez particulière et exploite une opportunité très séduisante de l’ATmega328. Traditionnellement, une entrée analogique reçoit d’un capteur quelconque une tension qui reste comprise dans la marge 0 à +5Vcc. Rien de bien neuf. Dans ces conditions, une telle entrée n’est pratiquement jamais « laissée en l’air », risquant de capter les charges électrostatiques ambiantes. Dans notre projet, contrairement aux lignes A et B, l’inverseur SW est concrétisé par un simple contact électrique qui est en liaison à la masse GND si on clique sur le bouton, ou « en l’air » le reste du temps. Il faut impérativement relier l’entrée analogique A1 au +5Vcc par une résistance de forçage à l’état « 1 ». Quand une entrée analogique est déclarée en entrée, une instruction du type digitalWrite(BP_central, HIGH); valide une telle résistance interne implantée de façon native dans le microcontrôleur, dont on simplifie ainsi nettement le câblage et l’utilisation. Dans la boucle de base du programme, la séquence qui traite SW est grisée en gris clair pour les instructions très banales. Nous savons qu’un parasite peut toujours venir s’imposer en ligne. Pour lutter au maximum contre des impulsions courtes, mais jamais totalement exclues, on se contente d’effectuer la comparaison à la moyenne des tensions extrêmes qui seront présentes en cours d’utilisation. Sur un clic on aura une CAN qui avoisine zéro, alors que la numérisation sera proche de 1023 le bouton de rotation n’étant pas sollicité axialement. En plaçant à 512, soit la moyenne, la valeur de comparaison, on élimine de façon radicale les « glitch ».

//===================== Traite le B.P. central =====================
if (analogRead(BP_central) < 512) {// Si le B.P. central est activé :
digitalWrite(SYN_EXT, HIGH); BIP(); // Allume la LED et fait un BIP.
while (analogRead(BP_central) < 512); // Attendre le relâché.
digitalWrite(SYN_EXT, LOW); Position_Codeur = 50;} // Recentrer le comptage.

L’anti rebonds est caché dans la temporisation du BIP sonore qui accompagne l’enfoncement, puis, une fois que la libération du bouton poussoir est détectée, dans le traitement qui suit. On ne peut rêver plus simple. Nous possédons toutes les séquences pour gérer OLED, le capteur rotatif et le circuit générateur de signaux H.F. Le moment est venu de commencer à développer un appareil de mesures complet, convivial et performant.

>>> Page suivante.

 

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *