Circuit intégré PCF8574 (classe ICPCF8574)

L'Arduino ne dispose que d'un nombre de pins de sortie limité. Dans un article précédent, un convertisseur Série/Parallèle 74HC595 avait été utilisé pour pallier à ce problème. Et cet technique avait été appliquée au pilotage d'un module LCD 1602.
Si le 74HC595 ne mobilise que 3 pins de l'Arduino, il existe une solution encore meilleure lorsque les économies de pins sont à l'ordre du jour, c'est de passer par le bus I2C. En effet, le bus I2C présente l'avantage de pouvoir être partagé par plusieurs périphériques compatible avec ce protocole.
Cet article présente le circuit PCF8574 qui a la même utilité que le 74HC595, à savoir qu'il fournit 8 sorties parallèle pilotée par le bus I2C. Ce qui permet de laisser libre les trois pins habituellement mobilisées par son concurrent. De plus, contrairement au 74HC595 dont la conversion ne fonctionne que dans le sens Série/Parallèle, le PCF8574 permet l'écriture et la lecture de l'état des broches parallèles.

Le circuit intégré PCF8574

Comme le 74HC595, le circuit intégré PCF8574 transforme un train de bits transmis en série en activant 8 broches en parallèle. Mais au lieu d'utiliser trois pins en entrées qui sont connectées à trois pins de sortie de l'Arduino, il utilise le bus I2C qui ne mobilise que les deux pins analogiques SDA (pin A4) et SCL (pin A5) de l'Arduino. Encore que celles-ci sont partagées par tous les  périphérique compatibles I2C. Autant dire que l'utilisation de ce circuit ne mobilise aucune pin de l'Arduino.
Il faut signaler ici qu'il existe plusieurs conditionnement de ce circuit. Et, pour chaque conditionnement, l'adressage n'est pas le même. On distingue notamment les conditionnements à base de PCF8574 pour lesquels l'adresse de base est 0x20 et ceux à base de PCF8574A pour lesquels l'adresse de base est 0x38.

Brochage du PCF8574

Le circuit intégré PCF8574 possède 16 broches dont la signification est la suivante :
NomDescription
1 à 3A0 à A2L'adresse du PCF8574 est 0x20 (0x38 pour le PCF8574A).
Cette adresse est incrémentée par la valeur de ces trois broches.
4 à 7P0 à P3Registres d'entrée/sortie parallèle 0 à 3.
8VSSAlimentation électrique du circuit.
Doit être reliée à GND de l'Arduino.
9 à 12P4 à P7Registres d'entrée/sortie parallèle 4 à 7.
13INTInterruption externe. 
14SCLLigne Serial Clock du bus I2C.
15SDALigne Serial Data du bus I2C.
16VDDAlimentation électrique du circuit.
Doit être reliée à +5V sur l'Arduino.
Le brochage indiqué ici est le brochage du circuit de marque Philips. En fonction du fabriquant, j'ai pu constater d'autres brochages possibles en fonction du conditionnement (boitier plastique ou céramique, circuit de surface ou DIP, etc.). Il y a même une version à 20 broches. Se référer systématiquement à la documentation technique pour s'assurer du bon brochage.

Adressage du PCF8574 sur le bus I2C

Comme abordé dans l'article traitant du bus I2C, chaque périphérique I2C possède une adresse sur 7 bits. Cette adresse est arrêtée par le constructeur. Pour le PCF8591, l'adresse peut changer en fonction de la version du circuit utilisé. L'adresse effective peut être vérifiée à l'aide du script d'analyse du bus I2C présenté dans l'article dédié à ce protocole.. La documentation du circuit de marque Philips (page 8) indique deux adresses possibles.
En fonction du câblage des trois broches A0, A1, A2 voici les adresses I2C mobilisées.
A2A1A0PCF8574PCF8574A
0000x20 (par défaut)0x38 (par défaut)
0010x210x39
0100x220x3A
0110x230x3B
1000x240x3C
1010x250x3D
1100x260x3E
1110x270x3F
Il est donc possible d'adresser ainsi, sur une même projet, huit PCF8574 (voire 16 en mixant les deux versions) connectés sur le même bus. Ce qui potentiellement permet d'envisager 128 sorties parallèles. 

