Dans ce tutoriel, vous allez apprendre à construire une horloge numérique toujours précise avec l’écran CrowPanel 3,5″ ESP32. Nous allons synchroniser notre horloge via WiFi avec un fournisseur de temps sur Internet et utiliser la bibliothèque TFT_eSPI pour créer une belle interface utilisateur pour l’écran.
C’est parti !
Matériel nécessaire
Pour ce projet, vous n’aurez besoin que du CrowPanel 3,5″ ESP32 Display d’ELECROW et de l’IDE Arduino. Le panneau est livré avec un câble USB et un câble DuPont 4 broches, donc aucun câble supplémentaire n’est nécessaire.

CrowPanel 3,5″ ESP32 Display
Makerguides is a participant in affiliate advertising programs designed to provide a means for sites to earn advertising fees by linking to Amazon, AliExpress, Elecrow, and other sites. As an Affiliate we may earn from qualifying purchases.
Caractéristiques du CrowPanel 3,5″ ESP32 Display
Le CrowPanel 3.5″ ESP32 Display d’ELECROW est un écran tactile résistif avec une résolution TFT de 480*320 et un ESP32-WROVER-B comme processeur de contrôle. Vous pouvez obtenir l’écran avec un joli boîtier en acrylique (voir photo ci-dessous), ce qui signifie que vous n’avez pas à fabriquer de boîtier vous-même.

En plus de cela, la carte dispose d’un emplacement pour carte TF, d’une interface UART, d’une interface I2C, d’une interface haut-parleur, d’un connecteur batterie avec fonction de charge et d’un petit port GPIO avec deux broches GPIO. Voir le schéma des broches ci-dessous :

Le tableau suivant indique quelles broches GPIO sont attribuées à chacune des trois interfaces IO.
| GPIO_D | IO25 ; IO32 |
| UART | RX(IO16) ; TX(IO17) |
| I2C | SDA(IO22) ; SCL(IO21) |
La carte peut être alimentée via le port USB (5V, 2A) ou en connectant une batterie LiPo standard 3,7V au connecteur BAT. Faites simplement attention à la polarité du connecteur et de la batterie. Si le câble USB et la batterie sont connectés en même temps, la carte chargera la batterie (courant de charge maximal : 500mA).
Vous pouvez programmer la carte avec différents environnements de développement comme Arduino IDE, Espressif IDF, Lua RTOS et Micro Python, et elle est compatible avec la bibliothèque graphique LVGL. Cependant, dans ce tutoriel, nous utiliserons l’IDE Arduino et la bibliothèque graphique TFT_eSPI pour créer l’interface utilisateur de notre horloge.
Série d’écrans CrowPanel ESP32
Notez que le CrowPanel 3,5″ ESP32 Display fait partie d’une famille complète d’écrans allant de 2,4 pouces à 7 pouces. Outre la différence de taille, ils diffèrent aussi par la résolution, le pilote d’affichage et le modèle ESP32. Voir le tableau ci-dessous.

Dans ce tutoriel, nous allons utiliser le 3.5″ display. Mais les exemples de code et la procédure d’installation seraient très similaires pour le 2.4″ display et le 2.8 » display, car ils utilisent le même ou un pilote d’affichage similaire (ILI9341, ILI9488). Voir les sections en jaune dans le tableau ci-dessus.
Pour les écrans plus grands (4,3″, 5″, 7″), cependant, les exemples de code ne fonctionneront probablement pas car la bibliothèque TFT_eSPI ne semble pas prendre en charge leurs pilotes (NV3047, IL6122, EK9716BD3). Je ne l’ai cependant pas testé. N’hésitez pas à poster un commentaire si vous avez essayé.
Créer la structure du projet
Avant d’entrer dans les détails, créons la structure du projet et la configuration pour la bibliothèque TFT_eSPI.
Ouvrez votre IDE Arduino et créez un projet « digiclock » et enregistrez-le (Enregistrer sous …). Cela va créer un dossier « digiclock » avec le fichier « digiclock.ino » à l’intérieur. Dans ce dossier, créez un autre fichier nommé « tft_setup.h« . Votre dossier de projet devrait ressembler à ceci :

