La plupart des montages électroniques faisant intervenir une interface homme-machine, ont des claviers matricés ou simples. Plusieurs technologies existent; deux d'entre elles sont:

Les claviers à membranes:

clavier a membrane 1

et les claviers à touches:

clavier a touches 1

Une fois intégrés dans les montages, il arrive souvent qu'en phase de tests, leur mise en oeuvre laisse apparaître des dysfonctionnements qui peuvent être causés par les rebonds des touches.

rebonds dans un montage

Dans cet article on va mettre en évidence le phénomène de rebonds produit par un bouton poussoir, puis apporter quelques idées de solutions pour réduire au maximum leur influence dans un montage.

Matériel:

    • Une alimentation stabilisée +5V;
    • Un oscilloscope numérique;
    • Un Arduino Uno;
    • Un bouton poussoir du type B3F-6001 ou équivalent;
    • Une plaque à essais;
    • Un 74HC14;
    • Des résistances de : 1x10 KΩ, 1x82 KΩ, 1x18 KΩ, 7x330 Ω;
    • Un condensateur de 1μF;
    • Un afficheur 7 segments à led à cathode commune.

D'où proviennent les rebonds?

Avant de répondre à cette question, voyons d'abord la structure du bouton poussoir qui sera utilisé et son fonctionnement.

Le bouton poussoir est constitué:

  • d'un support  (1) sur lequel se trouvent les contacts et vue bouton poussoir eles 4 broches de connexions;
  • d'une coupelle flexible (2) en métal;
  • d'un plongeur (3);
  • et d'un cache métallique rigide (4).

En général les broches de connexions sont reliées deux à deux. Lorsqu'on appui sur le plongeur, celui-ci fait action sur la coupelle métallique qui se déforme et établi un contact électrique entre les deux paires de broches.

Au cours de la déformation de la coupelle, il y a un laps de temps pour lequel elle n'occupe pas sa position finale stable. Si le bouton est câblé dans un montage, il en résulte une série de pics électriques; ce sont les rebonds.

illustration bouton rebonds

Les rebonds peuvent se produire aussi lorsqu'on relâche le bouton poussoir. Leur apparition n'est pas forcément systématique.

Mise en évidence:

Le montage de test:

montage test rebonds 2

Le bouton poussoir sera relié d'un côté à la masse et de l'autre à la résistance de 10 K qui elle même est reliée au +5 V de l'alimentation (1); le point milieu entre la résistance de 10 K et le bouton poussoir ira sur une voie d'entrée de l'oscilloscope (3).

On peut observer à l'oscilloscope, ce qui se passe quand on appui sur le bouton poussoir:

capture rebonds 2

Bref un seul appui peut générer plusieurs impulsions. Cela peut être vraiment néfaste et perturber le fonctionnement d'un montage électronique.

Comment supprimer les rebonds?

Si le montage fait intervenir des microcontrôleurs, et qu'il faut éliminer les rebonds, on peut adopter soit une solution câblée, soit une solution logicielle.

Anti-rebonds câblé:

Pour le cas du bouton poussoir utilisé dans cet exemple, une solution possible consiste à le connecter à une cellule R-C, pour filtrer le rebonds, puis relier la sortie cette cellule à l'entrée d'un inverseur à trigger de Schmitt: 

antirebonds materiel 2 

D'après les calculs, on a choisi R1 = 82 KΩ, R2 = 18 KΩ, et C = 1 µF.

{up readmore = Voir le détail | textless = fermer | class = btn btn-primary | panel-style = bd-gris p1}

Le montage est alimenté en +5 V;

On appui sur le bouton poussoir: le condensateur C se décharge à travers R2. L'équation de décharge sera:

\[\mathsf{U(t)=U_0e^{-(t/R2C)}}\tag{1}\label{1}\]

U(t) : tension instantanée aux bornes du condensateur.

U0 : tension initiale aux bornes du condensateur.

D'après le schéma proposé, la broche (+) du condensateur est reliée à l'entrée de la porte inverseuse. Le but est de calculer les composants R2 et C, pour que la tension aux bornes du condensateur reste dans certaines conditions au dessus du seuil de basculement de la porte inverseuse.  Selon la documentation technique du 74HC14, ce seuil de basculement est: Uth = 1,7 V (valeur minimum).

En supposant que le condensateur est initialement chargé (U0 = 5 V).

A partir de la formule (1):

\[\mathsf{R2=\frac{-t}{C{\ell}n(U_{th}/U_0)}}\]

On peut fixer C: une valeur très petite entraîne une résistance R2 très grande, ce qui pourrait, avec le courant de fuite créer une tension résiduelle à l'entrée de la porte.

On va choisir C = 1 µF et  t = 20 ms; sachant que:U0 = 5 V, et Uth = 1,7 V..  Tous calculs faits, R2 = 18,5 KΩ; une valeur 18 KΩ ± 5% peut convenir.

On relâche le bouton poussoir: le condensateur déchargé, se charge à travers une résistance R = R1 + R2, jusqu'à la valeur finale Uf = 5 V.

L'équation de charge sera:

\[\mathsf{U(t)=U_f(1-e^{(-t/RC)})}\tag{2}\label{2}\]

Selon la documentation technique du 74HC14, le seuil de basculement Uth = 0,9 V

A partir de la formule (2):

\[\mathsf{R=\frac{-t}{C{\ell}n(1-U_{th}/U_f)}}\]

C = 1 µF. Tous calculs faits on trouve R ≈ 100 KΩ; sachant que R2 = 18 KΩ,   R1 = 82 KΩ ± 5%.

{/up readmore}

Test de la solution:

Le circuit est cablé sur plaque à essai, ensuite on l'alimente en +5 V. A l'oscilloscope on visualise à la fois la tension au borne du bouton poussoir (couleur bleu) et la tension à la sortie de la porte inverseuse (couleur jaune).

Résultat obtenu:

antirebond materiel mo

Les signaux obtenus montrent qu'il n'y a plus de rebonds lorsqu'on appui sur le bouton poussoir. Même si le choix de t = 20 ms est un peu exagéré, cela peut convenir dans certaines situations.

Anti-rebonds logiciel:

Ce sujet concernant les rebonds a été traité depuis des années; les forums ne désemplissent pas. Il suffit de rechercher dans le net, et on tombera sur des idées de solutions.

Parmi celles-ci  on trouve des algorithmes qui utilisent des compteurs pour estimer une durée fixe à partir du moment où on appui sur le bouton poussoir pour supposer qu'il n'y a plus de rebonds, et d'autres qui traduisent de façon logicielle la structure que nous avons présenté comme une solution matérielle possible; en y intégrant un trigger de Schmitt soft.

Le site arduino.cc traite ce sujet; plusieurs exemples de solutions sont proposés.

Exemple de réalisation: compteur à affichage 7 segmant à LED

Montage:

montage bp anti rebond arduino 2 

A chaque appui sur le bouton poussoir, l'afficheur est incrémenté de 1, et revient à 0 lorsqu'il atteint 9, puis le comptage recommence

On va utiliser une librairie Arduino pour résoudre ce problème. Un bouton poussoir du type B3F-6001 (fabriqué par OMRON) sera utilisé.

Exemple de programme sans anti-rebonds:

Dans ce programme tout front descendant à chaque appui sur le bouton poussoir incrémente un compteur dont le contenu sera envoyé sur un afficheur 7 segments à LEDs.

Les segments de l'afficheur sont connectés sur les ports 4, 5, 6, 7, 8, 9, 10.

#define Seg_a   4
#define Seg_b   5
#define Seg_c   6
#define Seg_d   7
#define Seg_e   8
#define Seg_f   9
#define Seg_g   10

L'initialisation de l'interface série pour le debbogage a aussi été rajoutée.

Pour afficher le chiffre 0 par exemple, seul le segment g de l'afficheur sera éteint; tous les autres seront allumés. L'ensmble des combinaisons possibles pour tous les chiffres de 0 à 9 est définit par la variable numbersTable[10][7],c'est un tableau de booléens à 10 lignes (chiffres de 0 à 9) et 7 colonnes (segments "a" à "g" de l'afficheur).

La fonction  writeNumberToDisplay(int number), permet d'afficher le contenu de la variable "number". Selon les cas le chiffre correspondant sera affiché.

La fonction setup(), initialise la liaison série et tous les ports d'entrées-sorties qui sont utilisés. Les ports connectés aux segments sont des sorties, tant dis que le port sur lequel est connecté le bouton poussoir est une entrée.

Pour finir, le programme principal, vérifie en continu si le bouton poussoir est appuyé, puis incrémente la variable "counter", ensuite la fonction writeNumberToDisplay() est appelée pour afficher le contenu de la variable "counter".  Ensuite l'instruction "Serial.println(counter)"  rend disponible le contenu de la variable "counter" sur la liaison série.

Ci-après le listing du programme; lorsqu'il est exécuté, on peut observer l'influence des rebonds, à l'aide de l'afficheur et du moniteur série Arduino:


//***********************************
// Example of undebounced push button.
// Display the number of tap to one
// digit seven segment LED display.
// Using Arduino Uno
//************************************
// Date : 2023 - 11 - 10
// By : Jt BB
//************************************
// ***********************************
// Pins and Serial baud rate
// ***********************************
#define Seg_a   4
#define Seg_b   5
#define Seg_c   6
#define Seg_d   7
#define Seg_e   8
#define Seg_f   9
#define Seg_g   10
#define DEBUG_BAUD_RATE   115200 //For debug
//************************************
// Variables
//************************************
boolean numbersTable[10][7] = 
{//Array of numbers to display
  {1,1,1,1,1,1,0}, //0 - seg a,b,c,d,e,f : on g :off
  {0,1,1,0,0,0,0}, //1 - seg b,c : on a,d,e,f,g :off
  {1,1,0,1,1,0,1}, //2
  {1,1,1,1,0,0,1}, //3
  {0,1,1,0,0,1,1}, //4
  {1,0,1,1,0,1,1}, //5
  {1,0,1,1,1,1,1}, //6
  {1,1,1,0,0,0,0}, //7
  {1,1,1,1,1,1,1}, //8
  {1,1,1,1,0,1,1}  //9
};
//
int button_pin = 11;
int button_val_last;
//**********************************
// Functions
//**********************************
// Display number
void writeNumberToDisplay(int number)
      {
         switch(number)
            {
                case 0:
                   for (int i=0; i<7; i++){//display 0
                      digitalWrite(i+4, numbersTable[0][i]);
                   }
                break;
                //
                case 1:
                   for (int i=0; i<7; i++){//display 1
                      digitalWrite(i+4, numbersTable[1][i]);
                   }
                break;
                //
                case 2:
                   for (int i=0; i<7; i++){//display 2
                      digitalWrite(i+4, numbersTable[2][i]);
                   }
                break;
                //
                case 3:
                   for (int i=0; i<7; i++){//display 3
                      digitalWrite(i+4, numbersTable[3][i]);
                   }
                break;
                //
                case 4:
                   for (int i=0; i<7; i++){//display 4
                      digitalWrite(i+4, numbersTable[4][i]);
                   }
                break;
                //
                case 5:
                   for (int i=0; i<7; i++){//display 5
                      digitalWrite(i+4, numbersTable[5][i]);
                   }
                break;
                //
                case 6:
                   for (int i=0; i<7; i++){//display 6
                      digitalWrite(i+4, numbersTable[6][i]);
                   }
                break;
                //
                case 7:
                   for (int i=0; i<7; i++){//display 7
                      digitalWrite(i+4, numbersTable[7][i]);
                   }
                break;
                //
                case 8:
                   for (int i=0; i<7; i++){//display 8
                      digitalWrite(i+4, numbersTable[8][i]);
                   }
                break;
                //
                case 9:
                   for (int i=0; i<7; i++){//display 9
                      digitalWrite(i+4, numbersTable[9][i]);
                   }
                break;
                //
                default:
                //
                break;
            }
      }
//***********************************
// Setup
//***********************************
//
void setup() {
  // pins 4 to 10 as output 
          pinMode(Seg_a, OUTPUT);
          pinMode(Seg_b, OUTPUT);
          pinMode(Seg_c, OUTPUT);
          pinMode(Seg_d, OUTPUT);
          pinMode(Seg_e, OUTPUT);
          pinMode(Seg_f, OUTPUT);
          pinMode(Seg_g, OUTPUT);
          pinMode(button_pin, INPUT_PULLUP);
  // Serial monitor
  Serial.begin(DEBUG_BAUD_RATE);
}
//***********************************
// Main
//***********************************
void loop() {
  // put your main code here, to run repeatedly:
       int button_val;
       static int counter = 0;
       button_val = digitalRead(button_pin);
       if ((button_val_last == HIGH)&&(button_val == LOW))
          {
              counter++;
              if (counter > 9){counter =0;}
              Serial.println(counter);
          }
        writeNumberToDisplay(counter);
        button_val_last = button_val;
            
}

Il faudra garder à l'esprit que les rebonds apparaissent de façon aléatoire.

Exemple de programme avec anti-rebonds:

Un algorithme spécial ne sera pas développé ici pour résoudre ce problème; on utilisera la librairie ADebouncer de Arduino. On peut retrouver celle-ci dans le gestionnaire de librairie en tapant "debouncer". Le gestionnaire propose de nombreuses librairies des contributeurs.

La librairie ADebouncer fonctionne sur le principe d'un compteur qui estime après une durée prédéfinie que le signal sur le port est stable. Pour en savoir plus sur cette librairie et comment l'utiliser, il faut se rendre sur le dépôt du contributeur; le lien est accessible directement sur l'IDE Arduino.

Le programme précédent a été légèrement modifié par l'ajout de la librairie ADebouncer.

D'abord on ajoute la librairie au programme avec la directive:

#include "ADebouncer.h"

Ensuite on crée une instance appelée "debouncer":

ADebouncer debouncer;

Celle-ci permettra d'appeler des méthodes nécessaires pour filtrer les rebonds sur le port d'entrée du bouton poussoir.

Ci-après le listing du programme:


//***********************************
// Example of push button debouncing.
// Display the number of tap to one
// digit seven segment LED display.
// Using Arduino Uno
//***********************************
// Date : 2023 - 11 - 10
// By : Jt BB
//***********************************
#include "ADebouncer.h"

#define buttonPin 11         // Define the button input pin.
#define debouncePeroid 10    // Define the debounce period in milliseconds

ADebouncer debouncer;        // Debouncer variable.


// ***********************************
// Pins and Serial baud rate
// ***********************************
#define Seg_a   4
#define Seg_b   5
#define Seg_c   6
#define Seg_d   7
#define Seg_e   8
#define Seg_f   9
#define Seg_g   10
#define DEBUG_BAUD_RATE  115200 //Serial debug