Définition de la classe ICPCF8574

Afin de faciliter l'utilisation du circuit PCF8574 dans les projets, voici la classe ICPCF8574. La définition de cette classe est contenue dans le fichier ICPCF8574.h qui doit être inclus par le directive #include dans tous les projets dans lesquels le circuit est utilisé.
/* 1 */
#ifndef ICPCF8574_h
#define ICPCF8574_h

/* 2 */
#include "Component.h"
#include <Wire.h>

/* 3 */
class ICPCF8574 : public Component {
/* 4 */
  uint8_t __i2caddress;
/* 5 */
  byte __Pvalue;
 
  public:
/* 6 */
  static const uint8_t I2C_ADDRESS = 0x20;

/* 7 */
  ICPCF8574(uint8_t=0);
/* 8 */
  void begin();
  void loop() { }
/* 9 */
  void write(byte);
  void pinWrite(uint8_t, uint8_t);
  byte read();
  byte pinRead(uint8_t);
/* 10 */
  uint8_t I2CAddress() { return this->__i2caddress; }
  byte PValue() { return __Pvalue; }
};

/* 1 */
#endif // ICPCF8574_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. inclusion des définitions des classes utilisées par la classe ICPCF8574 par des directives #include :
    • Comme toutes les classes relatives à des composants électroniques, la classe ICPCF8591 implémente l'interface Component dont la définition se trouve dans le fichier Component.h.
    • La classe ICPCF8591 utilise la bibliothèque standard Wire dont la définition se trouve dans le fichier Wire.h.
  3. La classe ICPCF8574 implémente l'interface Component. Ce qui l'oblige à implémenter les méthodes membres begin() et loop().
  4. Le circuit PCF8574 ne mobilise aucune pin de l'Arduino. En revanche, il se positionne sur le bus I2C à une adresse particulière. Cette adresse est mémorisée dans l'attribut privé __i2caddress.
  5. L'attribut privé __Pvalue mémorise l'état courant des pins P0 à P7 du PCF8574. Un bit à 1 signifie que la tension sur la pin concernée est à +5V.
  6. L'adresse de base I2C du PCF8574 est mémorisée dans la constante de classe publique I2C_ADDRESS. Cette adresse peut être incrémentée, en fonction du câblage, par le constructeur de la classe pour calculer l'adresse effective du composant sur le bus I2C mémorisée dans l'attribut __i2caddress. C'est ce dernier qui doit être utilisé lors des opérations de lecture ou d'écriture.
  7. Le constructeur de la classe ICPCF8574  reçoit en paramètre le décalage par rapport à l'adresse de base. Ce décalage dépend du câblage des broches A0 à A2 du circuit. Ce paramètre peut également être utilisé pour les PCF8574A dont l'adresse de base n'est pas 0x20, mais 0x38.
  8. La classe ICPCF8574 implémentant l'interface Component, celle-ci doit implémenter les méthodes begin() et loop(). La méthode begin() doit être invoquée soit 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 ICPCF8574.
  9. La classe ICPCF8574 implémente deux méthodes publiques pour modifier l'état des pins P0 à P7 du circuit et deux autre pour en récupérer l'état. Ces quatre méthodes modifient l'attribut privé __Pvalue qui conserve l'état des broches P0 à P7 depuis la dernière opération.
    • write() permet de modifier l'état de toutes les pins P0 à P7 à la fois par le paramètre value.
    • pinWrite() fonctionne comme la fonction digitalWrite() de l'Arduino. Elle permet de positionner la pin Px du PCF8574 dont le numéro est passé en paramètre.
    • read() permet de lire un octet sur le bus I2C. Cet octet correspond à l'état physique (+5V ou 0V) des broches P0 à P7 du PCF8574.
    • pinRead() fonctionne comme la fonction digitalRead() de l'Arduino. Elle permet de récupérer l'état physique de la broche Px, ou x est le paramètre passé à la méthode.
  10. La classe expose ses attributs interne en lecture seule.
    • L'adresse I2C effective du PCF8574, calculée dans l'attribut __i2caddress est exposée par la méthode I2CAddress()
    • L'état courant des pins P0 à P7 du PCF8574, enregistré dans l'attribut __Pvalue,  est exposé par la méthode QValue().

