Projet Réveil-Matin (partie IX) - Affichage sur un écran LCD

Dans l'article précédent intitulé "Gestion de l'affichage de l'horloge", un dispositif d'affichage avait été élaboré sur la base d'une classe générique ClockDisplay assurant l'interface avec l'horloge pour créer une classe SerialClockDisplay délivrant l'heure sur le canal Série de l'Arduino.
Or, dans le cahier des charges du projet, il avait été évoqué l'utilisation d'un module LCD 1602 pour afficher l'heure. Le présent article traite de la prise en charge de ce module en développant une classe LCDClockDisplay qui sera instanciée à la place de SerialClockDisplay pour être passée en paramètre du constructeur de la classe Clock.

Câblage du module LCD 1602

Le brochage des écrans LCD est toujours le même :
NomDescription
1VSSAlimentation électrique du module.
Doit être reliée à GND de l'Arduino
2VDDAlimentation électrique du module.
Doit être reliée à +5V sur l'Arduino.
3V0Réglage du contraste
4RSSélection du registre
Le module possède deux registres : un pour les données, un pour les commandes.
5R/WLecture ou écriture.
6EEntrée de validation.
Cette broche a le même rôle que la touche [Enter] du clavier lorsqu'elle passe de +5V à 0V.
7 à 14D0 à D7Bus de données du module.
15AAnode du rétro-éclairage.
Doit être reliée à +5V sur l'Arduino. 
16KCathode du rétro-éclairage.
Doit être relié à GND de l'Arduino.
Comme on le voit, le brochage du module est assez gourmand en pins de l'Arduino. En effet, à lui tout seul, il nécessite 11 pins de l'Arduino : D0 à D7, RS, R/W et E. Les autres broches servant à l'alimentation du module. Or, l'Arduino n'offre que 13 pins numériques numérotées de 0 à 12 (14 avec la pin N°13 réservée à  la LED interne). Et notre projet Horloge nécessite trois pins supplémentaires pour les trois boutons et une pin pour le buzzer.
Heureusement, les commandes du module LCD 1602 permettent de n'utiliser que quatre bits de données (broches D4 à D7). Ce qui libère les quatre pins nécessaires au projet. De plus, le projet Horloge ne nécessite pas de lecture sur le module LCD. La broche R/W peut donc être maintenue en écriture (connecté à GND sur l'Arduino). Ce qui libère encore une pin.

Schéma électrique

  • Les broches d'alimentation du module VSS et VDD sont respectivement connectée à GND et +5V sur l'Arduino.
  • La broche V0 est connectée à un potentiomètre de 10 KΩ pour permettre le réglage du contraste de l'écran LCD.
  • La broche de sélection du registre RS est connectée à la pin 7 de l'Arduino.
  • La broche R/W est connectée à GND pour maintenir le module en écriture. 
  • La broche validation E est connectée à la pin 8 de l'Arduino.
  • Les broches D0 à D3 sont laissées libres. Les commandes de les données seront transmises au module 4 bits par 4 bits.
  • Les broches D4 à D7 sont connectées à l'Arduino respectivement sur les pins 9 à 12.
  • La broche A de l'anode des LEDs du rétro-éclairage, en série avec une résistance protection de 220 Ω est connectée à +5V sur l'Arduino.   
  • La broche K de la cathode des LEDs du rétro-éclairage est connectée à GND sur l'Arduino.
Ce câblage laisse les pins 0 à 6 de l'Arduino libre pour les autres composants de l'horloge.

Montage sur la platine d'expérimentation

Programmation du module LCD 1602

Installation des drivers

