Piloter 16 LEDs en ne mobilisant que 3 pins sur l'Arduino

Dans un article précédent, il avait été présenté une solution permettant de fournir 8 sorties supplémentaires à l'Arduino, en utilisant un convertisseur Série/Parallèle 74HC595, en ne mobilisant que trois pins sur le contrôleur.
En fait, les caractéristiques techniques du 74HC595 permettent de les chaîner pour multiplier le nombre de sorties, tout ne ne mobilisant que trois pins de sortie sur l'Arduino. Afin d'illustrer ce principe, cet article présente un projet permettant de piloter 16 LEDs de façon indépendantes en chaînant deux 74HC595.
Afin de faciliter l'utilisation de cette solution, une amélioration de la classe IC74hc595 sera décrite. Celle-ci permet de gérer jusqu'à quatre 74HC595 en chaîne en permettant de transférer des trains de 32 bits en Série sur les convertisseur.

Câblage du projet

Le principe du chaînage des convertisseurs 74HC595 est d'utiliser la pin Q7' (pin n°9), jusqu'alors laissée libre, en la connectant sur la pin DS (pin Data N°14) du 74HC595 suivant, et ainsi de suite.
Voici le schéma du projet  :
  • La pin n°2 de l'Arduino est reliée à la broche DS (Data) du premier 74HC595.
  • La pin n°3 de l'Arduino est reliée à la broche  ST_CP (Latch) des deux 74HC595.
  • La pin n°4 de l'Arduino est reliée à la broche  SH_CP (Clock) des deux 74HC795.
  • La broche Q7' du premier 74HC595 est reliée la broche DS (Data) du second. 
  • Les broches Q0 à Q7 de chaque 74HC595 sont respectivement connectées à l'anode d'une LED.
  • La cathode de chaque LED, protégée par une résistance de 220 Ohms, est reliée à la masse.
Voici ce schéma mis en oeuvre sur une platine d'expérimentation :

Définition de la classe IC74hc595M

La classe IC74hc595M est une amélioration de la classe IC74hc595 qui permet de gérer jusqu'à quatre 74HC595 en chaîne. Elle peut être utilisée en lieu et place de l’ancienne classe, à une petite différence toutefois. L'ancienne classe permettait de transférer les bits dans les deux sens grâce à un paramètre bitOrder. Afin de ne pas brouiller l'ordre des bits dans le chaînage, la transmission des bits, dans la classe IC74hc595M, se fera toujours avec bitOrder=MSBFIRST pour que la broche Q0 corresponde au bit de poids le plus faible.
/* 1 */
#ifndef IC74hc595M_h
#define IC74hc595M_h

/* 2 */
#include "Component.h"

/* 3 */
class IC74hc595M : Component {
/* 4 */
  uint8_t __dataPin;
  uint8_t __latchPin;
  uint8_t __clockPin;
/* 5 */
  byte __Qvalue;
/* 6 */
  IC74hc595M* __next;

/* 7 */
  void __shiftOut(uint32_t);