// ***********************************
// Variables
// ***********************************
boolean numbersTable[10][7] = 
{//this array give us the number to display
  {1,1,1,1,1,1,0}, //0 - seg a,b,c,d,e,f : on g :off
  {0,1,1,0,0,0,0}, //1 - seg b,c : on a,d,e,f,g :off
  {1,1,0,1,1,0,1}, //2
  {1,1,1,1,0,0,1}, //3
  {0,1,1,0,0,1,1}, //4
  {1,0,1,1,0,1,1}, //5
  {1,0,1,1,1,1,1}, //6
  {1,1,1,0,0,0,0}, //7
  {1,1,1,1,1,1,1}, //8
  {1,1,1,1,0,1,1}  //9
};

//************************************
// Functions
//************************************

// Display number
void writeNumberToDisplay(int number)
      {
         switch(number)
            {
                case 0:
                   for (int i=0; i<7; i++){//display 0
                      digitalWrite(i+4, numbersTable[0][i]);
                   }
                break;
                //
                case 1:
                   for (int i=0; i<7; i++){//display 1
                      digitalWrite(i+4, numbersTable[1][i]);
                   }
                break;
                //
                case 2:
                   for (int i=0; i<7; i++){//display 2
                      digitalWrite(i+4, numbersTable[2][i]);
                   }
                break;
                //
                case 3:
                   for (int i=0; i<7; i++){//display 3
                      digitalWrite(i+4, numbersTable[3][i]);
                   }
                break;
                //
                case 4:
                   for (int i=0; i<7; i++){//display 4
                      digitalWrite(i+4, numbersTable[4][i]);
                   }
                break;
                //
                case 5:
                   for (int i=0; i<7; i++){//display 5
                      digitalWrite(i+4, numbersTable[5][i]);
                   }
                break;
                //
                case 6:
                   for (int i=0; i<7; i++){//display 6
                      digitalWrite(i+4, numbersTable[6][i]);
                   }
                break;
                //
                case 7:
                   for (int i=0; i<7; i++){//display 7
                      digitalWrite(i+4, numbersTable[7][i]);
                   }
                break;
                //
                case 8:
                   for (int i=0; i<7; i++){//display 8
                      digitalWrite(i+4, numbersTable[8][i]);
                   }
                break;
                //
                case 9:
                   for (int i=0; i<7; i++){//display 9
                      digitalWrite(i+4, numbersTable[9][i]);
                   }
                break;
                //
                default:
                //
                break;
            }
      }
// ************************************************
// Setup 
// ************************************************

void setup() {
  pinMode(buttonPin, INPUT_PULLUP);               // Set the button mode as input pullup.  
  // I/O pins 4 to 10 are output 
          pinMode(Seg_a, OUTPUT);
          pinMode(Seg_b, OUTPUT);
          pinMode(Seg_c, OUTPUT);
          pinMode(Seg_d, OUTPUT);
          pinMode(Seg_e, OUTPUT);
          pinMode(Seg_f, OUTPUT);
          pinMode(Seg_g, OUTPUT);
  //
  debouncer.mode(DELAYED, debouncePeroid, HIGH);  // Set the debounce mode as delayed mode and debounce period as 10 ms, with the initial output in a HIGH state.
  Serial.begin(DEBUG_BAUD_RATE);
}

void loop() {
  static int counter = 0;
  debouncer.debounce(digitalRead(buttonPin));  // Save the debounced of the button state.
  if (debouncer.falling())
  {
    counter++;
    if (counter > 9){counter =0;}
    Serial.println(counter);
  }
  writeNumberToDisplay(counter);
     
}

 

Lorsqu'on exécute ce programme, le fonctionnement du montage est nettement amélioré. Le résumé de l'article est proposé dans cette vidéo.

 

 

Conclusions:

Les boutons poussoirs qu'ils soient à touches ou à membranes peuvent produire des rebonds; pas seleument; les contacts d'un relais ou d'un capteur de fin de course aussi peuvent en produire.

Ce sujet toujours d'actualité date de longtemps. Des solutions existent pour réduire voire supprimer les rebonds, les choix dépendent des exigences, et de l'environnement dans lequel le montage va évoluer.

 

JtBB

Dans un montage conçu autour d'Arduino, il est possible que le nombre d'entrées-sorties disponibles ne suffisent pas pour réaliser notre projet.
Que faire si on se retrouve dans un tel cas de figure? Des solutions existent.

On peut créer des entrées-sorties supplémentaires avec d'autres types de composants électroniques, puis dialoguer avec ceux-ci à l'aide d'un bus de communication série. Arduino sait communiquer par la liaison série RS232, par SPI ou par I2C.

Dans ce nouvel article on se propose de montrer qu'on peut augmenter le nombre d'entrées-sorties de la carte Arduino, à l'aide du bus I2C.

Comme exemple de mise en situation, on va réaliser une serrure codée dotée d'un module clavier-afficheur à cristaux liquides.

Pour finir, on essayera de mettre en évidence le côté pratique de ce type de montage avec une carte à microcontrôleurs à peine plus grande qu'un timbre poste dont la puissance est inversement proportionnelle à la taille: le module Seeeduino XIAO SAM21. Cette mini carte peut être achétée sur Amazon, Aliexpress, ou sur le site Gotronic, pour moins de 10Euros.

Dans cet article, on commencera par présenter le PCF8574, puis le schéma du montage et de ses sous-ensembles, ensuite le programme  et pour finir le test de fonctionnement.

Composants utilisés:

De quoi avons nous besoin pour ce montage:

- Arduino Uno, Seeeduino XIAO SAM21;
- Un clavier matriciel 16 touches;
- Un afficheur à cristaux liquides deux lignes 16 caractères retroéclairé;
- Un buzzer 5V;
- Deux PCF8574 Interfaces d'entrées-sorties I2C tout ou rien 8 bits;
- Un transiator BC547B;
- Une diode LED verte et une diode LED rouge de 3mm les deux;
- Deux résistances de 1Kohm;
- Trois résistances de 10KOhms;
- Une résistance de 100KOhms:
- Une résistance de 100Ohms;
- Deux connecteurs RJ12 (6P6C);
- Une carte à trous pastillée 120 x 85mm environ;
- Du câble plat de téléphonie 6 Conducteurs 500mm de longueur.

 

Présentation du PCF8574:


C'est une interface d'entrées-sorties I2C, qui fonctionne en tout ou rien. Tout d'abord, un bref aperçu du bus I2C.

Le bus I2C a été crée par la société PHILIPS Semiconductors, le côté vraiment pratique de ce type de bus réside dans le fait que tous les composants peuvent dialoguer avec un microprocesseur ou un microcontrôleur par l'intermédaire de deux fils reférencés par une masse. Ceci a eu pour conséquences la réduction drastique du nombres de pistes sur le circuit imprimé et la réduction du coût de fabrication pour les équipementiers.

Finalement l'I2C s'est enrichi en terme de diversité des commposants; on peut citer par exemple: des converstisseurs analogiques-numériques ou numériques-analogiques, des mémoires,divers décodeurs, des circuits audio,...etc; pour finir par devenir une norme de facto. Avec un débit de base de 100 kbits/s.

Au départ le Bus I2C avait été conçu pour des composants électroniques localisés à l'intérieur d'un appareil. Il est même possible à l'heure actuelle d'étendre sa portée à une distance théorique avoisinnant quelques centaines de mètres avec l'ajout d'un circuit d'interface dédié à cet effet.

Dans un bus I2C, tous les composants à l'exeption du maître possèdent une adresse unique codée sur 7 bits. Ce sont les 7 bits de poids fort, de l'octet que le maître devra envoyer aux esclaves. Voir tableau ci-dessous:

OctetBits
7(MSB) 6 5 4 3 2 1 0(LSB)
Adresse I2C L H L L A2 A1 A0 R/W
L = état bas = niveau logique 0;  H = état haut = niveau logique 1. 

Dans ce tableau, les bits  7, 6, 5, et 4 sont figés pour cet exemple, ils sont à 0100, il reste à l'utilisateur de configurer les bits A2, A1, et A0, en les reliant soit à un niveau logique 1, soit à niveau logique 0. Le dernier bit représente le bit d'écriture/lecture; lorsqu'il est à 1 le maître demande une opération de lecture, et lorsqu'il est à 0 c'est une opération d'écriture.

Exemple d'adresse: supposons que les broches A2, A1, et A0 sont toutes reliées à la masse, l'adresse I2C du composant sera : 0100000 ou 0x20 en hexadécimal.

Et si A2 et A1 sont à la masse et A0 à +5 Volts, l'adresse sera : 0100001 ou 0x21 en hexadécimal.

La valeur des 4 bits de poids forts de ce tableau dépend du type de composant, si c'est un PCF8574, ils prennent la valeur 0100, et si c'est un PCF8574A ce sera 0111.

Un PCF8574 n'aura pas la même adresse I2C qu'un PCF8574A, même si la configuration des broches A2, A1, et A0 est la même.
L'extrait ci-dessous tirée de la documentation technique de Texas Instruments, donne les huits adresses possibles du PCF8574 et du PCF8574A:

adresses i2c 8574

Il est donc possible, au vu de ceci, d'étendre à 128 le nombre d'entrées-sorties d'Arduino, si on utilise ces deux composants dans un même montage:

exemple plusieurs 8574 16 es

Les PCF8574 sont munies d'une sortie d'interruption. Lorsqu'ils fonctionnent comme entrées pour capteur, un changement d'état sur l'une des entrées produit un signal d'interruption sur cette sortie.
Le microcontrôleur peut ainsi aller voir ce qui se passe puis entreprendre une action appropriée en conséquence.

Schéma du montage:


Il est composé de deux PCF8574, reliés par I2C à Arduino. Un PCF8574 se chargera de gérer l'afficheur; celui-ci fonctionnera exclusivement en sortie, tandis que l'autre qui s'occupera du clavier; il fonctionnera en entrée et en sortie. Des résistances de Pull-Up, de 10 KOhms sont branchées sur les signaux SCL, SDA et INT. Pour l'écran LCD, une résistance ajustable de 5K à 20K, sert pour régler le contraste, les deux diodes indicatrices d'état, ainsi que le buzzer sont branchés sur les deux broches restantes du PCF8574.

Pour les connexions du clavier:

clavier 1 1 schema

Pour l'écran LCD:

ecran lcd 1 1 schema

Signaux du connecteur RJ12:

rj12 1 1

Le clavier:

principe clavier matrice

Les broches 1, 2, 3, et 4 correspondent respectivement aux colonnes 1, 2, 3, et 4; les broches 5, 6, 7, 8 correspondent respectivement aux lignes 1, 2, 3, et 4.

 

Comment décoder une touche appuyée d'un clavier matricé 16 touches?

On a connecté le clavier au PCF8574 de la façon suivante:

=> Colonne 1 -  E/S 0; Colonne 2 - E/S 1; Colonne 3 - E/S 2; Colonne 4 E/S 3.

=> Ligne 1 - E/S 4; Ligne 2 - E/S 5; Ligne 3 - E/S 6; Ligne 4 - E/S 7

Pour décoder une touche appuyée, on va d'abord commencer par placer les lignes en sorties, puis les colonnes en entrées. On écrira la valeur logique 0000, sur les lignes 1,2,3,et 4. Puisque les colonnes sont considérées comme des entrées, elles occupent toutes un niveau logique 1 (1111).

Par exemple si on appui sur la touche 8:

principe clavier 1 2

La colonne 2 prend la valeur 0. Le passage de l'état initial des colonnes 1111 vers l'état 1101, va provoquer une interruption de la part du PCF8574.

Dans la routine d'interruption, on va d'abord commencer par mémoriser l'état des 8 bits; ici: 00001101; puis mettre les colonnes à en sorties et à 0000, puis les lignes en entrées à 1111, pour obtenir un nouvel état:

principe clavier 1 3

Pendant ce temps la touche 8 est toujours appuyée. La nouvelle valeur du port d'Entrées/Sorties du PCF8574 sera: 10110000

Il suffit de faire un "Ou exclusif" entre les deux octets obtenus, pour obtenir un code unique correspondant à la touche appuyée. Ici 00001101 "Ou exclusif" 10110000 = 10111101.

Donc c'est bien la colonne 2 - ligne 3 qui a été solicitée.

Il suffifit de faire correspondre à chaque code obtenu un chiffre ou une lettre du clavier.

Il est aussi possible de gérér le clavier sans utiliser les interruptions. Dans ce cas il faudra placer lignes dans un état logique 0 par exemple, puis procéder à un balayage des colonnes, afin détecter une touche appuyée.

Dans les deux cas de figures ce sont des algorithmes difficiles à mettre en oeuvre.
Il existe dans l'IDE Arduino une librairie permettant de gérer un clavier I2C; c'est cette librairie qu'on va utiliser dans notre programme de démonstration.

L'afficheur LCD:


Il peut être piloté soit en 8 bits ou en 4 bits. On va choisir de le piloter sur 4 bits, avec en plus les broches de commande de l'afficheur RS et EN, celui-ci occupera en tout 6 broches d'entrées-sorties du PCF8574. Il nous reste donc 2 broches pour connecter le buzzer et les deux diodes.

Pour gérer l'écran LCD par un PCF8574; on va utiliser aussi une librairie disponible elle aussi dans l'IDE Arduino.

Voici le schéma du montage: réalisé à l'aide de ...

Pour cet essai, on a réalisé les connexions sur une plaque à trous pastillés:

carte pastillee lcd clavier

On peut maintenant procéder à la pose de l'écran LCD, puis du clavier.

La connexion avec la carte Arduino sera assurée par du câble 6 conducteur, et des connecteur RJ12
6 Pôles 6 Conducteurs.

rj12 1 2

Différentes connexions:  1 => SDA; 2 => SCL; 3 => INT; 5 => GND; 6 => +5V

Du côté d'Arduino, SDA correspond à la broche A4; SCL correspond à la broche A5. Le +5V et la masse de Arduino serviront pour alimenter le clavier et l'afficheur.

L'entrée d'interruption se fera sur la broche d'entrées-sorties n°2 de Arduino, c'est l'interruption INT0 qui est utilisée ici.


Les connecteurs RJ12, ont été récupérés, c'est le pourquoi ils sont un peu différents de ceux du commerce, mais le brochge reste le même. On peut aussi en trouver sur le net ou sur des sites marchands.
Il faudra aussi avoir en sa possession une pince à sertir adaptée pour assembler les câbles sur les connecteurs mâles.

Pour finir, seules trois broches d'entées-sorties ont été mises à contribution sur Arduino.

Voici ce qu'on obtient après avoir assemblé tous les composants sur le module clavier-afficheur:

ensemble clavier afficheur

Exemple de programme Arduino:

Le programme d'essai consiste à piloter une serrure codée sur 5 caractères, avec une possibilité de corriger un caractère erroné.