Les modules LCD sont, en principe, livrés avec leur drivers. Ces drivers sont en fait des bibliothèques compactées au ZIP. Pour le projet Horloge, le module utilisé est un module standard. Les drivers concernés peuvent être installés directement à partir de l'application Arduino utilisée pour la programmation. Elle est utilisable pour tous les modules LCD basés sur un circuit Hitachi HD44780. Sinon, la bibliothèque livrée avec le circuit est LiquidCrystal.zip.
  • Dans les menus de l'application Arduino, cliquer la commande Outil/Gérer les bibliothèques (raccourcis Ctrl+Maj+I).
  • Dans le Gestionnaire de bibliothèques, taper LCD dans la zone de recherche.
  • Choisir la bibliothèque LiquidCrystal standard.
  • Choisir la dernière version (par défaut).
  • Cliquer le bouton [Installer]

Définition de la classe LCDClockDisplay

/* 1 */
#ifndef LCDClockDisplay_h
#define LCDClockDisplay_h

/* 2 */
#include <LiquidCrystal.h>
#include "ClockDisplay.h"

/* 3 */
class LCDClockDisplay : public ClockDisplay {
/* 4 */
  static const int POS_CLOCK = 4;
  static const int POS_HOUR = 6;
  static const int POS_MINUTE = POS_HOUR + 3;
  static const int POS_ALARM = 15;
  static const char* BLINK2;
  static const char* BLINK8;
/* 5 */
  static const char* LABELS[];
/* 6 */
  static byte BELL_CHAR[];
  static byte EACUTE_CHAR[];
/* 7 */
  LiquidCrystal __LCD;

  protected:
/* 8 */
  virtual void _displayTime();
  virtual void _displayStatus();
  virtual void _displayAlarm();
  virtual void _doBlink(bool);

  public:
/* 9 */
  LCDClockDisplay(uint8_t, uint8_t, uint8_t, uint8_t, uint8_t, uint8_t);
/* 10 */
  virtual void begin();
};

/* 1 */
#endif // LCDClockDisplay_h
  1. Application de la bonne pratique de définir les classes d'une bibliothèque entre deux directive #ifndef et #endif en prévention de multiples définitions dans les projets composites.
  2. Inclusion des définitions des classes utilisées :
    • LiquidCrystal.h contient la définition des drivers du module LCD 1602. La notation entre < et > signifie que le fichier .h est lu dans le répertoire des bibliothèques de l'Arduino.
    • ClockDisplay.h est le fichier de définition présenté dans l'article traitant des dispositifs d'affichage du projet Horloge. La notation entre guillemets signifie que le fichier est lu dans le répertoire du projet.
  3. La classe LCDClockDisplay dérive la classe générique ClockDisplay. Elle hérite donc des trois méthodes publiques changeTime(), changeStatus() et changeAlarm() invoquées par la classe Clock. En revanche, elle doit implémenter les quatre méthode abstraites déclarées dans la classe de base.
  4. Pour fonctionner la classe LCDClockDisplay a besoin de plusieurs valeurs déclarées comme constantes de classe :
    • POS_CLOCK est la position de l'affichage de l'heure au format HH:MN:SS.
    • POS_HOUR est la position des heures en mode réglage dans le format HH:MN.
    • POS_MINUTE est la position des minutes en mode réglage au format HH:MN.
    • POS_ALARM est la position de la petite cloche qui indique que l'alarme est activée.
    • BLINK2 est une chaîne de caractères constituée de deux espaces, pour effacer la valeur en cours de réglage lors du clignotement.
    • BLINK8 est une chaîne de caractères «   :  :   »  pour effacer l'heure lors du clignotement de celle-ci quand le signal d'alarme est déclenché.
  5. LABELS est un tableau de chaînes de caractères indicé par rapport à l'état de l'horloge. Il permet de rassembler tous les affichages dans une seule entité. Il est défini comme une constante de classe (static const). 
  6. La classe LiquidCrystal qui implémente les drivers  du module LCD permet de personnaliser huit caractères. Ces caractères sont définis comme des tableaux de bits, chaque bit correspondant à un pixel. Cette fonctionnalité est utilisée pour les caractères  suivants :
    • BELL_CHAR est un tableau de bits permettant de dessiner une petite cloche. Ce caractère personnalisé est utilisé pour signaler que que l'alarme est activée (attribut privé __alarmStatus de la classe Clock a la valeur true). 
    • EACUTE_CHAR est un tableau de bits permettant de dessiner un caractère é (e accent aigu). En effet, le jeu de caractères du module LCD ne prend pas en charges les caractères accentués. Ce caractère est utilisé dans le tableau de libellés LABEL
  7. L'attribut privé __LCD contient une instance de la classe LiquidCrystal qui permet de piloter le module LCD.
  8. Les quatre méthodes héritées de la classe  abstraite parente ClockDisplay, doivent être implémentées pour que la classe LCDClockDisplay soit instanciable.
  9. L'interface publique de la classe concrète doit implémenter un constructeur. La classe LCDClockDisplay utilise un module électronique qui utilise 6 pins de l'Arduino. Leurs numéros respectif doivent être passés au constructeur de la classe.
  10. Le composant LiquidCrystal relatif au module LCD doit être initialisé pour pouvoir être utilisé. La méthode begin() est implémentée dans la classe LCDClockDisplay à cet effet. En revanche, l'implémentation de la méthode loop() est prise en charge dans la classe de base ClockDisplay et ne nécessite aucune implémentation  particulière dans les classes dérivées.