  public:
/* 8 */
  IC74hc595M(uint8_t, uint8_t, uint8_t, uint8_t=1);
/* 9 */
  ~IC74hc595M();
/* 10 */
  void begin();
  void loop() { }
/* 11 */
  void write(uint32_t);
  void pinWrite(uint8_t, uint8_t);
/* 12 */
  uint32_t QValue();
};
/* 1 */
#endif // IC74hc595M_h
  1. Application de la bonne pratique consistant à définir les classes d'une bibliothèque entre deux directives #ifndef et #endif en prévention de multiples définitions dans les projets composites.
  2. Comme toutes les classes relatives à des composants électroniques, la classe IC74hc595M implémente l'interface Component. Le fichier Component.h doit donc être inclus par la directive #include.
  3. La classe IC74hc595M implémente l'interface Component. Ce qui l'oblige à implémenter les méthodes membres begin() et loop().
  4. Les circuits 74HC595 mobilisent trois pins de l'Arduino (et trois seulement). Les numéros de celles-ci doivent être archivées dans des attributs privés.
    • __dataPin est le numéro de la pin de l'Arduino utilisée pour le flux de données. Cette pin doit être reliée à la pin DS (n°14) du 74HC595. 
    • __latchPin est le numéro de la pin de l'Arduino  utilisée pour valider le flux de données. Cette pin doit être reliée à la pin ST_CP (n°12) du 74HC595.
    • __clockPin est le numéro de la pin de l'Arduino  utilisée pour valider le flux de données. Cette pin doit être reliée à la pin SH_CP (n°11) du 74HC595.
  5. L'attribut privé __Qvalue mémorise l'état courant des pins Q0 à Q7 du 74HC595. Un bit à 1 signifie que la tension sur la pin concernée est à +5V. Chaque instance chaînée de la classe IC74c595M possède sont propre attribut __Qvalue sur 8 bits.
  6. Les circuit étant chaînés, cela programme en C++ par une chaîne, chaque instance de IC74hc595M pointant sur la suivante grâce à l'attribut __next. Celui-est déclaré comme un pointeur.
  7. La fonction shiftOut() ne transfert que 8 bits à la fois. Pour pouvoir transmettre plus de 8 bits, il faut effectuer plusieurs appel successifs à la fonction shiftOut() entre deux bascules de la broche Latch. La méthode privée __shiftOut() permet de transférer  32 bits maximum, ce qui permet de gérer quatre 74HC595 en chaîne. 
  8. Le constructeur de la classe IC74hc595M  reçoit quatre paramètres. Les trois premiers  correspondant respectivement aux pins Data, Latch et Clock mobilisées sur l'Arduino. Le dernier paramètre est le nombre de 74HC595 qui doivent être gérés. Par défaut, l'instance ne gère qu'un seul convertisseur.
  9. Comme la classe IC74hc595M  instancie dynamiquement les instances relatives aux 74HC595 chaînés avec l'opérateur new, il lui faut implémenter un destructeur pour détruire explicitement les instances créées par l'opérateur delete.
  10. La classe IC74hc595M implémentant l'interface Component, celle-ci doit implémenter les méthodes begin() et loop(). La méthode begin() doit être invoquée soi dans la méthode setup() de l'Arduino, soit dans la méthode begin() du composite utilisant le circuit. La méthode loop() ne fait rien. Elle est implémentée vide dans la définition de la classe IC74hc595M.
  11. La classe IC74hc595M implémente deux méthodes publiques pour modifier l'état des pins Q0 à Q7 du circuit :
    • write() permet de sérialiser 32 bits. En fonction du nombre de convertisseur pris en charge, ce sont les bits de poids le plus faible qui sont transmis. bitOrder vaut toujours MSBFIRST. Ce qui signifie que, pour chaque 74HC595, la pin Q0 correspond au bit de pois le plus faible de value
    • pinWrite() fonctionne comme la fonction digitalWrite() de l'Arduino. Elle permet de positionner la pin Qx du 74HC595 dont le numéro est passé en paramètre.
  12. La classe expose l'état courant des pins Q0 à Q7 du 74HC595, enregistré dans les attributs __Qvalue, des instances chaînées, par la méthode QValue(). Une opération binaire est effectuée à partir des attributs __Qvalue de toutes les instances chaînées pour fournir une valeur sur 32 bits.

Implémentation de la classe IC74hc595M

Constructeur de la classe IC74hc595M

Le circuit 74HC595 mobilise trois pins de l'Arduino. Les numéros de ces trois pins sont passés en paramètre du constructeur.
  • dataPin est le numéro de la pin de l'Arduino utilisée pour le flux de données. Cette pin doit être reliée à la pin DS (n°14) du 74HC595. 
  • latchPin est le numéro de la pin de l'Arduino  utilisée pour valider le flux de données. Cette pin doit être reliée à la pin ST_CP (n°12) du 74HC595.
  • clockPin est le numéro de la pin de l'Arduino  utilisée pour valider le flux de données. Cette pin doit être reliée à la pin SH_CP (n°11) du 74HC595.