Et dans votre IDE Arduino, vous devriez maintenant avoir deux onglets nommés « digiclock.ino » et « tft_setup.h« .

Cliquez sur l’onglet « tft_setup.h » pour ouvrir le fichier et copiez-y le code suivant :
#define ILI9488_DRIVER #define TFT_WIDTH 480 #define TFT_HEIGHT 320 #define TFT_BACKLIGHT_ON HIGH #define TFT_BL 27 #define TFT_MISO 12 #define TFT_MOSI 13 #define TFT_SCLK 14 #define TFT_CS 15 #define TFT_DC 2 #define TFT_RST -1 #define TOUCH_CS 33 #define SPI_FREQUENCY 27000000 #define SPI_TOUCH_FREQUENCY 2500000 #define SPI_READ_FREQUENCY 16000000 #define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH #define LOAD_FONT2 // Font 2. Small 16 pixel high font, needs ~3534 bytes in FLASH, 96 characters #define LOAD_FONT4 // Font 4. Medium 26 pixel high font, needs ~5848 bytes in FLASH, 96 characters #define LOAD_FONT6 // Font 6. Large 48 pixel font, needs ~2666 bytes in FLASH, only characters 1234567890:-.apm #define LOAD_FONT7 // Font 7. 7 segment 48 pixel font, needs ~2438 bytes in FLASH, only characters 1234567890:-. #define LOAD_FONT8 // Font 8. Large 75 pixel font needs ~3256 bytes in FLASH, only characters 1234567890:-. #define LOAD_GFXFF // FreeFonts. Include access to the 48 Adafruit_GFX free fonts FF1 to FF48 and custom fonts
Ce code indique à la bibliothèque TFT_eSPI quel écran nous utilisons. Plus précisément, nous indiquons à la bibliothèque les dimensions de l’écran (TFT_WIDTH, TFT_WIDTH), son pilote d’affichage (ILI9488_DRIVER), les broches SPI utilisées pour le contrôler et quelles polices charger. Sans ces réglages, vous ne pourrez rien afficher sur l’écran.
Pour plus d’informations, consultez le tutoriel CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Nous y expliquons comment configurer l’écran 2,8″. Mais les étapes et descriptions s’appliquent de la même façon à l’écran 3,5″. Seuls les réglages de dimensions et de pilote changent.
Calibrer l’écran tactile
Le CrowPanel 3,5″ est équipé d’un écran tactile résistif que vous devez d’abord calibrer avant de pouvoir l’utiliser avec la bibliothèque TFT_eSPI. Copiez le code ci-dessous dans le fichier « digiclock.ino« , compilez-le et téléversez-le sur le CrowPanel 3,5 ».
// digiclock.ino
#include "tft_setup.h"
#include "TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI();
void setup() {
Serial.begin(115200);
tft.begin();
tft.setRotation(0);
}
void loop() {
uint16_t cal[5];
tft.setRotation(1); // Landscape orientation!
tft.fillScreen(TFT_BLACK);
tft.setCursor(20, 0);
tft.setTextFont(2);
tft.setTextSize(1);
tft.setTextColor(TFT_WHITE, TFT_BLACK);
tft.print("Touch corners ... ");
tft.calibrateTouch(cal, TFT_MAGENTA, TFT_BLACK, 15);
tft.println("done.");
Serial.printf("cal: {%d, %d, %d, %d, %d}\n",
cal[0], cal[1], cal[2], cal[3], cal[4]);
delay(10000);
}
Lorsque le code s’exécute, l’écran affiche une flèche et vous demande de toucher le coin indiqué. Cela sera répété pour les trois autres coins. Utilisez le petit stylet fourni avec l’écran et essayez de toucher les coins le plus précisément possible.