Implémentation de la classe LCDClockDisplay

L'implémentation d'une classe concrète pour un dispositif d'affichage de l'horloge consiste à :
  • Créer un constructeur pour la classe concrète. Celui-ci doit permettre de recevoir en paramètres les numéros des pins de l'Arduino utilisées par les composants électroniques qui composent le dispositif d'affichage.
  • Surcharger la méthode begin() pour initialiser les composants électroniques du dispositif d'affichage et notamment définir les pins de l'Arduino en lecture ou en écriture par l'invocation de la  fonction pinMode(). ou éventuellement invoquer la méthode begin() des composants utilisés.
  • Implémenter les quatre méthodes abstraites de la classe ClockDisplay pour définir comment sont afficher les différents éléments de l'horloge.
#include "LCDClockDisplay.h"

/* 1 */
const char* LCDClockDisplay::BLINK2 = "  ";
const char* LCDClockDisplay::BLINK8 = "  :  :  ";

/* 2 */
byte LCDClockDisplay::BELL_CHAR[8] = {
  B00100,
  B01010,
  B01010,
  B01010,
  B11011,
  B10001,
  B11111,
  B00100
};

byte LCDClockDisplay::EACUTE_CHAR[8] = {
  B00010,
  B00100,
  B01110,
  B10001,
  B11111,
  B10000,
  B01110,
  B00000
};

/* 3 */
const char *LCDClockDisplay::LABELS[] = {"", "R\1glage heure:", "R\1glage Alarme:", "R\1glage heure:", "R\1glage heure:", "R\1glage Alarme:", "R\1glage Alarme:" } ;

/* 4 */
LCDClockDisplay::LCDClockDisplay(uint8_t lcd_RS, uint8_t lcd_E, uint8_t lcd_D4, uint8_t lcd_D5, uint8_t lcd_D6, uint8_t lcd_D7)
  : ClockDisplay(), __LCD(lcd_RS, lcd_E, lcd_D4, lcd_D5, lcd_D6, lcd_D7)
{
}

/* 5 */
void LCDClockDisplay::begin() {
  ClockDisplay::begin();
  this->__LCD.createChar(0, BELL_CHAR);
  this->__LCD.createChar(1, EACUTE_CHAR);
  this->__LCD.begin(16,2);
}

/* 6 */
void LCDClockDisplay::_doBlink(bool blinkStatus) {
  char strTime[10];
  switch (this->_clockStatus) {
    case HOUR_SETUP:
    case ALARM_HOUR_SETUP:
      sprintf(strTime, "%02d", this->_time.HH());
      this->__LCD.setCursor(POS_HOUR, 1);
      this->__LCD.print(blinkStatus ? strTime : BLINK2);
      break;
    case MINUTE_SETUP:
    case ALARM_MINUTE_SETUP:
      sprintf(strTime, "%02d", this->_time.MN());
      this->__LCD.setCursor(POS_MINUTE, 1);
      this->__LCD.print(blinkStatus ? strTime : BLINK2);
      break;
    case ALARM_ACTIVE:
      sprintf(strTime, "%02d:%02d:%02d", this->_time.HH(), this->_time.MN(), this->_time.SS());
      this->__LCD.setCursor(POS_CLOCK, 1);
      this->__LCD.print(blinkStatus ? strTime : BLINK8);
      break;
  }
}