Par ailleurs  le classe IC74hc595M permettant le chaînage des 74HC595, il faut indiquer combien de circuit 74HC595sont à prendre en charge. coutIC peut prendre une valeur de 1 à 4. N'importe quelle valeur sur 8bits est acceptée. Cependant il y aura toujours au moins un 74HC595 et au plus quatre de supportés.
IC74hc595M::IC74hc595M(uint8_t dataPin, uint8_t latchPin, uint8_t clockPin, uint8_t countIC) {
  this->__dataPin = dataPin;
  this->__latchPin = latchPin;
  this->__clockPin = clockPin;
  this->__Qvalue = 0;
  this->__next = NULL;
  if (countIC > 1) {
    this->__next = new IC74hc595M(dataPin, latchPin, clockPin, countIC - 1);
  }
}
  • Le trois numéros de pins sont mémorisées dans les trois attributs privés __dataPin, __latchPin, __clockPin.
  • La valeur de l'attribut __Qvalue est initialisée à 0. Ce qui signifie que toutes les pins Q0 à Q7 du 74HC595 sont positionnées à LOW. 
  • Par défaut, il n'y a aucun autre 74HC595 chaîné. La valeur du pointeur __next est NULL
  • Si countIC est supérieur à 1, une instance de la classe IC74hc595M est créée par l'opérateur new et affectée au pointeur __next. Les numéros de pins de l'Arduino sont transmis au constructeur de cette nouvelle instance. En effet, la transmission des bits se fait par l’intermédiaire des mêmes pins de l'Arduino, quelque soit le 74HC595 sollicité. En revanche le compteur countIC est décrémenté. De cette manière, l'instanciation dynamique se poursuit en chaîne jusqu'à ce qu'une instance  reçoive une valeur de countIC à 1.

Destructeur de la classe IC74hc595M

A la construction des instances de la classe IC74hc595M d'autres instances de cette même classe peuvent être créées en chaîne par l'opérateur new en fonction du paramètre countIC passé. Comme, le C++ ne dispose pas de Garbage Collector (ramasse-miette), toutes les instances créées dynamiquement par new, doivent être explicitement supprimées par delete. Le destructeur permet de supprimer explicitement ces instances.
IC74hc595M::~IC74hc595M() {
  if (this->__next != NULL) {
    delete this->__next;
  }
}
  • Si l'attribut privé __next n'est pas NULL, il contient l'adresse de l'instance créée dynamiquement par new à la construction. 
  • L'instance créée par new à la construction et pointée par l'attribut __next est détruite par l'opérateur delete
  • Cela va provoquer l'exécution du destructeur de celle-ci qui a son tour va supprimer l'instance suivante, et ainsi de suite, jusqu'à ce que __next soit NULL.

Méthode begin()

La méthode begin() est héritée de l'interface Component qui définit un polymorphisme sur tous les composants C++ en les obligeant à implémenter les méthodes begin() et loop(). Cette méthode doit être invoquée soit dans la méthode setup() du sketch Arduino, soit dans la méthode begin() des composites qui utilisent ce composant.
void IC74hc595M::begin() {
  pinMode(this->__latchPin, OUTPUT);
  pinMode(this->__dataPin, OUTPUT);
  pinMode(this->__clockPin, OUTPUT);
  digitalWrite(this->__latchPin, HIGH);
}
  • Les trois pins mobilisées sur l'Arduino par le circuit 74HC595 sont configurées en sortie (OUTPUT) en utilisant la fonction pinMode().
  • La pin Latch est pré-positionnée à HIGH. En effet, elle doit être positionnée à LOW pendant qu'une transmission d'un flux de bits est en cours.

Méthode publique write()

