Piloter un écran LCD sur le bus I2C de l'Arduino avec un PCF8574

Dans le projet Horloge, une version du dispositif d'affichage utilisait un module LCD 1602. Les modules d'affichage LCD ont pratiquement tous le même brochage :
NomDescription
1
VSS
Alimentation électrique du module.
Doit être reliée à GND de l'Arduino
2
VDD
Alimentation électrique du module.
Doit être reliée à +5V sur l'Arduino.
3
V0
Réglage du contraste
4
RS
Sélection du registre
Le module possède deux registres : un pour les données, un pour les commandes.
5
R/W
Lecture ou écriture.
6
E
Entrée de validation.
Cette broche a le même rôle que la touche [Enter] du clavier lorsqu'elle passe de +5V à 0V.
7 à 14
D0 à D7Bus de données du module.
15
A
Anode du rétro-éclairage.
Doit être reliée à +5V sur l'Arduino. 
16
K
Cathode du rétro-éclairage.
Doit être relié à GND de l'Arduino.
Dans sa version complète, le pilotage d'un écran LCD mobilise les 11 pins (surlignées en jaune dans le tableau ci-dessus) sur un contrôleur Arduino ou Raspberry. Heureusement, il est possible de réduire ce nombre à six, d'une part en bloquant l'écran LCD en écriture (pin R/W à 0) et en n'utilisant que les pin D4 à D7 pour transmettre les données par tranche de quatre bits, comme cela a été mis en oeuvre pour la classe LCDClockDisplay du projet Horloge. Ce fonctionnement est pris en charge en standard par le driver LiquidCrystal de l'Arduino.
Cependant, six pins mobilisées constituent déjà un nombre important sur un micro-contrôleur comme l'Arduino. Aussi, est-il intéressant de trouver des solutions pour réduire ce nombre.
Dans un article précédent, une solution qui ne mobilise que trois pins sur l'Arduino, basée sur un convertisseur Série/Parallèle 74HC595, avait été présentée pour résoudre ce problème. Mais il existe une autre solution, bien meilleure, qui permet de libérer ces trois pins en passant par le bus I2C de l'Arduino. Cette solution s'appuie sur un circuit intégré PCF8574 qui expose 8 entrées/sorties numériques supplémentaires commandées par le bus I2C.
Le présent article aborde la mise en oeuvre de la technologie I2C pour piloter un écran LCD à l'aide du circuit intégré PCF8574.

Câblage d'un module LCD 1602 couplé avec un PCF8574

Schéma électronique

Le principe cette nouvelle solution est le même que pour le convertisseur Série/Parallèle 74HC595, mais cette fois c'est un PCF8574 qui est inséré entre l'Arduino et le module LCD. Coté Arduino, la connexion se fait sur le bus I2C. Coté écran LCD, celui -ci est relié au PCF8574 par les 8 pins P0 à P7.
  • Les deux pins du bus I2C de l'Arduino SDA (A4) et SCL (A5 ) sont connectées aux broches SDA et SCL du PCF8574.  Deux résistances de de 10 KΩ, R3 et R4, assurent le pull-up des deux lignes SDA et SCL
  • Le module LCD est piloté par les broches P0P1 et P2 du PCF8574 qui sont connectées respectivement aux broches RS, RW et E du module LCD.
  • Les broches P4 à P7 du PCF8574 sont respectivement connectées aux broches D4 à D7 du module LCD. 
  • Les broche D0 à D3 sont laissée libre. Comme pour le 74HC595, le driver du PCF8574 gère le module LCD en mode 4 bits. 
  • Un potentiomètre R1 de 10 kΩ relié à la broche V0 du module LCD pour régler le contraste sur celui-ci.
  • L'anode A des LEDs du rétroéclairage est connectée à la broche P3 du PCF8574. Cela permet de gérer l'activation ou la désactivation du rétroéclairage par programmation pour faire des économies d'énergie.
  • Une résistance R2 de 220 Ω protège la cathode K des LEDs du rétroéclairage. 
Comme on peut le constater sur ce schéma, toutes les pins numériques de l'Arduino sont libres.

Montage sur la platine d'expérimentation