On va donc utiliser deux librairies de l'IDE Arduino: la librairie I2CKeypad.h pour la gestion du clavier, et la librairie LiquidCrystal_PCF8574.h pour l'afficheur LCD 2 x 16 caractères.

Le programme débute avec les directives:

#include "Wire.h"
#include "I2CKeyPad.h"
#include <LiquidCrystal_PCF8574.h>

Puis les constantes d'adresses I2C du clavier et de l'afficheur LCD:

const uint8_t KEYPAD_ADDRESS = 0x20;
const uint8_t LCD_ADDRESS = 0x21;

Ensuite les instances clavier et afficheur LCD sont déclarées comme suit:

I2CKeyPad keyPad(KEYPAD_ADDRESS);
LiquidCrystal_PCF8574 lcd(LCD_ADDRESS, LCD_EN, LCD_RS, LCD_P4, LCD_P5, LCD_P6, LCD_P7);

Pour l'afficheur on va définir où seront connectées toutes les broches, pour son fonctionnement.

Ensuite on définit le tableau de caractères du clavier. A noter dans ce tableau les constantes N et F qui pourraient s'afficher en cas de défaut de frappe; par exemple deux touches appuyées en même temps.

Puis, les constantes d'interruption, à savoir la broche et la constante volatile utilisée dans la routine d'interruption appelée keyChange.

Ensuite vient le sous-programme d'initialisation setup()

Juste une petite remarque; dans cette routine setup() on a ajouté l'instruction

uint8_t index = keyPad.getKey();

Cette instruction permet de forcer l'état du PCF8574, de façon à ce que celui-ci puisse générer l'interruption nécessaire si on veut faire fonctionner la clavier en interruption. Toutes fois si on choisit le mode polling au lieu des interruptions pour gérer le clavier, il n'y aura pas besoin de rajouter cette instruction dès le départ dans la rubrique setup().

Le programme principal loop() se chargera d'abord de vérifier si la varaiable keyChange est positionnée à "1". Cet état est positionné par la routine d'interruption, qui à chaque fois qu'une touche est appuyée. Le programme principal va donc appeler les fonctions de gestion du clavier pour voir la frappée, puis va effectuer le traitement en conséquence. Notre code secret ici est égal à 1256B.

Ci-joint le listing complet du programme:

///////////////////////////////////////////////////////////////
//
// Ver:
// Date:
// By JtBB #include "Wire.h" //By Brian T.Park #include "I2CKeyPad.h" //By Rob Tillaart #include <LiquidCrystal_PCF8574.h> //By Matthias Hertel const uint8_t KEYPAD_ADDRESS = 0x20; const uint8_t LCD_ADDRESS = 0x21; I2CKeyPad keyPad(KEYPAD_ADDRESS); char keys[] = {'1','2','3','A', // '4','5','6','B', // '7','8','9','C', // '*','0','#','D', // 'N','F'}; // N = NoKey, F = Fail //LCD constants pins are connected on pcf8574 IO pins #define LCD_RS 0 #define LCD_EN 1 #define LCD_P4 4 #define LCD_P5 5 #define LCD_P6 6 #define LCD_P7 7 // // set the LCD address to 0x21 for a 16 chars and 2 line display LiquidCrystal_PCF8574 lcd(LCD_ADDRESS, LCD_EN, LCD_RS, LCD_P4, LCD_P5, LCD_P6, LCD_P7); //IRQ pin const int IRQPIN = 2; // volatile flag used in IRQ routine volatile bool keyChange = false; //variables for password #define pwd_length 6 char Data[pwd_length]; //input password is stored here pwd_length char Password[pwd_length] = "1256B"; byte data_count = 0; //counter for characters entry char custom_key; //caracter to hold key input //Here we have define also 2 custom characters byte dotOff[] = { 0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000, 0b00000 }; byte dotOn[] = { 0b00000, 0b01110, 0b11111, 0b11111, 0b11111, 0b01110, 0b00000, 0b00000 }; //Interrupt subroutine void keyChanged() { keyChange = true; } // void setup() { Serial.begin(115200); Wire.begin(); //Call the connection wire lcd.begin(16, 2); // initialize the lcd //Character 1 --> dot Off; character 2 --> dot On lcd.createChar(1, dotOff); lcd.createChar(2, dotOn); // NOTE: PCF8574 will generate an interrupt on key press and release. pinMode(IRQPIN, INPUT); // attachInterrupt(digitalPinToInterrupt(IRQPIN), keyChanged, FALLING); keyChange = false; uint8_t index = keyPad.getKey(); //set initial state of pcf8574 IO lcd.home(); lcd.clear(); lcd.setCursor(3, 0); lcd.print("Hello And"); lcd.setCursor(3, 1); lcd.print("Wellcome!"); delay(2000); lcd.clear(); lcd.print("code:"); lcd.setCursor(5, 1); //cursor to lcd's 2nd line lcd.print("\01\01\01\01\01 ");//clear data on display and //print empty dots } // //Write data to pcf8574 to turn on/off led or buzzer void write_data(byte val) { Wire.beginTransmission(0x21); Wire.write(val); Wire.endTransmission(); } // void sound(void) { for (unsigned char a = 10; a > 0; a--) { delayMicroseconds(800); write_data(0b00000100); delayMicroseconds(800); write_data(0x00000000); } } //clear data from Data array void clearData(void){ while (data_count !=0){ Data[data_count--] = 0; } return; } // void loop(void) { if (keyChange) { delay(30); //delay for debouncing if (keyChange) { uint8_t index = keyPad.getKey(); // only after keyChange is handled it is time reset the flag keyChange = false; if (index != 16) //More than 2 keys are pressed at the same time { if (keys[index] != '*'){ Data[data_count] = keys[index]; lcd.setCursor(5 + data_count, 1); lcd.print("\02"); //comment for debug sound(); data_count++; }else {if (keys[index] == '*'){ if(data_count !=0){ sound(); data_count--; lcd.setCursor(data_count + 5, 1); lcd.print("\01"); //comment for debug lcd.setCursor(data_count + 5, 1); } } } } } } //Password length is reached? if (data_count == pwd_length-1){ lcd.setCursor(5, 1); if(!strcmp(Data, Password)){//Password //As the password is correct lcd.print("Unlocked!"); write_data(0b00001000); //add here other IO pin to drive relay delay(4000); write_data(0b00000000); //disable also the pin previously added }else{ //As the password is not correct lcd.print("Incorrect"); delay(3000); } lcd.setCursor(5, 1); //cursor to lcd's 2nd line lcd.print("\01\01\01\01\01 ");//clear data on display clearData(); } } // // -- END OF FILE --

Test de fonctionnement sur Arduino et Seeeduino:

 

Avant de poursuivre, la figure ci-dessous donne le brochage de ce module Seeeduino:

seeeduino broches

La carte Seeeduino est compatible avec les programmes Arduino, pour peu que certains registres spéciaux de l'Atmega328P ne soient pas mis à contribution pour être utilisés par le contrôleur ARM Cortex M0 de Seeeduino.

Il faudra aussi noter que cette carte délivre, et ne supporte qu'une tension de 3V3 sur chacune de ses entrées-sorties. D'où la présence nécessaire d'un adaptateur réversible 3V3 => 5 Volts dans ce montage. On peut aussi alimenter l'ensemble par une alimentation +5V externe, au cas où l'alimentation délivrée par l'USB ne fournit pas assez de puissance.

La carte à base de Seeeduino a été aussi montée sur plaque à trous pastillés.

 

On n'a pas pu résister à la tentation d'ajouter un connecteur pour un petit module relais fait maison, doté d'un relais de 10A/250V, de résistance de bobine 60 Ohms, commandée en +5V; pour simuler jusqu'au bout l'interface de puissance avec une gâche ou un autre dispositif de puissance. Le signal de commande est délivré par la sortie "Sig" du connecteur.

carte relais 2

On peut aussi opter pour un module relais du commerce comme celui de la photo ci-dessous; il faudra bien tenir compte du pouvoir de coupure du relais, si on veut commander des charges de puissance et de la tension de la bobine:

carte relais du commerce

Pour ce qui est du programme, quelques modifications mineures ont été apportées. La broche d'interruption se trouve désormais sur la broche d'entrées-sorties numéro 1 au lieu de la numéro 2 sur Arduino reservée pour l'interruption INT0. Ce choix a été fait au hasard car toutes les broches de Seeeduino peuvent fonctionner en interruption. La sortie "Sig" est commandée par la broche d'entrées-sorties 2 de Seeeduino.

Dans le programme précédent, les lignes suivantes ont été modifiées:

//IRQ pin
const int IRQPIN = 1;

Puis la ligne d'entrées-sorties destinée au relais a été rajoutée:

#define RELAY 2

Puis dans le setup, il ne faudra pas oublier de déclarer cette broche en sortie:

pinMode(RELAY, OUTPUT);

Exemple de programme pour Seeeduino :

///////////////////////////////////////////////////////////////
//
// Ver:
// Date:
// By JtBB #include "Wire.h" //By Brian T.Park #include "I2CKeyPad.h" //By Rob Tillaart #include <LiquidCrystal_PCF8574.h> //By Matthias Hertel const uint8_t KEYPAD_ADDRESS = 0x20; const uint8_t LCD_ADDRESS = 0x21; I2CKeyPad keyPad(KEYPAD_ADDRESS); char keys[] = {'1','2','3','A', // '4','5','6','B', // '7','8','9','C', // '*','0','#','D', // 'N','F'}; // N = NoKey, F = Fail //LCD constants pins are connected on pcf8574 IO pins #define LCD_RS 0 #define LCD_EN 1 #define LCD_P4 4 #define LCD_P5 5 #define LCD_P6 6 #define LCD_P7 7 // // set the LCD address to 0x21 for a 16 chars and 2 line display LiquidCrystal_PCF8574 lcd(LCD_ADDRESS, LCD_EN, LCD_RS, LCD_P4, LCD_P5, LCD_P6, LCD_P7); //IRQ pin const int IRQPIN = 1; //pin 2 for Arduino Uno // volatile flag used in IRQ routine volatile bool keyChange = false; //Relay pin #define RELAY 2 //Output for relay //variables for password #define pwd_length 6 char Data[pwd_length]; //input password is stored here pwd_length char Password[pwd_length] = "1256B"; byte data_count = 0; //counter for characters entry char custom_key; //caracter to hold key input //Here we have define also 2 custom characters byte dotOff[] = { 0b00000, 0b01110, 0b10001, 0b10001, 0b10001, 0b01110, 0b00000, 0b00000 }; byte dotOn[] = { 0b00000, 0b01110, 0b11111, 0b11111, 0b11111, 0b01110, 0b00000, 0b00000 }; //Interrupt subroutine void keyChanged() { keyChange = true; } // void setup() { //Serial.begin(115200); Wire.begin(); //Call the connection wire lcd.begin(16, 2); // initialize the lcd //Character 1 --> dot Off; character 2 --> dot On lcd.createChar(1, dotOff); lcd.createChar(2, dotOn); // NOTE: PCF8574 will generate an interrupt on key press and release. pinMode(IRQPIN, INPUT); // attachInterrupt(digitalPinToInterrupt(IRQPIN), keyChanged, FALLING); keyChange = false; pinMode(RELAY,OUTPUT); //to control relay digitalWrite(RELAY,LOW); uint8_t index = keyPad.getKey(); //set initial state of pcf8574 IO lcd.home(); lcd.clear(); lcd.setCursor(3, 0); lcd.print("Hello And"); lcd.setCursor(3, 1); lcd.print("Wellcome!"); delay(2000); lcd.clear(); lcd.print("code:"); lcd.setCursor(5, 1); //cursor to lcd's 2nd line lcd.print("\01\01\01\01\01 ");//clear data on display and //print empty dots } // //Write data to pcf8574 to turn on/off led or buzzer void write_data(byte val) { Wire.beginTransmission(0x21); Wire.write(val); Wire.endTransmission(); } // void sound(void) { for (unsigned char a = 10; a > 0; a--) { delayMicroseconds(800); //enough for me to hear buzzer sound write_data(0b00000100); delayMicroseconds(800); write_data(0x00000000); } } //clear data from Data array void clearData(void){ while (data_count !=0){ Data[data_count--] = 0; } return; } // void loop(void) { if (keyChange) { delay(30); //delay for debouncing if (keyChange) { uint8_t index = keyPad.getKey(); // only after keyChange is handled it is time reset the flag keyChange = false; if (index != 16) //More than 2 keys are pressed at the same time { if (keys[index] != '*'){ Data[data_count] = keys[index]; lcd.setCursor(5 + data_count, 1); lcd.print("\02"); //comment for debug sound(); data_count++; }else {if (keys[index] == '*'){ if(data_count !=0){ sound(); data_count--; lcd.setCursor(data_count + 5, 1); lcd.print("\01"); //comment for debug lcd.setCursor(data_count + 5, 1); } } } } } } //Password length is reached? if (data_count == pwd_length-1){ lcd.setCursor(5, 1); if(!strcmp(Data, Password)){//Password //As the password is correct lcd.print("Unlocked!"); write_data(0b00001000); //add here other IO pin to drive relay digitalWrite(RELAY, HIGH); //drive relay on delay(4000); write_data(0b00000000); //desactivate also the pin previously added digitalWrite(RELAY, LOW); //drive relay on }else{ //As the password is not correct lcd.print("Incorrect"); delay(3000); } lcd.setCursor(5, 1); //cursor to lcd's 2nd line lcd.print("\01\01\01\01\01 ");//clear data on display clearData(); } } // // -- END OF FILE --

On peut aussi voir le résumé de cet article en visualisant le lien ci-dessous:

Conclusion:

On peut dire que l'interface I2C constitue une bonne alternative pour augmenter le nombre d'entrées-sorties de Arduino. On a aussi vu qu'on peut profiter de la puissance de Seeeduino en lui ajoutant aussi des entrées-sorties suplémentaires avec la même interface.

Jusqu'à 128 entrées-sorties tout ou rien de plus grâce à l' I2C peut être considéré comme un compromis intéressant pour ce type d'amélioration. Comparativement si on avait choisi un bus SPI, il aurait fallut aussi 16 lignes supplémentaires pour la fonction Chip Select de chaque composant, et l'économie des broches escomptée aurait été réduite.

JtBB

Les afficheurs sept segments à LED sont très répandus. On les trouve dans beaucoup d'appareils ou dispositifs électroniques industriels, d'instrumentation, de mesures ou grand public.

Un afficheur sept segments à LED est constitué de sept LEDs et un point décimal, toutes les anodes ou les cathodes des différents segments sont reliées à un point commun; ainsi on a des afficheurs à Anode commune ou des afficheurs à Cathode commune.  

afficheur7seg2