À la fin du processus de calibration, le code affiche les 5 paramètres de calibration (coordonnées des coins et orientation de l’écran) dans le Moniteur Série. Vous devriez voir quelque chose comme ceci :
cal: { 243, 3669, 216, 3553, 7 }
Copiez ces paramètres quelque part, car vous en aurez besoin dans le code de l’horloge. La calibration se répète toutes les 10 secondes, donc vous pouvez recommencer plusieurs fois pour obtenir les paramètres les plus précis.
Pour plus de détails sur la calibration, consultez le tutoriel CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Notez cependant que la calibration y est faite pour l’écran en mode portrait, alors qu’ici nous utilisons l’écran en mode paysage (voir le setRotation(1) dans le code de calibration ci-dessus).
Voyons maintenant comment récupérer l’heure depuis Internet.
Télécharger l’heure depuis Internet
J’aime les horloges qui se règlent automatiquement à l’heure d’été et qui sont toujours précises, pour ne jamais avoir à les régler. Si vous avez le Wi-Fi et Internet, le plus simple est de télécharger régulièrement l’heure actuelle depuis un serveur de temps, puis d’ajuster l’horloge interne de l’ESP32. Pour plus d’infos, consultez le tutoriel sur la création d’un Automatic Daylight Savings Time Clock.
Ici, nous allons utiliser la même méthode que dans ce tutoriel. Nous allons utiliser le WorldTimeAPI pour obtenir l’heure.WorldTimeAPI est un service web simple qui renvoie l’heure actuelle en texte brut ou en JSON. Vous pouvez utiliser leur site pour obtenir l’heure d’un fuseau horaire donné ou basée sur l’adresse IP de votre ordinateur. Nous allons utiliser cette dernière option. C’est plus simple et notre horloge s’ajustera automatiquement à l’heure d’été et aussi si elle est déplacée dans un autre fuseau horaire.
Il est très facile de tester WorldTimeAPI. Cliquez simplement sur ce lien : http://worldtimeapi.org/api/ip ou saisissez-le dans la barre de recherche de votre navigateur. Notez que vous n’avez pas besoin de fournir explicitement votre adresse IP. Le service web la détecte automatiquement selon l’origine de la requête.
Dans votre navigateur, vous devriez voir une sortie similaire à celle-ci (j’ai ajouté un peu de mise en forme pour la lisibilité et masqué mon IP)
{
"abbreviation": "AEDT",
"client_ip": "122.150.000.000",
"datetime": "2023-11-16T12:09:46.409360+11:00",
"day_of_week": 4,
"day_of_year": 320,
"dst": true,
"dst_from": "2023-09-30T16:00:00+00:00",
"dst_offset": 3600,
"dst_until": "2024-04-06T16:00:00+00:00",
"raw_offset": 36000,
"timezone": "Australia/Melbourne",
"unixtime": 1700096986,
"utc_datetime": "2023-11-16T01:09:46.409360+00:00",
"utc_offset": "+11:00",
"week_number": 46
}
Cette sortie est au format JSON. Elle contient d’autres informations en plus de l’heure actuelle. Mais ce qui nous intéresse, c’est le champ datetime, qui donne l’heure locale actuelle. Dans cet exemple, il est "2023-11-16T12:09:46.409360+11:00".
À chaque fois que vous allez sur ce lien, vous obtenez l’heure mise à jour. Attention à ne pas le consulter trop souvent, sinon vous risquez d’être bloqué !
Avec tout ça, nous avons tout ce qu’il faut pour réaliser notre horloge numérique.
Réalisation de l’horloge numérique
Dans cette section, nous allons réaliser l’horloge numérique. Elle ressemblera à ceci :

Elle affiche l’heure actuelle (synchronisée via Internet), le jour, le mois et l’année, ainsi que deux boutons. Avec le bouton « 24h », on peut basculer entre le format 24h et 12h. Le bouton « day » permet de changer la luminosité de l’écran pour le mode nuit ou jour.
Pour réaliser cette horloge, commençons par la structure du projet.
Structure du dossier du projet
À l’étape précédente, vous avez déjà créé le dossier de projet « digiclock » avec un fichier « digiclock.ino » et « tft_setup.h » à l’intérieur. Ajoutez maintenant un autre fichier nommé « datestr.h« . Votre dossier de projet devrait ressembler à ceci :

