Bonjour à tous,
Après une très longue pause, je reprends la description du logiciel de mon réseau hivernal.
Classe AffichageComme le montre la vidéo de 2019, le réseau comporte un affichage LCD en face avant destiné à afficher l'état de la gare cachée ainsi que le mode de fonctionnement du réseau: manuel, semi-automatique, automatique.
Par exemple, voies vides, mode automatique:
Un train dans chaque sens sur chaque voie, mode manuel:
La classe
Affichage sera utilisée par la classe
GareCachee. Le header est le suivant:
//
// Affichage.h
//
// Gestion de l'affichage LCD.
//
// 2019.02.19 Création.
#ifndef AFFICHAGE_H
#define AFFICHAGE_H
#include <LiquidCrystal_I2C.h>
#include <stdint.h>
#include "Hardware.h"
#include "Tache.h"
// États de la gare cachée.
#define E_INCONNU 0
#define E_LIBRE 1
// Entrée par la gauche et sortie par la droite.
#define E_ENTREE_G 2
#define E_ARRIVE_G 3
#define E_ARRETE_D 4
#define E_SORTIE_D_DEM 5
#define E_AIG_SORT_D 6
#define E_SORTANT_D1 7
#define E_SORTANT_D2 8
// Entrée par la droite et sortie par la gauche.
#define E_ENTREE_D 9
#define E_ARRIVE_D 10
#define E_ARRETE_G 11
#define E_SORTIE_G_DEM 12
#define E_AIG_SORT_G 13
#define E_SORTANT_G1 14
#define E_SORTANT_G2 15
#define E_TRAVERS 16
#define NB_VOIES_GC 2
#define GC1 0
#define GC2 1
#define NB_AIG_GC 2
#define A4 0
#define A5 1
class Affichage: public Tache
{
public:
Affichage ();
void setup ();
void loop ();
void afficherVoieGC (uint8_t nxVoie, uint8_t exEtat);
void afficherAigGC (uint8_t nxAig, uint8_t exEtat);
void afficherMode (uint8_t nxMode);
void afficherTempo (const char acxLcd0[], const char acxLcd1[], uint32_t nxDuree);
private:
uint8_t aEtatVoies[NB_VOIES_GC];
uint8_t aEtatAig[NB_AIG_GC];
uint8_t eEtat;
uint32_t tDebut;
uint32_t nDureeTempo;
uint8_t nMode; // Mode de fonctionnement.
char acLcd [LCD_LIGNES][LCD_CHARS];
LiquidCrystal_I2C lcd;
void afficherGC (void);
void rafraichir (void);
};
#endif
Comme l'affichage doit tenir compte de l'état de la gare cachée, il m'a semblé naturel que les états possibles de la gare cachée soient définis ici.
À l'instar des autres classes du programme, celle-ci hérite de la classe
Tache, ce qui permet l'appel automatique de ses méthodes virtuelles
setup et
loop. Les autres méthodes publiques affichent l'état des voies de la gare cachée, l'état des aiguilles, le mode de fonctionnement, ainsi que des messages temporaires.
Dans la partie privée, on remarquera la déclaration d'une instance de la classe
LiquidCrystal_I2C. La classe fonctionnerait aussi très bien avec un affichage LCD connecté à 4 ou 8 broches de l'Arduino. J'ai fait le choix d'une liaison I2C par un câble blindé car elle est insensible aux perturbations causées par les moteurs d'aiguilles à solénoïde.
Le fichier .cpp de la classe est:
#include <LiquidCrystal.h>
//
// Affichage.cpp
//
// 2019.02.19 Création.
#include <Arduino.h>
#include <LiquidCrystal_I2C.h>
#include <stdint.h>
#include "Affichage.h"
#include "Aiguilles.h"
#include "Boutons.h"
#include "Hardware.h"
const uint8_t wagonChar[] = {
0b00000,
0b11111,
0b10101,
0b11111,
0b11111,
0b01010,
0b00000,
0b00000
};
const uint8_t locoDChar[] = {
0b00000,
0b11000,
0b11010,
0b11111,
0b11111,
0b01010,
0b00000,
0b00000
};
const uint8_t locoGChar[] = {
0b00000,
0b00011,
0b01011,
0b11111,
0b11111,
0b01010,
0b00000,
0b00000
};
const uint8_t A4devieChar[] = {
0b00000,
0b00000,
0b00000,
0b10000,
0b01000,
0b00100,
0b00010,
0b00001
};
const uint8_t A5devieChar[] = {
0b00000,
0b00000,
0b00000,
0b00001,
0b00010,
0b00100,
0b01000,
0b10000
};
const uint8_t jonctA4Char[] = {
0b10000,
0b01000,
0b00100,
0b00011,
0b00000,
0b00000,
0b00000,
0b00000
};
const uint8_t jonctA5Char[] = {
0b00001,
0b00010,
0b00100,
0b11000,
0b00000,
0b00000,
0b00000,
0b00000
};
#define C_WAGON 0
#define C_LOCO_D 1
#define C_LOCO_G 2
#define C_A4_DEV 3
#define C_A5_DEV 4
#define C_JCT_A4 5
#define C_JCT_A5 6
#define C_FLEC_G 0b01111111
#define C_FLEC_D 0b01111110
// Pour centrer l'affichage de la gare cachée.
#define POS_INIT 2
#define POS_A4 (POS_INIT+1)
#define POS_TRAIN (POS_INIT+4)
#define LONG_TRAIN 4
#define POS_A5 (POS_TRAIN+LONG_TRAIN+2)
#define POS_JCT_A4 (POS_A4+1)
#define POS_JCT_A5 (POS_A5-1)
#define PERIODE 20
// États possibles de l'affichage.
#define E_GARE_CACHEE 0
#define E_GARE_CACHEE_NOUV 1
#define E_MSG_TEMPO 2
#define DUREE_MSG_DEMARRAGE 7000
//Affichage::Affichage () : lcd (LiquidCrystal (LCD_RS, LCD_RW, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7)) {}
Affichage::Affichage () : lcd (LiquidCrystal_I2C (0x27, LCD_CHARS, LCD_LIGNES)) {}
void Affichage::setup ()
{
for (uint8_t nVoie = 0; nVoie < NB_VOIES_GC; nVoie++)
{
aEtatVoies[nVoie] = E_INCONNU;
} // for
for (uint8_t nAig = 0; nAig < NB_AIG_GC; nAig++)
{
aEtatAig[nAig] = NORMAL;
} // for
for (uint8_t nLig = 0; nLig < LCD_LIGNES; nLig++)
{
for (uint8_t nChar = 0; nChar < LCD_CHARS; nChar++)
{
acLcd [nLig][nChar] = ' ';
} // for
} // for
lcd.init ();
lcd.backlight ();
lcd.clear ();
lcd.createChar (C_WAGON, (uint8_t *)wagonChar);
lcd.createChar (C_LOCO_D, (uint8_t *)locoDChar);
lcd.createChar (C_LOCO_G, (uint8_t *)locoGChar);
lcd.createChar (C_A4_DEV, (uint8_t *)A4devieChar);
lcd.createChar (C_A5_DEV, (uint8_t *)A5devieChar);
lcd.createChar (C_JCT_A4, (uint8_t *)jonctA4Char);
lcd.createChar (C_JCT_A5, (uint8_t *)jonctA5Char);
eEtat = E_GARE_CACHEE_NOUV;
afficherTempo (RESEAU_HIVERNAL, VERSION, DUREE_MSG_DEMARRAGE);
} // Affichage::setup
#define BLANCS_1ERE 2
#define TRAITS_1ERE 12
#define BLANCS_2EME 4
#define TRAITS_2EME 6
void Affichage::loop ()
{
static uint8_t ePrev = 255;
#ifdef DEBUG_LCD
if (eEtat != ePrev)
{
Serial.print (millis (), DEC);
Serial.print (" - entrée loop - ");
Serial.println (eEtat, DEC);
}
ePrev = eEtat;
#endif
switch (eEtat)
{
case E_MSG_TEMPO:
if ((millis () - tDebut) > nDureeTempo)
{
eEtat = E_GARE_CACHEE_NOUV;
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.println (" - loop - E_GARE_CACHEE_NOUV");
#endif
} // if
break;
case E_GARE_CACHEE_NOUV:
afficherGC ();
eEtat = E_GARE_CACHEE;
tDebut = millis ();
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.println (" - loop - E_GARE_CACHEE");
#endif
break;
case E_GARE_CACHEE:
if ((millis () - tDebut) > PERIODE)
{
tDebut = millis ();
rafraichir ();
} // if
break;
} // switch
} // Affichage::loop
void Affichage::afficherVoieGC (uint8_t nxVoie, uint8_t exEtat)
{
aEtatVoies[nxVoie] = exEtat;
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.print (" - entrée afficherVoieGC: ");
Serial.println (eEtat, DEC);
#endif
if (eEtat != E_MSG_TEMPO)
{
eEtat = E_GARE_CACHEE_NOUV;
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.println (" - afficherVoieGC - E_GARE_CACHEE_NOUV");
#endif
} // if
} // Affichage::afficherVoieGC
void Affichage::afficherAigGC (uint8_t nxAig, uint8_t exEtat)
{
aEtatAig[nxAig] = exEtat;
if (eEtat != E_MSG_TEMPO)
{
eEtat = E_GARE_CACHEE_NOUV;
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.println (" - afficherAigGC - E_GARE_CACHEE_NOUV");
#endif
} // if
} // Affichage::afficherAigGC
void Affichage::afficherMode (uint8_t nxMode)
{
nMode = nxMode;
if (eEtat != E_MSG_TEMPO)
{
eEtat = E_GARE_CACHEE_NOUV;
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.println (" - afficherMode - E_GARE_CACHEE_NOUV");
#endif
} // if
} // Affichage::afficherMode
void Affichage::afficherTempo (const char acxLcd0[], const char acxLcd1[], uint32_t nxDuree)
{
uint8_t nChar;
#ifdef DEBUG_LCD
Serial.print (millis (), DEC);
Serial.print (" - E_MSG_TEMPO: ");
Serial.print (acxLcd0);
Serial.print (" / ");
Serial.println (acxLcd1);
#endif
if ((eEtat == E_GARE_CACHEE) || (eEtat == E_GARE_CACHEE_NOUV))
eEtat = E_MSG_TEMPO;
nDureeTempo = nxDuree;
tDebut = millis ();
for (nChar = 0; nChar < LCD_CHARS; nChar++)
{
acLcd[0][nChar] = ' ';
acLcd[1][nChar] = ' ';
} // for
for (nChar = 0; nChar < (strlen (acxLcd0) <= LCD_CHARS ? strlen (acxLcd0) : LCD_CHARS); nChar++)
{
acLcd[0][nChar] = acxLcd0[nChar];
} // for
for (nChar = 0; nChar < (strlen (acxLcd1) <= LCD_CHARS ? strlen (acxLcd1) : LCD_CHARS); nChar++)
{
acLcd[1][nChar] = acxLcd1[nChar];
} // for
rafraichir ();
} // Affichage::afficherTempo
void Affichage::afficherGC (void)
{
uint8_t nLig, nChar, n;
// 1ère ligne: caractères fixes.
nLig = 0;
nChar = 0;
for (n = 0; n < BLANCS_1ERE; n++)
{
acLcd [nLig][nChar++] = ' ';
} // for
for (n = 0; n < TRAITS_1ERE; n++)
{
acLcd [nLig][nChar++] = '-';
} // for
for (n = 0; n < BLANCS_1ERE; n++)
{
acLcd [nLig][nChar++] = ' ';
} // for
// 2ème ligne: caractères fixes.
nLig = 1;
nChar = 0;
for (n = 0; n < BLANCS_2EME; n++)
{
acLcd [nLig][nChar++] = ' ';
} // for
acLcd [nLig][nChar++] = C_JCT_A4;
for (n = 0; n < TRAITS_2EME; n++)
{
acLcd [nLig][nChar++] = '-';
} // for
acLcd [nLig][nChar++] = C_JCT_A5;
for (n = 0; n < BLANCS_2EME; n++)
{
acLcd [nLig][nChar++] = ' ';
} // for
// L'état des aiguilles.
if (aEtatAig[A4] == NORMAL)
{
acLcd [0][POS_A4] = '-';
}
else
{
acLcd [0][POS_A4] = C_A4_DEV;
} // if
if (aEtatAig[A5] == NORMAL)
{
acLcd [0][POS_A5] = '-';
}
else
{
acLcd [0][POS_A5] = C_A5_DEV;
} // if
// Puis l'état d'occupation des voies.
for (uint8_t nVoie = 0; nVoie < NB_VOIES_GC; nVoie++)
{
nLig = nVoie;
nChar = POS_TRAIN;
switch (aEtatVoies[nVoie])
{
case E_INCONNU:
acLcd [nLig][nChar++] = '?';
acLcd [nLig][nChar++] = '?';
acLcd [nLig][nChar++] = '?';
acLcd [nLig][nChar++] = '?';
break;
case E_LIBRE:
acLcd [nLig][nChar++] = '-';
acLcd [nLig][nChar++] = '-';
acLcd [nLig][nChar++] = '-';
acLcd [nLig][nChar++] = '-';
break;
case E_ARRETE_G:
acLcd [nLig][nChar++] = C_LOCO_G;
acLcd [nLig][nChar++] = C_WAGON;
acLcd [nLig][nChar++] = C_WAGON;
acLcd [nLig][nChar++] = C_WAGON;
break;
case E_ARRETE_D:
acLcd [nLig][nChar++] = C_WAGON;
acLcd [nLig][nChar++] = C_WAGON;
acLcd [nLig][nChar++] = C_WAGON;
acLcd [nLig][nChar++] = C_LOCO_D;
break;
case E_TRAVERS:
acLcd [nLig][nChar++] = C_FLEC_G;
acLcd [nLig][nChar++] = '-';
acLcd [nLig][nChar++] = '-';
acLcd [nLig][nChar++] = C_FLEC_D;
break;
} // switch
} // for
// Terminer par le mode de fonctionnement.
switch (nMode)
{
case MODE_MANUEL: acLcd[1][0] = 'M'; break;
case MODE_SEMI: acLcd[1][0] = 'S'; break;
case MODE_AUTO: acLcd[1][0] = 'A'; break;
} // switch
rafraichir ();
} // Affichage::afficherGC
void Affichage::rafraichir (void)
{
for (uint8_t nLig = 0; nLig < LCD_LIGNES; nLig++)
{
lcd.setCursor (0, nLig);
for (uint8_t nChar = 0; nChar < LCD_CHARS; nChar++)
{
lcd.write (acLcd [nLig][nChar]);
} // for
} // for
} // Affichage::rafraichir
Au début de ce fichier, des tableaux d'octets contiennent les caractères personnalisables utilisés pour afficher les trains et les positions des aiguilles. Une seule version du wagon est suffisante, mais il faut des versions gauche et droite de la loco pour distinguer le sens de marche des trains. Les octets en binaire définissent les bits à allumer pour chaque ligne, on retrouve bien les caractères affichés sur les 2 exemples ci-dessus. Il aurait été possible de déclarer ces caractères en mémoire programme plutôt qu'en RAM à l'aide de la directive PROGMEM, mais il me reste encore bien assez de RAM !
La création de ces caractères se fait dans la méthode
setup.
Les données à afficher sont préparées dans les tableaux
aEtatVoies,
aEtatAig et
acLcd. C'est ensuite la méthode privée
rafraichir qui effectue l'envoi à l'affichage LCD.
La classe comporte elle-même 3 états:
- E_GARE_CACHEE: état stable après mise à jour de l'affichage.
- E_GARE_CACHEE_NOUV: un nouvel état doit être affiché. C'est par exemple le cas si on a appelé l'une des 3 méthodes publiques: afficherVoiesGC, afficherAigGC, etc.
- E_GARE_CACHEE_TEMPO: affichage d'un message temporaire.
Portez-vous bien et meilleures salutations.