Ce câblage peut paraître complexe. Mais la solution de passer par le bus I2C pour piloter un module LCD 1602 est tellement pratique qu'elle a séduit les fabricants de composants qui la conditionnent avec un PCF8574 directement soudé sous le module. Un potentiomètre est également intégré pour le réglage du contraste (pilotage de la broche V0 du LCD). Ce qui réduit le câblage de l'ensemble à ceci :

Programmation d'un driver pour le module LCD 1602 connecté sur le bus I2C

Le développement du driver pour piloter le module LCD 1602  reprend l'essentiel du code programmé pour le 74HC595.
Seules, les méthodes de bas niveau sont à reconsidérer pour procéder aux écritures des commandes du LCD en les adressant sur le PCF8574.

Définition de la classe LiquidCrystal_I2C

La classe LiquidCrystal_I2C constitue le driver à utiliser pour piloter un module LCD 1602 connecté au bus I2C, en lieu et place du driver standard LiquidCrystal.
Les méthodes de la classe sont divisées en quatre catégories :
  • Les méthodes exposées dans l'interface publique de la classe LiquidCrystal_I2C. Cette interface reprend l'interface publique du driver LiquidCrystal décrite dans la documentation de référence de l'Arduino. De ce fait, la programmation métier du module LCD reste inchangée, à l'exception du constructeur à utiliser.
  • Les méthodes d'aide qui factorisent les fonctionnalités fréquemment utilisées.
  • Les méthodes de bas niveau liées notamment aux cycles d'initialisation et d'écriture à partir des éléments métriques extraits de la documentation techniques.

Le fichier LiquidCrystal_I2C.h

/* 1 */
#ifndef LiquidCrystal_I2C_h
#define LiquidCrystal_I2C_h

/* 2 */
#include <ICPCF8574.h>
#include "ILiquidCrystal.h"