Implémentation de la classe ICPCF8574

Constructeur de la classe ICPCF8574

L'adresse effective I2C du circuit PCF8574, fixée par défaut à 0x20, peut être décalée en fonction du câblage des broches A0 à A2.  Ce décalage, de 0 à 7, doit être fourni en paramètre du constructeur. Par ailleurs, pour le PCF8574A, l'adresse de base étant de 0x38, ce paramètre peut être  également utilisé pour calculer l'adresse effective de ce circuit. La valeur à passer varie alors de 0x18 à 0x1F.
ICPCF8574::ICPCF8574(uint8_t addrOffset)
{
  this->__i2caddress = I2C_ADDRESS + addrOffset;
  this->__Pvalue = 0;
}
  • L'adresse effective du composant __i2caddress est calculée en ajoutant à l'adresse de base I2C_ADDRESS (0x20), la valeur du paramètre addrOffset.
  • La valeur de l'attribut __Pvalue est initialisée à 0. Ce qui signifie que toutes les pins P0 à P7 du PCF8574 sont positionnées à LOW

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 ICPCF8574::begin() {
  Wire.begin();
}
  • Dans la cas de la classe ICPCF8574, celle-ci utilisant la bibliothèque standard Wire, il suffit d'invoquer la méthode begin() de celle-ci

Méthode write()

La méthode write() positionne les pins de sortie P0 à P7 du PCF8574 en fonction du paramètre value. Cette méthode modifie l'attribut privé __Pvalue en conséquence. 
void ICPCF8574::write(byte value) {
  Wire.beginTransmission(this->__i2caddress);
  Wire.write(value);
  if (Wire.endTransmission() == 0) {
    this->__Pvalue = value;
  }
}
  • La méthode commence par une préemption du bus I2C pour initialiser un flux de données avec le périphérique (donc le PCF8574) dont l'adresse I2C est celle contenue dans l'attribut __i2cadddress en invoquant la méthode beginTransmission() de la bibliothèque Wire
  • Puis la valeur value passée en paramètre est transmise au PCF8574 en invoquant la méthode write() de la bibliothèque Wire.
  • La transmission es effective après l'invocation de la méthode endTansmission() qui libère le bus I2C. Le résultat est en principe 0 si l'écriture s'est bien passée. Sinon un code erreur est renvoyé en résultat.
  • Si l'écriture a bien eu lieu, l'état des broches P0 à P7 est mis à jour dans l'attribut __Pvalue.

Méthode pinWrite()

La méthode pinWrite() de la classe ICPCF8574, pour les pins de sortie du circuit PCF8574, a le même rôle que la fonction digitalWrite() sur les pins de l'Arduino. Elle positionne la pin Px du PCF8574 dont le numéro x (de 0 à 7) est passé par le paramètre pin. Comme pour digitalWrite(), le paramètre value peut avoir la valeur HIGH (+5V) ou LOW (0V).
Cette méthode permet de disposer de 8 sorties supplémentaires. Elle est un peu plus lente à l'exécution que digitalWrite() car un train de huit bits est envoyé sur le bus I2C à chaque écriture.
void ICPCF8574::pinWrite(uint8_t pin, byte value) {
  byte PValue = this->__Pvalue;
  byte bitRange = 1 << pin;
  switch (value) {
    case HIGH:
      PValue |= bitRange;
      break;
    case LOW:
      PValue &= ~bitRange;
      break;
  }
  this->write(PValue);
}
  • La valeur de l'attribut __Pvalue est recopiée dans une variable locale PValue.
  • Si value vaut HIGH, un OU binaire est effectué sur PValue pour ne modifier que le bit de rang pin en le passant à 1.
  • Si value vaut LOW, un ET binaire est effectué sur PValue pour ne modifier que le bit de rang pin en le passant à 0.
  • Une fois modifiée, PValue est écrit sur le PCF8574 en invoquant la méthode write(). A chaque invocation de pinWrite(), les 8 bits de Pvalue sont transmis.