Par exemple pour commander un afficheur de type cathode commune avec Arduino Uno, il suffit de relier chaque segment, muni de sa résistance de protection, sur chacune des broches d'entrée-sortie libre, puis la cathode à la masse. Et, l'aide d'un programme simple on peut afficher des chiffres sur l'afficheur, ou des caractères spéciaiux comme A, b, C, E, F, H, P, ...

Et si on veut afficher des nombres sur huit, voire plus de chiffres (ou digits), avec une carte Arduino Uno? On peut utiliser de nouveau des ports d'entrées-sorties. Mais dans ce cas de figure, la place pour connecter d'autres capteurs ou relais, finira par manquer. 

Pourquoi ne pas déléguer ce rôle à des circuits dont la fonction est, justement, de piloter des afficheurs à LEDs ? Cela permettrai d'utiliser moins de ports d'entrées-sorties d'Arduino.

Dans cet article, on va présenter deux circuits intégrés qu'on peut utiliser avec Arduino ou tout autre microcontrôleur, pour ce type d'afficheur: le MAX7219, et le 74HC595 déjà abordé dans un autre article de ce site.

Utlisation du 74HC595:

Pour cela, on aura besoin de:

    • 2 - 74HC595,
    • 1 - ULN2803,
    • 8 - résistances de 330Ω,
    • 2 - pavés d'afficheurs à cathode commune 4 digits chacun,
    • Arduino Uno.

 

Présentation:

On ne reviendra pas sur la présentation du 74HC595 dans les lignes qui suivent. Il faudra se reporter sur l'article le concernant, publié dans ce site. Ce circuit intégré est très répandu; on peut le trouver sous diverses dénominations, comme par exemple SN74HC595, tout dépend du fabriquant. Autrement il y a aussi la documentation technique du constructeur.

Montage d'essai:

Le schéma de ce montage est composé de deux 74HC595, d'un ULN2803, de deux pavés d'afficheurs à cathode commune 4 digits chacun. Même s'ils sont partiellement représentés ici, chaque segment et point décimal, des deux pavés sont tous connectés ensemble à travers des résistances de 330Ω sur chacune des sorties de U2. Par exemple tous les segments A de tous les afficheurs sont connectés entre eux, de même pour tous les segments B, etc... Les cathodes communes sont connectées sur les sorties de U3, il faudra se référer à la documentation technique de l'afficheur choisi, pour avoir son brochage, ainsi que ses caractéristiques.

schema 74hc595

L'essentiel de ce schéma est représenté ci-dessus. Le circuit intgégré U1, reçoit sur son entrée DS des données sous forme série en provenance d'Arduino; l'horloge est envoyée simultanément à U1 et à U2; il en est de même pour la commande des bascules de sortie des deux circuits U1 et U2. La mise en cascade des deux circuits consiste à relier Q'7 de U1 à l'entrée DS de U2.

On a jugé intéressant d'insérer le circuit ULN2803, entre les sorties du circuit U1 et les cathodes de chaque digit. Car selon la documentation technique de Texas Instruments, le 74HC595 peut supporter ou délivrer un courant de 6mA sur chaque sortie. De plus, toujours selon la même documentation, le courant total délivré ne doit pas exéder 70mA! Mais dans ce montage, si tous les segments d'un seul digit sont allumés, le courant correspondant (10mA x 8) dépassera cette valeur !

Des résistances de 330Ω, ont été choisies afin de limiter le courant dans chaque segment des afficheurs à 10mA environ. Cette valeur qui est pourtant supérieure à 6mA, est convenable puisque lors de l'utilisation normale des afficheurs, les segments ne seront pas allumés de façon statique. Ils seront allumés périodiquement sur un intervalle de temps très court pour que l'oeil humain ne s'en aperçoive pas.

On a aussi deux bornes d'alimentation +5V et la masse, destinée à U1, U2 et U3. Cette alimentation peut être tirée de la carte Arduino, ou fournie par un dispositif externe, dans ce cas il faut juste veiller à ce que toutes les masses soient reliées.

Tout l'ensemble a été monté sur une plaque à trous pastillés, avec en plus deux condensateurs de découplage de 100nF  entre l'alimentation +5V et la masse de chacun des 74HC595:

montage test aff 2 74hc595

Programmes de test du montage:

 

Commençons d'abord par établir les liaisons entre le montage et la carte Arduino:

L'entrée Data in sera reliée à la broche 11 de la carte Arduino;

L'entrée Clock sera reliée à la broche 13 de la carte Arduino;

L'entrée Latch sera reliée à la broche 10 de la carte Arduino;

Les deux broches d'alimentation +5V et 0V seront reliées respectivement à +5V et 0V de la carte Arduino.

Des librairies permettant de piloter le circuit 74HC595, pour commander les afficheurs 7 segments, existent dans l'interface de programmation Arduino. On n'utilisera aucune d'entre elles dans les programmes d'exemple pour ce montage.

 

Programme n°1:

Dans ce programme, on se propose d'afficher le chiffre "4" sur le quatrième digit du montage en partant de la droite.

On va dans premier temps déclarer les connexions entre le montage et la carte Arduino à l'aide des directives suivantes:

#define clockPin 13
#define dataPin 11
#define latchPin 10

Puis les différents chiffres sont définis comme des variables booléennes sous forme de tableau 10 lignes 8 colonnes. 

boolean numbersTable[10][8] = //8
{//each segment array give us the number to display
  {1,1,1,1,1,1,0}, //0 - seg a,b,c,d,e,f : on g :off
  {0,1,1,0,0,0,0}, //1 - seg b,c : on a,d,e,f,g :off
  {1,1,0,1,1,0,1}, //2
  {1,1,1,1,0,0,1}, //3
  {0,1,1,0,0,1,1}, //4
  {1,0,1,1,0,1,1}, //5
  {1,0,1,1,1,1,1}, //6
  {1,1,1,0,0,0,0}, //7
  {1,1,1,1,1,1,1}, //8
  {1,1,1,1,0,1,1}  //9
};

Une valeur "1" signifie que le segment est allumé, et "0" il est éteint.

Par exemple pour afficher le chiffre 0, il faudrait allumer tous les segments à l'exeption du segment "G". Pour afficher 4, seuls les segments "B", "C", "F", "G" seront allumés (revoir la représentation de l'afficheur en début d'article).

De la même façon on définit la sélection des digits à l'aide du tableau de booléens suivant:

boolean digitsTable[8][8] =
{//7 seg display are common cathode; 1= digit on
  {1,0,0,0,0,0,0,0}, // 1st 
  {0,1,0,0,0,0,0,0}, // 2nd
  {0,0,1,0,0,0,0,0}, // 3th
  {0,0,0,1,0,0,0,0}, // 4th 
  {0,0,0,0,1,0,0,0}, // 5th
  {0,0,0,0,0,1,0,0}, // 6th
  {0,0,0,0,0,0,1,0}, // 7th  
  {0,0,0,0,0,0,0,1}  // 8th   
};

Puisque dans ce montage on a 8 digits de LEDs à cathode commune. Un digit sera sélectionné à la fois.

Deux fonctions essentielles sont appelées dans ce programme:

- La fonction fillDisplayTable(), qui permet de créer un tableau contenant le digit à sélectionner, ainsi que la valeur numérique à afficher codée en binaire;

void fillDisplayTable(int number, int digit_order, bool showDot) {
  for(int index = 7; index >= 0; index--){//digit to select
    displayTable[index] = digitsTable[digit_order - 1][index];
  }
  for(int index = 14; index >=8; index--){//number to display
    displayTable[index] = numbersTable[number - 1][index];
  }
  //to show the dot point
  if(showDot == true) {
    displayTable[15] = 1;
  }
   else {
    displayTable[15] = 0;
   }
}

On va remplir en premier la partie correspondant aux digits, puis ensuite le nombre à afficher, et pour finir le point décimal.

 

- La fonction  writeToDigit(), qui permet de récupérer chaque bit de ce tableau, puis, de l'écrire sur l'entrée Data in du module d'affichage;

void writeToDigit(int number, int order, bool showDot = false){
  fillDisplayTable(number, order, showDot);
  digitalWrite(latchPin, LOW); //enable output latch of 74HC595
  for(int i = 15; i >= 0; i--){
    digitalWrite(clockPin, LOW); //send bit per bit to 74HC595
    digitalWrite(dataPin, displayTable[i]);
    digitalWrite(clockPin, HIGH); //send
  }
  digitalWrite(latchPin, HIGH); //disable output latch
}

Les bits sont récupérés puis envoyés sur la broche Data in (ici dataPin), à chaque top d'horloge (clockPin). Une horloge de régistres Latch (latchPin) rend cette valeur numérique disponible en sortie des 74HC595; huit bits pour le premier et huit bits pour le second.

 

Le programme de test global se résume à ces lignes de code:

/*****************************************************
 *  Test for 7 segments led display,using two 74CH595 
 *  shift register and Arduino Uno.
* by: JtBB * Date: 11/09/2022, by JtBB * ver: ****************************************************/ //Arduino pin definition #define clockPin 13 #define dataPin 11 #define latchPin 10 //variables boolean numbersTable[10][8] = //8 {//each segment array give us the number to display {1,1,1,1,1,1,0}, //0 - seg a,b,c,d,e,f : on g :off {0,1,1,0,0,0,0}, //1 - seg b,c : on a,d,e,f,g :off {1,1,0,1,1,0,1}, //2 {1,1,1,1,0,0,1}, //3 {0,1,1,0,0,1,1}, //4 {1,0,1,1,0,1,1}, //5 {1,0,1,1,1,1,1}, //6 {1,1,1,0,0,0,0}, //7 {1,1,1,1,1,1,1}, //8 {1,1,1,1,0,1,1} //9 }; // boolean digitsTable[8][8] = {//7 seg display are common cathode; 1= digit on {1,0,0,0,0,0,0,0}, // 1st {0,1,0,0,0,0,0,0}, // 2nd {0,0,1,0,0,0,0,0}, // 3th {0,0,0,1,0,0,0,0}, // 4th {0,0,0,0,1,0,0,0}, // 5th {0,0,0,0,0,1,0,0}, // 6th {0,0,0,0,0,0,1,0}, // 7th {0,0,0,0,0,0,0,1} // 8th }; //all the binaries values will be store in this table boolean displayTable[16]; //functions in use void fillDisplayTable(int number, int digit_order, bool showDot); void writeToDigit(int number, int order, bool showDot); // //setup void setup(){ pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); pinMode(latchPin, OUTPUT); digitalWrite(clockPin, LOW); digitalWrite(dataPin, LOW); digitalWrite(latchPin, LOW); writeToDigit(4, 4,false); //4 on 4th digit } //loop to run the main program void loop(){ } //fill the table with binaries values to display, selecting //the digit to display first void fillDisplayTable(int number, int digit_order, bool showDot) { for(int index = 7; index >= 0; index--){ displayTable[index] = digitsTable[digit_order - 1][index]; } for(int index = 14; index >=8; index--){ displayTable[index] = numbersTable[number - 1][index]; } //to show the dot point if(showDot == true) { displayTable[15] = 1; } else { displayTable[15] = 0; } } //write the number on specified digit void writeToDigit(int number, int order, bool showDot = false){ fillDisplayTable(number, order, showDot); digitalWrite(latchPin, LOW); //enable output latch of 74HC595 for(int i = 15; i >= 0; i--){ digitalWrite(clockPin, LOW); //send bit per bit to 74HC595 digitalWrite(dataPin, displayTable[i]); digitalWrite(clockPin, HIGH); //send } digitalWrite(latchPin, HIGH); //disable output latch }

On écrit une seule fois le chiffre 4, car les bascules des 74HC595, vont mémoriser cet état.

On a le résultat ci-après:

affichage chiffre 4

Programme n°2:

On va afficher une série de chiffres (1,2,3,4,5,6,7,8) sur l'afficheur. On va reprendre le programme précédent, sauf que cette fois-ci, la fonction writeToDigit() sera appelée à dans la boucle principale, pour chaque chiffre qu'on voudra afficher.

Ci-dessous, le code de ce programme d'exemple:

/*****************************************************
 *  Test for 7 segments led display,using two 74CH595 
 *  shift register and Arduino Uno.
* by: JtBB * Date: 11/09/2022, by JtBB * ver: ****************************************************/ //Arduino pin definition #define clockPin 13 #define dataPin 11 #define latchPin 10 //variables boolean numbersTable[10][8] = //8 {//each segment array give us the number to display {1,1,1,1,1,1,0}, //0 - seg a,b,c,d,e,f : on g :off {0,1,1,0,0,0,0}, //1 - seg b,c : on a,d,e,f,g :off {1,1,0,1,1,0,1}, //2 {1,1,1,1,0,0,1}, //3 {0,1,1,0,0,1,1}, //4 {1,0,1,1,0,1,1}, //5 {1,0,1,1,1,1,1}, //6 {1,1,1,0,0,0,0}, //7 {1,1,1,1,1,1,1}, //8 {1,1,1,1,0,1,1} //9 }; // boolean digitsTable[8][8] = {//7 seg display are common cathode; 1= digit on {1,0,0,0,0,0,0,0}, // 1st {0,1,0,0,0,0,0,0}, // 2nd {0,0,1,0,0,0,0,0}, // 3th {0,0,0,1,0,0,0,0}, // 4th {0,0,0,0,1,0,0,0}, // 5th {0,0,0,0,0,1,0,0}, // 6th {0,0,0,0,0,0,1,0}, // 7th {0,0,0,0,0,0,0,1} // 8th }; //all the binaries values will be store in this table boolean displayTable[16]; //functions in use void fillDisplayTable(int number, int digit_order, bool showDot); void writeToDigit(int number, int order, bool showDot); // //setup void setup(){ pinMode(clockPin, OUTPUT); pinMode(dataPin, OUTPUT); pinMode(latchPin, OUTPUT); digitalWrite(clockPin, LOW); digitalWrite(dataPin, LOW); digitalWrite(latchPin, LOW); } //loop to run the main program void loop(){ writeToDigit(8, 1,true); //write 1 on first digit writeToDigit(7, 2,false); // 2 on second digit writeToDigit(6, 3, false); //3 on third writeToDigit(5, 4,false); //4 on 4th digit writeToDigit(4, 5,false); // 5 on 5th digit writeToDigit(3, 6, false); //6 on 6th digit writeToDigit(2, 7, false); //7 on 7th digit writeToDigit(1, 8, false); //8 on 8th digit } //fill the table with binaries values to display, selecting //the digit to display first void fillDisplayTable(int number, int digit_order, bool showDot) { for(int index = 7; index >= 0; index--){ displayTable[index] = digitsTable[digit_order - 1][index]; } for(int index = 14; index >=8; index--){ displayTable[index] = numbersTable[number - 1][index]; } //to show the dot point if(showDot == true) { displayTable[15] = 1; } else { displayTable[15] = 0; } } //write the number on specified digit void writeToDigit(int number, int order, bool showDot = false){ fillDisplayTable(number, order, showDot); digitalWrite(latchPin, LOW); //enable output latch of 74HC595 for(int i = 15; i >= 0; i--){ digitalWrite(clockPin, LOW); //send bit per bit to 74HC595 digitalWrite(dataPin, displayTable[i]); digitalWrite(clockPin, HIGH); //send } digitalWrite(latchPin, HIGH); //disable output latch }

