Piloter un écran LCD avec un 74HC595 sur Arduino

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.
Cet article présente une solution, basée sur un convertisseur Série/Parallèle 74HC595, qui ne mobilise que trois pins sur l'Arduino. D'abord sera abordée, en première partie, le câblage électronique. Ensuite, la programmation d'un driver en C++ sera présentée pour piloter cette configuration. Puis enfin, le driver développé sera mis en oeuvre dans un sketch Arduino.

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

Schéma électronique

Le principe de la solution est d'insérer un convertisseur Série/Parallèle 74HC595 entre l'Arduino et le module LCD.
  • Trois pins seulement de l'Arduino sont mobilisée pour piloter le 74HC595. Le câblage est celui déjà adopté dans les articles précédents. Les pins 2, 3 et 4 de l'Arduino sont connectée aux broches DS (Data), ST_CP (Latch) et SH_CP (Clock) du convertisseur 74HC595.
  • Le module LCD est piloté par les broches Q0 et Q1 du 74HC595 qui sont connectées respectivement aux broches RS et E du module LCD.
  • La broche R/W du module LCD est reliée à la masse GND pour maintenir celui-ci en écriture (la lecture n'est pas gérée par le driver).
  • Les broches Q4 à Q7 du 74HC595 sont respectivement connectées aux broches D4 à D7 du module LCD. 
  • Les broche D0 à D3 sont laissée libre. Le driver 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.
  • Une résistance R2 de 220 Ω protège l'anode A des LEDs du rétroéclairage. 
  • Les pins Q2 et Q3 du 74HC595 sont libres. Elle peuvent être utilisées comme deux sorties supplémentaires en extension de celles disponibles sur l'Arduino.

Montage sur la platine d'expérimentation

Programmation d'un driver pour le module LCD 1602 interfacé par un 74HC595

Le développement de drivers pour les composants électroniques est assez délicat. En effet, un composant électronique numérique ne permet pas le passage de 1 à 0 (et inversement) sur des tensions fixes qui serait +5V et 0V. Ils subissent des phénomènes physiques qui relèvent de l'électronique analogique, comme les résonances, les retards phase. De plus, ils fonctionnent aussi comme des récepteurs électromagnétiques, ce qui génère également de phénomènes parasites. Aussi, les cycles de phase de changement d'état et le timing sont des éléments importants à prendre en compte. Ceux-ci sont décrit dans des abaques et des chronogrammes sur les documentations techniques. Et il est important de les respecter.
Le module LCD 1602 est basé sur un composant Hitachi HD44780U. Plusieurs éléments sont à prendre en compte pour ce composant dans la documentation technique :
  • L'organigramme de la figure 24, page 46 et le timing à respecter pour le cycle d'initialisation en mode 4 bits. Pour info, l'initialisation en mode 8 bits est décrit dans la page précédente.
  • Le jeu d'instruction du tableau  de la table 6, page 24, et de la figure 11, page 28.
  • Le chronogramme du cycle d'écriture décrit dans la figure 25, page 58.
Au final, il suffit de deux fonctions de base pour faire fonctionner le LCD :
  • Une fonction permettant de définir la commande à exécuter. Dans ce cas, la pin RS du LCD est à LOW et les bits écrits sur le bus correspondent à l'instruction à exécuter.
  • Une fonction permettant d'écrire les données paramètres des commandes. Dans ce cas, la pin RS du LCD est à HIGH et les bits écrits sur le bus sont les données paramètres (comme le code d'un caractère par exemple. 
Que ce soit pour désigner l'instruction à exécuter ou les données paramètres à passer, le cycle d'écriture en mode 4 bits est toujours le même :
  • L'état de la broche RS du LCD détermine s'il s'agit d'une commande (RS à LOW) ou d'une donnée (RS à HIGH).
  • Positionner les 4 bits (B4 à B7) de poids fort de l'octet à écrire sur les broches D4 à D7 du LCD.
  • Envoyer une impulsion à HIGH de 450 nanosecondes minimum du la broche E du LCD.
  • Puis maintenir la broche E du LCD à LOW pendant au moins 37 microsecondes avant de procéder à l'écriture suivante.
  • Positionner les 4 bits de poids faible restant (B0 à B3) de l'octet à écrire sur les broches D4 à D7 du LCD.
  • Envoyer une nouvelle impulsion à HIGH de 450 nanosecondes minimum du la broche E du LCD.
  • Puis maintenir la broche E du LCD à LOW pendant au moins 37 microsecondes avant de procéder à l'écriture de l'octet suivant.

Définition de la classe LiquidCrystal_74hc595

La classe LiquidCrystal_74hc595 constitue le driver à utiliser pour piloter un module LCD 1602, interfacé par un convertisseur 74HC595, 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_74hc595. 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_74hc595.h

/* 1 */
#ifndef LiquidCrystal_74hc595_h
#define LiquidCrystal_74hc595_h

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

/* 3 */
// Commandes du LCD
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80

// Bits indicateurs des mode d'afffichage
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00

// Bits indicateurs ON/OFF pour l'affichage
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00

// Bits idicateurs pour les décalages de l'affichage et du curseur.
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00

// Bit indicateurs pour les fonctions d'affichage
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00

/* 4 */
class ILiquidCrystal : public Component, public Print {

  public:
  virtual void begin(uint8_t, uint8_t, uint8_t)=0;
  virtual void loop() { }

  virtual void clear()=0;
  virtual void home()=0;

  virtual void noDisplay()=0;
  virtual void display()=0;
  virtual void noBlink()=0;
  virtual void blink()=0;
  virtual void noCursor()=0;
  virtual void cursor()=0;
  virtual void scrollDisplayLeft()=0;
  virtual void scrollDisplayRight()=0;
  virtual void leftToRight()=0;
  virtual void rightToLeft()=0;
  virtual void autoscroll()=0;
  virtual void noAutoscroll()=0;

  virtual void createChar(uint8_t, uint8_t[])=0;
  virtual void setCursor(uint8_t, uint8_t)=0;
  virtual size_t write(uint8_t)=0;
};

/* 5 */
class LiquidCrystal_74hc595 : public ILiquidCrystal {
/* 6 */
  IC74hc595 __IC74hc595;
/* 7 */
  uint8_t __lcd_RS;
  uint8_t __lcd_E;
  uint8_t __dataPins[4];
/* 8 */
  uint8_t __dataFilter;
  int __rowOffsets[4];
  uint8_t __displayFunction;
  uint8_t __displayControl;
  uint8_t __displayMode;
  uint8_t __numLines;

public:
/* 9 */
  LiquidCrystal_74hc595(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
  LiquidCrystal_74hc595(uint8_t, uint8_t, uint8_t);

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

/* 11 */
  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:
/* 10 */
  void begin();
/* 12 */
  void __setRowOffsets(int, int, int, int);
  void __setDataFilter();
  void __setCommandFlag(uint8_t, uint8_t&, uint8_t, bool);
  inline void __command(uint8_t);
/* 13 */
  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 :
    • Comme toutes les classe relatives à des composants électroniques, la classe LiquidCrystal_74hc595 implémente l'interface Component défini dans le fichier Component.h. Elle doit implémenter les méthodes begin() et loop()
    • La classe utilise la classe IC74hc595 définie dans IC74hc595.h pour instancier un attribut.
    • La classe implémente l'interface Print défini dans Print.h. Elle doit implémenter la méthode write().
  3. Définition de toutes les constantes utilisées par la classe LiquidCrystal_74hc595. Les valeurs de ces constantes sont extraites des pages 24 à 28 de la documentation technique du composant Hitachi HD44780U.
  4. L'interface ILiquidCrystal permet de déclarer un polymorphisme pour tous les drivers pilotant des écran LCD. Il reprend toutes les méthodes publique de la classe standard LiquidCrystal de l'Arduino qui sont déclarées en méthodes virtuelles pures. Ce qui permet, lorsqu'on développe un nouveau driver, ne ne pas en oublier dans l'implémentation.   
  5. La classe LiquidCrystal_74hc595 n'a plus qu'à implémenter 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
  6. Le module LCD 1602 est connecté à un 74HC595. Sa programmation utilise nécessairement la classe IC74hc595. Une instance de cette classe __IC74hc595 est déclarée comme attribut privé.
  7. Sauvegarde des numéros de pins du 74HC595 mobilisées reçues en paramètres du constructeur.
    • __lcd_RS est le numéro de la pin du 74HC595 à laquelle est raccordée la pin RS du module LCD.
    • __lcd_E est le numéro de la pin du 74HC595 à laquelle est raccordée la pin E du module LCD.
    • __dataPins est le tableau des quatre numéros des pins du 74HC595 auxquelles sont raccordées  les pins D4 à D7 du module LCD.
  8. 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().
  9. La classe LiquidCrystal_74hc595 possède deux constructeurs. Le premier reçoit deux séries de paramètre, d'abord les trois pins Data, Latch et Clock mobilisées sur l'Arduino auxquelles sont reliées les broches DS , ST_CP et SH_CP du 74HC595, puis les numéros des six pins Qx du 74HC595 reliées aux broches RS, E et D4 à D7 du module LCD. Le second ne reçoit que les trois paramètres Data, Latch et Clock, assumant que les pins du 74HC595 reliées au module LCD sont Q0, Q1 et Q4 à Q7. Ce qui correspond à l'usage standard de la configuration.
  10. 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_74hc595 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.
  11. La classe LiquidCrystal_74hc595 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.
  12. 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.
  13. 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_74hc595

Constructeurs de la classe LiquidCrystal_74hc595

La classe LiquidCrystal_74hc595 possède deux constructeurs.  
LiquidCrystal_74hc595::LiquidCrystal_74hc595(uint8_t dataPin, uint8_t latchPin, uint8_t clockPin, uint8_t lcd_RS, uint8_t lcd_E, uint8_t lcd_D4, uint8_t lcd_D5, uint8_t lcd_D6, uint8_t lcd_D7)
    : __IC74hc595(dataPin, latchPin, clockPin)
{
  this->__lcd_RS = lcd_RS;
  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->__setDataFilter();
  this->__displayFunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
}

LiquidCrystal_74hc595::LiquidCrystal_74hc595(uint8_t dataPin, uint8_t latchPin, uint8_t clockPin)
    : LiquidCrystal_74hc595(dataPin, latchPin, clockPin, 0, 1, 4, 5, 6, 7)
{
}
  • Le premier reçoit deux séries de paramètre, d'abord les trois pins DataLatch et Clock mobilisées sur l'Arduino auxquelles sont reliées les broches DS , ST_CP et SH_CP du 74HC595, puis les numéros des six pins Qx du 74HC595 reliées aux broches RSE et D4 à D7 du module LCD. 
    • Il commence  par invoquer le constructeur de l'attribut __IC74hc595 en lui passant les trois premier paramètres.
    • Puis il initialise les autre attributs privés à partir du reste des paramètres passés.
  • Le second ne reçoit que les trois paramètres DataLatch et Clock, assumant que les pins du 74HC595 reliées au module LCD sont Q0Q1 et Q4 à Q7. Ce qui correspond à l'usage standard de la configuration. Il invoque le constructeur précédent avec les paramètres par défaut assumés.

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

La classe LiquidCrystal_74hc595 implémente deux méthodes begin().
void LiquidCrystal_74hc595::begin(void) {
  this->__IC74hc595.begin();
}

void LiquidCrystal_74hc595::begin(uint8_t cols, uint8_t lines, uint8_t dotsize) {
/* 1 */
  this->begin();
/* 2 */
  this->__numLines = lines;
  if (lines > 1) {
    this->__displayFunction |= LCD_2LINE;
  }
/* 3 */
  this->__setRowOffsets(0x00, 0x40, 0x00 + cols, 0x40 + cols);
/* 4 */
  if ((dotsize != LCD_5x8DOTS) && (lines == 1)) {
    this->__displayFunction |= LCD_5x10DOTS;
  }
/* 5 */
  delayMicroseconds(50000);
/* 6 */
  this->__IC74hc595.pinWrite(this->__lcd_RS, LOW);
  this->__IC74hc595.pinWrite(this->__lcd_E, LOW);
/* 7 */
  // Premier essai
  this->__write4bits(0x03);
  delayMicroseconds(4500); // Attendre 4.1ms minimum
  // Second essai
  this->__write4bits(0x03);
  delayMicroseconds(150); // Attendre  0.1 ms minimum
  // Troisième essai
  this->__write4bits(0x03);
/* 8 */
  this->__write4bits(0x02);
/* 9 */
  this->__command(LCD_FUNCTIONSET | this->__displayFunction);
/* 10 */
  this->__displayControl = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
  this->display();
/* 11 */
  this->clear();
/* 12 */
  this->__displayMode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
  this->__command(LCD_ENTRYMODESET | this->__displayMode);
}
  • La première méthode begin(), sans paramètre, est héritée de Component. Elle initialise l'instance __IC74hc595 en invoquant sa méthode begin().
    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_74hc595 de l'invoquer de l'extérieur, sa définition est déclarée private.
  • 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. C'est de loin la plus compliquée à écrire. Car elle nécessite d'éplucher attentivement la documentation technique du circuit. Elle est héritée de ILiquidCrystal
    1. Elle commence par invoquer la méthode bégin() précédente (sans paramètre) qui initialise le 74HC595. Ceci est important car le 74HC595 doit être initialisé pour pouvoir procéder au lancement des commandes dans la suite du script. 
    2. Le nombre de ligne est enregistré dans l'attribut privé __numLines.  Si le nombre de lignes est  2, elle active le flag correspondant dans l'attribut __displayFunction.  
    3. Elle invoque la méthode __setRowOffset() pour initialiser le tableau __rowOffset. Ce tableau contient les paramètres de la mémoire d'écran. 
    4. Par défaut la taille des caractères affichés est de 5x8 pixels. Mais pour certains écrans LCD qui ne comportent qu'une seule ligne, elle est de 5x10 pixels. Dans ce cas, le flag correspondant est activé dans l'attribut __displayFunction.
    5. Là commence le cycle d'initialisation du LCD décrit dans l’organigramme de la page 46 de la documentation du composant Hitachi HD44780U pour initialiser le mode 4 bits.
      Il faut attendre au moins 45 avant après que la tension  ait dépassé 2.7 Volts (15 ms suffise si la tension dépasse  4.5 Volts) avant de pouvoir lancer les premières commandes. Ici, la fonction delayMicroseconds()  est invoquée pour un temps d'attente de 50 millisecondes. C'est peut être voir un peu large. Mais cela n'a que peu d'importance puisque la destination de la méthode begin() est de n'être invoquée qu'une seule fois dans ma fonction setup() du  sketch.
    6. Après ce temps d'attente, il est possible de lancer les premières commandes. On commence par positionner les broches E et RS du LCD en invoquant la méthode pinWrite() sur l'instance __IC74hc595.
    7. Ensuite, il faut écrire trois fois de suite la valeur  binaire 0011 à avec un temps d'attente de 4.1 ms minimum entre les deux premier essai et 0.1 ms entre le deuxième et le troisième. Ici on invoque la fonction delayMicroseconds() pendant 4.5 ms la première fois, et 0.1 ms la deuxième, entre chaque invocation de la méthode __write4bits().
    8. Ensuite,  il faut écrire la valeur binaire 0010 pour désigner explicitement le passage en mode 4 bits.
    9. La commande FonctionSet (code 0x20) du LCD est lancée en passant l'attribut __displayFunction en paramètre. Ce paramètre contient les informations sur  le mode 4 bit, sur le nombre de ligne  (1 ou 2) et sur la taille des caractères (5x8 pixels ou 5x10 pixels). La commande est lancée en invoquant la méthode __command().
    10. L'attribut __displayControl est positionné pour que l'affichage soit activé, et que les curseurs soient non visibles. Le LCD possède deux curseurs, un souligné clignotant qui peut être commandé par les méthodes publiques blink() et noBlink()), et un pavé fixe (qui lui est commandé par cursor() et noCursor()). La commande DisplayControl (code 0x08) du LCD est lancée en invoquant la méthode publique display() qui prend en compte l'attribut __displayControl comme paramètre.
    11. La commande ClearDisplay (code 0x01) est lancée en invoquant la méthode publique clear(). Ce qui a pour résultat d'effacer l'écran.
    12. Le mode d'affichage est initialisé en passant l'attribut __displayMode à la commande EntryModeSet (code 0x04). Le mode d'affichage par défaut affiche les caractères de la gauche vers la droite en les alignant à gauche.

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

Ces méthodes sont celles héritées de l'interface ILiquidCrystal énumérant les méthodes publiques exposées par le driver standard LiquidCristal de l'Arduino. Leur fonctionnement est rigoureusement identique à la library originale.
/* 1 */
void LiquidCrystal_74hc595::clear()
{
  this->__command(LCD_CLEARDISPLAY);
  delayMicroseconds(2000);
}

/* 2 */
void LiquidCrystal_74hc595::home()
{
  this->__command(LCD_RETURNHOME);
  delayMicroseconds(2000);
}

/* 3 */
void LiquidCrystal_74hc595::setCursor(uint8_t col, uint8_t row)
{
  const size_t max_lines = sizeof(this->__rowOffsets) / sizeof(*this->__rowOffsets);
  if ( row >= max_lines ) {
    row = max_lines - 1;
  }
  if ( row >= this->__numLines ) {
    row = this->__numLines - 1;
  }
  this->__command(LCD_SETDDRAMADDR | (col + this->__rowOffsets[row]));
}

/* 4 */
void LiquidCrystal_74hc595::noDisplay() {
  this->__setCommandFlag(LCD_DISPLAYCONTROL, this->__displayControl, LCD_DISPLAYON, false);
}

/* 5 */
void LiquidCrystal_74hc595::display() {
  this->__setCommandFlag(LCD_DISPLAYCONTROL, this->__displayControl, LCD_DISPLAYON, true);
}

/* 6 */
void LiquidCrystal_74hc595::noCursor() {
  this->__setCommandFlag(LCD_DISPLAYCONTROL, this->__displayControl, LCD_CURSORON, false);
}

/* 7 */
void LiquidCrystal_74hc595::cursor() {
  this->__setCommandFlag(LCD_DISPLAYCONTROL, this->__displayControl, LCD_CURSORON, true);
}

/* 8 */
void LiquidCrystal_74hc595::noBlink() {
  this->__setCommandFlag(LCD_DISPLAYCONTROL, this->__displayControl, LCD_BLINKON, false);
}

/* 9 */
void LiquidCrystal_74hc595::blink() {
  this->__setCommandFlag(LCD_DISPLAYCONTROL, this->__displayControl, LCD_BLINKON, true);
}

/* 10 */
void LiquidCrystal_74hc595::scrollDisplayLeft(void) {
  this->__command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVELEFT);
}

/* 11 */
void LiquidCrystal_74hc595::scrollDisplayRight(void) {
  this->__command(LCD_CURSORSHIFT | LCD_DISPLAYMOVE | LCD_MOVERIGHT);
}

/* 12 */
void LiquidCrystal_74hc595::leftToRight(void) {
  this->__setCommandFlag(LCD_ENTRYMODESET, this->__displayMode, LCD_ENTRYLEFT, true);
}

/* 13 */
void LiquidCrystal_74hc595::rightToLeft(void) {
  this->__setCommandFlag(LCD_ENTRYMODESET, this->__displayMode, LCD_ENTRYLEFT, false);
}

/* 14 */
void LiquidCrystal_74hc595::autoscroll(void) {
  this->__setCommandFlag(LCD_ENTRYMODESET, this->__displayMode, LCD_ENTRYSHIFTINCREMENT, true);
}

/* 15 */
void LiquidCrystal_74hc595::noAutoscroll(void) {
  this->__setCommandFlag(LCD_ENTRYMODESET, this->__displayMode, LCD_ENTRYSHIFTINCREMENT, false);
}

/* 16 */
void LiquidCrystal_74hc595::createChar(uint8_t location, uint8_t charmap[]) {
  location &= 0x7;
  this->__command(LCD_SETCGRAMADDR | (location << 3));
  for (int i=0; i<8; i++) {
    this->write(charmap[i]);
  }
}
  1. La méthode clear() efface l'écran.
    La commande du LCD  ClearDisplay (code 0x01) est lancée en invoquant la méthode __command(). Cette commande prend quelques millisecondes à s'exécuter. Selon la documentation, il faudrait normalement passer en mode lecture pour surveiller le flag BF (Busy Flag) avant de lancer la commande suivante. Mais comme, afin d'économiser le pins mobilisées, la broche R/W du LCD est maintenue en écriture (à 0V), il faut attendre au moins 1.5ms pour être sûr que le flag BF est libéré.
    Ici, un délai arbitraire de 2 ms est effectué en invoquant la fonction delayMicroseconds().
  2. La méthode home() positionne le curseur en position 0,0.
    La commande du LCD ReturnHome (code 0x02) est lancée en invoquant la méthode __command(). Comme pour la méthode clear(), un délai de 2ms et nécessaire pour s'assurer que le flag BF est libéré.
  3. La méthode setCursor() positionne le curseur à l'endroit passé en paramètre. Le premier paramètre col est la colonne (en général de 0 à 15). Le second paramètre row est la ligne (en général de 0 à 1).
    Un contrôle, par rapport aux paramètres passés à la méthode begin(), et enregistrés dans le tableau __rowOffsets,  est effectué afin  de  ne pas causer de plantage du LCD en adressant la mémoire DDRAM de celui-ci de façon inadéquate.
    La commande du LCD SetDDRAMAddress (code 0x80) est lancée en invoquant la méthode __command() en passant en paramètre l'adresse DDRAM correspondant à la position du curseur.
  4. La méthode noDisplay() désactive l'affichage sur le LCD. Après l'exécution de cette méthode, plus rien ne 'affiche.
    La commande du LCD DisplayControl (code 0x08) est lancée pour mettre le flag LCD_DISPLAYON à 0 en invoquant la méthode __setCommandFlag(). Cette méthode modifie l'attribut __displayControl.
  5. La méthode display() active l'affichage. C'est le mode de fonctionnement par défaut initialisé par la méthode begin().
    Elle fait exactement l'inverse de la précédente en positionnant LCD_DISPLAYON à 1. Cette méthode modifie également l'attribut __displayControl.
  6. La méthode noCursor() désactive l'affichage du curseur plein sur le LCD. C'est le mode de fonctionnement par défaut initialisé par la méthode begin().
    La commande du LCD DisplayControl (code 0x08) est lancée pour mettre le flag LCD_CURSORON à 0 en invoquant la méthode __setCommandFlag(). Cette méthode modifie l'attribut __displayControl.
  7. La méthode cursor() active l'affichage du curseur plein.
    Elle fait exactement l'inverse de la précédente en positionnant LCD_CURSORON à 1. Cette méthode modifie également l'attribut __displayControl.
  8. La méthode noBlink() désactive l'affichage du curseur souligné clignotant sur le LCD. C'est le mode de fonctionnement par défaut initialisé par la méthode begin().
    La commande du LCD DisplayControl (code 0x08) est lancée pour mettre le flag LCD_BLINKON à 0 en invoquant la méthode __setCommandFlag(). Cette méthode modifie l'attribut __displayControl.
  9. La méthode blink() active l'affichage du curseur souligné clignotant. Elle fait exactement l'inverse de la précédente en positionnant LCD_BLINKON à 1. Cette méthode modifie également l'attribut __displayControl.
  10. La méthode scrollDisplayLeft() déplace le texte affiché et le curseur d'un caractère vers la gauche.
    Une invocation de la méthode __setCommandFlag() lance la commande CursorShift (code 0x10) avec les flags LCD_DISPLAYMOVE et LCD_MOVELEFT en paramètres.
  11. La méthode scrollDisplayRight() déplace le texte affiché et le curseur d'un caractère vers la droite.
    Une invocation de la méthode __setCommandFlag() lance la commande CursorShift (code 0x10) avec les flags LCD_DISPLAYMOVE et LCD_MOVERIGHT en paramètres.
  12. La méthode leftToRight() configure l'affichage pour que le curseur se déplace de la gauche vers la droite. C'est le mode de fonctionnement par défaut initialisé par la méthode begin().
    La commande du LCD EntryModeSet (code 0x04) est lancée pour mettre le flag LCD_ENTRYLEFT à 1 en invoquant la méthode __setCommandFlag(). Cette méthode modifie l'attribut __displayMode.
  13. La méthode rightToLeft() configure l'affichage pour que le curseur se déplace de la droite vers la gauche. Elle fait exactement l'inverse de la précédente en positionnant LCD_ENTRYLEFT à 0. Cette méthode modifie également l'attribut __displayMode.
  14. La méthode autoscroll() configure l'affichage pour que le texte se décale automatiquement lorsqu'un caractère et affiché à une extrémité de l'écran. L'extrémité considérée et le sens de déplacement dépend du sens de l'écriture configuré par les méthodes leftToRight() ou rightToLeft().
    La commande du LCD EntryModeSet (code 0x04) est lancée pour mettre le flag LCD_ENTRYSHIFTINCREMENT à 1 en invoquant la méthode __setCommandFlag(). Cette méthode modifie l'attribut __displayMode.
  15. La méthode noAutoScroll() supprime le décalage activé par la méthode autoscroll(). C'est le mode de fonctionnement par défaut initialisé par la méthode begin().
    Elle fait exactement l'inverse de la précédente en positionnant LCD_ENTRYSHIFTINCREMENT à 0. Cette méthode modifie également l'attribut __displayMode.
  16. La méthode  createChar() permet de créer un caractère personnalisé.
    Le LCD permet de créer 8 caractères personnalisé stocké avec un code de 0 à 7 passé en premier paramètre locationlocation est tronqué sur trois bit, car on ne peut créer que 8 caractères.
    Les caractères sont définis dans un tableau de 8 membres (ou 10 membres en fonction du paramètre dotSize passé à la méthode begin()), un par ligne de pixels, chaque membre contenant les 5 bits relatifs aux 5 pixels de la ligne. Ce tableau charmap est passé en second paramètre.

Les méthodes privées d'initialisation

La méthode begin() invoque deux méthodes privées utilisées pour initialiser certains attributs.Ces deux méthodes ne sont invoquées qu'une seule fois à l'initialisation de l'écran LCD. En revanche les valeur calculées sont utilisées plusieurs fois lors du lancement des commandes sur le LCD. 
void LiquidCrystal_74hc595::__setRowOffsets(int row0, int row1, int row2, int row3)
{
  this->__rowOffsets[0] = row0;
  this->__rowOffsets[1] = row1;
  this->__rowOffsets[2] = row2;
  this->__rowOffsets[3] = row3;
}

void LiquidCrystal_74hc595::__setDataFilter() {
  this->__dataFilter = 0;
  for (int i = 0; i < 4; i++) {
    this->__dataFilter |= (0x01 << this->__dataPins[i]);
  }
}
  • La méthode __setRowOffsets() permet d'initialiser l'attribut __rowOffsets. Ce tableau contient les déplacements à effectuer dans la mémoire d'écran. La valeurs des paramètres passés dépend de ceux passés à la méthode begin() (taille de l'écran, taille des caractères).
  • La méthode __setDataFilter() permet d'initialiser l'attribut __dataFilter. Cet attribut est un filtre utilisé par la méthode __write4bits()  construit à partir du tableau __dataPins contenant les broches du 74HC595 utilisées pour les broches D4 à D7 du LCD. 