La méthode write() positionne les pins de sortie Q0 à Q7 de la chaîne de 74HC595 en fonction du paramètre value. Dans la classe IC74hc595M, value est codé sur 32 bits. L'ordre des bits est effectué de la façon suivante :

  • Les octets sont transmis par train de 8 bits.
  • Pour chaque octet transmis respectivement à l'un des 74HC595, Q0 correspond au bit de poids le plus faible. 
  • Les pins Q0 à Q7 du premier 74HC595 de la chaîne correspond aux bits de poids le plus faible (les plus à droite) de value. Les autres bits sont transmis via la pin Q7' aux autres 74HC595 de la chaîne.
void IC74hc595M::write(uint32_t value) {
  digitalWrite(this->__latchPin, LOW);
  digitalWrite(this->__clockPin, LOW);
  this->__shiftOut(value);
  digitalWrite(this->__latchPin, HIGH);
}
  • La première étape consiste à positionner la pin Latch à 0 pour indiquer au 74HC595 qu'un flux de bits va lui être transmis. 
  • Avant la transmission, on s'assure que la pin Clock est à la position 0 pour éviter une interprétation erronée de celle-ci par des circuits électronique synchronisant les changements d'état sur le flanc montant de l'horloge.
  • La fonction shiftOut() ne permettant de ne transférer qu'un seul octet, on utilise la méthode privée __shiftOut(), décrite ci-dessous, pour transmettre un paramètre de 32 bits. Si la chaîne comprend quattre 74HC595, la méthode __shiftOut() effectuera quatre appels successifs à la fonction shiftOut() pour transmettre, huit bits par huit bits, les 32 bits de value avant d'effectuer la bascule de la pin Latch.
  • Une fois la transmission terminée, la pin Latch est repositionnée à 1.

Méthode privée __shiftOut()

La fonction de base shiftOut() de l'Arduino ne permet  de transférer qu'un paramètre de type byte (8 bits). Aussi la classe IC74hc595M implémente une méthode privée __shiftOut() dont le paramètre value fait 32 bits. Les pins Q0 à Q7 du premier 74HC595 de la chaîne correspond aux bits de poids le plus faible (les plus à droite) de value. Les autres bits sont transmis via la pin Q7' aux autres 74HC595 de la chaîne.
void IC74hc595M::__shiftOut(uint32_t value) {
  if (this->__next != NULL) {
    this->__next->__shiftOut(value >> 8);
  }
  this->__Qvalue = value & 0x000000FF;
  shiftOut(this->__dataPin, this->__clockPin, MSBFIRST, this->__Qvalue);
}
  • Pour respecter l'ordre des bits transmis pour que Q0 soit toujours le bit de poids le plus faible de l'octet le plus faible (le plus à droite), il faut commencer par la transmission sur le 74HC595 chaîné, s'il existe (__next != NULL).  Dans ce cas, on ne passe dans la chaîne que les bits restants, d'où le décalage de 8 bits vers la droite.
  • Chaque instance de IC74hc595M archive l'état des bits qu'elle transmet via le 74HC595 qu'elle pilote, dans l'attribut de 8 bits __Qvalue. __Qvalue ne contient que les 8 bits de poids faible (les plus à droite) de value. Les autres bits sont déjà passé à l'instance pointée par __next.
  • Les 8 bits ainsi réservé sont transmis au 74HC595 piloté par cette instance par l'invocation de la fonction shiftOut(). Les autres invocations de shiftOut() de 8 bits sont effectuées par les instances pointées par __next jusqu'à ce que __next soit NULL.

Méthode publique pinWrite()

