In questo tutorial imparerai come costruire un Orologio Digitale sempre preciso con il CrowPanel 3.5″ ESP32 Display. Sincronizzeremo l’orologio tramite WiFi con un provider di orario su internet e useremo la libreria TFT_eSPI per creare una bella interfaccia grafica per il Display.
Iniziamo!
Componenti necessari
Per questo progetto ti servirà solo il CrowPanel 3.5″ ESP32 Display di ELECROW e l’Arduino IDE. Il pannello viene fornito con un cavo USB e un cavo DuPont a 4 pin, quindi non avrai bisogno di altri cavi.

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.
Caratteristiche del CrowPanel 3.5″ ESP32 Display
Il CrowPanel 3.5″ ESP32 Display di ELECROW è un touch screen resistivo con display TFT a risoluzione 480*320 e un ESP32-WROVER-B come processore di controllo. Puoi acquistare il display con una bella custodia in acrilico (vedi foto sotto), così non dovrai costruirne una tu.

Inoltre, la scheda è dotata di uno slot per schede TF, un’interfaccia UART, un’interfaccia I2C, un’interfaccia per altoparlante, un connettore per batteria con funzione di ricarica e una piccola porta GPIO con due pin GPIO. Guarda il pinout qui sotto:

La tabella seguente mostra a quali pin GPIO sono assegnate le tre interfacce IO.
| GPIO_D | IO25; IO32 |
| UART | RX(IO16); TX(IO17) |
| I2C | SDA(IO22); SCL(IO21) |
La scheda può essere alimentata tramite la porta USB (5V, 2A) oppure collegando una batteria LiPo standard da 3,7V al connettore BAT. Fai solo attenzione alla polarità del connettore e della batteria. Se il cavo USB e la batteria sono collegati contemporaneamente, la scheda caricherà la batteria (corrente massima di carica 500mA).
Puoi programmare la scheda usando diversi ambienti di sviluppo come Arduino IDE, Espressif IDF, Lua RTOS e Micro Python, ed è compatibile con la libreria grafica LVGL. Tuttavia, in questo tutorial useremo l’Arduino IDE e la libreria grafica TFT_eSPI per creare l’interfaccia utente del nostro Orologio.
Serie CrowPanel ESP32 Display
Nota che il CrowPanel 3.5″ ESP32 Display fa parte di una famiglia di display che va da 2.4 pollici fino a 7 pollici. Oltre alla differenza di dimensioni, cambiano anche la risoluzione, il driver del display e il modello di ESP32. Vedi la tabella qui sotto.

In questo tutorial useremo 3.5″ display. Ma gli esempi di codice e la procedura di configurazione sarebbero molto simili per il 2.4″ display e il 2.8” display, dato che usano lo stesso o un driver display simile (ILI9341, ILI9488). Vedi le sezioni evidenziate in giallo nella tabella sopra.
Per i display più grandi (4.3″, 5″, 7″), invece, probabilmente gli esempi di codice non funzioneranno perché la libreria TFT_eSPI sembra non supportare i loro driver (NV3047, IL6122, EK9716BD3). Tuttavia, non ho effettivamente testato questa cosa. Sentiti libero di scrivere nei commenti se hai provato.
Crea la struttura del progetto
Prima di entrare nei dettagli, creiamo la struttura del progetto e la configurazione per la libreria TFT_eSPI.
Apri l’Arduino IDE e crea un progetto chiamato “digiclock” e salvalo (Salva con nome …). Questo creerà una cartella “digiclock” con il file “digiclock.ino” al suo interno. In questa cartella crea un altro file chiamato “tft_setup.h“. La tua cartella di progetto dovrebbe apparire così:

E nell’Arduino IDE ora dovresti avere due tab chiamate “digiclock.ino” e “tft_setup.h“.

