/*******************************************************************************************\
|* *|
|* Horloge LCD 4x20 pilotée par un serveur NTP (Liaison Parallèle). *|
|* *|
\*******************************************************************************************/
/*******************************************************************************************\
|* Liste des Bibliothèques. *|
\*******************************************************************************************/
#include <WiFi.h> // Bibliothèque de raccordement au réseau WiFi
#include <LiquidCrystal.h> // Bibliothèque de gestion des afficheurs LCD alphanumériques
#include "time.h" // Bibliothèque de gestion du temps
#include "esp_sntp.h" // Bibliothèque du serveur NTP
/*******************************************************************************************\
|* Paramètrage de l'afficheur LCD : bus parallèle 4 bits, 20 caractères et 4 lignes *|
\*******************************************************************************************/
const uint8_t rs = 32, en = 33, d4 = 25, d5 = 26, d6 = 27, d7 = 14 ;
LiquidCrystal LCD(rs, en, d4, d5, d6, d7) ;
/*******************************************************************************************\
* Liste des Variables. *|
\*******************************************************************************************/
// Variables d'Entrées/Sorties
const int8_t BP_backlight = 15 ; // Interrupteur de commande du rétroéclairage de l'afficheur LCD
bool status_BP_backlight = false ; // État du bouton poussoir de commande du rétroéclairage de l'afficheur LCD
const int8_t LCD_backlight = 12 ; // Sortie de rétroéclairage de l'afficheur LCD
bool status_LCD_backlight = true ; // État du rétroéclairage de l'afficheur LCD
// Variables de communication
const char ssid[32] = "WokwiGUEST" ; // Nom du réseau WiFi
const char password[32] = "" ; // Mot de passe du réseau WiFi
const char hostname[32] = "Horloge ESP32" ; // Nom de l'hôte sur le réseau WiFi
const char ntpServer[32] = "pool.ntp.org" ; // Adresse du serveur NTP
const char timeZone[64] = "CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00" ; // Définition du fuseau horaire (voir https://sites.google.com/a/usapiens.com/opnode/time-zones)
// Variables temporelles
const int16_t NTPmaj = 10 ; // Période de mise à jour sur le serveur NTP (en secondes)
struct tm timeinfo ; // Structure des informations du temps récupéré sur le serveur NTP
int16_t Heure, Minute, Seconde, JourdelaSemaine, Jour, Mois, Annee ; // Variables date et heures
String JourdelaSemaine_en_Texte[7] = {"LUNDI","MARDI","MERCREDI","JEUDI","VENDREDI","SAMEDI", "DIMANCHE"} ;
String Mois_en_Texte[12] = {"JANVIER","FEVRIER","MARS","AVRIL","MAI","JUIN","JUILLET","AOUT","SEPTEMBRE","OCTOBRE","NOVEMBRE","DECEMBRE"} ;
uint64_t TON1 = 0 ; // Valeur à atteindre par la temporisation n° 1
uint64_t TON2 = 0 ; // Valeur à atteindre par la temporisation n° 2
// Caractères customisés
byte x10[8] = {0x07,0x07,0x07,0x00,0x00,0x00,0x00,0x00} ;
byte x11[8] = {0x1F,0x1F,0x1F,0x00,0x00,0x00,0x00,0x00} ;
byte x12[8] = {0x1C,0x1C,0x1C,0x00,0x00,0x00,0x00,0x00} ;
byte x13[8] = {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07} ;
byte x14[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C} ;
byte x15[8] = {0x1F,0x1F,0x1F,0x07,0x07,0x07,0x07,0x07} ;
byte x16[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00} ;
byte x17[8] = {0x00,0x00,0x00,0x00,0x0E,0x0E,0x0E,0x00} ;
// Variables d'affichage
String Date ; // Date à afficher sur le LCD
String Date_Old ; // Date affichée précédemment
int16_t iCaractere = 0 ; // Premier caractère à afficher
bool notSyncNTP = false ; // Notification de synchronisation au serveur NTP
/*******************************************************************************************\
* Programme d'initialisation à n'exécuter qu'une seule fois. *|
\*******************************************************************************************/
void setup() // Programme d'initialisation à n'exécuter qu'une seule fois
{
// Mise en service de la communication série vers la console
Serial.begin(115200) ; // Vitesse de communication : 115 200 Bauds
// Initialisation des entrées/sorties
pinMode (BP_backlight, INPUT_PULLUP) ; // Entrée du bouton poussoir de commande du rétroéclairage de l'afficheur LCD
pinMode (LCD_backlight, OUTPUT) ; // Sortie de commande du rétroéclairage de l'afficheur LCD
// Configuration de l'afficheur LCD
LCD_config() ; // Configuration de l'afficheur LCD
// Connexion au réseau WiFi
WiFi_connect() ; // Connexion au réseau WiFi
// Initialisation de la synchronisation avec le serveur NTP
NTP_init() ; // Initialisation de la synchronisation avec le serveur NTP
}
/*******************************************************************************************\
|* Programme principal à exécuter en boucle répétitive *|
\*******************************************************************************************/
void loop() // Programme principal à exécuter en boucle répétitive
{
if (notSyncNTP) // Synchronisation au serveur NTP effectuée ?
{
printSyncNTP() ; // Affichage de la notification de synchronisation au serveur NTP
}
if (Tempo1(250)) // Période de 0,25 secondes
{
// Détermination de l'heure et de la date locale
localTime() ; // Détermination de l'heure et de la date locale
// Affichage de la date et de l'heure
printLocalTime() ; // Affichage de la date et de l'heure
}
// Commande du rétroéclairage de l'afficheur LCD
if (backlightLCD()) digitalWrite(LCD_backlight, LOW) ; // Allumage du rétroéclairage de l'afficheur LCD
else digitalWrite(LCD_backlight, HIGH) ; // Extinction du rétroéclairage de l'afficheur LCD
}
/*******************************************************************************************\
| Fonction de configuration de l'afficheur LCD. *|
\*******************************************************************************************/
void LCD_config() // Fonction de configuration de l'afficheur LCD
{
// Initilisation de l'afficheur
LCD.begin(20,4) ; // Initialisation de l'afficheur LCD
digitalWrite(LCD_backlight, LOW) ; // Activation du rétro-éclairage
LCD.clear() ; // Effacement de l'afficheur LCD
// Transfert des caractères customisés
LCD.createChar(0,x10) ; // Transfert du caractère customisé n° 0
LCD.createChar(1,x11) ; // Transfert du caractère customisé n° 1
LCD.createChar(2,x12) ; // Transfert du caractère customisé n° 2
LCD.createChar(3,x13) ; // Transfert du caractère customisé n° 3
LCD.createChar(4,x14) ; // Transfert du caractère customisé n° 4
LCD.createChar(5,x15) ; // Transfert du caractère customisé n° 5
LCD.createChar(6,x16) ; // Transfert du caractère customisé n° 6
LCD.createChar(7,x17) ; // Transfert du caractère customisé n° 7
}
/*******************************************************************************************\
| Fonction de connexion au réseau WiFi. *|
\*******************************************************************************************/
void WiFi_connect() // Fonction de connexion au réseau WiFi
{
// Initialisation du WiFi
WiFi.mode(WIFI_STA) ; // Définition du mode station du WiFi
WiFi.setHostname(hostname) ; // Définition du nom de l'hôte sur le réseau WiFi
// Connexion au réseau WiFi
Serial.printf("Connexion au : %s", ssid) ; // Affichage du SSID de connexion
Serial.println() ;
LCD.setCursor(0,0) ; LCD.print("Connexion au : ") ; LCD.setCursor(0,1) ; LCD.print(ssid) ; // Affichage LCD du réseau à connecter
WiFi.begin (ssid, password) ; // Connexion au réseau WiFi
LCD.setCursor(0,2) ; // Passage à la ligne suivante du LCD
int i = 0 ;
while (WiFi.status() != WL_CONNECTED) // Attente de connexion
{
if ( i < 60 )
{
if ((i % 20) == 0) // Le curseur est en bout de ligne ?
{
LCD.setCursor(0,2) ;
for(int j = 0 ; j < 20 ; j ++) LCD.print(" ") ; // Effacement de la ligne
LCD.setCursor(0,2) ;
}
LCD.print("*") ; // Affichage LCD d'une *
i ++ ;
delay(100) ; // Attente de 0,1 secondes
}
else goto WiFi_Fault ; // Erreur de connexion WiFi
}
delay(1000) ; // Attente de 1 seconde
// Affichage, sur le Terminal Série, des informations de connexion
Serial.println("") ;
Serial.printf(R"(
Détails de la connexion :
-----------------------------------------------------
Nom du Réseau WiFi : %s
Nom de l'Hôte : %s
Adresse IP : %s
Adresse Mac : %s
Force du signal WiFi : %d %%
)", WiFi.SSID().c_str(),
WiFi.getHostname(),
WiFi.localIP().toString().c_str(),
WiFi.macAddress().c_str(),
WiFi.RSSI()) ;
// Affichage LCD des informations de connexion
LCD.clear() ; // Effacement de l'afficheur LCD
LCD.setCursor(0,0) ; LCD.print(WiFi.getHostname()) ; // Affichage LCD de la connexion WiFi
LCD.setCursor(0,1) ; LCD.print(" est connecte") ;
LCD.setCursor(0,2) ; LCD.printf("WiFi : %s", WiFi.SSID()) ; // Affichage LCD du nom du réseau
LCD.setCursor(0,3) ; LCD.printf("IP : %s", WiFi.localIP().toString().c_str()) ; // Affichage LCD de l'adresse IP
delay(5000) ; // Attente de 5 secondes
LCD.clear() ; // Effacement de l'afficheur LCD
return ;
WiFi_Fault : // Erreur de connexion WiFi
LCD.setCursor(0,2) ;
for(int j = 0 ; j < 20 ; j ++) LCD.print(" ") ; // Effacement de la ligne
delay(1000) ; // Attente de 1 seconde
LCD.setCursor(0,2) ;
LCD.print("Erreur de connexion") ; // Affichage LCD de l'erreur de connexion WiFi
delay(1000) ; // Attente de 1 seconde
goto WiFi_Fault ;
}
/*******************************************************************************************\
| Fonction d'initialisation de la synchronisation avec le serveur NTP. *|
\*******************************************************************************************/
void NTP_init() // Initialisation de la synchronisation avec le serveur NTP
{
// Affichage LCD des informations de synchronisation avec le serveur NTP
LCD.setCursor(0,0) ; LCD.print("Synchronisation avec") ; // Affichage LCD de la synchronisation NTP
LCD.setCursor(0,1) ; LCD.print("le serveur NTP") ; // Affichage LCD de la synchronisation NTP
// Initialisation de la synchronisation au serveur NTP
sntp_set_time_sync_notification_cb(time_sync_notification) ; // Notification de synchronisation au serveur NTP terminée
//sntp_set_sync_interval(NTPmaj * 1000UL) ; // Définition de la période de synchronisation au serveur NTP
configTzTime(timeZone, ntpServer) ; // Synchronisation au serveur NTP
delay(2000); // Attente de 2 secondes
}
/*******************************************************************************************\
| Fonction de notification de synchronisation au serveur NTP terminée. *|
\*******************************************************************************************/
void time_sync_notification(struct timeval *tv) // Fonction de notification de synchronisation au serveur NTP terminée
{
notSyncNTP = true ;
}
/*******************************************************************************************\
| Fonction d'affichage de la notification de synchronisation au serveur NTP. *|
\*******************************************************************************************/
void printSyncNTP() // Fonction d'affichage de la notification de synchronisation au serveur NTP
{
LCD.clear() ; // Effacement de l'afficheur LCD
LCD.setCursor(0, 1) ;
LCD.print("Mise a jour du temps") ; // Affichage LCD du texte de mise à jour du temps
localTime() ; // Acquisition de la date et de l'heure locale
Serial.println("\nMise a jour du temps") ; // Affichage sur le Terminal Série de la mise à jour du temps
Serial.printf("le %d/%02d/%d a %d:%02d:%02d", Jour, Mois, Annee, Heure, Minute, Seconde) ; // Affichage sur le Terminal Série de la date et l'heure locale
Serial.println() ;
delay(2000) ; // Attente de 2 secondes
LCD.clear() ; // Effacement de l'afficheur LCD
notSyncNTP = false ;
}
/*******************************************************************************************\
| Fonction d'acquisition de la date et de l'heure locale. *|
\*******************************************************************************************/
void localTime() // Fonction d'acquisition de la date et de l'heure locale
{
// Acquisition de la date et de l'heure locale
getLocalTime(&timeinfo) ; // Acquisition de la date et de l'heure locale
// Extraction de la date et de l'heure
Heure = timeinfo.tm_hour ; // Extraction de l'heure
Minute = timeinfo.tm_min ; // Extraction des minutes
Seconde = timeinfo.tm_sec ; // Extaction des secondes
if (timeinfo.tm_wday == 0) JourdelaSemaine = 7 ; // Extaction du jour de la semaine
else JourdelaSemaine = timeinfo.tm_wday ;
Jour = timeinfo.tm_mday ; // Extaction du jour
Mois = timeinfo.tm_mon + 1 ; // Extaction du mois
Annee = timeinfo.tm_year + 1900 ; // Extaction de l'année
}
/*******************************************************************************************\
| Fonction d'inscription de la date et de l'heure sur l'afficheur LCD. *|
\*******************************************************************************************/
void printLocalTime() // Fonction d'inscription de la date et de l'heure sur l'afficheur LCD
{
// Écriture sur l'afficheur LCD de l'heure locale
if ((Heure/10)==0) doNumber(10,0,0) ; // Affichage d'un triple espace
else doNumber((Heure/10),0,0) ; // Affichage du chiffre des dizaines d'heure
doNumber((Heure%10),0,3) ; // Affichage du chiffre des unités d'heure
doNumber(11,0,6) ; // Affichage des deux points
doNumber((Minute/10),0,7) ; // Affichage du chiffre des dizaines de minute
doNumber((Minute%10),0,10) ; // Affichage du chiffre des unités de minute
doNumber(11,0,13) ; // Affichage des deux points
doNumber((Seconde/10),0,14) ; // Affichage du chiffre des dizaines de seconde
doNumber((Seconde%10),0,17) ; // Affichage du chiffre des unités de seconde
// Écriture sur l'afficheur LCD de la date
Date = " * "
+ JourdelaSemaine_en_Texte[(JourdelaSemaine - 1)]
+ " "
+ Jour
+ " "
+ Mois_en_Texte[(Mois - 1)]
+ " "
+ Annee ;
// La date a changée ?
if (Date != Date_Old)
{
iCaractere = 0 ; // Réécriture depuis le premier caractère
Date_Old = Date ;
}
// Initialisation du texte à afficher sur l'afficheur LCD
String D = "" ;
// Construction de la chaîne de caractères à afficher sur LCD
for (int i = 0 ; i < 20 ; i ++) {D += Date.charAt((iCaractere + i) % Date.length()) ;}
// Détermination du prochain premier caractère à afficher
if (iCaractere < Date.length()) iCaractere ++ ;
else iCaractere = 0 ;
// Affichage du Texte sur le LCD (le texte à afficher est une chaîne de caractères nommée D)
LCD.setCursor(0,3) ; LCD.print(D) ; // Envoi de la chaîne de caractères sur le LCD
}
/*******************************************************************************************\
|* Fonction d'allumage et d'extinction du rétroéclairage de l'afficheur LCD. *|
|* *|
|* Retourne "true" si le rétroéclairage doit être allumé. *|
|* Retourne "false" si le rétroéclairage doit être éteint. *|
\*******************************************************************************************/
bool backlightLCD() // Fonction d'allumage et d'extinction du rétroéclairage de l'afficheur LCD
{
// Si front montant du bouton de poussoir, alors inversion de l'état du rétroéclairage
if (!status_BP_backlight && !digitalRead(BP_backlight)) // Front montant du bouton de poussoir du rétroéclairage ?
{
status_LCD_backlight = !status_LCD_backlight ; // Inversion de l'état du rétroéclairage
status_BP_backlight = true ; // Mémorisation de l'état du bouton poussoir
}
// Si bouton poussoir relaché, alors remise à zéro de la mémorisation de l'état du bouton poussoir
if (digitalRead(BP_backlight)) // Bouton poussoir relaché ?
{
status_BP_backlight = false ; // Mémorisation de l'état du bouton poussoir
}
// Renvoi de l'état du rétroéclairage
return(status_LCD_backlight) ; // Renvoi de l'état du rétroéclairage
}
/*******************************************************************************************\
* Fonction retournant le nombre de millisecondes depuis le démarrage du programme. *|
* *|
* Retourne le nombre de milliseconde depuis le démarrage du programme sous la forme d’un *|
* nombre entier non signé de 64 bits. *|
\*******************************************************************************************/
uint64_t superMillis() // Fonction retournant le nombre de millisecondes depuis le démarrage du programme
{
static uint32_t nbRollover = 0 ; // Nombre de dépacement de millis
static uint32_t previousMillis = 0 ; // Sauvegarde du nombre de millis précédent
uint32_t currentMillis = millis() ; // Sauvegarde du nombre de millis courant
if (currentMillis < previousMillis) {nbRollover ++ ;} // Dépassement de millis ?
previousMillis = currentMillis ; // Sauvegarde de la valeur courante de millis
uint64_t finalMillis = nbRollover ; // Construction de finalMillis
finalMillis <<= 32 ; // Décallage de 32 bits vers la gauche
finalMillis += currentMillis ; // Ajout de currentMillis
return finalMillis ; // Retourne le nombre de millisecondes depuis le démarrage du programme
}
/*******************************************************************************************\
|* Fonctions réalisants des temporisations. *|
|* *|
|* TONv : présélection de la temporisation en millisecondes (constante 32 bits). *|
|* Retourne "true" si la temporisation est terminée. *|
|* Retourne "false" si la temporisation est en cours. *|
\*******************************************************************************************/
bool Tempo1(uint32_t TONv) // Fonction réalisant la temporisation n° 1
{
uint64_t Temps = superMillis() ; // Sauvegarde du temps courant
if (Temps > TON1) {TON1 = Temps + TONv ; return true ; } // Temporisation écoulée ?
else return false ;
}
bool Tempo2(uint32_t TONv) // Fonction réalisant la temporisation n° 2
{
uint64_t Temps = superMillis() ; // Sauvegarde du temps courant
if (Temps > TON2) {TON2 = Temps + TONv ; return true ; } // Temporisation écoulée ?
else return false ;
}
/*******************************************************************************************\
|* Fonction dessinant un chiffre en triple hauteur & triple largeur. *|
|* *|
|* Le chiffre "num" est à positionner à la ligne "l" et à la colonne "c". *|
\*******************************************************************************************/
void doNumber(byte num, byte l, byte c) // Fonction dessinant un chiffre en triple hauteur & triple largeur
{
switch(num)
{
case 0 : LCD.setCursor(c,l) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ; // Affichage du chiffre "0"
LCD.setCursor(c,l+1) ; LCD.write(byte(3)) ; LCD.write(byte(6)) ;
LCD.write(byte(4)) ; LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ;
LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 1 : LCD.setCursor(c,l) ; LCD.write(byte(6)) ; LCD.write(byte(5)) ; LCD.write(byte(6)) ; // Affichage du chiffre "1"
LCD.setCursor(c,l+1) ; LCD.write(byte(6)) ; LCD.write(byte(3)) ; LCD.write(byte(6)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(6)) ; LCD.write(byte(0)) ; LCD.write(byte(6)) ;
break ;
case 2 : LCD.setCursor(c,l) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ; // Affichage du chiffre "2"
LCD.setCursor(c,l+1) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 3 : LCD.setCursor(c,l) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ; // Affichage du chiffre "4"
LCD.setCursor(c,l+1) ; LCD.write(byte(6)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 4 : LCD.setCursor(c,l) ; LCD.write(byte(3)) ; LCD.write(byte(6)) ; LCD.write(byte(4)) ; // Affichage du chiffre "4"
LCD.setCursor(c,l+1) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; LCD.write(byte(2)) ;
break ;
case 5 : LCD.setCursor(c,l) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ; // Affichage du chiffre "5"
LCD.setCursor(c,l+1) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 6 : LCD.setCursor(c,l) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ; // Affichage du chiffre "6"
LCD.setCursor(c,l+1) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 7 : LCD.setCursor(c,l) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ; // Affichage du chiffre "7"
LCD.setCursor(c,l+1) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; LCD.write(byte(2)) ;
break ;
case 8 : LCD.setCursor(c,l) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ; // Affichage du chiffre "8"
LCD.setCursor(c,l+1) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 9 : LCD.setCursor(c,l) ; LCD.write(byte(3)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ; // Affichage du chiffre "9"
LCD.setCursor(c,l+1) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(4)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(0)) ; LCD.write(byte(1)) ; LCD.write(byte(2)) ;
break ;
case 10 : LCD.setCursor(c,l) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; // Affichage d'un triple espace
LCD.setCursor(c,l+1) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ; LCD.write(byte(6)) ;
break ;
case 11 : LCD.setCursor(c,l) ; LCD.write(byte(7)) ; // Affichage des deux points
LCD.setCursor(c,l+1) ; LCD.write(byte(7)) ;
LCD.setCursor(c,l+2) ; LCD.write(byte(6)) ;
break ;
}
}