et votre IDE Arduino devrait maintenant avoir trois onglets : « digiclock.ino« , « datestr.h » et « tft_setup.h » :

Le fichier « tft_setup.h » contient déjà le bon code. Mais les deux autres fichiers (« digiclock.ino« , « datestr.h« ) doivent être mis à jour. Commençons par le fichier « datestr.h« .
Noms des jours et des mois
En plus de l’heure, nous voulons aussi afficher la date actuelle en texte sur l’écran. Par exemple : Jeu, Mai 30, 2024. Cependant, WorldTimeAPI ne nous donne pas les noms du mois ou du jour. Nous devons convertir un numéro de mois, comme 5, en nom de mois « Mai », par exemple.
La bibliothèque TimeLib que nous allons utiliser plus tard propose des fonctions pour cela, mais je n’ai pas réussi à les faire fonctionner. Un problème d’allocation mémoire lors de l’écriture dans une chaîne via sprintf, que je n’avais pas envie de résoudre.
Nous définissons donc nos propres noms de mois et de jours. C’est très simple et cela a l’avantage de pouvoir personnaliser les noms comme vous voulez. Noms complets (Lundi), trois lettres (Lun), deux lettres (LU), majuscules ou minuscules, autre langue… c’est vous qui décidez. Copiez simplement le code suivant dans le fichier « datestr.h » et modifiez-le si besoin.
// datestr.h
const char *DAYSTR[] = {
"ERROR",
"Sun",
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat"
};
const char *MONTHSTR[] = {
"ERROR",
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December"
};
Programme principal
Dans cette section, nous allons implémenter le programme principal, le cœur de notre horloge. Copiez le code suivant dans le fichier « digiclock.ino« . Remplacez complètement le code de calibration qui s’y trouvait.
// digiclock.ino
#include "tft_setup.h"
#include "stdarg.h"
#include "WiFi.h"
#include "TFT_eSPI.h"
#include "TFT_eWidget.h"
#include "HTTPClient.h"
#include "ArduinoJson.h"
#include "TimeLib.h"
#include "datestrs.h"
#define WIFI_SSID "YOUR_SSID"
#define WIFI_PASSPHRASE "YOUR_PWD"
#define URL "http://worldtimeapi.org/api/ip"
StaticJsonDocument<2048> doc;
uint16_t cal[5] = { 243, 3669, 216, 3553, 7 };
char timeStr[20];
char dateStr[40];
TFT_eSPI tft = TFT_eSPI();
ButtonWidget btn1 = ButtonWidget(&tft);
ButtonWidget btn2 = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btn1, &btn2 };
bool is24h = true;
bool isDay = true;
void btn1_pressed(void) {
if (btn1.justPressed()) {
is24h = !btn1.getState();
btn1.drawSmoothButton(is24h, 1, TFT_DARKGREY, is24h ? "24h" : "12h");
}
}
void btn2_pressed(void) {
if (btn2.justPressed()) {
isDay = !btn2.getState();
btn2.drawSmoothButton(isDay, 1, TFT_DARKGREY, isDay ? "day" : "night");
}
}
void initButtons() {
uint16_t w = 100;
uint16_t h = 50;
uint16_t y = tft.height() - h + 12;
uint16_t x = tft.width() / 2;
tft.setTextFont(4);
btn1.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "24h", 1);
btn1.setPressAction(btn1_pressed);
btn1.drawSmoothButton(is24h, 1, TFT_BLACK);
btn2.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "day", 1);
btn2.setPressAction(btn2_pressed);
btn2.drawSmoothButton(isDay, 1, TFT_BLACK);
}
void handleButtons() {
tft.setTextFont(4);
uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
uint16_t x = 0, y = 0;
bool touched = tft.getTouch(&x, &y);
for (uint8_t b = 0; b < nBtns; b++) {
if (touched) {
if (btns[b]->contains(x, y)) {
btns[b]->press(true);
btns[b]->pressAction();
}
} else {
btns[b]->press(false);
btns[b]->releaseAction();
}
}
}
bool shouldSyncTime() {
time_t t = now();
bool wifi_on = WiFi.status() == WL_CONNECTED;
bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
return wifi_on && should_sync;
}
void syncTime() {
delay(1000);
HTTPClient http;
http.begin(URL);
if (http.GET() > 0) {
String json = http.getString();
auto error = deserializeJson(doc, json);
if (!error) {
int Y, M, D, h, m, s, ms, tzh, tzm;
sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
&Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
setTime(h, m, s, D, M, Y);
}
}
http.end();
}
void updateDisplay() {
time_t t = now();
sprintf(timeStr, " %2d:%02d ",
(is24h ? hour(t) : hourFormat12(t)), minute(t));
sprintf(dateStr, " %s, %s %d, %d ",
DAYSTR[weekday(t)], MONTHSTR[month(t)], day(t), year(t));
uint16_t color = isDay ? TFT_WHITE : TFT_DARKGREY;
tft.setTextColor(color, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(3);
tft.drawString(timeStr, tft.width() / 2, 120, 7);
tft.setTextSize(1);
tft.drawString(dateStr, tft.width() / 2, 230, 4);
}
void setup(void) {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
while (WiFi.status() != WL_CONNECTED)
delay(500);
tft.init();
tft.setTouch(cal);
tft.fillScreen(TFT_BLACK);
tft.setRotation(1);
initButtons();
}
void loop() {
if (shouldSyncTime())
syncTime();
updateDisplay();
handleButtons();
delay(50);
}
C’est pas mal de code ! Dans les sections suivantes, nous allons le décortiquer pour comprendre comment tout fonctionne ensemble.
La plupart du code est basé sur ces trois tutoriels : comment construire un Automatic Daylight Savings Time Clock, comment construire un LED Ring Clock with WS2812, et le CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Si vous avez du mal à comprendre certains passages, allez jeter un œil à ces ressources.
Bibliothèques
On commence par inclure les bibliothèques nécessaires. En plus de la bibliothèque « TFT_eSPI« , vous devrez install les bibliothèques « TFT_eWidget« , « ArduinoJson » et « TimeLib« . Utilisez simplement le gestionnaire de bibliothèques de l’IDE Arduino comme d’habitude.
Les autres bibliothèques : « stdarg », « WiFi » et « HTTPClient » font partie du core ESP32 et n’ont pas besoin d’être installées séparément. Et « tft_setup.h » et « datestrs.h » sont les fichiers du projet Arduino digiclock que nous avons créé plus tôt. Ils existent déjà mais doivent être inclus comme indiqué.
#include "tft_setup.h" #include "stdarg.h" #include "WiFi.h" #include "TFT_eSPI.h" #include "TFT_eWidget.h" #include "HTTPClient.h" #include "ArduinoJson.h" #include "TimeLib.h" #include "datestrs.h"
Ces bibliothèques fournissent les fonctionnalités nécessaires pour la connexion WiFi, les requêtes HTTP, le parsing JSON, la gestion du temps et la création de l’interface utilisateur de notre horloge.
Constantes et objets
Ensuite, nous définissons quelques constantes et objets. Le StaticJsonDocument capture la réponse de l’API WebTime à notre requête de temps.cal contient les paramètres de calibration de l’écran. Et timeStr et dateStr sont des buffers de caractères que nous utiliserons pour formater l’heure et la date affichées.
StaticJsonDocument<2048> doc;
uint16_t cal[5] = { 243, 3669, 216, 3553, 7 };
char timeStr[20];
char dateStr[40];
TFT_eSPI tft = TFT_eSPI();
ButtonWidget btn1 = ButtonWidget(&tft);
ButtonWidget btn2 = ButtonWidget(&tft);
ButtonWidget* btns[] = { &btn1, &btn2 };
bool is24h = true;
bool isDay = true;
TFT_eSPI est l’objet qui contrôle l’écran, et btn1 et btn2 sont les deux objets boutons affichés à l’écran. Nous stockons aussi les objets boutons dans un tableau btns pour simplifier la gestion des événements. Et is24h et isDay sont deux indicateurs booléens qui représentent l’état des deux boutons.
Fonctions des boutons
Les fonctions btn1_pressed() et btn2_pressed() sont appelées quand le bouton correspondant sur l’écran tactile est pressé. Elles modifient les indicateurs d’état is24h et isDay et changent l’apparence du bouton.
void btn1_pressed(void) {
if (btn1.justPressed()) {
is24h = !btn1.getState();
btn1.drawSmoothButton(is24h, 1, TFT_DARKGREY, is24h ? "24h" : "12h");
}
}
void btn2_pressed(void) {
if (btn2.justPressed()) {
isDay = !btn2.getState();
btn2.drawSmoothButton(isDay, 1, TFT_DARKGREY, isDay ? "day" : "night");
}
}
Initialisation des boutons
La fonction initButtons() crée l’apparence initiale et définit l’emplacement des deux boutons à l’écran.
void initButtons() {
uint16_t w = 100;
uint16_t h = 50;
uint16_t y = tft.height() - h + 12;
uint16_t x = tft.width() / 2;
tft.setTextFont(4);
btn1.initButtonUL(x - w - 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "24h", 1);
btn1.setPressAction(btn1_pressed);
btn1.drawSmoothButton(is24h, 1, TFT_BLACK);
btn2.initButtonUL(x + 10, y, w, h, TFT_DARKGREY, TFT_BLACK, TFT_DARKGREY, "day", 1);
btn2.setPressAction(btn2_pressed);
btn2.drawSmoothButton(isDay, 1, TFT_BLACK);
}
Si on les affichait entièrement, les boutons apparaîtraient comme des rectangles arrondis, comme sur l’image ci-dessous

Cependant, j’ai placé les boutons de façon à ce qu’ils soient à moitié hors de l’écran, ce qui leur donne un aspect d’onglets :

Je trouve que c’est plus joli, mais si vous préférez, changez simplement la coordonnée y à y = tft.height() - h - 10 pour afficher le bouton en entier.
Notez aussi qu’il faut appeler tft.setTextFont(4), car la fonction initButtonUL() n’a pas de paramètre pour définir une police.
Gestion des événements des boutons
La fonction handleButtons() gère les événements des boutons. Elle parcourt tous les boutons stockés dans le tableau btns. Si un événement tactile est détecté via getTouch() et que les coordonnées x,y de l’événement sont dans la zone du bouton (btns[b]->contains(x, y)), la fonction du bouton correspondant est appelée.
void handleButtons() {
tft.setTextFont(4);
uint8_t nBtns = sizeof(btns) / sizeof(btns[0]);
uint16_t x = 0, y = 0;
bool touched = tft.getTouch(&x, &y);
for (uint8_t b = 0; b < nBtns; b++) {
if (touched) {
if (btns[b]->contains(x, y)) {
btns[b]->press(true);
btns[b]->pressAction();
}
} else {
btns[b]->press(false);
btns[b]->releaseAction();
}
}
}
Vérification de la synchronisation de l’heure
Nous voulons synchroniser l’horloge interne de l’ESP32 avec l’heure Internet téléchargée depuis WorldTimeAPI. Mais il ne faut pas synchroniser trop souvent, sinon WorldTimeAPI risque de nous bloquer !
La fonction shouldSyncTime() vérifie si la minute courante est zéro et 3 secondes, ou si l’année courante est 1970, puis retourne vrai pour indiquer qu’il faut synchroniser l’heure. Cela veut dire qu’on synchronise chaque heure, 3 secondes après l’heure pile, pour prendre en compte le changement d’heure d’été qui peut avoir lieu à l’heure pile. Donc, au pire, notre horloge aura 3 secondes de décalage.
bool shouldSyncTime() {
time_t t = now();
bool wifi_on = WiFi.status() == WL_CONNECTED;
bool should_sync = (minute(t) == 0 && second(t) == 3) || (year(t) == 1970);
return wifi_on && should_sync;
}
On vérifie aussi si l’année courante est 1970. C’est l’année que l’horloge ESP32 affiche au démarrage (début de Unix time). Cela veut dire qu’au démarrage de l’ESP32, la première chose à faire est de synchroniser l’heure, même si ce n’est pas à l’heure pile. Sinon, l’heure pourrait être complètement fausse pendant une heure !
Synchroniser l’heure
La fonction syncTime() télécharge l’heure actuelle depuis WorldTimeAPI et règle l’horloge interne de l’ESP32 en conséquence. Pour cela, elle effectue une GET request vers l’URL spécifiée et analyse la réponse JSON avec la bibliothèque ArduinoJson.
Depuis la structure JSON, elle extrait l’année, le mois, le jour, l’heure, la minute, la seconde et le fuseau horaire, puis règle l’heure interne de l’ESP32 avec la fonction setTime() de la bibliothèque TimeLib.
void syncTime() {
delay(1000);
HTTPClient http;
http.begin(URL);
if (http.GET() > 0) {
String json = http.getString();
auto error = deserializeJson(doc, json);
if (!error) {
int Y, M, D, h, m, s, ms, tzh, tzm;
sscanf(doc["datetime"], "%d-%d-%dT%d:%d:%d.%d+%d:%d",
&Y, &M, &D, &h, &m, &s, &ms, &tzh, &tzm);
setTime(h, m, s, D, M, Y);
}
}
http.end();
}
Mise à jour de l’affichage
La fonction updateDisplay() lit d’abord l’heure interne actuelle via now() et, à partir de là, remplit les buffers timeStr et dateStr avec une représentation texte de l’heure et de la date. Selon l’état is24h du bouton de format d’heure, l’heure est affichée en format 24h ou 12h.
Notez les espaces supplémentaires dans les chaînes de format, par exemple " %s, %s %d, %d ". Ils sont nécessaires car les chaînes d’heure et de date ont des longueurs variables et la fonction updateDisplay() réécrit simplement l’heure affichée sans effacer l’écran. Cela évite le scintillement, mais il faut prévoir les espaces.
void updateDisplay() {
time_t t = now();
sprintf(timeStr, " %2d:%02d ",
(is24h ? hour(t) : hourFormat12(t)), minute(t));
sprintf(dateStr, " %s, %s %d, %d ",
DAYSTR[weekday(t)], MONTHSTR[month(t)], day(t), year(t));
uint16_t color = isDay ? TFT_WHITE : TFT_DARKGREY;
tft.setTextColor(color, TFT_BLACK);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(3);
tft.drawString(timeStr, tft.width() / 2, 120, 7);
tft.setTextSize(1);
tft.drawString(dateStr, tft.width() / 2, 230, 4);
}
Une fois l’heure et la date dans les buffers, il suffit de les afficher à l’écran en appelant drawString() avec la chaîne et les coordonnées.setTextDatum(MC_DATUM) centre le texte, et setTextColor() utilise une couleur blanche ou gris foncé, selon l’état isDay du bouton jour/nuit.
La fonction updateDisplay() affiche en fait quatre écrans différents, selon l’état des indicateurs is24h et isDay. L’image ci-dessous montre ces quatre écrans :

À cause de la caméra et de l’éclairage, les écrans paraissent bleutés, alors qu’en réalité les couleurs sont noir, blanc et gris. La différence entre le mode nuit et jour est aussi plus marquée en vrai.
Notez que la fonction updateDisplay() ne fonctionnera pas pour d’autres tailles d’écran sans quelques modifications. Il faudra ajuster la taille de la police (autre police ou taille), et la position verticale des chaînes d’heure et de date est codée en dur et devra être adaptée.
Fonction Setup
Dans la fonction setup(), on commence par établir la connexion Wi-Fi puis on initialise l’écran TFT. La calibration de l’écran tactile via setTouch(cal) est importante, car on utilise les paramètres de calibration. Sans cela, les boutons ne fonctionneront pas correctement. Notez aussi qu’on met l’écran en mode paysage avec setRotation(1).
void setup(void) {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSPHRASE);
while (WiFi.status() != WL_CONNECTED)
delay(500);
tft.init();
tft.setTouch(cal);
tft.fillScreen(TFT_BLACK);
tft.setRotation(1);
initButtons();
}
J’utilise un fond noir, mais vous pouvez choisir une autre couleur. Il faudra alors aussi ajuster la couleur de fond des chaînes et des boutons dans updateDisplay() et initButtons().
La dernière étape de la configuration est l’appel à initButtons(), qui crée et dessine les boutons.
Fonction Loop
Avec toutes les fonctions d’aide ci-dessus, la boucle principale est maintenant très simple. On vérifie d’abord s’il faut synchroniser l’ESP32 avec l’heure Internet. Si oui, on appelle syncTime() pour le faire.
void loop() {
if (shouldSyncTime())
syncTime();
updateDisplay();
handleButtons();
delay(50);
}
Ensuite, on met simplement à jour l’affichage avec l’heure actuelle, puis on appelle handleButtons() pour gérer les événements des boutons. La boucle tourne toutes les 50 ms, ce qui est assez rapide pour réagir aux pressions sur les boutons. Les autres fonctions pourraient tourner moins souvent, par exemple toutes les 100 à 500 ms, mais la vitesse plus élevée ne pose pas de problème.
Et voilà ! Une petite horloge toujours à l’heure et super sympa !
Conclusions
L’horloge numérique que nous avons réalisée fonctionne très bien, mais voici quelques idées pour l’améliorer encore.
Par exemple, au lieu de changer la luminosité manuellement, on pourrait ajouter une photorésistance (LDR) pour l’ajuster automatiquement selon la lumière ambiante. Et il n’est pas très utile d’afficher l’heure si personne n’est là pour la voir. Avec un capteur PIR, on pourrait détecter un mouvement et activer l’horloge si une personne est détectée. Voir le tutoriel LED Ring Clock with WS2812 pour plus d’infos.
Vous pouvez aussi ajouter d’autres boutons pour changer le format de la date ou pour piloter un éclairage externe. Regardez le CrowPanel 2.8″ ESP32 Display : Easy Setup Guide, où l’on contrôle deux LEDs depuis l’écran. WorldTimeAPI permet aussi de spécifier des fuseaux horaires, donc vous pourriez avoir des boutons pour changer de fuseau ou de ville.
Sinon, au lieu de WorldTimeAPI, vous pouvez aussi vous connecter à un serveur SNTP, ce qui permettrait de synchroniser l’heure plus souvent. Voir le tutoriel How to synchronize ESP32 clock with SNTP server.
Comme le CrowPanel a une interface I2C, vous pouvez facilement ajouter un capteur de température, d’humidité ou de qualité de l’air pour afficher plus d’infos à l’écran. Consultez les tutoriels AM2320 digital temperature and humidity sensor Arduino tutorial et Interfacing Arduino and SGP30 Versatile Air Quality Sensor pour plus de détails.
Enfin, en plus de l’heure, vous pouvez aussi télécharger les données météo actuelles sur Internet et les afficher. Regardez OpenWeather. Ils proposent un forfait gratuit suffisant pour des mises à jour météo horaires.
Si vous avez d’autres idées ou des questions, n’hésitez pas à laisser un commentaire. Sinon, amusez-vous bien ; )
Liens
Voici quelques liens qui m’ont été utiles pour écrire ce post.
- CrowPanel 3.5″-ESP32 Display
- CrowPanel ESP32 Display Wiki
- CrowPanel ESP32 Display User_Manual
- CrowPanel 3.5″ Schematic Diagram
- CrowPanel ESP32 Display Video Tutorials
- ESP32-LVGL-DESK-CLOCK
- TFT 3.5″ Touch Screen & ESP32 built in – Elecrow review
- Getting Started Tips 3.5″ Elecrow TFT ESP32
- Review of Elecrow’s 3.5-inch and 7.0-inch ESP32 display modules using Arduino programming

