ESP32 NTP: ora da internet con fuso orario, OLED e backup RTC
Come ottenere l'ora NTP con l'ESP32: configTzTime e fuso orario, getLocalTime, strftime, orologio OLED, backup DS3231, attività giornaliera e ntptime su Pico W.
Ottenere l'ora con ESP32 NTP significa collegare la scheda ai server orari di internet e conoscere data e ora con una precisione di qualche decina di millisecondi, senza alcun modulo orologio. Quando l'ESP32 è connesso al Wi-Fi, una sola chiamata a configTzTime() prende l'ora, applica il tuo fuso orario e poi la scheda continua a contare da sola. Orologi da scrivania, irrigazione programmata, timestamp per il data logging, una sveglia al mattino o luci che si accendono a un'ora fissa non hanno più bisogno di un modulo RTC separato.
In questa guida vediamo passo passo come funziona NTP, come impostare il fuso orario giusto per l'Italia (CET/CEST), come scrivere la data in qualsiasi formato con getLocalTime e strftime, come mostrare l'ora su un display OLED, come conservarla con un modulo DS3231 quando cade il Wi-Fi o manca la corrente e come eseguire un'attività a un'ora precisa ogni giorno. Nell'ultima sezione faremo lo stesso sul Raspberry Pi Pico W con il modulo MicroPython ntptime.