La méthode privée __setCommandFlag()

La commande __setCommandFlag() permet de positionner un flag dans l'un des registres du LCD. Elle reçoit quatre paramètres :

  • command est la commande du LCD à exécuter. C'est un code sur 8 bits. Chaque  bit désigne une commande différente. Les commandes peuvent être les suivantes : 
    • 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.
  • flags est l'attribut privé concerné dans la classe LiquidCrystal_74hc595. En effet, non seulement la méthode __setCommandFlag() modifie l'état des flags internes du LCD, mais met à jour également l'attribut privé concerné pour que l'état du LCD soit toujours cohérent avec l'état de l'instance LiquidCrystal_74hc595 . C'est pourquoi ce paramètre est passé par référence.
  • flag est le filtre des flags modifiés. Plusieurs flags relatifs à un même attribut peuvent être modifiés à la fois. Il suffit de mettre le bit correspondant dans flag à 1. Les bits non modifiés reste à 0.
  • on est l'action à effectuer sur les flags. Si on est true, les flags correspondant aux bits positionnés à 1 dans flag, sont positionné à 1 sur le LCD. Si on est à false, les flags correspondants aux bits positionnés à 1 dans flag sont remis à 0. Les autres bits ne sont pas modifiés.
La méthode __setCommandFlag() invoque la méthode privée __command() pour exécuter l'action à effectuer. 
void LiquidCrystal_74hc595::__setCommandFlag(uint8_t command, uint8_t &flags, uint8_t flag, bool on) {
  if (on) {
    flags |= flag;
    this->__command(command | flags);
  } else {
    flags &= ~flag;
    this->__command(command | flags);
  }
}

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_74hc595::__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_74hc595::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_74hc595::__send(uint8_t value, uint8_t mode) {
  this->__IC74hc595.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() sur l'instance __IC74HC595 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_74hc595::__write4bits(uint8_t value) {
  uint8_t qValue = 0;
  for (int i = 0; i < 4; i++) {
    qValue |= ((value & 0x01) << this->__dataPins[i]);
    value >>= 1;
  }
  qValue |= (this->__IC74hc595.QValue() & ~this->__dataFilter);
  this->__IC74hc595.write(qValue);
  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_74hc595::__pulseEnable(void) {
  this->__IC74hc595.pinWrite(this->__lcd_E, LOW);
  delayMicroseconds(1); 
  this->__IC74hc595.pinWrite(this->__lcd_E, HIGH);
  delayMicroseconds(1);
  this->__IC74hc595.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.

Utilisation du driver LiquidCrystal_74hc595 dans un Sketch Arduino

L'interface publique étant la même, le driver LiquidCrystal_74hc595 s'utilise de la même manière que le driver standard LiquidCrystal de l'Arduino. A une différence près, le constructeur ne prend que trois paramètres correspondant aux trois pins Data, Latch et Clock du 74HC595.
#include "LiquidCrystal_74hc595.h"

// Déclaration des pin du 74HC595
#define IC_DATA 2
#define IC_LATCH 3
#define IC_CLOCK 4

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

LiquidCrystal_74hc595 LCD(IC_DATA, IC_LATCH, IC_CLOCK);

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

void loop() {
}

Conclusion

Grâce à un convertisseur Série/Parallèle 74HC595, le pilotage d'un écran LCD ne prend plus que trois pins de l'Arduino au lieu des six nécessaires pour une connexion directe.
Cet article peut constituer un exemple de la réalisation d'un driver pour un composant ou un groupe de composant donné.  Dans l'article suivant, ce driver sera utilisé dans le projet Horloge pour améliorer le dispositif d'affichage de l'heure sur un écran LCD. 

Commentaires

Posts les plus consultés de ce blog

Afficheur à LED 7 segments (classe SegmentLedDisplay)

Piloter un clavier matriciel 16 touches (classe MatrixKeypad)

Piloter un clavier matriciel sur le bus I2C avec un PCF8574