/* 7 */
void LCDClockDisplay::_displayTime() {
  char strTime[10];
  int pos = 0;
  switch (this->_clockStatus) {
    case HOUR_DISPLAY:
    case ALARM_ACTIVE:
      sprintf(strTime, "%02d:%02d:%02d", this->_time.HH(), this->_time.MN(), this->_time.SS());
      pos = POS_CLOCK;
      break;
    default:
      sprintf(strTime, "%02d:%02d", this->_time.HH(), this->_time.MN());
      pos = POS_HOUR;
      break;
  }
  this->__LCD.setCursor(pos, 1);
  this->__LCD.print(strTime);
}

/* 8 */
void LCDClockDisplay::_displayStatus() {
  this->__LCD.clear();
  this->__LCD.setCursor(0, 0);
  this->__LCD.print(LABELS[(int)this->_clockStatus]);
  this->_displayAlarm();
}

/* 9 */
void LCDClockDisplay::_displayAlarm() {
  this->__LCD.setCursor(POS_ALARM, 0);
  this->__LCD.write(this->_alarmStatus ? 0 : ' ');
}
  1. Les constantes de classe, déclaréee dans le fichier de définition, sont implémentée ici :
    • BLINK2 est une chaîne de caractères constituée de deux espaces, pour effacer la valeur en cours de réglage lors du clignotement.
    • BLINK8 est une chaîne de caractères «   :  :   »  pour effacer l'heure lors du clignotement
  2. Les drivers du module LCD permet de définir des caractères personnalisés. Ce sont des tableaux de bits de 8x5 pixels.
    • BELL_CHAR est un tableau de bits permettant de dessiner une petite cloche. Ce caractère personnalisé est utilisé pour signaler que que l'alarme est activée (attribut privé __alarmStatus de la classe Clock a la valeur true). Ce caractère aura le code 0.
    • EACUTE_CHAR est un tableau de bits permettant de dessiner un caractère é (e accent aigu). En effet, le jeu de caractères du module LCD ne prend pas en charges les caractères accentués. Ce caractère est utilisé dans le tableau de libellés LABEL. Ce caractère aura le code 1
  3. Le tableau de chaînes de caractère LABELS, déclaré dans la définition de la classe LCDClockDisplay dans le fichier LCDClockDisplay.h, doit être implémenté en extension dans le fichier LCDClockDisplay.cpp. Pour chaque état de l'énumération ClockStatus, il défini une chaîne de caractères qui peut être affichée sur le dispositif d'affichage.
    A remarqué que, comme l'écran LCD n'affiche pas de caractère accentué, le caractère é est remplacé par un caractère personnalisé de code 1 en utilisant la barre d'échappement \1 de la syntaxe C++.
  4. La classe LCDClockDisplay utilise le driver LiquidCrystal auquel il faut indiquer le 8 pins de l'Arduino utilisées. Ces numéros de pin doivent être fournis — dans l'ordre RS, E, D4, D5, D6, D7  — au constructeur de la classe LCDClockDisplay qui les transmet en invoquant le constructeur de la classe LiquidCrystal sur l'attribut __LCD, après avoir invoqué le constructeur de la classe parente ClockDisplay.
  5. Dans la méthode begin(), il y a plusieurs chose à effectuer :
    • Invoquer la méthode begin() de la classe parente ClockDisplay. Cela induit l'initialisation du timer de clignotement implémentée dans celle-ci.
    • Création du caractère personnalisé défini en constante de classe par le tableau de bits BELL_CHAR, avec le code 0, en invoquant la méthode createChar() sur l'instance de l'écran __LCD.
    • Création du caractère personnalisé é défini dans le tableau EACUTE_CHAR, avec comme code 1, en invoquant la méthode createChar() sur l'instance de l'écran __LCD.
    • Invocation de la méthode begin() de l'instance de l'écran __LCD en indiquant au driver que le module utilisé fait deux lignes de seize caractères.
  6. En mode réglage et lorsque le signal d'alarme est déclenché, l'affichage de l'heure doit clignoter. Ni le module LCD 1602, ni ses drivers, ne permettent le clignotement des caractères affichés. Aussi, pour implémenter cette fonctionnalité, on utilise un timer défini dans la classe de base ClockDisplay qui va périodiquement (toutes les 500 ms en fait) invoquer la méthode _doBlink() en passant en paramètre alternativement la valeur true ou la valeur false. Lorsque le paramètre blinkStatus vaut true, l'heure est affichée normalement. Lorsqu'il vaut false, l'heure est remplacée par l'une des chaînes de caractères BLINK2 ou BLINK8 définis en constante. L'affichage est mis en forme dans une variable locale strTime en utilisant la méthode print() invoqué sur l'instance __LCD. La donnée affichée est positionnée sur l'écran en invoquant la méthode setCursor() sur l'instance __LCD. La donnée affichée en clignotant et sa position dépendent de l'état de l'horloge :
    • Dans les états HOUR_SETUP (réglage de la valeur HH de l'heure) et ALARM_HOUR_SETUP (réglage de la valeur HH du déclenchement de l'alarme), la valeur clignotante est HH et sa position est POS_HOUR (dont la valeur définie en constante est 6) sur la deuxième ligne (dont la valeur est 1, 0 étant le numéro de la première ligne).
    • Dans les états MINUTE_SETUP (réglage de la valeur MN de l'heure) et ALARM_MINUTE_SETUP (réglage de la valeur MN du déclenchement de l'alarme) la valeur clignotante est MN  et sa position est POS_MINUTE (dont le valeur est 9) sur la deuxième ligne.
    •  Dans l'état ALARM_ACTIVE (lorsque le signal d'alarme se déclenche), la valeur clignotante est HH:MN:SS dont la position est POS_CLOCK (dont la valeur est 4).
  7. La méthode _displayTime()  affiche l'heure contenue dans l'attribut protégé _time (déterminé par la classe Clock en invoquant la méthode publique changeTime()). L'affichage de l'heure e fait toujours sur la deuxième ligne de l'écran (valeur 1), la première ligne (valeur 0) étant réservée à l'affichage de l'état de l'horloge. La valeur de cet attribut et son format d'affichage dépendent également de l'état de l'horloge :
    • Dans les états HOUR_DISPLAY (mode normal) et ALARM_ACTIVE (signal d'alarme déclenché) le format d'affichage est HH:MN:SS et la position horizontale est POS_CLOCK (valeur 4). A signaler que, comme vu ci-dessus, dans l'état ALARM_ACTIVE, l'affichage de HH:MN:SS clignote.
    • Dans tous les autres états (modes de réglage) le format d'affichage est HH:MN. A remarquer que, comme vu ci-dessus, en fonction de l'état de réglage considéré, la valeur HH ou la valeur MN clignote pour indiquer quelle est la valeur en cours de réglage. 
  8. La méthode _displayStatus() affiche l'état dans lequel se trouve l'horloge sur la première ligne de l'écran. Elle affiche le libellé du tableau LABELS correspondant à l'état de l'horloge contenu dans l'attribut _alarmStatus. Afin de ne pas récupérer des caractères parasites préalablement affiché, l'écran est effacé à chaque changement d'état en invoquant la méthode clear() sur l'instance __LCD
  9. Lorsque l'alarme est activée (valeur de l'attribut _alarmStatus à true), la méthode _displayAlarm() affiche une petite cloche. Le caractère affiché est le caractère de code 0 créé avec la méthode createChar()  sur l'instance __LCD à partir du tableau de bits BELL_CHAR.