Clicca sulla tab “tft_setup.h” per aprire il file e copia il seguente codice al suo interno:
#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
Questo codice dice alla libreria TFT_eSPI quale display stiamo usando. In particolare, comunichiamo alla libreria le dimensioni del display (TFT_WIDTH, TFT_WIDTH), il driver del display (ILI9488_DRIVER), quali pin SPI vengono usati per controllarlo e quali font caricare. Senza queste impostazioni non potrai visualizzare nulla sul display.
Per informazioni più dettagliate dai un’occhiata al tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Lì spieghiamo come configurare il Display da 2.8″. Ma i passaggi e le descrizioni valgono anche per il Display da 3.5″. Cambiano solo le impostazioni per dimensioni e driver dello schermo.
Calibrazione del touchscreen
Il CrowPanel 3.5″ Display è dotato di un touch screen resistivo che devi calibrare prima di poterlo usare con la libreria TFT_eSPI. Copia il codice qui sotto nel file “digiclock.ino“, compilalo e caricalo sul CrowPanel 3.5” Display.
// 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);
}
Quando il codice è in esecuzione, il display mostra una freccia e ti chiede di toccare l’angolo verso cui punta. Questo verrà ripetuto anche per gli altri tre angoli. Usa il piccolo pennino fornito con il Display e cerca di toccare gli angoli il più precisamente possibile.

Al termine della calibrazione, il codice stampa i 5 parametri di calibrazione (coordinate degli angoli e orientamento dello schermo) sul Serial Monitor. Dovresti vedere qualcosa del genere:
cal: { 243, 3669, 216, 3553, 7 }
Copia questi parametri da qualche parte, perché ti serviranno nel codice dell’orologio. La calibrazione si ripete ogni 10 secondi, così puoi fare più tentativi per ottenere i parametri più precisi.
Per maggiori dettagli sul processo di calibrazione dai un’occhiata al tutorial CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Nota però che lì la calibrazione viene fatta per il display in orientamento verticale, mentre qui usiamo il display in modalità orizzontale (vedi il setRotation(1) nel codice di calibrazione sopra).
Ora vediamo come ottenere le informazioni sull’orario da internet.
Scaricare i dati dell’orario da Internet
Mi piacciono gli orologi che si regolano automaticamente per l’ora legale e che sono sempre precisi, così non devo mai sistemarli. Se hai Wi-Fi e internet, il modo più semplice per ottenere questo risultato è scaricare regolarmente l’orario corrente da un time server e poi aggiornare l’orologio interno dell’ESP32. Per maggiori dettagli su questo argomento dai un’occhiata al tutorial su come costruire un Automatic Daylight Savings Time Clock.
Qui useremo lo stesso metodo di quel tutorial. In particolare, useremo la WorldTimeAPI per ottenere i dati dell’orario.WorldTimeAPI è un semplice servizio web che restituisce l’orario corrente come testo semplice o JSON. Puoi usare il loro sito per ottenere l’orario di un fuso orario specifico o in base all’indirizzo IP del tuo computer. Noi useremo quest’ultimo. È più semplice e così il nostro orologio si adatterà automaticamente non solo all’ora legale ma anche quando viene spostato in un altro fuso orario.
Provare WorldTimeAPI è facilissimo. Basta cliccare su questo link: http://worldtimeapi.org/api/ip oppure inserirlo nella barra di ricerca del browser. Nota che non devi fornire esplicitamente il tuo indirizzo IP. Il servizio web lo rileva automaticamente in base alla provenienza della richiesta.
Nel browser dovresti vedere un output simile al seguente (ho aggiunto un po’ di formattazione per renderlo più leggibile e oscurato il mio 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
}
Questo output è in formato JSON. Contiene anche altre informazioni oltre all’orario corrente. Ma a noi interessa in particolare il campo datetime, che ci dà l’orario locale attuale. In questo esempio è "2023-11-16T12:09:46.409360+11:00".
Ogni volta che vai su questo link otterrai un orario aggiornato. Fai solo attenzione a non ricaricare troppo spesso la pagina, altrimenti potresti essere bloccato!
Con questo abbiamo tutti gli elementi per realizzare il nostro Orologio Digitale.
Implementazione di un Orologio Digitale
In questa sezione andremo a realizzare l’Orologio Digitale. Avrà questo aspetto:

Mostra l’orario attuale (sincronizzato via internet), giorno, mese e anno e due pulsanti. Con il pulsante “24h” possiamo passare dal formato 24h al 12h. Il pulsante “day” permette di cambiare la luminosità del display per la modalità notte e giorno.
Per realizzare questo orologio, partiamo dalla struttura del progetto.
Struttura della cartella di progetto
Nel passaggio precedente hai già creato la cartella di progetto “digiclock” con i file “digiclock.ino” e “tft_setup.h” al suo interno. Ora aggiungi un altro file chiamato “datestr.h“. La tua cartella di progetto dovrebbe apparire così:

e nell’Arduino IDE ora dovresti avere tre tab: “digiclock.ino“, “datestr.h” e “tft_setup.h“:

Il file “tft_setup.h” è già compilato con il codice corretto. Ma gli altri due file (“digiclock.ino“, “datestr.h“) vanno aggiornati. Iniziamo dal file “datestr.h“.
Nomi dei giorni e dei mesi
Oltre all’orario, vogliamo anche mostrare la data attuale come testo sul display. Ad esempio, Gio, Mag 30, 2024. Tuttavia, da WorldTimeAPI non otteniamo i nomi del mese o del giorno attuali. Dobbiamo convertire un numero di mese, come 5, nel nome del mese “Mag”, ad esempio.
La libreria TimeLib che useremo più avanti, in realtà ha delle funzioni per questo ma non sono riuscito a farle funzionare. C’è un problema di allocazione memoria quando si scrive su una stringa tramite sprintf, che non avevo voglia di risolvere.
Quindi definiamo noi stessi i nomi dei mesi e dei giorni. È molto semplice e ha il vantaggio che puoi modificarli come preferisci. Nomi completi (Lunedì), tre lettere (Lun), due lettere (LU), maiuscolo o minuscolo, lingua diversa… decidi tu. Copia il seguente codice nel file “datestr.h” e modificalo se vuoi.
// 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"
};
Programma principale
In questa sezione andremo a implementare il programma principale, il cuore del nostro orologio. Copia il seguente codice nel file “digiclock.ino“. Sostituisci completamente il codice di calibrazione che c’era prima.
// 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);
}
Ora, questo è un bel po’ di codice. Nelle sezioni seguenti lo analizziamo e vediamo come le varie parti lavorano insieme.
La maggior parte del codice si basa su questi tre tutorial: Come costruire un Automatic Daylight Savings Time Clock, come costruire un LED Ring Clock with WS2812, e il CrowPanel 2.8″ ESP32 Display : Easy Setup Guide. Se hai difficoltà a capire qualche parte del codice o delle spiegazioni qui sotto, dai un’occhiata lì.
Librerie
Per prima cosa includiamo le librerie necessarie. Oltre alla libreria “TFT_eSPI“, dovrai install le librerie “TFT_eWidget“, “ArduinoJson” e “TimeLib“. Usa semplicemente il Library Manager dell’Arduino IDE come al solito.
Le altre librerie: “stdarg”, “WiFi” e “HTTPClient” fanno parte del core ESP32 e non serve installarle separatamente. E “tft_setup.h” e “datestrs.h” sono i file del progetto Arduino digiclock che abbiamo creato prima. Esistono già ma vanno inclusi come mostrato.
#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"
Queste librerie forniscono le funzionalità necessarie per la connessione WiFi, richieste HTTP, parsing JSON, gestione dell’orario e creazione dell’interfaccia utente per il nostro orologio.
Costanti e oggetti
Poi definiamo alcune costanti e oggetti. Il StaticJsonDocument cattura la risposta della WebTime API alla nostra richiesta di orario.cal contiene i parametri di calibrazione per il display. E timeStr e dateStr sono buffer di caratteri che useremo per formattare l’orario e la data visualizzati.
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 è l’oggetto per controllare il display e btn1 e btn2 sono i due oggetti pulsante mostrati sul display. Salviamo anche gli oggetti pulsante in un array btns per semplificare la gestione degli eventi dei pulsanti. E is24h e isDay sono due flag booleani che rappresentano lo stato dei due pulsanti.
Funzioni dei pulsanti
Le funzioni btn1_pressed() e btn2_pressed() vengono chiamate quando il relativo pulsante sul touch screen viene toccato. Impostano i flag di stato dei pulsanti is24h e isDay e cambiano l’aspetto del pulsante.
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");
}
}
Inizializzazione dei pulsanti
La funzione initButtons() crea l’aspetto iniziale e definisce la posizione dei due pulsanti sullo schermo.
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);
}
Se visualizzati completamente, i pulsanti apparirebbero come rettangoli arrotondati come nella foto sotto