La méthode pinWrite() de la classe IC74hc595, pour les pins de sortie la chaîne de circuits 74HC595, a le même rôle que la fonction digitalWrite() sur les pins de l'Arduino. Elle positionne la pin Qx des 74HC595. Sauf qu'ici le numéro x dans le paramètre pin peut aller de 0 à 31.
Comme pour digitalWrite(), le paramètre value peut avoir la valeur HIGH (+5V) ou LOW (0V).
Cette méthode permet de disposer de 32 sorties supplémentaires en ne mobilisant que trois pins sur l'Arduino.
void IC74hc595M::pinWrite(uint8_t pin, uint8_t value) {
  uint32_t Qvalue = this->QValue();
  uint32_t bitRange = 1 << pin;
  switch (value) {
    case HIGH:
      Qvalue |= bitRange;
      break;
    case LOW:
      Qvalue &= ~bitRange;
      break;
  }
  this->write(Qvalue);
}
  • L'état global des pins Qx de la chaîne  des 74HC595, renvoyé par la méthode QValue() est sauvegardé dans une variable locale Qvalue sur 32 bits.
  • Le bit a modifié est positionné dans la variable locale bitRange sur 32 bits par un décalage de pin bits vers la gauche.
  • Si value vaut HIGH, un OU binaire est effectué sur la variable locale Qvalue pour ne modifier que le bit de rang pin en le passant à 1.
  • Si value vaut LOW, un ET binaire est effectué sur la variable locale Qvalue pour ne modifier que le bit de rang pin en le passant à 0.
  • Une fois modifié, la variable locale Qvalue est écrite sur les 74HC595 en invoquant la méthode write()

Méthode publique QValue()

La méthode QValue() renvoie l'état global de toutes les pins Qx de la chaîne de 74HC595 sur 32 bits. Quel que soit le nombre de 74HV595, la valeur renvoyée es toujours sur 32 bits, les bits de poids le plus fort (le plus à gauche) non utilisés étant positionnés à 0.
L'ordre des bits renvoyé est tel que le bit de poids le plus faible (le plus à droite) de la valeur renvoyée soit relative à l'état de la pin Q0 du 74HC595. L'état de la pin Q0 du 74HC595 se trouve en position 8, et ainsi de suite. 
uint32_t IC74hc595M::QValue() {
  uint32_t nextQValue = 0;
  if (this->__next != NULL) nextQValue = this->__next->QValue();
  return (nextQValue << 8) + this->__Qvalue;
}
  • Une variable local nextQValue est initialisée à 0 au cas ou le pointeur __next pointant sur l'instance suivante soit NULL
  • Si le pointeur __next n'est pas NULL, il pointe sur l'instance suivante de la classe IC74hc595M qui pilote le 74HV595 suivant. Il est donc possible de récupérer l'état des pin Qx des autre 74HC595 chaînés en invoquant récursivement sur l'instance pointée la méthode QValue().
  • La valeur récupérée est décalée de 8 bits vers la gauche pour pouvoir y ajouter la valeur de l'attribut privé __Qvalue mémorisant l'état courant des pins Q0 à Q7 du 74HC595 piloté par l'instance courante. 
  • Cette façon de procéder permet de reconstituer récursivement l'état des pins de chaque 74HC595 sur 32  bits par des décalages successifs de 8 bits vers la gauche. Le résultat de 32 bits ainsi obtenu est fourni en résultat de la fonction.

Utilisation de la classe IC74hc595M dans un sketch Arduino

L'utilisation de la classe IC74hc595M dans un sketch Arduino s'effectue exactement de la même façon que pour la classe IC74hc595. Elle peut même être utilisée à la place de celle-ci lorsqu'un seul 74HC595 est utilisé. Voici, ci-dessous, en exemple, le sketch permettant de piloter 16 LEDs de façon indépendantes en utilisant deux circuits 74HC595 comme présenté dans le projet décrit en introduction de cet article, tout en ne mobilisant que 3 pins sur l'Arduino.
Dans ce projet, les LEDs s'allume les unes après les autres. La LED allumée change toutes 300 ms.
/* 1 */
#define IC_DATA 2
#define IC_LATCH 3
#define IC_CLOCK 4

/* 2 */
#include "IC74hc595M.h"

/* 3 */
IC74hc595M ic(IC_DATA, IC_LATCH, IC_CLOCK, 2);

/* 4 */
uint32_t millis0;
uint8_t pinLed;

/* 5 */
void setup() {
  ic.begin();
  millis0 = 0;
  pinLed = 0;
}