Ci-après, le résultat obtenu lorsqu'on exécute ce programme:

affichage nombre 12345

D'après cette photo, on remarque une baisse de l'intensité lumineuse globale, par rapport au test n°1, lorsqu'on a affiché le chiffre 4 tout seul. Ceci est justifié par le fait que l'écriture de chaque chiffre se fait de façon répétitive.

 

Utlisation du MAX7219:

Matériel nécessaire pour réaliser les tests avec un MAX7219:

    • un module pré-monté constitué de 8 afficheurs 7 segments à LEDs et un MAX7219;
    • une carte Arduino Uno.

Présentation:

C'est un circuit intgéré spécialement conçu pour des afficheurs 7 segments à LEDs, ou des matrices à LEDs, pour affichage de messages lumineux.

Le MAX7219 peut piloter jusqu'à 8 afficheurs 7 segments à LEDs ou une matrice de 64 LEDs. Ce composant possède une mémoire RAM interne, un décodeur BCD code-B, un circuit de balaye multiplexé, et offre la possibilité de régler l'intensité lumineuse des LEDs par programmation. Il n'a besoin que d'une résistance pour fixer le courant à travers les segments et peut consommer jusqu'à 350mA, lorsque tous les segments de tous les afficheurs sont allumés y compris les points décimaux. Il peut être mis en sommeil (Shutdown), pour baisser la consommation à 150μA.

Si on veut gérer plus de 8 afficheurs 7 segments, il suffit de mettre en cascade les circuits. Toute fois, pour le cas où plusieurs circuits sont utilisés, le constructeur préconise de veiller à l'équiibrage du nombre d'afficheurs par circuits: par exemple supposons qu'on veut faire un affichage sur 12 afficheurs 7 segments à LEDs, on aura besoin de 2 circuits intégrés; il ne faudrait pas connecter 8 segments sur le premier circuit et 4 segments sur le second; cela risquerait de créer une différence d'intensité lumineuse sur certaines LEDs, due à la fréquence de balayage des étages de sorties du circuit qui commandent l'afficheur. Le constructeur préconise de monter 6 afficheurs sur le premier MAX7219, et 6 autres sur le second circuit intégré.

 

Brochage:

max7219 pin out 1

Le composant possède une entrée de donnée Din, une entrée d'horloge CLK, et une entrée de validation LOAD. Les sorties des segments et des digits occupent presque la totalitré des broches, le reste concerne l'alimentation, et l'entrée ISET sur laquelle est connectée la résistance de limitation de courant dans les LEDs.

Pour plus de détails, il ne faut pas hésiter à consulter la documentation technique du composant.

 

Le montage:

Pour les essais, on s'est contenté d'une carte achetée sur internet, ayant huits afficheurs et le MAX7219 déjà montés:

seven seg displ3

Les liaisons de l'afficheur avec la carte Arduino:

L'entrée DIN sera reliée à la broche 11 de la carte Arduino;

L'entrée PCLK sera reliée à la broche 13 de la carte Arduino;

L'entrée LOAD sera reliée à la broche 10 de la carte Arduino;

Les deux broches d'alimentation +5V et 0V seront reliées respectivement à +5V et 0V de la carte Arduino.

On a le câblage suivant:

aff 7 seg arduino 1

Programme de test de l'afficheur piloté par un MAX7219:

Le programme consiste à afficher le nombre 12345678 sur l'afficheur, pour faire simple on a utilisé la librairie LedControl, qui est présente dans l'interface de programmation Arduino. Il suffit d'aller sur le site arduino.cc pour avoir plus de détails sur cette librairie. Le programme d'essai est proposé ci-dessous:

#include 
//variable
String val = "12345678";//
//Create a LedControl for one device
LedControl seg_display1 = LedControl(11,13,10,1); 
void setup() {
  // put your setup code here, to run once:
     seg_display1.shutdown(0, false); //MAX7219 wake up
     seg_display1.setIntensity(0, 2); //set brightness to min, max is 255
     
}

void loop() {
  // put your main code here, to run repeatedly:
  for(int i = 0; i<=7; i++){
       seg_display1.setChar(0, (7 -i), val[i], false);
     }
  
}

La chaîne de caractères 12345678 peut être consdérée ici comme un tableau, c'est la raison pour laquelle dans la boucle "for" du programme principale on a la variable val[i], dont la valeur sera affichée. Le sous-programme setup(), s'occupe uniquement de l'initialisation du MAX7219.

L'exécution du programme donne le résultat suivant:

affichage 12345678 7219

Le MAX7219 offre une possibilité de régler l'intensité lumineuse des LEDs de façon logicielle, la librairie ledControl posséde le fonction setIntensity(), qui pourra être appelée pour cela.

 

MAX7219 vs 74HC595

Pour piloter un afficheur 7 segments à LEDs sur 8 digits, que choisir? MAX7219 ou 74HC595?

 

Commençons d'abord par le prix:

Même si on a besoin de 2 x 74HC595 et d'un ULN2803, pour piloter 8 digits, le prix de l'ensemble est inférieur à celui d'un MAX7219.

 

La consommation:

Le montage à 74HC595 consomme moins que le montage avec un MAX7219.

 

L'encombrement:

Même si le MAX7219 a 24 broches contre 16 pour le 74HC595, il occupe moins de place dans un circuit imprimé, car on a besoin de deux 74HC595 et d'un ULN2803; ces trois circuits peuvent ainsi entraîner des pistes de cuivre supplémentaires, soit des probabiltés de pannes potentielles.

 

La mise en cascade:

Les deux circuits offrent la possibilité de mise en cascade, mais si on veut piloter plus de 8 digits, il serait plutôt avantageux d'utiliser des MAX7219.

 

Réglage d'intensité lumineuse des LEDs:

Le MAX7219, permet de régler de façon logicielle l'inytensité lumineuse des LEDs, cela offre une très grande flexibilité, nul besoin de modifier le montage déjà fabriqué.

 

Résistance de limitation de courant:

Le MAX7219 n'a besoin que d'une seule résistance pour assurer la limitation du courant dans les LEDs. Tant dis que le 74HC595 en a besoin de 8 pour chacune des segments A,B,C,D,E,F,G et le point décimal.

 

Si on s'arrête à ces quelques points de comparaison, on peut simplement dire que pour piloter un afficheur 7 segments 8 digits à LEDs, tout dépend des contraintes imposées et du prix qu'on mettra pour la réalisation.

 

Mise en oeuvre:

On va terminer cet article par la réalisation d'une horloge à réglage heure-minute à deux boutons poussoirs. L'horloge de démonstratiuon utilise un afficheur 8 digits à cathode commune, un MAX7219, et une carte Arduino Uno.

Le programme d'exemple de horloge est proposé ci-dessous:

/*
 * This simple digital clock taken as example is using MAX7219 to
 * display hours, minutes and seconds.
 * minutes and hours can be updated with two buttons.
 * ver:
 * date:
 * by: JtBB
 */
#include 
//buttons for settings
#define modeSelectButton 7
#define settingsButton 6
//variables
unsigned long previousMillis = 0;
unsigned long elapsed_time = 10000;
unsigned long mm_setGo = 0;
unsigned long hh_setGo = 0;
unsigned long mm_setTime = 0; 
unsigned long hh_setTime = 0;
unsigned int hours = 0;
unsigned int minutes = 0;
unsigned int seconds = 0;
bool msCounter = false;
//file type
enum timeStates{IDLE_State, MM_MODE, HH_MODE, SETTINGS};
//Create a LEdControl for one device
LedControl seg_display1 = LedControl(11,13,10,1);

//functions prototypes
void displayTimeToSeg(unsigned int hours, unsigned int minutes, unsigned int seconds);
void updateTimeCounters(void);
void displayHours(unsigned int hours_d, unsigned int hours_u);
void displayMinutes(unsigned int minutes_d, unsigned int minutes_u);
void displaySecondss(unsigned int seconds_d, unsigned int seconds_u);
void doTimeStates(void);

//setup
void setup() {
  // put your setup code here, to run once:
     pinMode(modeSelectButton, INPUT);
     pinMode(settingsButton, INPUT);
     seg_display1.shutdown(0, false); //MAX7219 wake up
     seg_display1.clearDisplay(0); //clear display
     seg_display1.setIntensity(0, 4); //8 set brightness to medium, max is 255
}

//
void loop() {
  // put your main code here, to run repeatedly:

  if(msCounter == true){
          displayTimeToSeg(hours,minutes,seconds);   
        }
  doTimeStates();
  updateTimeCounters();
 
}

//
//functions for digital clock
//update time to hours, minutes and seconds counters
void updateTimeCounters(void){
  unsigned long currentMillis = millis();
  //
  if (currentMillis - previousMillis >= 1000){//1000ms elapsed
    previousMillis = currentMillis;
    seconds++;
    msCounter = true;
  }
  //
  if (seconds == 60){
             minutes++;
             seconds = 0;
  }
  //
  if (minutes == 60){
             hours++;
             minutes = 0;
  }
  //
  if (hours == 24){
             hours = 0;
  }
}

//Diplay time to 7 segments led
void displayTimeToSeg(unsigned int hh, unsigned int mm, unsigned int ss){
  unsigned int hh_d = hh/10;
  unsigned int hh_u = hh%10;
  unsigned int mm_d = mm/10;
  unsigned int mm_u = mm%10;
  unsigned int ss_d = ss/10;
  unsigned int ss_u = ss%10;
 
  //display hours
  displayHours(hh_d, hh_u);
  //display minutes
  displayMinutes(mm_d, mm_u);
  //display seconds
  displaySecondes(ss_d, ss_u);
}

//display hours, minutes and seconds functions
void displayHours(unsigned int h_d, unsigned int h_u){
    seg_display1.setDigit(0,7,h_d,false);
    seg_display1.setDigit(0,6,h_u,true);
}

//
void displayMinutes(unsigned int m_d, unsigned int m_u){
    seg_display1.setDigit(0,4,m_d,false);
    seg_display1.setDigit(0,3,m_u,true);
}
//
void displaySecondes(unsigned int s_d, unsigned int s_u){
    seg_display1.setDigit(0,1,s_d,false);
    seg_display1.setDigit(0,0,s_u,true);
}
// blink minutes
void blinkMinutes(void){
        delay(150);
        seg_display1.setChar(0,4,' ',false);
        seg_display1.setChar(0,3,' ',true);
        delay(150);
}
// blink hours
void blinkHours(void){
        delay(150);
        seg_display1.setChar(0,7,' ',false);
        seg_display1.setChar(0,6,' ',true);
        delay(150);
}
//
void doTimeStates(void){
  static enum timeStates state = IDLE_State;
    switch(state){
          case IDLE_State: //IDLE state
          
            if(digitalRead(modeSelectButton) == 1){
              mm_setGo = millis();
              state = MM_MODE;         
            }       
          
          break;
    
          case MM_MODE: //minutes settings
    
            mm_setTime = millis();
            blinkMinutes();
    
            //Settings
            if(digitalRead(settingsButton) == 1){
                     minutes++;
               }
               
            if(digitalRead(modeSelectButton) == 1){
                     hh_setGo = millis();
                     state = HH_MODE;       
               }
    
            //time out
            if(mm_setTime - mm_setGo >= 10000){
                   state = IDLE_State;    
               }
            
          break;
    
          case HH_MODE: //hours settings
    
            hh_setTime = millis();
            blinkHours();
    
            //Settings
            if(digitalRead(settingsButton) == 1){
                      hours++;
               }
               
            if(digitalRead(modeSelectButton) == 1){
              state = MM_MODE; //
            }
    
             //time out
            if(hh_setTime - hh_setGo >= 10000){
                   state = IDLE_State;    
               }
          break;
    
          default:
             //nothing to do
          break;
          
      }//end switch
 
}

Les démonstrations ainsi que le résumé sont proposé dans le lien de la vidéo; il ne faut pas oublier d'activer le sous-titrage par manque d'une partie du son dans la vidéo. 

souris logo youtube

JtBB

 

Les machines à commande numérique, tels que les perceuses, les imprimantes 3D, utilisent des moteur pas à pas pour exécuter avec précision les mouvements. Il suffit de donner au système de pilotage le nombre pas à effectuer.

Même s'ils ne sont pas fréquents dans ce type de machines, les moteurs à courant continus quant à eux ont pour avantage de tourner beaucoup plus vite, avec un temps de réponse court; au prix des erreurs de positionnement.

Mais, si on a un capteur qui permet de connaître à tout instant la position angulaire de l'arbre d'un moteur à courant continu, les choses peuvent changer;  il devient possible de supprimer ces erreurs et commander avec précision l’axe du moteur en position ou en vitesse. C'est le principe des servomoteurs, que l'on peut rencontrer en robotique par exemple.

Il existe deux types de moteurs à courant continus: des moteurs à conrant continu avec balais (Brush motor), et des moteurs à courant continu sans balais (Brushless motor).

Cette réalisation a pour but de commander en position l'axe d'un moteur à courant continu à balais, doté d’un codeur incrémental.

 

 

Avant de poursuivre, voici les différents points qu'on va aborder:

          • Le principe de fonctionnement du montage;
          • La réalisation pratique de la maquette;
          • La mise en oeuvre du programme de commande;
          • Le test de fonctionnement;
          • La conclusion.

De quoi aura-t-on besoin?

 

  • Une carte Arduino Uno;
  • Un moto-réducteur muni d'un codeur incrémental à effet hall, ayant pour caractéristiques:
      •    Tension nominale: 12V;
      •    Nombre de tours/mn: 110;
      •    Courant à vide: 120mA;
      •    Courant en charge: 350mA;
      •    Puissance dévéloppée: 3W;
      •    Couple en charge: 3,4Kg.cm;
      •    Couple de décrochage: 13Kg.cm;
      •    Courant de décrochage: 1A;
      •    Rapport de réduction: 1:90              
  • Un pont en H basé sur le circuit L298
  • Un bloc d'alimentation 12V/2A
  • Un potentiomètre 10KΩ - 1tour

 

Principe de fonctionnement du montage:

Schéma de principe:

principe control pos

1 : Potentiomètre

2 : Différentiateur

3 : Dispositif de contrôle - commande

4 : Moteur à courant continu 12V

5 : Capteur de position magnétique à effet Hall

Fonctionnement:

La consigne d'angle souhaitée sera fixée grâce au potentiomètre (1); le système de contrôle - commande (3) envoie un ordre électrique au moteur (4) qui déplacera son axe vers la position θ désirée. Un capteur de position (5), indique la position de l'axe du moteur au différentiateur (2); celui-ci va calculer la différence entre la position réelle cet axe et la position de consigne. L'erreur calculée par le différentiateur sera prise en compte par le système de contrôle, qui pourra ainsi envoyer de nouveau un ordre au moteur (si cette erreur n'est pas nulle) afin que celui-ci ramene son axe vers la position de consigne.

Ce montage fonctionne ainsi en boucle fermée. L'erreur de position est évaluée en permanence, dès que celle-ci est différente de zéro, le système de contrôle va donc envoyer un ordre pour corriger la position du moteur de façon proportionnelle.

 

Approche théorique:

On peut dire dans ce cas présent qu'on a affaire à une boucle de régulation; et le dispositif de contrôle (3) en est le régulateur, qui en théorie, est un régulateur PID.

On peut modéliser ce régulateur de la façon suivante:

regulateur theorique

P : représente le terme Proportionnel à l'erreur courante;

I : représente le terme Intégral, il tient compte des valeurs passées de l'erreur;

D : représente le terme Derivée, il permet d'estimer le sens futur de l'erreur.

En ce qui concerne les signaux,

r(t) :  est la consigne ou la position souhaitée;

c(t) :  l'image de la position de l'arbre moteur;

e(t) :  l'erreu de position;

u(t) :  le signal de contrôle, issue du régulateur;

y(t) :  l'angle du déplacement de l'arbre moteur.

 

L'erreur est déterminée par l'équation (1): e(t) = r(t) - c(t)  

Le rôle du régulateur consiste à ramener cette erreur au plus près possible de zéro. Ainsi la correction apportée prendra la forme d'un signal u(t), dont l'équation (2) est donnée par:

\[u(t) = K_p e(t) + K_i \int_{0}^{t} e(τ)dτ + K_d \frac{de(t)}{dt}\]

Ces deux équations vont servir pour le programme de la carte Arduino.

Dans l'équation (2), on a les constantes:

  • Kp : la constante proportionnelle, détermine la rapidité du mouvement du moteur vers sa nouvelle position;
  • Ki  : la constante intégrale, détermine comment le moteur va se positionner à la fin de son mouvement. L'angle de positionnement n'est pas tout à fait égal à la valeur de consigne. Il en résulte une erreur appelé erreur statique. C'est cette erreur statique que le terme Integral va minimiser le plus possible.
  • Kd : constante dérivée, lorsque le moteur va vers la position demandée, il dépassera celle-ci, oscillera éventuellement, avant de se stabiliser. C'est donc ce dépassement que la constante dérivée va réduire.

Le choix de ces trois constantes n'est pas le fait du hasard, un mauvais choix peut entraîner des résultats inattendus. Le montage peut se mettre à osciller (l'arbre moteur fera des va et vient sur lui-même sans jamias se stabiliser), par exemple. Il existe des méthodes permettant de déterminer ces constantes:  par des abaques ou autres outils numériques.

Réalisation pratique de la maquette:

La tension de consigne issue du potentiomètre 10KΩ-1tour, pourra varier de 0 à 5V. A 0V, l'arbre moteur sera à 0°; à 5V il sera à 360°.

Le capteur utilisé est un codeur incrémental magnétique, directement fixé sur l'arbre moteur.  Ce codeur possède un disque à pôles magnétiques, et deux capteurs inductifs montés en quadrature, générant ainsi des signaux carrés décalés d'un quart de période lorsque le disque est en rotation.

La détection des fronts montants et/ou descendants permet de déterminer le nombre d'impulsions par tours, ou le sens de rotation du moteur.

Le calcul de l'erreur et de la tension de commande sera effectué par la carte Arduino. Afin de piloter le moteur à courant continu 12V, une carte d'interface de puissance, sous forme de pont en H, construite autour d'un L298 sera utilisée. Ce type de carte se trouve dans le commerce. Mais on a utilisé pour la démonstration une carte faite maison, dont certaines connectiques ont été configurées selon nos besoins. Le principe reste le même.

 

Le câblage dans sa globalité:

montage 1 moteur

Ce montage utilise un pont en H basé sur le circuit intégré L298 issue du commerce. Les différentes connexions donnent:

Côté moteur:

  • le fil rouge et le fil noir sont reliés aux sorties Moteur du pont en H;
  • le fil vert représente la masse de l'encodeur magnétique, il est relié à la masse de la carte Arduino et celle du pont en H;
  • le fil violet c'est la sortie A des impulsions de l'encodeur, il est relié à la broche 2 de la carte Arduino;
  • le fil jaune c'est la sortie B des impulsions de l'encodeur, il est relié à la broche 4 de la carte Arduino;
  • le fil marron clair c'est l'alimentation +5V de l'encodeur, il est relié à +5V du pont en H.

Côté carte pont en H:

  • le fil rouge de l'alimentation stabilisée est relié à +12V du pont en H;
  • le fil noir de l'alimentation stabilisée est relié à la masse du pont en H (en somme tooutes les masses sont reliées);
  • le fil marron foncé est une entrée de commande du moteur, il est relié à la broche 5 de la carte Arduino;
  • le fil gris c'est l'autre entrée de commande du moteur, il est relié à la broche 6 de la carte Arduino;
  • le fil bleu c'est l'entrée "Enable" du pont en H, il est relié à la broche 7 de la carte Arduino.

 

Pour le potentiomètre:

  • les deux fils d'extrémité, l'orange et le bleu sont reliés respectivement à +5V et à la masse;
  • le point milieu, fil gris, est relié à la broche A0 de la carte Arduino.

L'alimentation stabilisée sera réglée à +12V.

Attention ce schéma de câblage montre les liaisons entre les différents composants du montage. Le code de couleur utilisé dépend du type du moteur, du codeur, du pont en H utilisés. Il sera donc nécessaire d'avoir la documentation technique du moteur ainsi que du module en H.

Le programme de commande:

Pour réaliser le programme de commande, on va respecter le principe présenté dans l'approche théorique vue précédemment. Le programme principal devra contenir les fonctions qui vont permettre de calculer l'erreur de position, du régulateur PID, les paramètres de régulation seront fournis sous forme de constante.

Dans un souci de facilité de lecture toutes les variables ainsi que les commentaires sont en Anglais.

La mise en oeuvre du programme va se scinder en plusieurs étapes:

      1. Création d'un programme de test pour connaitre le nombre d'impulsions de codeur pour un tour complet de l'arbre moteur;
      2. Création du programme permettant de piloter la position de l'arbre moteur; mise en oeuvre et test;
      3. Pour aller plus loin commander simultanément deux moteurs à courant continu. 

Calcul du nombre d'impulsions du codeur incrémental:

L'entrée A du codeur incrémental sera relié à la broche 2 de la carte Arduino;

L'entrée B du codeur incrémental sera relié à la broche 4 de la carte Arduino

 #define encoderPinA 2
 #define encoderPinB 4

Les impulsions comptées seront stockées dans la variable "pulses_counted",  et envoyées sur le port série de la carte Arduino, puis affichées à l'écran.

On commencera par définir les les broches 2 et 4 de la carte Arduino en entrée; puis la vitesse de transfert sur la liaison série:

  Serial.begin(9600);
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);

Pour compter les impulsions on utilisera les interruptions par détection de front sur la broche 2 de la carte Arduino

attachInterrupt(digitalPinToInterrupt(encoderPinA), Dc_Motor_Encoder,RISING);

A chaque front détecté, le sous_progamme "Dc_Motor_Encoder" sera exécuté. Dans ce sous programme on va juste vérifier l'état de la broche 4; si elle est à l'état haut, alors la variable "pulses_counted" sera incrémentée, et si elle est à l'état bas, la variable sera décrémentée.

Le programme princinpal se contente juste d'envoyer le contenu de cette variable vers la liaison série.

Ci-après le programme de test complet:

/*==============================================================
 * This test program is for rotary encoder, we are using
 * Arduino Uno.
 * For encoder, pin A is connected to pin 2 of Arduino and pin B 
 * is connected to pin 4 of Arduino
 * Author: JtBB
 */
//Encoder pin connections with Arduino
#define encoderPinA 2
#define encoderPinB 4 
//Variables
int pulses_counted = 0;
//Setup
void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  pinMode(encoderPinA, INPUT);
  pinMode(encoderPinB, INPUT);
  attachInterrupt(digitalPinToInterrupt(encoderPinA), Dc_Motor_Encoder,RISING);
}

void loop() {
  // put your main code here, to run repeatedly:
  Serial.print("Result:   ");
  Serial.println(pulses_counted);
  //end of encoder output printing
}
//This called by isr, is to count encoder pulses
void Dc_Motor_Encoder(){
  int b = digitalRead(encoderPinB);
  if (b > 0){
    pulses_counted ++;
  }else
    {
      pulses_counted --;
    }
}

Après avoir Uploadé le programme sur la carte Arduino, à l'aide du moniteur série de l'IDE Arduino, on constate bien que le nombre d'impulsions du codeur augmente ou diminue lorsqu'on fait tourner l'arbre moteur à la main dans un sens ou dans l'autre; capteur alimenté en +5V.

Le potentiomètre étant relié à l'entrée analogique A0 de la carte Arduino, et alimenté en +5V, on a jugé inutile d'exécuter le même test que celui fait avec le codeur; étant donné que, le convertisseur analogique - numérique de la carte Arduino est un convertisseur 10 bits. Donc si la tension sur le curseur du potentiomètre varie de 0 à 5V, la valeur numérique va varier de 0 à 1024.

Commande du moteur en position

Pour réaliser ce programme, on peut utiliser la formule du régulateur vue dans l'un des paragraphes précédents, pour calculer u(t), c'est cette grandeur qui servira pour faire avancer ou reculer le moteur jusqu'à la position désirée.

Mais dans le cas présent, on va plutôt utiliser la librairie PID de l'environnement de programmation intégré d'Arduino. Cette librairie comporte plusieurs fonctions utiles pour réaliser un régulateur PID à base d'Arduino. On ne va pas s'étendre dessus cette librairie est expliquée en long et en large par son auteur dans le site arduino/cc. Un grand merci à: Bret Beauregard, pour son formidable boulot.

On commence donc par charger la librairie avec l'instruction:

#include 

Ensuite on va attribuer des noms de variables aux différentes broches qui seront utilisées par Arduino pour ce fait:

#define encodPinA   2
#define encodPinB   4 
#define enablePin;  7
#define motorA      5 
#define motorB      6

Puis les variables: input, output, setpoint, sont toutes du types "double";

La variable encoderPos contiendra le nombre d'impulsions total du codeur incrémental, dans le sens de rotation directe et inverse. Puisqu'elle sera appelée par le sous programme d'interruption, elle est du type "volatile long".

Les constantes Kp, Ki, et Kd ont été choisies pour mettre en oeuvre l'exemple proposé, celles-ci peuvent être changées selon les cas.

A présent il faudra créer une instance appelée motor grâce à l'instruction:

PID motor(&input, &output, &setpoint, Kp, Ki, Kd, DIRECT);

Il est possible qu'il y ait des dysfonctionnements, à la mise sous tension du montage, dans ce cas changer la variable "DIRECT", par "REVERSE", ou alors inverser les deux entrées du codeur sur la carte Arduino.

Le sous programme Setup() va se charger d'initialiser les broches de la carte, la liaison série (qui peut servir ici pour le déboggage), des fonctions SetMode(), SampleTime(), SetoutputLimit().