/* 3 */
class LiquidCrystal_I2C : public ILiquidCrystal {
/* 4 */
  ICPCF8574 __ICPpcf8574;
/* 5 */
  uint8_t __lcd_RS;
  uint8_t __lcd_RW;
  uint8_t __lcd_E;
  uint8_t __dataPins[4];
  uint8_t __lcd_LED;
/* 6 */
  uint8_t __dataFilter;
  int __rowOffsets[4];
  uint8_t __displayFunction;
  uint8_t __displayControl;
  uint8_t __displayMode;
  uint8_t __numLines;

public:
/* 7 */
  LiquidCrystal_I2C(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
  LiquidCrystal_I2C(uint8_t);

/* 8 */ 
  void begin(uint8_t, uint8_t, uint8_t=LCD_5x8DOTS);

/* 9 */
  void clear();
  void home();
  void noDisplay();
  void display();
  void noBlink();
  void blink();
  void noCursor();
  void cursor();
  void scrollDisplayLeft();
  void scrollDisplayRight();
  void leftToRight();
  void rightToLeft();
  void autoscroll();
  void noAutoscroll();
  void createChar(uint8_t, uint8_t[]);
  void setCursor(uint8_t, uint8_t);
  virtual size_t write(uint8_t);

  private:
/* 8 */
  void begin();
/* 10 */
  void __setRowOffsets(int, int, int, int);
  void __setDataFilter();
  void __setCommandFlag(uint8_t, uint8_t&, uint8_t, bool);
  inline void __command(uint8_t);
/* 11 */
  void __send(uint8_t value, uint8_t mode);
  void __pulseEnable(void);
  void __write4bits(uint8_t value);
};

#endif // LiquidCrystal_74hc595_h
  1.  Application de la bonne pratique qui consiste à définir les classes d'une bibliothèque entre deux directives #ifndef et #endif afin de prévenir la définition multiple de la même classe dans des projets composites.
  2. Inclusion de la définition des autres composants utilisés :
    • La classe utilise la classe ICPCF8574 définie dans ICPCF8574.h pour instancier un attribut.
    • La classe implémente l'interface ILiquidChristal défini dans ILiquidCrystal.h
  3. La classe LiquidCrystal_I2C implémentant l'interface ILiquidCrystal qui elle-même dérive les interfaces Component et Print, cela oblige à implémenter, non seulement les méthodes publiques abstraites déclarées dans ILiquidCrystal mais ausi begin() et loop() héritées de Component et write() héritée de Print
  4. Le module LCD 1602 est connecté à un PCF8574. Sa programmation utilise nécessairement la classe ICPCF8574. Une instance de cette classe __ICpcf8574 est déclarée comme attribut privé.
  5. Sauvegarde des numéros de pins du PCF8574 mobilisées reçues en paramètres du constructeur.
    • __lcd_RS est le numéro de la pin du PCF8574 à laquelle est raccordée la pin RS du module LCD.
    • __lcd_RW est le numéro de la pin du PCF8574 à la quelle est raccordée la pin RW du module LCD.
    • __lcd_E est le numéro de la pin du PCF8574 à laquelle est raccordée la pin E du module LCD.
    • __dataPins est le tableau des quatre numéros des pins du PCF8574 auxquelles sont raccordées  les pins D4 à D7 du module LCD.
    • __lcd_LED est le numéro de la pin du PCF8574 à laquelle est raccordée l'anode A du module LCD.
  6. Déclarations des attributs privés de travail du driver :
    • __dataFilter un filtre binaire construit à partir du tableau __dataPins pour ne sélectionner que les pins Qx du 74HC595.
    • __rowOffsets est un tableau de quatre membres contenant les paramètres de l'écran par rapport à la mémoire. Le contenu de ce tableau dépend des paramètres passé à la méthode begin() initialisant le composant. 
    • __displayFunction contient les paramètres de fonctionnement de l'écran LCD. Chaque bit correspond à un mode de fonctionnement. Ce paramètre dépend des paramètres reçus par la begin().
    • __displayControl contient l'état de l'écran. Sa valeur est modifiée par les méthodes publiques display(), noDisplay(), cursor(), noCursor(), blink() et noBlink().
    • __displayMode contient le mode de fonctionnement de l'affichage. Sa valeur est modifiée par les méthodes publiques leftToRight(), rightToLeft(), autoscroll() et noAutoscroll().
    • __numLines contient le nombre de lignes acceptées par l'écran. Il est initialisé par la méthode begin().
  7. La classe LiquidCrystal_I2C possède deux constructeurs. Le premier reçoit deux séries de paramètre, d'abord le décalage de l'adresse I2C comme pour tous les composants connectés au bus I2C, puis les numéros des 8 pins Px du PCF8574 reliées aux broches RS, RWEA et D4 à D7 du module LCD. Le second ne reçoit que le décalage de l'adresse I2C en assumant la connexion respectives des broches RS, RW, E, A et D4 à D7 aux pins P0 à P7 du PCF8574. Ce qui correspond au câblage du module LCD 1602 sur lequel est soudé le PCF8574.
  8. La méthode loop() ne fait rien. Elle est déjà implémentée vide dans l'interface ILiquidCrystal. En revanche, l'implémentation des méthodes begin() est obligatoire du fait que la classe LiquidCrystal_I2C implémente l'interface Component. Mais pour cette classe, deux version de begin() sont implémentées :
    • La méthode begin(), sans paramètre, héritée de Component. Celle-ci initialise les autres composants hérités de Component comme l'instance __IC74hc595 en invoquant leur méthode begin() respective. Celle-ci ne doit pas être utilisée par les programme utilisant la classe. C'est pourquoi elle est reléguée dans l'interface privée.
    • La méthode begin(), avec trois paramètres, héritée de ILiquidCrystal. Celle-ci initialise l'écran LCD , mais  invoque également la précédente. Elle est déclarée dans l'interface publique et n'est la seule qui puisse être invoquée dans les programmes qui utilisent la classe. Elle reprend l’organigramme de la page 46 de la documentation du composant Hitachi HD44780U pour les commandes à passer dans le cycle d’initialisation pour un fonctionnement en mode 4 bits.
  9. La classe LiquidCrystal_I2C implémente toutes les méthodes publiques de la classe standard LiquidCrystal de l'Arduino héritées de l'interface ILiquidCystal. Ces méthodes fonctionnent exactement de la même façon que pour la classe standard.
  10. Déclaration des méthodes de travail privées. Ces méthodes factorisent le codage et permettent une lecture et une maintenant plus facile du code.
    • __setRowOffsets() permet d'initialiser le tableau de l'attribut __rowOffsets en fonction des paramètres passés au constructeur pour la gestion de la mémoire d'écran du LCD. 
    • __setDataFilter() permet d'initialiser l'attribut __dataFilter à partir du tableau __dataPins pour constituer un filtre utilisé pour l'écriture de données dans le 74HC595. 
    • __setCommandFlag() permet de positionner un flag dans l'un des registres du LCD en lançant la commande adéquate. Cette méthode est utilisée par les méthodes de l'interface publique.
    • __command() permet de lancer une commande sur l'écran LCD. Les commandes possibles sont celles du tableau  de la table 6, page 24, et de la figure 11, page 28 de la documentation du composant Hitachi HD44780U.
  11. Déclaration des méthodes de bas niveau :
    • __send() génère un cycle permet d'envoyer soit une commande (broche RS du 74HC595 à 0) soit un caractère codé sur 8 bits (broche RS du 74HC595 à 1) 
    • __pulseEnable() envoie une impulsion sur la broche E du 74HC595.
    • __write4bits() écrit un mot de 4 bits  en respectant le cycle d'écriture et le timing décrit dans la figure 25, page 58 de la documentation du composant Hitachi HD44780U.

Implémentation de la classe LiquidCrystal_I2C

Constructeurs de la classe LiquidCrystal_I2C

La classe LiquidCrystal_I2C possède deux constructeurs.  
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t offsetI2C, uint8_t lcd_RS, uint8_t lcd_RW, uint8_t lcd_E, uint8_t lcd_D4, uint8_t lcd_D5, uint8_t lcd_D6, uint8_t lcd_D7, uint8_t lcd_LED)
__ICpcf8574(offsetI2C)
{
  this->__lcd_RS = lcd_RS;
  this->__lcd_RW = lcd_RW;
  this->__lcd_E = lcd_E;
  this->__dataPins[0] = lcd_D4;
  this->__dataPins[1] = lcd_D5;
  this->__dataPins[2] = lcd_D6;
  this->__dataPins[3] = lcd_D7;
  this->__lcd_LED = lcd_LED;

  this->__setDataFilter();
  this->__displayFunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
}

LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t dataPin, uint8_t latchPin, uint8_t clockPin)
    : LiquidCrystal_I2C(offsetI2C, 0, 1, 2, 4, 5, 6, 7, 3)
{
}
  • Le premier reçoit deux séries de paramètre, d'abord le décalage de l'adresse I2C par rapport à l'adresse de base (0x20) du PCF8574, puis les numéros des huit pins Px du 74HC595 reliées aux broches RS, RWED4 à D7  et A (anode des LEDs du  rétroéclairage) du module LCD. 
    • Il commence  par invoquer le constructeur de l'attribut __ICpcf8574 en lui passant le premier paramètre.
    • Puis il initialise les autre attributs privés à partir du reste des paramètres passés.
  • Le second ne reçoit que le décalage de l'adresse I2C, assumant que les pins du PCF8574 reliées au module LCD sont P0 à P7. Ce qui correspond au câblage lorsque le module LCD 1602 est conditionné avec un PCF8574 soudé. Pour info, l'adresse I2C de ce conditionnement est en général 0x27. Le décalage de l'adresse I2C à passer en paramètre est 0x07. Le module peut être aussi soudé à un PCF8574A dont l'adresse de base est 0x38. Dans ce cas le paramètre à passé est 0x1F.

Les deux méthodes begin() de la classe LiquidCrystal_I2C 