Tuttavia, ho impostato la posizione in modo che i pulsanti siano in realtà a metà fuori dallo schermo, così sembrano delle tab:

Secondo me così sono più belli, ma se non ti piace basta cambiare la coordinata y in y = tft.height() - h - 10 per visualizzare il pulsante completo.
Nota anche che dobbiamo chiamare tft.setTextFont(4), dato che la funzione initButtonUL() non ha un parametro per impostare il font.
Gestione eventi dei pulsanti
La funzione handleButtons() gestisce gli eventi dei pulsanti. Scorre tutti i pulsanti salvati nell’array btns. Se viene rilevato un evento touch tramite getTouch() e le coordinate x,y dell’evento sono all’interno dell’area del pulsante (btns[b]->contains(x, y)), viene chiamata la funzione corrispondente.
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();
}
}
}
Controllo della sincronizzazione oraria
Vogliamo sincronizzare l’orologio interno dell’ESP32 con l’orario internet che scarichiamo da WorldTimeAPI. Tuttavia, non vogliamo sincronizzare troppo spesso, altrimenti WorldTimeAPI ci bloccherà!
La funzione shouldSyncTime() controlla se il minuto corrente è zero e sono passati 3 secondi, oppure se l’anno corrente è 1970, e in tal caso restituisce true, indicando che dobbiamo sincronizzare l’orario. Quindi sincronizziamo ogni ora e lo facciamo 3 secondi dopo l’ora esatta per cogliere il cambio dell’ora legale che potrebbe avvenire allo scoccare dell’ora. Nel peggiore dei casi il nostro orologio sarà sfasato di 3 secondi.
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;
}
Controlliamo anche se l’anno corrente è 1970. Questo è l’anno che l’orologio ESP32 riporta appena acceso (inizio dell’Unix time). Quindi, quando l’ESP32 viene acceso, la prima cosa che facciamo è sincronizzare l’orario, anche se non siamo all’ora esatta. Senza questo, l’orario potrebbe essere completamente sbagliato fino a un’ora!
Sincronizzazione dell’orario
La funzione syncTime() scarica l’orario attuale da WorldTimeAPI e imposta di conseguenza l’orologio interno dell’ESP32. Per farlo, effettua una GET request all’URL specificato e analizza la risposta come JSON usando la libreria ArduinoJson.
Dalla struttura JSON estrae anno, mese, giorno, ora, minuti, secondi e informazioni sul fuso orario e imposta l’orario interno dell’ESP32 usando la funzione setTime() della libreria 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();
}
Aggiornamento del display
La funzione updateDisplay() legge prima l’orario interno attuale tramite now() e in base a questo riempie i buffer timeStr e dateStr con una rappresentazione testuale di orario e data. A seconda dello stato is24h del pulsante formato orario, l’orario viene scritto in formato 24h o 12h.
Nota gli spazi extra nelle stringhe di formato, ad esempio " %s, %s %d, %d ". Sono necessari perché le stringhe di orario e data hanno lunghezza variabile e la funzione updateDisplay() semplicemente sovrascrive l’orario attualmente visualizzato invece di cancellare lo schermo. Questo evita lo sfarfallio, ma servono gli spazi.
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);
}
Una volta che abbiamo orario e data nei buffer stringa, li visualizziamo semplicemente sullo schermo chiamando drawString() con la stringa e le coordinate.setTextDatum(MC_DATUM) centra il testo, e setTextColor() usa un colore bianco o grigio scuro, a seconda dello stato isDay del pulsante giorno/notte.
La funzione updateDisplay() in pratica visualizza quattro schermate diverse, a seconda dello stato dei flag is24h e isDay. L’immagine sotto mostra queste quattro schermate:

A causa della fotocamera e dell’illuminazione, gli schermi appaiono blu, mentre a occhio nudo i colori reali sono nero, bianco e grigio. Anche la differenza tra modalità notte e giorno è più evidente dal vivo.
Nota che la funzione updateDisplay() non funzionerà con display di dimensioni diverse senza alcune modifiche. Dovrai regolare la dimensione del font usando un font o una dimensione diversa. Anche la posizione verticale delle stringhe di orario e data è codificata e andrebbe adattata.
Funzione di setup
Nella funzione setup(), prima stabiliamo la connessione Wi-Fi e poi inizializziamo il display TFT. Importante è la calibrazione del touch screen tramite setTouch(cal), dove usiamo i parametri di calibrazione. Senza questo i pulsanti non funzioneranno correttamente. Nota anche che impostiamo lo schermo in modalità orizzontale tramite 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();
}
Io uso uno sfondo nero, ma puoi sceglierne uno diverso. Tuttavia, dovrai anche regolare il colore di sfondo per le stringhe e i pulsanti in updateDisplay() e initButtons().
L’ultima parte del setup è la chiamata a initButtons(), che crea e disegna i pulsanti.
Funzione loop
Con tutte le funzioni di supporto sopra, il loop principale ora è molto semplice. Prima controlliamo se dobbiamo sincronizzare l’ESP32 con l’orario internet. Se sì, chiamiamo syncTime() per farlo.
void loop() {
if (shouldSyncTime())
syncTime();
updateDisplay();
handleButtons();
delay(50);
}
Dopo aggiorniamo semplicemente il display con le informazioni orarie attuali e poi chiamiamo handleButtons() per gestire gli eventi dei pulsanti. Il loop gira ogni 50ms, che è abbastanza veloce per rispondere ai tocchi. Le altre funzioni potrebbero girare anche più lentamente, ad esempio ogni 100-500ms, ma la velocità maggiore non dà problemi.
Ed eccolo qui! Un orologio sempre preciso e davvero cool!
Conclusioni
L’Orologio Digitale che abbiamo realizzato funziona benissimo, ma ecco alcune idee per migliorarlo ancora.
Ad esempio, invece di cambiare manualmente la luminosità potremmo aggiungere una fotoresistenza (LDR) per farlo in automatico, in base alla luce ambiente. Inoltre, non ha molto senso mostrare l’orario se non c’è nessuno a guardarlo. Con un sensore PIR potremmo rilevare il movimento e attivare l’orologio se viene rilevata una persona. Vedi il tutorial LED Ring Clock with WS2812 per maggiori informazioni.
Potresti voler aggiungere altri pulsanti per cambiare il formato della data o per comandare luci esterne. Dai un’occhiata al CrowPanel 2.8″ ESP32 Display : Easy Setup Guide, dove controlliamo due LED dal Display. WorldTimeAPI permette anche di specificare i fusi orari, quindi potresti avere pulsanti per cambiare fuso orario o città.
In alternativa a WorldTimeAPI, potresti anche collegarti a un server SNTP, che ti permetterebbe di sincronizzare l’orario più frequentemente. Dai un’occhiata al tutorial How to synchronize ESP32 clock with SNTP server.
Dato che il CrowPanel ha un’interfaccia I2C, puoi facilmente aggiungere un sensore di temperatura, umidità o qualità dell’aria per mostrare più informazioni sul display. Dai un’occhiata ai tutorial AM2320 digital temperature and humidity sensor Arduino tutorial e Interfacing Arduino and SGP30 Versatile Air Quality Sensor per maggiori dettagli.
Infine, oltre alle informazioni sull’orario puoi anche scaricare i dati meteo attuali da internet e visualizzarli. Dai un’occhiata a OpenWeather. Hanno un piano gratuito con abbastanza richieste per aggiornamenti meteo orari.
Se hai altre idee o domande lascia pure un commento. Altrimenti buon divertimento ; )
Link
Ecco alcuni link che ho trovato utili per scrivere questo 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