Cos'è NTP e come ottiene l'ora l'ESP32?
NTP (Network Time Protocol) è un protocollo usato dagli anni '80 per sincronizzare gli orologi di computer e dispositivi tramite internet. I server sono organizzati in livelli chiamati strati: i server di strato 1 sono collegati direttamente a un orologio atomico o al GPS, quelli di strato 2 prendono l'ora da loro e così via. L'ESP32 chiede l'ora con un piccolo pacchetto sulla porta UDP 123; il server risponde con un timestamp che contiene i secondi dal 1900 più una frazione di secondo. L'ESP32 tiene conto del tempo di andata e ritorno del pacchetto e regola il proprio orologio. Con una connessione domestica la precisione ottenuta è di solito tra 10 e 50 millisecondi, più che sufficiente per un orologio.
Lo stack di rete lwIP dell'ESP32 usa SNTP, una versione semplificata di NTP. Ricevuta l'ora, l'ESP32 la conserva come ora di sistema e continua a contare con un timer derivato dal suo quarzo; non va su internet ogni secondo. In background interroga di nuovo il server a intervalli regolari (per impostazione predefinita ogni 3 ore nel core Arduino ESP32) e corregge le piccole derive. L'ora di sistema è tenuta in UTC; l'ora locale che vedi viene calcolata applicando la regola del fuso orario.
| Concetto | Valore / spiegazione |
|---|---|
| Protocollo e porta | SNTP, UDP 123 |
| Timestamp | Secondi dal 1900 (l'ESP32 li converte in tempo Unix dal 1970) |
| Precisione tipica | 10–50 ms con una connessione domestica |
| Risincronizzazione | Predefinita 3 ore in Arduino ESP32, si cambia con sntp_set_sync_interval() (minimo 15 s) |
| Numero massimo di server | 3 (i nomi passati a configTzTime) |
| Traffico internet | Richiesta e risposta sono pacchetti NTP da 48 byte; al massimo pochi kilobyte al giorno |
| Cosa serve | ESP32, una rete Wi-Fi a 2,4 GHz e una connessione a internet |
configTime e configTzTime: impostare il fuso orario
Nel core Arduino ESP32 ci sono due modi per avviare NTP. configTime(gmtOffset_sec, daylightOffset_sec, server1, server2, server3) riceve lo scostamento da UTC e quello dell'ora legale in secondi. Per la Turchia, per esempio, si scrive configTime(10800, 0, "pool.ntp.org"), perché 3 ore sono 10.800 secondi e la Turchia resta a UTC+3 tutto l'anno senza ora legale dal 2016. Questo metodo è semplice, ma non conosce le date di cambio dell'ora della tua regione; in Italia il passaggio avverrebbe nel giorno sbagliato, quindi conviene usare configTzTime.
Il modo corretto è configTzTime(TZ, server1, server2, server3). Questa funzione riceve una stringa di fuso orario in formato POSIX che contiene anche le regole dell'ora legale, e l'ESP32 cambia l'ora da solo ogni anno. Per l'Italia la stringa è "CET-1CEST,M3.5.0,M10.5.0/3": CET è l'abbreviazione dell'ora solare, -1 significa che bisogna togliere un'ora all'ora locale per ottenere UTC, CEST è l'ora legale, M3.5.0 vuol dire «ultima domenica di marzo» e M10.5.0/3 «ultima domenica di ottobre alle 3:00». Gli sketch qui sotto usano il fuso turco "<+03>-3"; sostituisci semplicemente TZ_INFO con il valore della tabella.
| Regione | Stringa TZ (configTzTime) | Equivalente con configTime |
|---|---|---|
| Turchia (UTC+3 tutto l'anno) | <+03>-3 | configTime(10800, 0, ...) |
| Italia, San Marino, Svizzera | CET-1CEST,M3.5.0,M10.5.0/3 | Non adatto, usa configTzTime |
| Regno Unito, Portogallo | GMT0BST,M3.5.0/1,M10.5.0 | Non adatto, usa configTzTime |
| USA costa est (New York) | EST5EDT,M3.2.0,M11.1.0 | Non adatto, usa configTzTime |
| Azerbaigian, Georgia (UTC+4) | <+04>-4 | configTime(14400, 0, ...) |
| UTC (per i timestamp) | UTC0 | configTime(0, 0, ...) |
Cosa serve e collegamenti
Per vedere l'ora nel Monitor seriale bastano un ESP32 e una rete Wi-Fi. Più avanti nella guida usiamo un display OLED SSD1306 da 0,96 pollici per mostrare l'ora, un modulo RTC DS3231 per conservarla durante i blackout e un modulo relè per l'esempio dell'attività programmata. L'OLED e il DS3231 condividono lo stesso bus I2C, quindi si collegano agli stessi due pin. Puoi consultare la piedinatura dell'ESP32 con lo strumento Pinout.
| Pin del componente | ESP32 | Raspberry Pi Pico W | Nota |
|---|---|---|---|
| OLED / DS3231 VCC | 3V3 | 3V3 (OUT) | Entrambi funzionano a 3,3 V |
| OLED / DS3231 GND | GND | GND | Massa comune |
| OLED / DS3231 SDA | GPIO 21 | GP4 | Linea dati I2C |
| OLED / DS3231 SCL | GPIO 22 | GP5 | Linea di clock I2C |
| Modulo relè IN | GPIO 26 | GP15 | Esempio di attività programmata |
| Modulo relè VCC | 5V (VIN) | VBUS | La maggior parte delle bobine relè vuole 5 V |
Passo passo: il tuo primo sketch ESP32 NTP
- Assicurati che il pacchetto schede ESP32 sia installato nell'Arduino IDE e scegli ESP32 Dev Module in Strumenti. Se non sai come fare, leggi l'articolo Cos'è l'ESP32.
- Non serve nessuna libreria aggiuntiva: WiFi.h e time.h sono inclusi nel core ESP32.
- Sostituisci WIFI_SSID e WIFI_PASS con la tua rete e TZ_INFO con "CET-1CEST,M3.5.0,M10.5.0/3".
- Carica lo sketch e apri il Monitor seriale a 115200 baud.
- Prima vedrai i puntini del Wi-Fi, poi dopo qualche secondo data e ora aggiornate ogni secondo.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi"; // Wi-Fi adı / Wi-Fi name
5const char* WIFI_PASS = "Sifre123"; // Wi-Fi şifresi / Wi-Fi password
6const char* TZ_INFO = "<+03>-3"; // Türkiye: UTC+3, yaz saati yok / Turkey: UTC+3, no DST
7
8void setup() {
9 Serial.begin(115200);
10 WiFi.begin(WIFI_SSID, WIFI_PASS);
11 Serial.print("Wi-Fi");
12 while (WiFi.status() != WL_CONNECTED) { // bağlanana kadar bekle / wait until connected
13 delay(500);
14 Serial.print(".");
15 }
16 Serial.println(" OK");
17
18 // NTP sunucuları + saat dilimi / NTP servers + time zone
19 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
20
21 struct tm t;
22 while (!getLocalTime(&t)) { // ilk eşitlemeyi bekle (5 sn deneme) / wait for the first sync (5 s per try)
23 Serial.println("NTP bekleniyor / waiting for NTP");
24 }
25 Serial.println("Saat alindi / time received");
26}
27
28void loop() {
29 struct tm t;
30 if (getLocalTime(&t)) {
31 Serial.println(&t, "%d.%m.%Y %H:%M:%S"); // ör. 16.11.2026 14:05:09 / e.g. 16.11.2026 14:05:09
32 }
33 delay(1000);
34}
35| Riga | Cosa fa? |
|---|---|
| configTzTime(TZ_INFO, ...) | Avvia il client SNTP, imposta server e fuso orario; ritorna subito, l'ora arriva in background |
| getLocalTime(&t) | Riempie una struct tm con l'ora locale; se l'ora non c'è ancora, aspetta fino a 5 secondi e restituisce false |
| Serial.println(&t, "...") | La classe Print dell'ESP32 può stampare una struct tm direttamente con un formato strftime |
| time(nullptr) | Restituisce i secondi dal 1970 (tempo Unix, UTC) |
C'è un dettaglio importante: getLocalTime() decide se l'ora è «valida» guardando l'anno. All'avvio l'ora di sistema è il 1° gennaio 1970 e la funzione restituisce false finché l'anno non è successivo al 2016. Così il codice che registra dati o esegue attività prima che arrivi NTP non crea per errore voci datate 1970, purché controlli il valore restituito. Il secondo parametro è il tempo di attesa in millisecondi; se non vuoi bloccare loop(), scrivi getLocalTime(&t, 0) così prova una sola volta.
struct tm: le parti dell'ora
getLocalTime() scrive l'ora scomposta in una struct tm. Con questi campi confronti ore, giorni o mesi quando costruisci attività programmate. Due campi confondono spesso i principianti: il mese parte da 0 e l'anno si conta dal 1900.
| Campo | Intervallo | Esempio (lunedì 16 novembre 2026, 14:05:09) |
|---|---|---|
| tm_sec | 0–59 (60 con il secondo intercalare) | 9 |
| tm_min | 0–59 | 5 |
| tm_hour | 0–23 | 14 |
| tm_mday | 1–31 | 16 |
| tm_mon | 0–11 (0 = gennaio) | 10 |
| tm_year | Anni dal 1900 | 126 (1900 + 126 = 2026) |
| tm_wday | 0–6 (0 = domenica) | 1 |
| tm_yday | 0–365 (giorno dell'anno, da 0) | 319 |
Formattare data e ora con strftime
strftime() scrive l'ora di una struct tm in un array di caratteri secondo un modello. I codici con il segno di percentuale vengono sostituiti dal valore corrispondente, tutti gli altri caratteri restano invariati. Dimensiona l'array sull'output più lungo più uno (per lo zero finale): «14:05:09» ha 8 caratteri, quindi l'array deve avere almeno 9 byte. I codici più usati:
| Codice | Significato | Esempio di output |
|---|---|---|
| %H / %M / %S | Ora (00–23) / minuto / secondo | 14 / 05 / 09 |
| %I e %p | Ora in formato 12 ore e AM/PM | 02 PM |
| %d / %m / %Y | Giorno / mese (01–12) / anno a quattro cifre | 16 / 11 / 2026 |
| %y | Anno a due cifre | 26 |
| %A / %a | Giorno della settimana lungo / breve (in inglese) | Monday / Mon |
| %B / %b | Mese lungo / breve (in inglese) | November / Nov |
| %j | Giorno dell'anno (001–366) | 320 |
| %F / %T | Scorciatoie per %Y-%m-%d / %H:%M:%S | 2026-11-16 / 14:05:09 |
| %Z | Abbreviazione del fuso | +03 (oppure CET / CEST) |
La libreria C dell'ESP32 conosce solo i nomi inglesi di giorni e mesi; %A scrive sempre «Monday». Per una data in italiano tieni i nomi in array tuoi e usi tm_wday e tm_mon come indice. L'esempio qui sotto lo fa con nomi turchi; sostituiscili con «domenica, lunedì, …» e «gennaio, febbraio, …». Dato che il Monitor seriale supporta UTF-8, le lettere accentate si vedono bene. Lo sketch produce una data leggibile e il formato ISO ordinabile (2026-11-16 14:05:09) per i log.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO = "<+03>-3";
7
8// tm_wday: 0 = Pazar ... 6 = Cumartesi / 0 = Sunday ... 6 = Saturday
9const char* GUNLER[] = {"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"};
10const char* AYLAR[] = {"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran",
11 "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"};
12
13void setup() {
14 Serial.begin(115200);
15 WiFi.begin(WIFI_SSID, WIFI_PASS);
16 while (WiFi.status() != WL_CONNECTED) delay(500);
17 configTzTime(TZ_INFO, "pool.ntp.org");
18}
19
20void loop() {
21 struct tm t;
22 if (!getLocalTime(&t)) return; // saat henüz yok / time not set yet
23
24 char saat[9]; // "HH:MM:SS" + '\0'
25 char iso[20]; // "YYYY-MM-DD HH:MM:SS" + '\0'
26 strftime(saat, sizeof(saat), "%H:%M:%S", &t);
27 strftime(iso, sizeof(iso), "%Y-%m-%d %H:%M:%S", &t);
28
29 // Türkçe tarih: 16 Kasım 2026 Pazartesi / Turkish date
30 Serial.printf("%d %s %d %s %s\n", t.tm_mday, AYLAR[t.tm_mon], t.tm_year + 1900, GUNLER[t.tm_wday], saat);
31 Serial.printf("Kayit / log: %s epoch: %ld\n", iso, (long)time(nullptr));
32 delay(1000);
33}
34Mostrare ora e data su un display OLED
Per trasformare l'output del Monitor seriale in un vero orologio da scrivania, il display OLED SSD1306 da 0,96 pollici è ideale: costa poco, si collega con due fili e mostra cifre grandi e nitide. Lo sketch qui sotto mostra in alto il giorno della settimana e la data, al centro l'ora in caratteri grandi e in basso lo stato del Wi-Fi. Poiché ridisegna solo quando cambia il secondo, e non a ogni giro del loop, non occupa inutilmente il bus I2C. Installa le librerie Adafruit SSD1306 e Adafruit GFX dal Gestore librerie; il display è spiegato nell'articolo Display OLED (SSD1306).
1#include <WiFi.h>
2#include <Wire.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO = "<+03>-3";
10// OLED yazı tipinde Türkçe harf yok / the OLED font has no Turkish letters
11const char* GUNLER[] = {"Pazar", "Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi"};
12
13Adafruit_SSD1306 display(128, 64, &Wire, -1);
14int sonSaniye = -1; // son çizilen saniye / last drawn second
15
16void yaz(const char* mesaj) { // ekrana tek satır / one line on screen
17 display.clearDisplay();
18 display.setTextSize(1);
19 display.setCursor(0, 0);
20 display.print(mesaj);
21 display.display();
22}
23
24void setup() {
25 Wire.begin(21, 22); // SDA = 21, SCL = 22
26 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
27 display.setTextColor(SSD1306_WHITE);
28 yaz("Wi-Fi baglaniyor...");
29
30 WiFi.begin(WIFI_SSID, WIFI_PASS);
31 while (WiFi.status() != WL_CONNECTED) delay(300);
32 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
33 yaz("NTP bekleniyor...");
34}
35
36void loop() {
37 struct tm t;
38 if (!getLocalTime(&t, 0)) { // 0 = beklemeden dene / try without waiting
39 delay(200);
40 return;
41 }
42 if (t.tm_sec == sonSaniye) { // saniye değişmediyse çizme / redraw only on a new second
43 delay(20);
44 return;
45 }
46 sonSaniye = t.tm_sec;
47
48 char saat[9], tarih[11];
49 strftime(saat, sizeof(saat), "%H:%M:%S", &t);
50 strftime(tarih, sizeof(tarih), "%d.%m.%Y", &t);
51
52 display.clearDisplay();
53 display.setTextSize(1);
54 display.setCursor(0, 0);
55 display.print(GUNLER[t.tm_wday]); // gün adı / day name
56 display.setCursor(68, 0);
57 display.print(tarih); // 10 karakter = 60 piksel / 10 chars = 60 px
58 display.setTextSize(2);
59 display.setCursor(16, 26);
60 display.print(saat); // 8 x 12 = 96 piksel genişlik / 96 px wide
61 display.setTextSize(1);
62 display.setCursor(0, 56);
63 display.print(WiFi.status() == WL_CONNECTED ? "Wi-Fi: var" : "Wi-Fi: yok");
64 display.display();
65}
66Nota che i nomi dei giorni sono scritti senza lettere accentate: il font predefinito di Adafruit GFX non ha caratteri come è, ì o ù e al loro posto mostra simboli senza senso. Con dimensione del testo 2 ogni carattere è largo 12 pixel; «14:05:09», di 8 caratteri, occupa 96 pixel e, partendo dal pixel 16, risulta esattamente centrato sullo schermo da 128 pixel. Se preferisci un LCD a caratteri 16x2, stampa gli stessi output di strftime con lcd.print(); il collegamento è spiegato nell'articolo Display LCD I2C.
Cosa succede all'ora se cade il Wi-Fi? Backup RTC con DS3231
La domanda più frequente è: se va via il Wi-Fi, va via anche l'ora? No. Una volta ricevuta l'ora via NTP, l'ESP32 continua a contare con il proprio timer; la perdita del Wi-Fi non ferma l'orologio. Poiché questo timer funziona con il quarzo da 40 MHz, deriva di circa uno o due secondi al giorno, e la sincronizzazione successiva corregge l'errore quando torna il Wi-Fi. Il vero problema è un blackout o un riavvio con il pulsante EN: allora l'ora di sistema torna al 1970 e, senza Wi-Fi, l'ESP32 non ha modo di conoscere l'ora.
| Situazione | L'ora si conserva? | Cosa fare |
|---|---|---|
| Il Wi-Fi cade per un po' | Sì, l'ESP32 continua a contare | Niente; NTP corregge al ritorno del Wi-Fi |
| Deep sleep (sonno profondo) | Sì, il timer RTC continua a contare | Per sonni lunghi, risincronizza con NTP a qualche risveglio |
| Blackout o pulsante EN | No, l'orologio torna al 1970 | Aggiungi un modulo RTC a batteria come il DS3231 |
| Nessun internet (campo, magazzino) | NTP non arriva mai | Prendi l'ora solo da un modulo RTC |
Durante il deep sleep l'ora non viene contata dal quarzo principale ma da un oscillatore RC interno meno preciso. Questo oscillatore risente della temperatura, quindi nei sonni lunghi l'orologio può derivare di alcuni secondi o più; nei progetti a batteria che dormono per ore è una buona abitudine sincronizzare con NTP ogni qualche risveglio. I dettagli sono nell'articolo ESP32 Deep Sleep.

Il modo migliore per avere un orologio che resista ai blackout è combinare le due cose: all'avvio prendi subito l'ora dal modulo DS3231, correggila con NTP appena c'è il Wi-Fi e aggiorna il DS3231 a ogni sincronizzazione NTP. Nello sketch qui sotto il DS3231 memorizza l'ora UTC e non quella locale; così la regola del fuso sta in un solo posto (la stringa TZ) e i cambi dell'ora legale non rovinano mai l'RTC. Per intercettare la sincronizzazione NTP si registra una funzione di callback con sntp_set_time_sync_notification_cb(). Poiché questa funzione gira dentro il task di rete, lì non si fa alcun lavoro I2C; alza solo un flag e loop() si occupa di scrivere nel DS3231. Lo sketch usa la libreria RTClib.
1#include <WiFi.h>
2#include <Wire.h>
3#include <RTClib.h>
4#include <sys/time.h>
5#include "esp_sntp.h"
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO = "<+03>-3";
10
11RTC_DS3231 rtc;
12volatile bool ntpGeldi = false; // NTP eşitlemesi oldu mu? / did an NTP sync happen?
13
14void ntpCallback(struct timeval* tv) { // her NTP eşitlemesinde çağrılır / called on every NTP sync
15 ntpGeldi = true; // I2C işini loop() yapsın / let loop() do the I2C work
16}
17
18void setup() {
19 Serial.begin(115200);
20 Wire.begin(21, 22);
21 setenv("TZ", TZ_INFO, 1); // NTP gelmese de saat dilimi geçerli / TZ valid even without NTP
22 tzset();
23
24 // 1) Sistem saatini önce DS3231'den kur (DS3231'de UTC saklanır) / set system time from the DS3231 (stores UTC)
25 if (rtc.begin() && !rtc.lostPower()) {
26 struct timeval tv = { (time_t)rtc.now().unixtime(), 0 };
27 settimeofday(&tv, nullptr);
28 Serial.println("Saat DS3231'den / time from DS3231");
29 }
30
31 // 2) Wi-Fi gelince NTP saati düzeltir; bağlantı sonra gelse de SNTP yeniden dener
32 // NTP corrects the clock once Wi-Fi is up; SNTP keeps retrying if the link comes later
33 sntp_set_time_sync_notification_cb(ntpCallback);
34 WiFi.begin(WIFI_SSID, WIFI_PASS);
35 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
36}
37
38void loop() {
39 if (ntpGeldi) {
40 ntpGeldi = false;
41 rtc.adjust(DateTime((uint32_t)time(nullptr))); // DS3231'e UTC yaz / write UTC to the DS3231
42 Serial.println("DS3231 NTP ile guncellendi / DS3231 updated from NTP");
43 }
44
45 struct tm t;
46 if (getLocalTime(&t, 0)) {
47 Serial.println(&t, "%d.%m.%Y %H:%M:%S");
48 } else {
49 Serial.println("Saat yok: NTP ve DS3231 bekleniyor / no time yet");
50 }
51 delay(1000);
52}
53Eseguire un'attività a un'ora fissa ogni giorno
L'uso più comune dell'ora NTP è l'attività programmata: far girare la pompa di irrigazione per 10 minuti alle 07:30, accendere le luci del giardino alle 19:00, inviare una misura ogni ora in punto. Il metodo è semplice: leggi l'ora in loop() e avvia l'attività quando ora e minuto coincidono con l'obiettivo. La vera finezza è evitare che l'attività scatti centinaia di volte nello stesso minuto. Per questo memorizzi in una variabile il giorno in cui è stata eseguita (tm_yday), così non riparte mai due volte nello stesso giorno. Lo sketch qui sotto attiva il relè sul GPIO 26 per 10 minuti ogni mattina dal lunedì al venerdì alle 07:30; dato che misura la durata con millis(), il codice non si blocca mai.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO = "<+03>-3";
7
8const int ROLE_PIN = 26; // röle modülü IN / relay module IN
9const int GOREV_SAAT = 7; // her gün 07:30 / every day at 07:30
10const int GOREV_DAKIKA = 30;
11const unsigned long SURE_MS = 10UL * 60 * 1000; // 10 dakika çalış / run for 10 minutes
12
13int sonGun = -1; // görevin çalıştığı son gün (tm_yday) / last day the task ran
14bool roleAcik = false;
15unsigned long acilisMs = 0;
16
17void setup() {
18 Serial.begin(115200);
19 pinMode(ROLE_PIN, OUTPUT);
20 digitalWrite(ROLE_PIN, LOW);
21 WiFi.begin(WIFI_SSID, WIFI_PASS);
22 while (WiFi.status() != WL_CONNECTED) delay(500);
23 configTzTime(TZ_INFO, "pool.ntp.org");
24}
25
26void loop() {
27 struct tm t;
28 if (getLocalTime(&t, 0)) { // saat geçerliyse / only when the time is valid
29 bool haftaici = (t.tm_wday >= 1 && t.tm_wday <= 5); // Pzt-Cuma / Mon-Fri
30 if (haftaici && t.tm_hour == GOREV_SAAT && t.tm_min == GOREV_DAKIKA && t.tm_yday != sonGun) {
31 sonGun = t.tm_yday; // bugün bir kez / once per day
32 digitalWrite(ROLE_PIN, HIGH);
33 roleAcik = true;
34 acilisMs = millis();
35 Serial.println(&t, "%H:%M gorev basladi / task started");
36 }
37 }
38 if (roleAcik && millis() - acilisMs >= SURE_MS) { // süre dolunca kapat / switch off after the period
39 digitalWrite(ROLE_PIN, LOW);
40 roleAcik = false;
41 Serial.println("Gorev bitti / task finished");
42 }
43 delay(200);
44}
45- Confrontare i secondi: una condizione come t.tm_sec == 0 salta l'attività se il loop perde quel secondo (mentre il Wi-Fi si riconnette o durante un delay lungo). Controllare minuto e giorno è più sicuro.
- Verificare che l'ora sia valida: non eseguire attività finché getLocalTime() restituisce false, altrimenti subito dopo l'avvio prenderai decisioni sbagliate basate sul 1970.
- Attività saltata: se la corrente va via alle 07:29 e torna alle 07:35, quel giorno l'attività non parte. Se è importante, scrivi la condizione come «sono passate le 07:30 e oggi non è ancora partita» e salva sonGun in flash con Preferences.
- Più attività: tieni ora, minuto e durata in un array e usa una variabile dell'ultimo giorno separata per ogni attività.
- Carichi ad alta corrente: comanda pompa o lampada tramite un modulo relè o un MOSFET, mai direttamente da un pin.
Cambiare l'intervallo di sincronizzazione e monitorare NTP
L'intervallo predefinito di 3 ore va bene per la maggior parte dei progetti. Per sincronizzare più spesso (per esempio all'aperto con forti escursioni termiche) o ridurre il traffico, passa il nuovo intervallo in millisecondi a sntp_set_sync_interval(). Chiamala prima di configTzTime(), perché il nuovo intervallo vale dall'avvio successivo o alla scadenza di quello in corso. Per le regole del protocollo non può essere inferiore a 15 secondi. sntp_get_sync_status() restituisce SNTP_SYNC_STATUS_COMPLETED una volta dopo ogni sincronizzazione completata e poi si azzera, così puoi intercettare ogni nuova sincronizzazione.
1#include <WiFi.h>
2#include <time.h>
3#include "esp_sntp.h"
4
5const char* TZ_INFO = "<+03>-3";
6
7void setup() {
8 Serial.begin(115200);
9 WiFi.begin("AgAdi", "Sifre123");
10 while (WiFi.status() != WL_CONNECTED) delay(500);
11
12 sntp_set_sync_interval(6UL * 60 * 60 * 1000); // 6 saatte bir eşitle (en az 15 sn) / sync every 6 hours (min 15 s)
13 configTzTime(TZ_INFO, "pool.ntp.org"); // ayar sntp_init() öncesi verilir / set before sntp_init()
14}
15
16void loop() {
17 if (sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
18 Serial.println("Yeni NTP esitlemesi / new NTP sync");
19 }
20 delay(1000);
21}
22Quale server NTP usare?
La scelta più comune è pool.ntp.org: un insieme di migliaia di server gestiti da volontari in tutto il mondo che a ogni richiesta ti dà l'indirizzo di un server vicino. I sottodomini per paese (it.pool.ntp.org, ch.pool.ntp.org) avvicinano ancora di più il server. In Italia è molto usato anche ntp1.inrim.it, il server dell'Istituto Nazionale di Ricerca Metrologica. configTzTime() accetta fino a tre nomi di server; se il primo non risponde, passa al successivo.
| Server | Caratteristica | Nota |
|---|---|---|
| pool.ntp.org | Pool di volontari, server più vicino | Prima scelta per l'uso generale |
| it.pool.ntp.org / ntp1.inrim.it | Pool nazionale / ente metrologico italiano | Latenza un po' più bassa |
| time.cloudflare.com | Infrastruttura di una grande azienda | Buono come server di riserva |
| time.google.com | Spalma i secondi intercalari (leap smear) | Non mescolarlo con altri server |
| IP del tuo router | Alcuni router offrono NTP locale | Utile in una rete locale senza internet |
NTP sul Raspberry Pi Pico W: ntptime
Sul Raspberry Pi Pico W (e sul Pico 2 W) lo stesso lavoro lo fa il modulo MicroPython ntptime. ntptime.settime() interroga il server NTP e imposta l'RTC interno del Pico su UTC. MicroPython non ha stringhe di fuso orario come l'ESP32; per l'ora locale aggiungi lo scostamento in secondi a UTC (3 × 3600 per la Turchia nell'esempio, 3600 per l'Italia in inverno e 7200 in estate). ntptime prende l'ora una sola volta e non risincronizza automaticamente; nei progetti che girano a lungo, chiamare settime() una o due volte al giorno elimina la deriva. Anche l'RTC del Pico W si azzera quando manca la corrente, quindi il backup con DS3231 vale anche qui. La connessione del Pico W al Wi-Fi è spiegata nell'articolo Pico W Wi-Fi e web server.
1import network
2import ntptime
3import time
4
5SSID = "AgAdi"
6PASS = "Sifre123"
7UTC_OFFSET = 3 * 3600 # Türkiye UTC+3 / Turkey UTC+3
8# localtime(): 0 = Pazartesi / 0 = Monday
9GUNLER = ("Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi", "Pazar")
10
11wlan = network.WLAN(network.STA_IF)
12wlan.active(True)
13wlan.connect(SSID, PASS)
14while not wlan.isconnected(): # bağlanana kadar bekle / wait until connected
15 time.sleep(0.5)
16
17ntptime.host = "pool.ntp.org"
18for deneme in range(5): # NTP bazen zaman aşımına uğrar / NTP may time out
19 try:
20 ntptime.settime() # dahili RTC'yi UTC'ye ayarlar / sets the internal RTC to UTC
21 break
22 except OSError:
23 time.sleep(2)
24
25def yerel_saat(): # UTC + 3 saat / UTC + 3 hours
26 return time.localtime(time.time() + UTC_OFFSET)
27
28son_gun = -1
29while True:
30 yil, ay, gun, saat, dk, sn, hgun, ygun = yerel_saat()
31 print("{:02d}.{:02d}.{} {} {:02d}:{:02d}:{:02d}".format(gun, ay, yil, GUNLER[hgun], saat, dk, sn))
32 if saat == 7 and dk == 30 and ygun != son_gun: # her gün 07:30'da bir kez / once a day at 07:30
33 son_gun = ygun
34 print("Gorev calisti / task ran")
35 time.sleep(1)
36In MicroPython, time.localtime() restituisce una tupla di otto elementi: anno, mese (1–12), giorno, ora, minuto, secondo, giorno della settimana e giorno dell'anno. Rispetto alla struct tm dell'ESP32 ci sono due differenze: il mese parte da 1 e il giorno della settimana conta 0 = lunedì. ntptime.settime() solleva OSError quando la rete va in timeout; per questo lo sketch ha un ciclo di cinque tentativi.
Idee di progetto con l'orologio ESP32 NTP
- Orologio da scrivania Wi-Fi: mostra ora, data e temperatura della stanza su un display OLED e abbassa la luminosità di notte.
- Orologio LED gigante: mostra l'ora NTP come testo scorrevole su una matrice LED MAX7219.
- Irrigazione programmata: ogni mattina misura l'umidità del terreno e, se serve, fai girare una mini pompa d'acqua per un tempo fisso.
- Data logging con timestamp: scrivi le letture dei sensori con data ISO su una scheda SD.
- Orologio con meteo: aggiungi accanto all'ora il meteo preso da internet; l'articolo ESP32 meteo continua da qui.
- Report del mattino: ogni giorno alle 08:00 invia al telefono via Telegram la temperatura di casa e i movimenti rilevati durante la notte.
Errori comuni e soluzioni
| Sintomo | Causa probabile | Soluzione |
|---|---|---|
| L'ora è 1970 o non arriva mai | Wi-Fi non connesso o porta NTP (UDP 123) bloccata | Controlla prima WiFi.status(); su reti scolastiche o aziendali prova un'altra rete o l'hotspot del telefono |
| L'ora è indietro di esattamente 1 o 2 ore | Nessun fuso impostato, viene mostrato UTC | configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", ...) |
| L'ora sbaglia del doppio dello scostamento | Segno invertito nella stringa TZ ("CET+1") | Scrivi "CET-1CEST,..." oppure "<+03>-3" |
| Il mese è indietro di uno, l'anno è 126 | tm_mon conta da 0, tm_year dal 1900 | Usa tm_mon + 1, tm_year + 1900 oppure strftime |
| L'attività scatta più volte nello stesso minuto | Nessuna protezione dalle ripetizioni | Memorizza il giorno di esecuzione (tm_yday) e controllalo |
| Il loop si blocca 5 secondi a ogni giro | Senza ora valida getLocalTime() aspetta 5000 ms per impostazione predefinita | Usa getLocalTime(&t, 0) dentro loop() |
| Simboli strani nel nome del giorno sull'OLED | Il font non ha lettere accentate | Scrivi i giorni in ASCII o passa alla libreria U8g2 |
ESP32 NTP è il modo più economico e comodo per aggiungere un'ora esatta a un progetto: con poche righe di codice prende l'ora al secondo, gestisce da solo fuso orario e ora legale e, con un DS3231, resiste anche ai blackout. Nell'app BlueGrays trovi progetti di ora reale con ESP32 NTP, orologio digitale con RTC DS1307, display meteo su OLED e registratore di temperatura con timestamp, passo passo con schema e codice.
Domande frequenti
L'ESP32 può tenere l'ora senza internet?
Sì, dopo averla ricevuta via NTP: l'ESP32 continua a contare l'ora di sistema con il proprio timer e la perdita del Wi-Fi non ferma l'orologio. Se però manca la corrente o la scheda si riavvia, l'ora torna al 1970. Nei luoghi senza internet, o per un orologio che resista ai blackout, aggiungi un modulo RTC a batteria come il DS3231.
Perché l'ora NTP dell'ESP32 è sbagliata di una o due ore?
Senza fuso orario l'ESP32 mostra l'ora UTC, cioè un'ora indietro rispetto all'Italia in inverno e due in estate. Usa configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"). Se lo sfasamento è il doppio, il segno della stringa TZ è invertito.
Come si imposta l'ora legale sull'ESP32?
Il modo più semplice è configTzTime con un fuso POSIX che contiene le regole di cambio. Per l'Italia è "CET-1CEST,M3.5.0,M10.5.0/3"; l'ESP32 cambia da solo l'ultima domenica di marzo e di ottobre. configTime con uno scostamento estivo fisso non conosce queste date.
Ogni quanto l'ESP32 si sincronizza con NTP?
Nel core Arduino ESP32 SNTP interroga di nuovo il server ogni 3 ore per impostazione predefinita e nel frattempo tiene l'ora con il proprio timer. Puoi cambiare l'intervallo in millisecondi con sntp_set_sync_interval(), ma per protocollo non può scendere sotto i 15 secondi.
Serve un modulo RTC con l'ESP32?
No, se il dispositivo è sempre alimentato e raggiunge il Wi-Fi; NTP dà un'ora più precisa. Se ti serve l'ora giusta dopo un blackout prima che torni internet, o se il dispositivo lavorerà senza internet, aggiungi un modulo RTC a batteria come il DS3231.
Come si ottiene l'ora NTP sul Raspberry Pi Pico W?
In MicroPython, dopo la connessione al Wi-Fi, bastano import ntptime e ntptime.settime(); così l'RTC interno del Pico viene impostato su UTC. Per l'ora italiana usa time.localtime(time.time() + 3600) in inverno e + 7200 in estate. ntptime non risincronizza da solo, quindi ripeti settime() una o due volte al giorno.
Progetti da realizzare dopo questo articolo
Vedi tutti i progetti
ESP32: ora reale da Internet tramite NTP
L'ESP32 si connette al Wi-Fi e recupera l'ora reale con precisione al millisecondo dai server NTP (Network Time Protocol). Grazie a configTzTime(), il fuso orario della Turchia (TRT, UTC+3) viene applicato automaticamente e il passaggio all'ora legale è gestito tramite la stringa POSIX TZ. L'ora viene mostrata sul Serial Monitor e su un display LCD I2C opzionale. configTzTime() avvia il servizio SNTP; vengono eseguite risincronizzazioni periodiche finché è disponibile l'accesso alla rete, e

Orologio digitale con ESP32 e RTC DS1307 (I2C + LCD)
In questo progetto, utilizziamo il modulo orologio in tempo reale (RTC) DS1307 per visualizzare l'ora e la data su un display LCD 16x2 I2C. Il DS1307 è un RTC con batteria di supporto che mantiene l'ora anche se l'ESP32 si spegne. Entrambi i moduli condividono la stessa linea I2C (SDA/SCL), permettendoci di imparare a controllare più dispositivi utilizzando solo due cavi di segnale. Cosi imparerai il protocollo I2C e il funzionamento dei moduli RTC nella pratica.

Connessione Wi-Fi STA con ESP32 (con display LCD)
In questo progetto colleghiamo l'ESP32 alla rete Wi-Fi domestica in modalità Station (STA) in modo sicuro e tollerante agli errori. Separiamo SSID e password in un file secrets.h per impedire fughe su Git, impostiamo un meccanismo di riconnessione automatica e visualizziamo il livello del segnale RSSI e l'indirizzo IP su un display LCD I2C. Il LED interno funge da indicatore di stato.

ESP32 HTTPClient + API meteo (con display OLED SSD1306)
In questo progetto, utilizzando l'ESP32, effettuiamo una chiamata REST all'API meteo (wttr.in) tramite Internet utilizzando HTTPClient.h. A differenza della precedente versione con LCD I2C, qui utilizziamo un display OLED SSD1306 (128×64), che consente di utilizzare caratteri di grandi dimensioni, icone vettoriali e una ricca presentazione visiva su un unico schermo. Per le condizioni meteorologiche, 7 diverse icone vettoriali (soleggiato, parzialmente nuvoloso, nuvoloso, piovoso, nevoso, temp

ESP32 HTTPClient + API meteo (con schermo LCD)
In questo progetto, utilizzando l'ESP32, effettuiamo una chiamata REST all'API meteo (wttr.in) tramite Internet utilizzando HTTPClient.h. Analizziamo la risposta JSON restituita tramite ArduinoJson v7 e visualizziamo le informazioni relative a città, temperatura, umidità, condizioni meteorologiche e vento sul display LCD I2C sia in turco che in inglese. wttr.in è gratuito e non richiede una chiave API: basta digitare il nome di una città qualsiasi per ottenere le condizioni meteorologiche in t

ESP32 Deep Sleep — Risparmio della batteria (OLED + contatore in RTC RAM)
La modalità Deep Sleep dell'ESP32 riduce il consumo di corrente a circa 10 µA spegnendo la CPU e la maggior parte dei dispositivi periferici ed è di fondamentale importanza per i dispositivi IoT alimentati a batteria. In questo progetto utilizziamo tre diverse fonti di risveglio: il timer (15 secondi), un pin esterno (EXT0 — pulsante GPIO 33) e il sensore tattile (T0/GPIO 4). Le variabili contrassegnate con RTC_DATA_ATTR vengono preservate durante la modalità Deep Sleep: il contatore di avvio,

Data Logger di Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Costruisci un data logger di temperatura con timestamp usando ESP32! Leggi temperatura/umidità dal DHT22 ogni minuto, ottieni l'ora reale dal DS1307 RTC e registra su scheda microSD in formato CSV. L'OLED mostra dal vivo temperatura (font grande), percentuale di umidità, timestamp e contatore di log. Il file /temp_log.csv sulla scheda SD si apre in Excel, Google Sheets o Python pandas per l'analisi — perfetto per il monitoraggio di serre, tracciamento della temperatura domestica, raccolta

Schermo LCD I2C per ESP32 (16x2)
In questo progetto visualizziamo del testo collegando un display LCD a 16x2 caratteri all'ESP32 tramite il protocollo I2C. Grazie all'I2C possiamo controllare il display utilizzando solo 2 pin di dati (SDA e SCL); si elimina così lo spreco dei 6+ pin tipici del collegamento parallelo classico. Il codice stampa un messaggio fisso sulla prima riga e un contatore che aumenta ogni secondo sulla seconda riga. Inoltre, all'inizio, eseguendo lo scanner I2C, stampiamo l'indirizzo del display sul Serial
Altri articoli
App BlueGrays
Costruisci questo circuito passo passo sul telefono
Schema, codice ed elenco componenti in un’unica schermata. Oltre 200 progetti Arduino, ESP32 e Pico da realizzare.
Inquadra con il telefono
Punta la fotocamera sul codice: si apre lo store del tuo telefono.
oppure apri lo store