Comme la classe LiquidCrystal_74hc595, la classe LiquidCrystal_I2C implémente deux méthodes begin().
La première (sans paramètre) est héritée de l'interface Component et est spécifique à la classe. Elle doit être surchargée dans la classe LiquidCrystal_I2C pour initialiser le composant PCF8574. Cette méthode ne doit pas être utilisée par les programme utilisant la classe. Pour empêcher les programmeurs qui utilisent la classe LiquidCrystal_I2C de l'invoquer de l'extérieur, sa définition est déclarée private.
void LiquidCrystal_I2C::begin(void) {
  this->__ICpf8574.begin();
}
La seconde méthode begin() reçoit trois paramètres : le nombre de colonnes, le nombre de lignes et un indicateur correspondant à la taille du caractère en pixel. Elle est héritée de l'interface ILiquidCrystal. C'est celle qui doit être invoquée dans les programmes utilisant le module LCD.
Son code est rigoureusement identique à celui présenté pour la classe LiquidCrystal_74hc595. Les explications sont à consulter dans l'article relatif à celle-ci. La méthode begin() sans paramètre est invoquée en premier afin de procéder à l'initialisation de l'interface de connexion utilisée. Pour le reste, il s'agit de la séquence d'initialisation standard du module LCD en actionnant les commandes adéquates. 
void LiquidCrystal_I2C::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
  this->begin();
  this->__displayFunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
  if (lines > 1) {
    this->__displayFunction |= LCD_2LINE;
  }
  this->__numLines = lines;
  this->__setRowOffsets(0x00, 0x40, 0x00 + cols, 0x40 + cols);
  if ((dotsize != LCD_5x8DOTS) && (lines == 1)) {
    this->__displayFunction |= LCD_5x10DOTS;
  }
  delayMicroseconds(50000);
  this->__pinWrite(this->_lcd_RS, LOW);
  if (this->__lcd_RW != 0xFF) {
    this->__pinWrite(this->__lcd_RW, LOW);
  }
  this->__pinWrite(this->__lcd_E, LOW);
  if (this->__lcd_LED != 0xFF) {
    this->__pinWrite(this->__lcd_LED, HIGH);
  }
  this->__write4bits(0x03);
  delayMicroseconds(4500);
  this->__write4bits(0x03);
  delayMicroseconds(150);
  this->__write4bits(0x03);
  this->__write4bits(0x02);
  this->__command(LCD_FUNCTIONSET | this->__displayFunction);
  this->__displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
  this->display();
  this->clear();
  this->__displayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
  this->__command(LCD_ENTRYMODESET | this->__displayMode);
}

Méthode de l'interface publique héritées de l'interface ILiquidCrystal