Méthode read()

La méthode read() récupère l'état des broches P0 à P7 du PCF8574. La valeur correspondante est lue sur le bus I2C et fournie en résultat après avoir été affectée à l'attribut __Pvalue.. 
byte ICPCF8574::read(){
  Wire.requestFrom(int(this->__i2caddress), 1);
  if (Wire.available()) {
    this->__Pvalue = Wire.read();
  }
  return this->__Pvalue;
}
  • La méthode commence par une préemption du bus I2C pour initialiser un flux de données en lecture sur le bus I2C par l'invocation de la méthode requestFrom() de la bibliothèque Wire. Le premier paramètre est l'adresse du périphérique (contenue dans l'attribut __i2caddress). Le second paramètre est le nombre d'octets attendus (ici un seul).
  • Les octets attendus sont lus un par un par l'invocation de la méthode read() de la bibliothèque Wire
  • La méthode available() de la bibliothèque Wire retourne true tant qu'il reste un octets à lire. Ici, un seul octet est attendu. Si la lecture a bien eu lieu, l'attribut __Pvalue est modifié et sa nouvelle valeur est fournie en résultat.

Méthode pinRead()

La méthode pinRead() de la classe ICPCF8574, pour les pins de sortie du circuit PCF8574, a le même rôle que la fonction digitalRead() sur les pins de l'Arduino. Elle récupère l'état de la pin Px du PCF8574 dont le numéro x (de 0 à 7) est passé par le paramètre pin. Comme pour digitalWrite(), le paramètre value peut avoir la valeur HIGH (+5V) ou LOW (0V).
Cette méthode permet de disposer de 8 entrée supplémentaires. Elle est un peu plus lente  à l'exécution que digitalWrite() car un train de huit bits est reçu du bus I2C à chaque lecture.
byte ICPCF8574::pinRead(uint8_t pin) {
  byte filter = 0x01 << pin;
  byte PValue = read();
  return (PValue & filter) ? HIGH : LOW;
}
  • Un filtre est calculé par rapport au numéro de pin passé en paramètre pour mettre à 1 le bit bx de la variable locale filter, ou x est la valeur pin passé en paramètre . 
  • L'état des pin P0 à P7 du PCF8574 est lu par l'invocation de la read() ci-dessus et affecté à la variable locale PValue.
  • Un ET logique avec filter est effectué sur PValue pour ne conserver que l'état du bit recherché.
  • L'état de ce bit HIGH ou LOW est renvoyé en résultat de la méthode.

Conclusion

La classe ICPCF8574, présentée dans cet article, permet d'étendre les capacités de base d'un Arduino, en offrant 8 entrées/sorties numériques supplémentaires sans mobiliser de pin sur celui-ci, puisque seul le bus I2C est utilisé. Il s'agit donc d'une véritable extension bien meilleure que celle apportée par le circuit 74HC595.
En association avec le PCF8591 (autre composant exploitant le bus I2C) qui permet une extension des entrées/sorties analogiques, le PCF8574 constitue LA solution à adopter pour tous les projets gourmands en I/O comme le sont les projets de robotique pour lesquels de nombreux capteurs et acteurs doivent être pilotés.
Cet article ne présente par d'exemple d'utilisation de la classe ICPCF8574. En effet, celle-ci implique nécessairement un composant actif qui, la plupart du temps, exige à lui tout seul un article dédié. Cependant deux exemples seront prochainement présentés :

  • Le pilotage du module LCD 1602 (déjà utilisé avec le convertisseur 74HC595) par le bus I2C comme exemple de l'utilisation de la classe ICPCF8574 en écriture sur le bus I2C..
  • Le pilotage d'un clavier 16  touches comme exemple de la mise en oeuvre de la classe ICPCF8574 en utilisation mixte lecture/écriture sur le bus I2C. Ce clavier, très gourmand en pins lorsqu'il est connecté en direct sur l'Arduino, libère les 8 pins qu'il mobilise habituellement en passant par le bus I2C,  

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