Les impulsions de commande sont en fait des ondes PWM (modulées en largeur d'impulsion); lorsqu'elles sont utilisées pour piloter un moteur à courant continu cells-ci ont tendance à générer un bruit qui peut rapidement devenir nuisible même lorsque le moteur est à l'arrêt.

Pour éliminer ce bruit on a utilisé toujours dans Setup(), une instruction directement adressée au niveau du régistre interne du composant en question:


TCCR0B = TCCR0B & 0b11111000 | 1;

Le sous-programmme pwmOut() se charge de commander le moteur dans les deux sens de rotation, et le sous-programme read_Encoder() lit et cumule ou retranche le nombre d'impulsions du codeur incrémental.

Et pour finir le programme principal va lire la valeur du potentiomètre pour la consigne (setpoint), puis le nombre total d'impulsions du codeur pour savoir la position de l'arbre du moteur, puis effectuera le calcul en se servant de la formule mathématique discutée auparavant, puis enverra le résultat sous forme d'un signal PWM au moteur pour tourner dans un sens ou dans l'autre.

Ci-dessous le programme complet du régulateur:

/*==============================================================
 * Motor position control and rotary encoder as feedback, we are using
 * Arduino Uno.
 * Rotary encoder, pin A is connected to pin 2 of Arduino and pin B 
 * is connected to pin 4 of Arduino
 * Author: JtBB
 */
#include <PID_v1.h>
#define encodPinA       2                      // Quadrature encoder A pin
#define encodPinB       4                      // Quadrature encoder B pin
#define enablePin       7
#define motorA          5                      // PWM outputs to L298N H-Bridge motor driver module
#define motorB          6 
//
#define setpointInput   A0                     // Setpoint from potentiometer
//Program variables
double kp = 5.4 , ki = 1.0 , kd = 0.01;             //You can modify this for your own need
double input = 0, output = 0;
double setpoint = 0;
long temp;
volatile long encoderPos = 0;
const int encoderPulsesperRev = 1024; PID motor(&input, &output, &setpoint, kp, ki, kd, DIRECT); //REVERSE or DIRECT if turn continuously void setup() { pinMode(encodPinA, INPUT_PULLUP); // quadrature encoder input A pinMode(encodPinB, INPUT_PULLUP); // quadrature encoder input B pinMode(enablePin, OUTPUT); attachInterrupt(digitalPinToInterrupt(encodPinA),read_Encoder,RISING); // update encoder position TCCR0B = TCCR0B & 0b11111000 | 1; // PWM frequency to prevent motor noise see ATMEGA328P datasheet motor.SetMode(AUTOMATIC); motor.SetSampleTime(1); motor.SetOutputLimits(-255, 255); digitalWrite(enablePin, LOW); Serial.begin (115200); // for debugging } void loop() { setpoint = analogRead(setpointInput); //Potentiometer is connected to A0 setpoint = map(setpoint, 0, 1023, 0, encoderPulsesperRev); //map for rotation of motor input = encoderPos; // data from encoder Serial.println(encoderPos); // monitor motor position for debug porpose Serial.println(setpoint); motor.Compute(); // calculate new output pwmOut(output); // drive L298N H-Bridge module } void pwmOut(int out) { // to H-Bridge board digitalWrite(enablePin, HIGH); if (out > 0) { analogWrite(motorA, out); // drive motor CW analogWrite(motorB, 0); } else { analogWrite(motorA, 0); analogWrite(motorB, abs(out)); // drive motor CCW } } void read_Encoder() { // pulse and direction, direct port reading to save cycles int b = digitalRead(encodPinB); if (b > 0){ encoderPos++; } else { encoderPos--; } }

Test de fonctionnement:

Après avoir Uploadé le programme vers la carte Arduino Uno le programme est prêt à fonctionner. On peut tourner le potentiomètre et on observera l'axe du moteur bouger. Ici on a choisi un motoréducteur à 110tr/mn de vitesse maximale, afin d'avoir de déplacer l'axe du moteur plus lentement. attention à la position initiale de l'axe du moteur et du potentiomètre avant de mettre le tout sous-tension.

Pour aller plus loin on se propose de commander deux moteurs simulatnément avec deux potentiomètres. On va donc utiliser deux moteurs de robot LEGO, car ceux-ci sont aussi équipés chacun d'un codeur incrémental, tout comme le moteur à courant continu de l'exemple précedent.

 

Le programme:

Il est intégralement calqué sur le programme précédent à ceci près que deux instances motor1 et motor2 ont été crées pour être utilisées par la fonction PID.

Ci-dessous le listing du programme:

/*===================================================
 * Position control of two DC motor with Arduino Uno
 * Autor: JtBB
 * Ver: 
 * Setpoint are made with two potentiometers
 */
 //We are using PID library. Thanks to Bret Beauregard for
 //his great job
 #include 
 //Driver pins to Arduino
 #define enablePin1     7 //enablePin for motor 1
 #define enablePin2     8 //enablePin for motor 2
 #define motor1A        5 //pwm output for motor 1
 #define motor1B        6 //pwm output for motor 1
 #define motor2A        9 //pwm output for motor 2
 #define motor2B        10 //pwm output for motor 2
 //Encoder pins to Arduino
 #define encoderPinA1   2 //encoder A pin 1
 #define encoderPinB1   3 //encoder B pin 1
 #define encoderPinA2   4 //encoder A pin 2
 #define encoderPinB2   11 //encoder B pin 2
 //Potentiometer pins to Arduino
 #define setpointInput1      A0 //potentiometer 1
 #define setpointInput2      A1 //potentiometer 2
 //Variables
 //
 //PID parameters
 double kp1 = 5.0, ki1 = 1.0, kd1 = 0.01;
 double kp2 = 5.0, ki2 = 1.0, kd2 = 0.01;
 double input1 = 0, input2 = 0, output1 = 0, output2 = 0;
 double setpoint1 = 0, setpoint2 = 0;
 //Encoder variables
 volatile long encoderAPos = 0;
 volatile long encoderBPos = 0;
const int encoderPulsesperRev = 1024; PID motor1(&input1, &output1, &setpoint1, kp1, ki1, kd1, DIRECT); PID motor2(&input2, &output2, &setpoint2, kp2, ki2, kd2, REVERSE); //Setup void setup(){ motorsInit(); encodersInit(); motorsCtlInit(); TCCR0B = TCCR0B & 0b11111000 | 1; //change PWM frequency on 5 - 6 pins TCCR1B = TCCR1B & 0b11111000 | 1; // set 31KHz PWM to prevent motor noise Serial.begin(115200); } //Main program void loop(){ double value1 = analogRead(setpointInput1); setpoint1 = map(value1, 0, 1023, 0, encoderPulsesperRev); //map for motor 1 rotation double value2 = analogRead(setpointInput2); setpoint2 = map(value2, 0, 1023, 0, encoderPulsesperRev); //255 map for motor 2 rotation Serial.println(encoderBPos); input1 = encoderAPos; input2 = encoderBPos; motor1.Compute(); pwmOut1(output1); motor2.Compute(); pwmOut2(output2); } //Functions //Motors initialization void motorsInit(){ pinMode(enablePin1, OUTPUT); pinMode(enablePin2, OUTPUT); pinMode(motor1A, OUTPUT); pinMode(motor1B, OUTPUT); pinMode(motor2A, OUTPUT); pinMode(motor2B, OUTPUT); digitalWrite(enablePin1, LOW); digitalWrite(enablePin2, LOW); } //Encoders initialization void encodersInit(){ pinMode(encoderPinA1, INPUT_PULLUP); pinMode(encoderPinA2, INPUT_PULLUP); pinMode(encoderPinB1, INPUT_PULLUP); pinMode(encoderPinB2, INPUT_PULLUP); attachInterrupt(digitalPinToInterrupt(encoderPinA1), read_EncoderA,RISING); attachInterrupt(digitalPinToInterrupt(encoderPinB1), read_EncoderB,RISING); } //motor control initialization void motorsCtlInit(){ motor1.SetSampleTime(1); motor2.SetSampleTime(1); motor1.SetOutputLimits(-255, 255); motor2.SetOutputLimits(-255, 255); motor1.SetMode(AUTOMATIC); motor2.SetMode(AUTOMATIC); } //Encoders interrupt routine //Encoder A void read_EncoderA() { // pulse and direction, direct port reading to save cycles int enca = digitalRead(encoderPinA2); if (enca > 0){ encoderAPos++; // if(digitalRead(encodPinB1)==HIGH) count ++; } else { encoderAPos--; // if(digitalRead(encodPinB1)==LOW) count --; } } //Encoder B void read_EncoderB() { // pulse and direction, direct port reading to save cycles int encb = digitalRead(encoderPinB2); if (encb > 0){ encoderBPos++; // if(digitalRead(encodPinB1)==HIGH) count ++; } else { encoderBPos--; // if(digitalRead(encodPinB1)==LOW) count --; } } //To motor driver //Send PWM signal to motor 1 void pwmOut1(int out1){ if(out1 > 0){ digitalWrite(enablePin1, HIGH); analogWrite(motor1A, out1); analogWrite(motor1B, 0); } else { digitalWrite(enablePin1, HIGH); analogWrite(motor1A, 0); analogWrite(motor1B, abs(out1)); } } //Send PWM signal to motor 1 void pwmOut2(int out2){ if(out2 > 0){ digitalWrite(enablePin2, HIGH); analogWrite(motor2A, out2); analogWrite(motor2B, 0); } else { digitalWrite(enablePin2, HIGH); analogWrite(motor2A, 0); analogWrite(motor2B, abs(out2)); } }

La vidéo accessible par le lien ci-après montre le fonctionnement de la maquette avec un moteur et avec deux moteurs: elle n'a pas de son, il faudra activer le sous-titrage. Nous présentons vraiment toutes nos excuses pour cette défaillance.

Conclusion:

Ce montage a permis de voir qu'on peut piloter un moteur à courant continu en position. Le capteur de boucle utilisé est un codeur incrémental. On pourrait aussi utiliser un potentiomètre fixé sur l'arbre moteur si on est sûr que le nombre de tours de l'arbre moteur ne dépassera pas 1 (pour un potentiomètre 1 tour) ou 10 (pour un potentiomètre 10 tours). Si on opte pour ce choix alors il faudra aussi tenir compte de la résolution du convertisseur analogique numérique.

Un autre point à noter aussi c'est que dans ce montage, l'algorithme de comptage du nombre d'impulsions du codeur mis en oeuvre comporte des pertes de résolution du fait qu'on teste un seul signal parmi le deux. Pour améliorer la résolution, il faudrait tester les deux signaux lorsque l'un ou l'autre change; cela suppose une entrée d'interruption supplémentaire rien que pour le codeur. En conséquence on a plutôt accepté de perdre en résolution afin de piloter deux moteurs avec la carte Arduino Uno.

JtBB

Arduino Uno est basé sur le microcontrôleur ATMEGA328P, ce microcontrôleur posséde trois Timers:

        • Le Timer0 ;
        • Le Timer1 ;
        • Le Timer2.

 

Tout utilisateur de la carte Arduino Uno  a déjà utilisé au moins une fois ces périphériques internes dans son programme ne serait ce que lorsqu’il fait appel aux fonctions prédéfinies :  mcros(),  millis()delay(), ou delayMicroseconds().

 

Cet article on abordera les points suivants:

 Présentation globale des Timers;

 Les interruptions du Timer;

 Des exemples de mise en oeuvre :

            - Faire clignoter une LED toutes les 0,5s en utlisant le Timer en débordement;

            - Faire clignoter une LED toutes les 0,5s en utilisant le Timer en comparaison;

            - Faire tourner deux interruptions en même temps sur le microcontrôleur;

Une application pratique des interruptions Timer: Mesure de la vitesse de rotation d'un moteur.

 

Présentation globale des Timers:

C’est quoi un Timer ?

Un Timer ou Timer / Counter, est une sorte d’horloge intégrée au microcontrôleur qui permet de mesurer la durée d’un événement. Par exemple pendant combien de temps un port d’entré est resté à l’état haut?  Un Timer peut fournir des fonctions de comptage, de mesure de  durée  ou de génération de signaux, dans un montage.

Ces événements qui font l’objet des mesures, peuvent être internes  ou externes.

Chaque Timer possède des registres de configuration internes, mais nous n’allons pas tous les présenter  ici, il faudra donc se référer à la documentation technique du composant pour des renseignements plus détaillés concernant leur utilisation.

Un certain nombre de ces registres  joue un rôle très important dans la programmation des Timers :

Mais avant de les citer, juste une petite convention d’écriture :  "x" représente le Timer (0, 1, 2),  "y" sera mis pour A ou B.

TCCRx : Timer Counter Control Register ;  c’est dans ce registre que l’on pourra configurer le Timer x.

TCNTx : Timer Counter Register ; c’est ici qu’on retrouve la valeur de comptage actuelle du Timer x.

OCRx :   Output Compare Register.

ICRx :    Input Compare Register.

TIMSKx :Timer Conuter Interrupt Mask.  Ce registre contient des bits qui permettent d’activer ou de désactiver les interruptions.

TIFRx :   Timer Counter Interupt Flag Register.  Ses bits indiquent l’interruption en cours.

 

Le Timer 0 et le Timer 2  fonctionnent sur 8 bits c’est-à-dire qu’ils peuvent compter de 0 à 255 (28 - 1) ; tant dis que le Timer 1 quant à lui est sur 16 bits; il est capable de compter de 0 à 65535 (216 -1).

Lorsqu’un Timer atteint sa valeur maximale de comptage (255 pour le Timer 0 et Timer 2, ou 65535 pour le Timer 1), il est réinitialisé à 0 puis le comptage recommence, jusqu’à sa valeur maximale de nouveau, puis ainsi de suite, le cycle se poursuit.

cycle timer

Grâce à ce mode de fonctionnement, une interruption peut être déclenchée lorsqu’un Timer atteint sa valeur maximale ou alors une valeur intermédiaire fixée par le programmeur.

cycle timer 2

Les interruptions du Timer:

Lorsque une interruption survient, le microcontrôleur arrête le programme qu’il était en train d’exécuter, puis traite en priorité la routine d’interruption, avant de poursuivre avec  le programme en cours.

Chaque Timer peut générer un ou plusieurs types d’interruptions.

Pour les Timers 0 et 2 :

Interruption par débordement (Overflow), dont la routine dans l’environnement de développement Arduino (IDE) sera : ISR(TIMERx_OVF_vect).

Interruption par comparaison (Compare Match), dans l’IDE Arduino sera : ISR(TIMERx_COMPy_vect).

Pour le Timer 1 :

On retrouve les mêmes interruptions citées précédemment. Il faut noter que les Timer 0 et 1, on a en plus, l’Interruption par changement d'état d'une broche spécifique, dans l’IDE arduino sera : ISR(TIMERx_CAPT_vect).

Avant de poursuivre, voyons comment fonctionne une interruption par comparaison?

Une valeur de comparaison est chargée dans un registre, lorsque la valeur comptée par le Timer est égale à celle du registre en question, une interruption est générée.

 

Pour qu’une interruption puisse être exécutée, il faut que deux conditions soient réunies :

L’interruption doit être activée ;

Le bit correspondant de l’interruption doit être positionné dans le registre de masque d’interruption.

 

L’horloge ou base de temps du Timer peut être interne c’est-à-dire à partir de l’horloge du microcontrôleur, ou alors externe.

On va se contenter ici de faire fonctionner les Timers  à partir de l'horloge interne au composant.

Un petit mot pour clore cette présentation

Dans l’environnement de développement Arduino,  les fonctions prédéfinies sei() et cli() permettent respectivement d’activer ou de désactiver les interruption. On peut aussi utliser les fonctions prédéfinies interrupts(), ou noInterrupts().

L’usage des fonctions  attachInterrupt() et detachInterrupt(), est réservée aux interruptions externes.

 

Exemples  de mise en œuvre :

Les exemples sur les Timers seront mis en œuvre avec la carte Arduino Uno, la figure ci-après montre l’équivalence entre les broches du microcontrôleur et les connecteurs disponibles de la carte Arduino Uno :

Atmega pin mapping

Exemple 1 :  on veut écrire un programme qui fait clignoter une LED toutes les 0,5s.

L’horloge utilisée par le microcontrôleur ATMEGA328P dans la carte Arduino est de 16MHz ; ce qui veut dire que chaque impulsion aura une durée de \[\frac{1}{16 000000}s\] Soit 62,5ns ou 62,5x10-9s

Sachant qu’on veut compter jusqu’à 0,5s le Timer doit compter jusqu’à  8000000 avant de déclencher une interruption qui va allumer ou éteindre la diode. Ce chiffre est carrément énorme, car on a vu que la valeur maximale des Timers 0 et 2 était de 255. Même si on prend le Timer 1, lui aussi est limité à une valeur de 65535. La solution à ce problème revient donc à diviser cette fréquence de 16MHz  par 256 (autrement dit à multiplier 62,5ns par 256) ce qui nous donnera 16µs. En divisant 0,5s par 16µs, on obtient 31250. Le Timer aura désormais besoin de compter que jusqu’à 31250, pour faire clignoter la LED toutes les 0,5s.  Le Timer 1 sera plus approprié à cet effet, si on divise d'abord l'horloge par 256.

Heureusement,  le constructeur du microcontrôleur a tout prévu en insérant un pré-diviseur dans la puce du composant.

On a donc besoin de 2 registres principaux pour contrôler les Timers, ils sont nommés  TCCRxA (Timer Counter Control Register A) et TCCRxB.  Ce qui, pour le Timer 1 s’appellera TCCR1A et TCCR1B.

Le registre TCCR1A permet au Timer 1 de fonctionner  en mode PWM (Pulse Wide Modulation ou modulation par largeur d’impulsions),  afin de générer une telle forme de signal sur les broches 9 et 10 du composant (voir figure ATMEGA Pin mapping ci-dessus). 

Pour l’exemple du clignotement de la LED, on va utiliser uniquement le registre TCCR 1B. Les bits CS10, CS11 et CS12 de ce registre permettront de positionner le pré diviseur.  Pour une division par 256, on aura CS12 = 1,  CS11 = 0 et CS10 = 0:

Registres Timer

Selon cet extrait de la documentation technique, on peut faire une division par 1, 8, 64, 256, 1024.

Les valeurs de comptage sont stockées dans un registre interne nommé TCNT1 ; celui-ci est formé de 2 registres à 8 bits, puisque le Timer 1 fonctionne sur 16 bits. Sa remise à zéro consiste à écrire la valeur 0 dans son contenu.

Un autre registre très important est utile pour le Timer 1, il s’agit du registre TIMSK1. 

registretimsk1atmega328p

Il permet de valider les interruptions sur entrée de capture, par comparaison A et B, puis par débordement du Timer 1. Pour plus de détails sur ce registre, il faut se reporter à la documentation technique du composant. Puisque nous voulons déclencher l'interruption lorsque le Timer 1 déborde, seul le bit TOIE1 nous intéresse.

Voici  une idée de code de mise en œuvre du Timer 1, dans laquelle on va se contenter d’allumer une LED, pendant 0,5s puis l’éteindre sur la même durée, en utilisant le fonctionnement  par débordement :

/*****using Timer 1 first example – 02/16/2022  by JTBB */
//Variables
bool LED_state = LOW ;
const int LED_pin = 12 ;

//Setup
void setup() {
     pinMode(LED_pin, OUTPUT);
     TCCR1A = 0 ;
     TCCR1B = 0 ;
     TCNT1 = 0 ;
     TCCR1B |= (1 << CS12) ; //prescaler to 256, we can also use binary notation according to the compiler
     TIMSK1  |= (1 << TOIE1) ; //enable Timer 1 overflow

}

void loop(){ //put your main code here

}

ISR(TIMER1_OVF_vect) {
     LED_state = !LED_state ; //invert the state
     digitalWrite(LED_pin, LED_state) ; //of this pin
}

Il faut juste noter dans ce programme donné en exemple que l’opérateur  «| » permet d’écrire individuellement des valeurs  « 1 » dans les bits du registre concerné; en revanche si on voulait écrire des « 0 », il faudra utiliser l’opérateur « & ».

 

En exécutant ce programme, on remarque qu'il ne fonctionne pas tout à fait comme on pouvait s’y attendre. En effet la LED reste allumée pendant 1s environ puis s’éteint pendant la même durée. Ce ne sont pas les 0,5s prévu par le programme, malgré un pré – diviseur positionné pour faire des divisions de la base de temps par 256. 

Pourquoi ce résultat ?  En effet notre exemple fonctionne en débordement du Timer 1 ; cela veut dire que le registre TCNT1 commencera le comptage à partir de 0 puis ira jusqu’à 65535, il y aura débordement, donc une interruption ;  puis le cycle recommencera.

Si on fait un petit calcul, déjà effectué dans les lignes précédentes, avec une horloge de 16MHz, si on divise par 256, on aura une impulsion toutes les 16µs.  Ensuite, 16µs x 65535 = 1 ; du moins pas exactement 1, mais 1,04856s

Si on veut faire clignoter la LED toutes 0,5s ; il faudra plutôt stocker le nombre d’impulsions souhaitées dans un registre appelé OCR1A,  puis comparer le contenu de ce registre au contenu du registre TCNT1. En cas d’égalité des deux valeurs une interruption sera exécutée.  C’est le mode Compare/Match ;  on commencera par positionner le bit OCIE1A à 1.

 

Exemple 2: Reprenons l’exemple précédent

/*****using Timer 1 second example – 02/16/2022 edited by JTBB */

//Variables
bool LED_state = LOW ;
const int LED_pin = 12 ;
//Setup

void setup() {

  pinMode(LED_pin, OUTPUT);
  TCCR1A = 0 ;
  TCCR1B = 0 ;
  TCCR1B |= (1 << CS12) ; //prescaler to 256, we can also use binary notation according to the compiler
  TIMSK1 |= (1 << OCIE1A); //enable compare match
  OCR1A = 31250; //set this value to the register to match timer 1

}

void loop(){ //put your main code here

}

ISR(TIMER1_COMPA_vect) {

  TCNT1 = 0; //reset this for next interrupt
  LED_state = !LED_state ; //invert the state
  digitalWrite(LED_pin, LED_state) ; //of this pin

}


En effet nous obtenons un clignotement de la LED toutes les 0,5s.  Dans la routine d’interruption, le contenu de TCNT1 sera remis à 0, afin de relancer le cycle.

 

Exemple 3:

Modifions les exemples précédents en mettant en oeuvre deux interruptions Timer en même temps. Une fera clignoter une LED sur la broche numérique 12, et l'autre fera clignoter une LED sur la broche 10, à une fréquence différente.

Pour cela on va se servir en plus du Timer 2, en plus du Timer 1 déjà mis en œuvre.

/*****using Timer 1 third example – 02/16/2022 edited by JTBB */

//Variables
bool LED1_state = LOW;
bool LED2_state = LOW;
const int LED1_pin = 12;
const int LED2_pin = 10;

//Setup

void setup() {

  pinMode(LED1_pin, OUTPUT);
  pinMode(LED2_pin, OUTPUT);
  //for Timer 1 interrupt 0,5s
  TCCR1A = 0 ;
  TCCR1B = 0 ;
  TCCR1B |= (1 << CS12) ; //prescaler to 256, we can also use binary notation according to the compiler
  TIMSK1 |= (1 << OCIE1A); //enable compare match
  OCR1A = 31250; //set this value to the register to match timer 1

  //for Timer 2 interrupt 12,5ms

  TCCR2A = 0;
  TCCR2B = 0;
  TCCR2B |= (1 << CS22)|(1 << CS21)|(1 << CS20); //prescaler = 1024
  TIMSK2 |= (1 << OCIE2B); //enable Timer 2 compare match
  OCR2B = 195;

}

void loop(){ //put your main code here

}

//timer 2 interrupt vector

ISR(TIMER2_COMPB_vect) {

  //
  TCNT2 = 0 ; //reset this for next interrupt
  LED2_state = !LED2_state ; //invert the state
  digitalWrite(LED2_pin, LED2_state) ; //of this pin

}

//timer 1 interrupt vector

ISR(TIMER1_COMPA_vect) {

  TCNT1 = 0; //reset this for next interrupt
  LED1_state = !LED1_state ; //invert the state
  digitalWrite(LED1_pin, LED1_state) ; //of this pin

}

Rien de bien significatif au niveau visuel puisque la diode pilotée par le Timer 2 clignote beaucoup trop rapidement. Il faut remarquer que le Timer 2 fonctionne sur 8 bits, donc il ne pourra compter que de 0 à 255. Cela signifie que si par exemple on veut faire clignoter la diode pour 0.125s ; la valeur de pré chargement de 1953, sera illégale pour le registre OCR2B, et de ce fait, en fonction du  compilateur utilisé, celle-ci sera tronquée ; par conséquent le résultat attendu ne sera pas au rendez-vous puisqu’on risque d’obtenir n’importe quoi.  On s’est contenté de mettre la valeur 195 qui en théorie nous donnera un clignotement de 12,5ms environ, et la LED est constamment allumée.  On  ne pourra pas utiliser le Timer 2, pour générer un signal de durée 0.5s pour allumer ou éteindre la LED; à moins de modifier la valeur du quartz de la carte Arduino. 

Pour mettre en œuvre une interruption externe, par exemple un comptage d’objets qui passe devant un capteur, ou la génération d’un signal PWM, pour réguler la vitesse d’un moteur à courant continu, il faudra utiliser les broches dédiées du microcontrôleur.

Application des Timers du microcontrôleur ATMEGA328P:

Voici la problématique :  La structure mécanique d'une mini perçeuse manuelle de circuit imprimés, vieille de plus de vingt ans, ne permettait plus d’assurer un bon perçage (trous voilés, pastilles du circuit imprimé qui sautent, à cause d’un mandrin pourrit, …à moins d’être recalcitrant pour à tout prix l’utiliser, inutile d’aller appeler le voisin au secours après..). 

La perceuse a dont été entièrement démontée, puis le moteur récupéré. Mais aucune caractéristique inscrite sur celui-ci. 

On se propose de mesurer la vitesse de ce moteur à courant continu lorsqu’il est alimenté en +12V. Il est possible de pousser l'étude plus loin afin d'en déterminer d'autres paramètres mais cela ne pourra pas être fait dans cet article.

Pour mesurer la vitesse de rotation d'un moteur, il faut disposer d’un tachymètre.  Mais problème n’avons pas ce type de matériel à portée de main.  Alors il faut en fabriquer un.

Le tachymètre que nous voulons réaliser permettra de mesurer la vitesse de rotation du moteur  en utilisant une fourche optique à rupture de faisceau (la raison de choix ce réside sur la fait c'est ce que nous avons sous la main). On peut très bien utiliser un capteur optique à réflexion à Infrarouge, on devra obtenir le même résultat.

De quoi avons nous besoin?

  • Une carte Arduino Uno;
  • Un capteur optique à rupture de faisceau (un capteur à réflexion serait plus adapté pour ce genre d'utilisation, et pourra fonctionner sans problème avec notre exemple);
  • Une résistance de 180 Ohms;
  • Une résistance de 10 KOhms;
  • Une petite plaque de soudure à trous pastillés.

Après sourdure, voici le capteur monté:

capteur led ir 1

Pour mettre en place le dispositif de mesure, on a d'abord collé, puis renforcé avec du stoch transparent, un petit morceau de plastique blanc, sur un coupleur fixé sur l'arbre moteur. Il vaut mieux utiliser le montage tel qu'il fait dans le cas présent pour de faibles vitesses de rotation, pour les grandes vitesses ce morceau de plastique doit obligatoirement être remplacé par un autre système plus rigide, au besoin fixé sur l'arbre moteur par un autre système autre que la colle.

On peut voir que ce bout de plastique passera dans la fourche du capteur:

capteur et detecteur montes

Ci-après montre le dispositif d'essai definitif :

banc de test du moteur1

La sortie d'impulsions du capteur sera connecté sur l'entrée numérique 8 de la carte Arduino Uno.

Le programme:

Principe de la mesure: Lorsqu'une impulsion arrive sur l'entrée numérique 8 de l'Arduino, celle-ci va déclencher une interruption sur son front montant. Aussitôt un compteur interne démarre, puis s'arrête à la prochaine impulsion. Le nombre d'impulsions comptées n'aura de sens que si on connait la durée entre deux impulsions. D'où l'intérêt d'utiliser l' interruption en mode capture. Ainsi si l'on tient compte de la durée d'une impulsion d'horloge (62,5ns), du nombre nombre d'impulsions enregistrées entre deux front montants, on peut calculer la vitesse en tours par secondes, et faire la conversion en tours par minutes.

Le résultat sera donc envoyé par liaison série depuis l'Arduino vers le PC et affiché à l'écran.

Ci dessous, une idée possible du programme:

//***********************************************************
// Easy Tachometer                                          *
// Edited: 02/19/2022; by JTBB                              *
// We use Timer1 overflow interrupt to count incoming pulses*
// to Uno Digital Pin 8.                                    *
//***********************************************************

//Variables
 
volatile unsigned long CountOvf;
volatile unsigned long startTime;
volatile unsigned long endofTime;

volatile boolean Start;
volatile boolean readyToGo;

// timer overflows vector
ISR (TIMER1_OVF_vect)
{
   CountOvf++;
}

// timer capture vector
ISR (TIMER1_CAPT_vect)
{
   unsigned int timer1Value;
   timer1Value = ICR1;
   unsigned long CountOvfOld = CountOvf;

// if just missed an overflow
   if ((TIFR1 &(TOV1 == 1)) && timer1Value < 0x7FFF){
        CountOvfOld++;
     }

// wait until we noticed last one
   if (readyToGo){
        return;
     }

   if (Start)
     {
       startTime = (CountOvfOld << 16) + timer1Value;
       Start = false;
       return;
     }

    endofTime = (CountOvfOld << 16) + timer1Value;
    readyToGo = true;
    TIMSK1 = 0; //reset interrupt now
}

void SetupForInterrupts ()
{
    noInterrupts (); //disable interrupts
    Start = true;
    readyToGo = false; //get ready for next time
    //
    TCCR1A = 0;
    TCCR1B = 0;

    TIFR1 |= (1 << ICF1) | (1 << TOV1); // clear flags
    TCNT1 = 0; // Timer1 counter to 0
    CountOvf = 0; //

    // Timer 1 - counts clock pulses
    TIMSK1 |= (1 << TOIE1) | (1 << ICIE1); // Timer 1 overflow and input capture
    // start Timer 1, no prescaler
    TCCR1B |= (1 << CS10) | (1 << ICES1); // Input Capture Edge Select
    interrupts (); //enable interrupts now
}

void setup ()
{
    Serial.begin(115200);
    Serial.println("Vitesse de rotation: ");

    SetupForInterrupts (); // set up for interrupts
}

void loop ()
{
    // wait until a measured value change
    if (!readyToGo){
       return;
     }

    // period is elapsed time
    unsigned long elapsedTime = endofTime - startTime;
    float rpm = round(60*(16000000.00 / float (elapsedTime)));
    // clock 16 MHz

    if (rpm <= 20) {
          Serial.println("Lent ou arrêt");
       }
    else{
          Serial.print ("Vitesse: ");
          Serial.print (rpm);
          Serial.println (" Trs/mn. ");
       }
    // This delay is for Serial print stuff
    delay (500);

    SetupForInterrupts ();
} // end

Le lien ci-après montre la vidéo ci-après résume les différents points abordés dans cet article:

Conclusion:

D’autres idées de mise en oeuvre existent pour utiliser le timers de l'ATMEGA328P, par exemple on peut se contenter d’utiliser les fonctions prédéfinies comme par exemple millis(), ceci sans passer par la manipulation directe des registres du Timer.

Cet article avait pour but de présenter les Timers qu'on trouve dans nos cartes Arduino Uno, et de montrer comment ceux-ci pouvaient être utilisés sans avoir besoin de faire appel aux fonctions prédéfinies dans l'IDE Arduino.

D'autres fonctionnalités qui peuvent être fournies par ces Timers n'ont pas été abordées non plus. Il convient de se reporter à la documentation technique du constructeur, pour avoir une présentation détaillée sur ces périphériques que sont les Timers.

JtBB

 

 

Ce site web utilise des cookies

Certains d’entre eux sont essentiels pour son fonctionnement et d’autres nous aident à améliorer l’expérience utilisateur (cookies traceurs). Vous pouvez décider vous-même si vous autorisez ou non ces cookies. Merci de noter que, si vous les rejetez, certaines fonctionnalités du site pourront être défaillantes.