/* 6 */
void loop() {
  uint32_t now = millis();
  if ((now - millis0) > 300) {
    ic.pinWrite(pinLed, LOW);
    pinLed++;
    if (pinLed > 15) pinLed = 0;
    ic.pinWrite(pinLed, HIGH);
    millis0 = now;
  }
}
  1. Il est d'une bonne pratique que de déclarer en constantes les numéros de pins utilisées tant sur l'Arduino que sur le circuit 74HC595 :
    • IC_DATA est le numéro de pin de l'Arduino sur laquelle est connectée la pin Data du 74HC595.
    • IC_LATCH est le numéro de pin de l'Arduino sur laquelle est connectée la pin Latch du 74HC595.
    • IC_CLOCK est le numéro de pin de l'Arduino sur laquelle est connectée la pin Clock du 74HC595.
  2. Le sketch utilisant la classe IC74hc595M, la définition de celle-ci doit être incluse par une directive #include
  3. La classe IC74hc595M est instanciée par la variable ic à laquelle on passe les numéros de pins de l'Arduino utilisées dans l'ordre exigé par le constructeur. Le dernier paramètre indique que deux 74HC595 sont utilisés dans le projet. A remarquer que si ce dernier paramètre était omis, la valeur 1 serait prise par défaut, considérant que le projet n'utilise qu'un seul 74HC595. Dans ce cas la classe IC74hc595M fonctionnerait exactement comme la classe IC74hc595 décrite dans le précédant aticle.
  4. Deux variables de travail sont utilisées pour le sketch :
    • millis0 contient l'origine du temps sur 32 bits.
    • pinLed contient le numéro de la LED qui sera allumée. Initialisée à 0, elle sera incrémentée à chaque itération de la fonction loop()
  5. L'instance ic est initialisée dans la méthode setup() en invoquant sur celle-ci la méthode bégin(). Les deux variables de travail sont initialisées à 0 
  6. Dans la méthode loop(), à chaque itération, la  LED de rang pinLED est allumée et la précédente éteinte. 
    • On utilise la fonction millis() pour ne provoquer cet événement que toutes les 300 ms. 
    • La LED de rang pinLED est éteinte en invoquant la  méthode pinWrite() sur l'instance ic.
    • pinLed est incrémentée. Si la valeur dépasse 15, la variable pinLed est remise à 0 (le projet ne pilote de 16 LED dont le rang est de 0 à 15).
    • La nouvelle LED de rang pinLED est allumée en invoquant la  méthode pinWrite() sur l'instance ic.
  7. A remarquer que, contrairement à la plupart des composants dérivés de la classe Component, il n'est pas nécessaire d'invoquer la méthode loop() sur l'instance ic. Car, comme vu dans la définition de la classe IC74hc595M, celle-ci ne fait rien. 

Conclusion

La classe IC74hc595M, présentée dans cet article, permet d'étendre les capacités de base d'un Arduino, en offrant jusqu'à 32 sorties parallèles supplémentaires en fonction du nombre de circuit 74HC595 utilisés. Elle renforce la classe IC74hc595 présentée dans l'article précédent. Elle peut même éventuellement la remplacer, dans le cas où  un seul 74HC595 serait utilisé.
Dans l'exemple de l'utilisation de l'afficheur LED d'un chiffre à 7 segments, la classe IC74hc595 avait été utilisée pour piloter ce composant.
Dans un prochain article, l'usage d'un afficheur à quatre chiffres sera présenté. Celui-ci peut utiliser un 74HC595, et la classe IC74hc595 qui le pilote, de la même façon. Cependant, quatre pins supplémentaire sont nécessaires sur l'Arduino, pour discriminer le chiffre à afficher. La classe  IC74hc595m permettra de prendre en charge ces quatre pins nécessaire sur un deuxième 74HC595 tout en ne mobilisant que trois pins sur l'Arduino pour prendre en charge tout l'afficheur.

Commentaires

Posts les plus consultés de ce blog

Afficheur à LED 7 segments (classe SegmentLedDisplay)

Piloter un clavier matriciel sur le bus I2C avec un PCF8574

Utiliser Visual Studio Code pour développer sur Arduino