Le code des méthodes publiques de la classe LiquidCrystal_I2C héritées de l'interface ILiquidCrystal est rigoureusement identique à celui des méthodes de la classe LiquidCrystal_74hc595.

     Les méthodes privées d'initialisation

    Les deux méthodes privées invoquées par la méthode begin() pour initialiser certains attributs est le même que pour la classe LiquidCrystal_74hc595.  

    La méthode privée __command()

    La méthode __command() est une méthode de bas niveau qui lance une commande interne du LCD. Le paramètre value est une combinatoire sur 8 bits entre le code la commande à exécuter et éventuellement les flags à modifier. Cette valeur est écrite sur le bus des données du LCD par une invocation de la méthode __send() en positionnant la broche RS à 0 pour indiquer qu'il s'agit d'une commande. Les commandes peuvent être les suivantes : 
    • LCD_CLEARDISPLAY (code 0x01) — Cette commande est lancée par la méthode publique clear().
    • LCD_RETURNHOME  (code 0x02) — Cette commande est lancée par la méthode publique home().
    • LCD_ENTRYMODESET (code 0x04) — Cette commande est lancée par les méthodes leftToRight(), rightToLeft(), autoscroll() et noAutoscroll().
    • LCD_DISPLAYCONTROL (code 0x08) — Cette commande est lancée par les méthodes publiques display(), noDisplay(), cursor(), noCursor(), blink() et noBlink().
    • LCD_CURSORSHIFT (code 0x10) — Cette commande est lancée par les méthodes publiques scrollDisplayLeft() et scrollDisplayRight().
    • LCD_FUNCTIONSET (code 0x20) — Cette commande est lancée par la méthode begin() pour initialiser le fonctionnement de l'écran en fonction des paramètres passés.
    • LCD_SETCGRAMADDR (code 0x40) — cette commande est lancée par la méthode publique createChar()
    • LCD_SETDDRAMADDR (code 0x80) — Cette commande est lancée par la méthode publique setCursor().
    inline void LiquidCrystal_I2C::__command(uint8_t value) {
      this->__send(value, LOW);
    }

    La méthode publique write() 

    La méthode write(), héritée de l'interface Print, permet d'écrire un caractère sur l'écran LCD. Le paramètre value est le code du caractère à écrire sur l'écran. Les codes de 0 à 7 corresponde à des caractères créés par la méthode createChar(). Les autres codes correspondent au code ASCII. Les caractères accentués ne sont pas supportés.
    size_t LiquidCrystal_I2C::write(uint8_t value) {
      this->__send(value, HIGH);
      return 1;
    }
    • La méthode write() fonctionne comme la méthode __command() en invoquant la méthode __send(). Mais cette fois, la broche RS du LCD est positionnée à 1.
    • La primitive write() déclarée dans l'interface exige un résultat de type size_t correspondant au nombre de caractère écrit. La broche R/W du LCD étant maintenue 0 (en écriture) par soucis d'économie de pins mobilisées, il est impossible de savoir si l'écriture s'est bien passée ou pas. On renvoie 1 comme résultat en assumant que le caractère a bien été écrit sur l'écran.

    La méthode privée __send()

    La méthode __send() écrit un octet (paramètre value) sur l'écran LCD. La distinction entre les commandes et le caractères est discriminée par le paramètre mode. S'il vaut 0 (LOW), ils'agit d'une commande. S'il vaut 1 (HIGH), il s'agit du code d'un caractère.  
    void LiquidCrystal_I2C::__send(uint8_t value, uint8_t mode) {
      this->__pinWrite(this->__lcd_RS, mode);
      this->__write4bits(value>>4);
      this->__write4bits(value);
    }
    • La broche RS du LCD est positionnée à 1 ou à 0, en fonction du paramètre mode, en invoquant la méthode __pinWrite() pour distinguer s'il s'agit d'une commande à lancer ou d'un caractère à écrire.
    • Comme le LCD a été configuré en mode 4 bits, l'octet value est écrit en deux fois : d'abord les 4 bits de poids fort, puis les 4 bits de poids faible. Deux invocations de la méthode __write4bits() sont donc nécessaires pour écrire un octets. 

    La méthode privée __write4bits()

    La méthode __write4bits() écrit 4 bits  sur l'écran LCD. Il s'agit ici d'une méthode de très bas niveau car elle suit le cycle d'écriture décrit dans la documentation technique. Seuls les 4 bits de poids faible de value sont pris en compte. Les autres sont ignorés. L'écriture aurait pu être effectuée par quatre invocation de la méthode pinWrite() sur chaque pin du tableau __dataPins. Mais à chaque pin modifiée, un train de 8 bits aurait été envoyé au 74HC595. C'est pourquoi, l'écriture des quatre bits est effectuée en  une seule fois par l'invocation de la méthode write() sur l'instance __IC74hc595.
    void LiquidCrystal_I2C::__write4bits(uint8_t value) {
      uint8_t PValue = 0;
      for (int i = 0; i < 4; i++) {
        PValue |= ((value & 0x01) << this->__dataPins[i]);
        value >>= 1;
      }
      PValue |= (this->__ICpcf8574.PValue() & ~this->__dataFilter);
      this->__ICpcf8574.write(PValue);
      this->__pulseEnable();
    }
    • Une variable locale qValue est initialisée à 0.
    • Les bits de qValue sont positionnés sur leur rang respectif à partir des quatre bits de poids faible de value
    • Afin de ne pas modifier d'autres pin du 74HC595 que les pins reliées aux broches D4 à D7 du LCD, et notamment de préserver l'état des pins reliées aux broches RS et E du LCD, la valeur précédente de l'état des pins Qx du 74HV595 est récupéré par l'invocation de la méthode QValue()  sur l'instance __IC74hc595. Les bits non concernés par les données sont isolés grâce à une opération logique avec l'attribut __dataFilter calculé à l'initialisation par la méthode __setDataFilter() et sont ajoutés à la variable locale qValue. Celle-ci contient tous les bits du 74HC595 où seuls sont modifiés les 4 bits relatifs aux pins du 74HC595 reliées aux broches D4 à D7 du LCD.
    • Le train de huit bits constitué dans la variable qValue est transmis au LCD via le 74HC595 en invoquant la méthode write() sur l'instance __IC74hc595.
    • Les quatre broches D4 à D7 du LCD étant maintenant positionnées correctement, la valeur est validée en envoyant une impulsion sur la broche E du LCD par l'invocation de la méthode __pulseEnable().

    La méthode privée __pulseEnable()

    La méthode __pulseEnable() envoie une impulsion de 1 milliseconde sur la broche E du LCD pour valider les données positionnées sur les broches D4 à D7.
    void LiquidCrystal_I2C::__pulseEnable(void) {
      this->__pinWrite(this->__lcd_E, LOW);
      delayMicroseconds(1); 
      this->__pinWrite(this->__lcd_E, HIGH);
      delayMicroseconds(1);
      this->__pinWrite(this->__lcd_E, LOW);
      delayMicroseconds(100);
    }
    • La broche E du LCD est positionnée 0 au cas où l'état de la broche serait dans un état indéterminé. Comme l'indique la documentation technique, la validation est effectuée sur un flanc montant du signal.
    • La broche E du LCD est positionnée à 1 pendant 1 microseconde. La documentation technique précise que la durée de l'impulsion doit être d'au moins 450 nanosecondes.
    • La broche E du LCD est repositionnée à 0. 
    • L'exécution du traitement correspondant à l'écriture dure un certain temps (la documentation technique préconise au moins 37 microsecondes). Ici la broche E est maintenue à 0 pendant 100 microsecondes avant de permettre le cycle d'écriture suivant.

    La méthode privée __pinWrite()

    La méthode __pinWrite() fonctionne comme la fonction digitalWrite() de l'Arduino. Elle modifie l'état de la pin passée en paramètre avec la valeur value qui peut être HIGH ou LOW.
    void LiquidCrystal_I2C::__pinWrite(uint8_t pin, uint8_t value) {
      this->__ICpcf8574.pinWrite(pin, value);
    }
    • La méthode __pinWrite() transfère les deux paramètres reçus au PCF8574 en invoquant la méthode pinWrite() sur l'instance __ICpcf8574.

    Utilisation du driver LiquidCrystal_I2C dans un Sketch Arduino

    L'interface publique étant la même, le driver LiquidCrystal_I2C  s'utilise de la même manière que le driver standard LiquidCrystal de l'Arduino ou que le  LiquidCrystal_74hc595. A une différence près, le constructeur ici ne prend qu'un seul paramètre.
    #include "LiquidCrystal_I2C.h"

    #define I2C_OFFSET 0x07

    byte BELL_CHAR[8] = {
      B00100,
      B01010,
      B01010,
      B01010,
      B11011,
      B10001,
      B11111,
      B00100
    };

    LiquidCrystal_I2C LCD(I2C_OFFSET);

    void setup() {
      Serial.begin(9600);
      LCD.begin(16,2);
      I2C::scanI2CDeviceBus();
      LCD.createChar(1, BELL_CHAR);
      LCD.clear();
      LCD.print("Hello world !");
      LCD.setCursor(15, 0);
      LCD.write(1);
    }

    void loop() {
    }
    Le décalage de 0x07 est celui du module LCD 1602 utilisé pour l'article, pour lequel un PCF8574 est soudé et répond sur l'adresse I2C 0x27 comme le montre le scan du bus I2C obtenu par l'instruction I2C::scanI2CDeviceBus() :
    Analyse…
          0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F
    00:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    10:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    20:  -- -- -- -- -- -- -- 27 -- -- -- -- -- -- -- --
    30:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    40:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    50:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    60:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    70:  -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
    Analyse du bus I2C terminée !

    Conclusion

    Grâce à l'interface PC8574, en passant par le bus I2C, le pilotage d'un écran LCD laisse libres toutes les pins numériques de l'Arduino.
    En comparant le code respectif des deux classes LiquidCrystal_74hc595, qui pilote le module 1602 avec un 74HC595 et  LiquidCrystal_I2C, qui pilote le même module via le bus I2C par un PCF8574, on constate que les deux seules méthodes qui les distinguent sont les méthodes sont les méthodes de très bas niveau __pinWrite() et __write4bits(). La programmation objet permet de regrouper ce code commun dans une  classe générique abstraite. L'écriture de drivers spécifiques est donc simplifiée puisqu'elle se résume à dériver cette dernière en surchargeant les deux méthodes spécifiques. C'est l'option qui a été retenue dans la bibliothèque LCDDisplay qui sera employée dans tous les futurs articles ou le module LCD 1602 est utilisé.
    Dans cet article, le PCF8574 est employé exclusivement en écriture. Le prochain article présentera comme exemple, le pilotage d'un clavier matriciel à 16 touches connecté au bus I2C pour lequel le PCF8574 est configuré en lecture sur quatre pins P0 à P3 et en écriture sur les quatre autres P4 à P7 pour scanner les touches pressées.

    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