A remarquer que la méthode _displayAlarm() est elle-même invoquée par la méthode _displayStatus(). En effet, à chaque changement d'état l'écran est effacé par l'invocation de la méthode clear(). En conséquence, lorsqu'on passe dans l'un des modes de réglage par exemple, la petite cloche est effacée, alors que l'alarme est toujours activée. Il faut donc la ré-afficher.

Utilisation de la classe LCDClockDisplay dans un sketch Arduino

#include "LCDClockDisplay.h"
#include "ClockAlarm.h"
#include "Clock.h"


// Déclaration des pins de connexion du LCD sur l'Arduino.
#define LCD_RS 7
#define LCD_E 8
#define LCD_D4 9
#define LCD_D5 10
#define LCD_D6 11
#define LCD_D7 12 
// Déclaration des pins de connexion des boutons de commande.
#define BTN_CMD 5
#define BTN_UP 3
#define BTN_DOWN 4
// Déclaration de la pin de connexion du buzzer passif.
#define BUZZER 2

LCDClockDisplay clockDisplay(LCD_RS, LCD_E, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
BuzzerClockAlarm clockAlarm(BUZZER);
Clock myclock(clockDisplay, clockAlarm, BTN_CMD, BTN_UP, BTN_DOWN);


void setup() {
  myclock.begin();
}

void loop() {
  myclock.loop();
}
L'utilisation de la classe LCDClockDisplay dans un sketch Arduino est peu différent de celle de la classe SerialClockDisplay, si ce n'est le fait que le module LCD 1602 mobilise 6 pins de l'Arduino qu'il faut passer en paramètres du constructeur. Dans l'exemple ci-dessus, les broches RS, E, D4, D5, D6 et D7 du module sont respectivement connectées au pins 7 à 12 de l'Arduino.

Exemples d'affichages :

A remarquer la petite cloche en haut à droite de l'écran indiquant que l'alarme est activée.
Fonctionnement normalMode réglage de l'heureMode réglage de l'alarme

Conclusion

Cet article conclut le projet Horloge. En, effet tous les composants nécessaires ont été présentés dans les articles ci dessous :
  • Partie I - Le cahier des charges du projet Horloge ainsi que la présentation du fonctionnement général de l'application sous la forme d'un automate.
  • Partie II - La partie électronique du projet Horloge avec les schémas de câblage.
  • Partie III - L’architecture de l'application avec la définition de  la classe Clock et l'utilisation de celle-ci dans un sketch Arduino. .
  • Partie IV - L'implémentation de la classe Clock et la traduction de l'automate en code C++.
  • Partie V - Gestion de l'heure avec la classe Time.
  • Partie VI - Générateur de tics avec la classe ClockTime dérivée de la classe Time.
  • Partie VII - Gestion de l'affichage de l'horloge avec la classe abstraite ClockDisplay et un exemple de l'implémentation concrète de celle-ci avec la classe SerialClockDisplay assurant la'affichage sur le canal Série de l'Arduino.
  • Partie VIII - Gestion de l'alarme de l'horloge avec la classe abstraite ClockAlarm et un exemple de l'implémentation concrète de celle-ci avec la classe BuzzerClockAlarm émettant le signal d'alarme sur un buzzer passif.
  • Partie IX (cet article) - implémentant  la classe LCDClockDisplay à partir de la classe ClockDisplay pour assurer l'affichage sur un écran LCD, comme le prévoyait le cahier des charges. 
Le projet a été développé entièrement en langage C++ en utilisant les concepts de la programmation par objets. De plus plusieurs design pattern ont été utilisé pour assurer un bon découplage entre les classes. Ce qui va permettre de présenter une autre version de ce projet où l'affichage sera effectué sur des modules LED-7 segments pour lequel seul la classe du système d'affichage LEDClockDisplay remplacera la classe LCDClockDisplay sans rien changer aux autres classe du projet.  

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