In diesem Tutorial lernst du, wie du die ESP32-Uhr mit einem SNTP-Server synchronisierst, um immer die genaue Zeit zu haben und die Uhr nie wieder einstellen zu müssen.
Durch die Synchronisierung der Uhr über das Internet mit einem SNTP-Zeitserver stellen wir sicher, dass unsere Uhr immer die genaue Zeit anzeigt, unabhängig von der Genauigkeit der internen ESP32-Uhr oder Änderungen der Sommerzeit.
Beginnen wir mit den benötigten Teilen.
Benötigte Teile
Du benötigst nur einen ESP32, ein LCD und einige Kabel. Anstelle des unten aufgeführten ESP32-C3 Mini Development Boards kannst du auch jeden anderen ESP32 verwenden.

ESP32-C3 Mini

USB-C-Kabel

LCD 16×2

Dupont-Kabelsatz

Breadboard
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.
NTP-Zeitserver
NTP steht für Network Time Protocol, und ein NTP-Server ist ein spezialisiertes Computersystem, das genaue Zeitinformationen für andere Internetgeräte bereitstellt.
Ein NTP-Client fragt regelmäßig einen oder mehrere NTP-Server ab, um die aktuelle Zeit zu erhalten und synchronisiert seine interne Uhr basierend auf dem empfangenen Zeitstempel. Im Fall eines ESP32 als Client verbindet sich der ESP32 über einen Router und Wi-Fi mit dem Internet und initiiert eine Verbindung zu einem Pool von NTP-Servern. Einer der NTP-Server antwortet mit einem Zeitstempel, der vom ESP32 empfangen und zur Anpassung seiner internen Uhr verwendet wird.

Die NTP-Server selbst sind in einem hierarchischen, geschichteten System organisiert, dessen Ursprung hochpräzise Zeitmessgeräte wie Atomuhren, GNSS (einschließlich GPS) oder andere Funkuhren sind (autoritative Zeitquelle). Die einzelnen Ebenen werden Stratum genannt.

Typischerweise verbindet man sich nicht mit einem bestimmten NTP-Server (IP-Adresse), sondern mit einem Pool von vielen NTP-Servern. Mehr dazu im nächsten Abschnitt.
NTP-Pool
Der NTP Pool ist eine dynamische Sammlung vernetzter NTP-Server. Die folgende Tabelle zeigt die Anzahl der NTP-Server im ntp.org-Pool in verschiedenen geografischen Regionen.

Du forderst Zeitinformationen von einem NTP-Server im Pool an, indem du den Namen „pool.ntp.org“ verwendest. Das System versucht, den nächstgelegenen verfügbaren Server für dich zu finden. Dies ist der empfohlene Weg.
Du kannst jedoch auch die Zeit von NTP-Servern in bestimmten Regionen oder Ländern anfordern. Zum Beispiel bezieht sich die Adresse „de.pool.ntp.org“ auf einen Pool von NTP-Servern in Deutschland (DE). Du kannst auch continental zones verwenden, wie z.B. europe, north-america, oceania oder asia.pool.ntp.org. Schließlich kannst du eine numerische Präfix hinzufügen, wenn du mehrere Servernamen benötigst, z.B. „0.de.pool.ntp.org“, „1.de.pool.ntp.org“.
STNP vs NTP
Es gibt zwei Protokolle zur Zeitsynchronisation: NTP (Network Time Protocol) und SNTP (Simple Network Time Protocol). NTP ist genauer und komplexer, während SNTP eine vereinfachte Version von NTP ist.
SNTP wurde speziell für kleine Computer und Mikrocontroller entwickelt. Es wurde für geringere Speicheranforderungen und Rechenleistung als NTP konzipiert. Obwohl viel weniger präzise als NTP, liefert SNTP dennoch eine Zeit mit einer Genauigkeit von etwa 100 Millisekunden.
Typischerweise wird es von kleinen Netzwerkgeräten wie IP-Kameras, DVRs, IP-Telefonen, Routern, Consumer-Geräten und auch von Mikrocontrollern wie dem ESP32 verwendet.
Synchronisierung der ESP32-Uhr mit SNTP-Server
Der folgende Code zeigt, wie man die interne Uhr eines ESP32 mit der Zeit eines SNTP-Servers synchronisiert. So kann der ESP32 die genaue Zeit halten, indem er sie periodisch vom Server aktualisiert. Schau dir zuerst den kompletten Code an, danach besprechen wir die Details.
// makerguides.com: synchronizing ESP32 time with SNTP server
#include "WiFi.h"
#include "esp_sntp.h"
const char* ssid = "SSID";
const char* password = "PASSWORD";
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
void notify(struct timeval* t) {
Serial.println("synchronized");
}
void initSNTP() {
sntp_set_sync_interval(1 * 60 * 60 * 1000UL); // 1 hour
sntp_set_time_sync_notification_cb(notify);
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setTimezone();
}
void wait4SNTP() {
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
delay(100);
Serial.println("waiting ...");
}
}
void setTimezone() {
setenv("TZ", "AEST-10AEDT,M10.1.0,M4.1.0/3", 1);
tzset();
}
void printTime() {
struct tm timeinfo;
getLocalTime(&timeinfo);
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}
void setup() {
Serial.begin(115200);
initWiFi();
initSNTP();
wait4SNTP();
}
void loop() {
printTime();
delay(10000);
}
Bibliotheken
Wir beginnen mit dem Einbinden der benötigten Bibliotheken. WiFi.h für die Internetverbindung und esp_sntp.h für die Kommunikation mit einem SNTP-Server. Beide Bibliotheken sind Standardbibliotheken des ESP32-Cores und müssen nicht installiert werden.
#include "WiFi.h" #include "esp_sntp.h"
Konstanten und Variablen
Als nächstes definieren wir die Konstanten und Variablen, die das Programm benötigt. Hier geben wir SSID und Passwort für das Wi-Fi-Netzwerk an, mit dem sich der ESP32 verbinden soll. Natürlich musst du diese durch deine eigenen Wi-Fi-Zugangsdaten ersetzen.
const char* ssid = "SSID"; const char* password = "PASSWORD";
WiFi-Initialisierung
Die initWiFi() Funktion ist dafür zuständig, den ESP32 mit dem angegebenen WiFi-Netzwerk zu verbinden. Sie prüft kontinuierlich den Verbindungsstatus, bis die Verbindung erfolgreich hergestellt ist.
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
SNTP-Initialisierung
In der initSNTP() Funktion konfigurieren wir die SNTP-Einstellungen für die Zeitsynchronisation. Dazu gehört das Festlegen des Synchronisationsintervalls, des Servernamens, des Betriebsmodus und der Zeitzone.
void notify(struct timeval* t) {
Serial.println("synchronized");
}
void initSNTP() {
sntp_set_sync_interval(60 * 60 * 1000UL); // 1 hour
sntp_set_time_sync_notification_cb(notify);
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setTimezone();
}
Schauen wir uns die einzelnen Funktionsaufrufe und ihre Aufgaben genauer an.
sntp_set_sync_interval(interval_ms) bestimmt, wie häufig wir die interne Uhr des ESP32 mit dem SNTP-Server synchronisieren. Das Zeitintervall wird in Mikrosekunden angegeben, und ein Intervall von 60 * 60 * 1000UL Mikrosekunden bedeutet, dass wir jede Stunde synchronisieren. Sende keine zu häufigen Anfragen. Sinnvolle Abfrageintervalle liegen typischerweise zwischen ein- bis zweimal täglich und bis zu 5 Mal pro Stunde.
sntp_set_time_sync_notification_cb(callback) ermöglicht es, eine Benachrichtigungsfunktion (callback) anzugeben, die bei jeder Synchronisation aufgerufen wird. Im Beispielcode definieren wir dafür die Funktion notify(), die einfach „synchronized“ ausgibt. In den meisten Anwendungen ist das nicht nötig, aber es ist sehr hilfreich zum Testen und Debuggen!
esp_sntp_setoperatingmode(operating_mode) setzt den operating mode. Das ist typischerweise ESP_SNTP_OPMODE_POLL – wir fragen den SNTP-Server ab. Es gibt aber auch ESP_SNTP_OPMODE_LISTENONLY.
esp_sntp_setservername(idx, server) wird verwendet, um den Namen/die Adresse des Servers festzulegen. Du kannst mehrere Server angeben, wenn du möchtest. Zum Beispiel:
esp_sntp_setservername(0, "pool.ntp.org"); esp_sntp_setservername(1, "de.pool.ntp.org"); esp_sntp_setservername(2, "time.nist.gov");
esp_sntp_init() startet den SNTP-Dienst mit den oben genannten Parametern. Es gibt auch eine Stopp-Funktion: esp_sntp_stop() zum Beenden des Dienstes.
Schließlich müssen wir die Zeitzone einstellen, da der SNTP-Server die Zeit in UTC zurückgibt. Dieser Teil ist in einer separaten Funktion implementiert, die wir im nächsten Abschnitt besprechen.
Wenn du mehr über ESP-Timer-Funktionen erfahren möchtest, schau dir das ESP32 Programming Guide for System Time an. Beachte, dass es auch eine configTime() Funktion im Arduino-Core gibt, die im Wesentlichen dasselbe macht wie unsere initSNTP() Funktion. Allerdings nimmt sie einen gmtOffset_sec und daylightOffset_sec als Parameter, aber ich konnte sie mit der australischen Zeitzone nicht richtig zum Laufen bringen, und sie erlaubt nicht, das Synchronisationsintervall einzustellen.
Zeitzonenkonfiguration
Die setTimezone() Funktion setzt die Zeitzone für den ESP32 über die TZ-Umgebungsvariable. Da ich derzeit in Australien lebe, habe ich sie auf „AEST-10AEDT,M10.1.0,M4.1.0/3“ gesetzt, was der Australian Eastern Standard Time (AEST) mit Sommerzeitumstellung entspricht.
void setTimezone() {
setenv("TZ", "AEST-10AEDT,M10.1.0,M4.1.0/3", 1);
tzset();
}
Die Bestandteile dieser Zeitzonendefinition sind wie folgt
- AEST: Australian Eastern Standard Time
- -10: UTC-Versatz von 10 Stunden vor der koordinierten Weltzeit (UTC)
- AEDT: Australian Eastern Daylight Time
- M10.1.0: Umstellung auf Sommerzeit erfolgt am ersten Sonntag im Oktober
- M4.1.0/3: Rückstellung auf Normalzeit erfolgt am ersten Sonntag im April, mit einem 3-Stunden-Unterschied zur UTC.
Für andere Zeitzonendefinitionen schau dir das Posix Timezones Database an. Kopiere einfach den dort gefundenen String für deine Zeitzone in die setenv() Funktion.
Warten auf Synchronisation
Die wait4SNTP() Funktion prüft kontinuierlich den Synchronisationsstatus mit dem SNTP-Server, bis die Synchronisation abgeschlossen ist. Sie gibt während des Wartens eine Nachricht aus.
void wait4SNTP() {
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
delay(100);
Serial.println("waiting ...");
}
}
Zeit ausgeben
Die printTime() Funktion ruft die aktuelle Zeit ab und gibt sie formatiert mit der struct tm Datenstruktur aus.
void printTime() {
struct tm timeinfo;
getLocalTime(&timeinfo);
Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");
}
"%A, %B %d %Y %H:%M:%S" sind format specifiers, die bestimmen, wie die timeinfo formatiert wird, und die Mitglieder der tm struct sind wie folgt:
Member Type Meaning Range tm_sec int seconds after the minute 0-61* tm_min int minutes after the hour 0-59 tm_hour int hours since midnight 0-23 tm_mday int day of the month 1-31 tm_mon int months since January 0-11 tm_year int years since 1900 tm_wday int days since Sunday 0-6 tm_yday int days since January 1 0-365 tm_isdst int Daylight Saving Time flag
Setup-Funktion
In der setup() Funktion initialisieren wir die serielle Kommunikation, verbinden uns mit Wi-Fi, initialisieren SNTP und warten, bis die Zeitsynchronisation abgeschlossen ist.
void setup() {
Serial.begin(115200);
initWiFi();
initSNTP();
wait4SNTP();
}
Beachte, dass du die Wi-Fi-Verbindung nach dem Setup nicht ausschalten kannst. Manche Tutorials machen hier einen Fehler. Ohne Wi-Fi kann der ESP32 nicht mit dem SNTP-Server synchronisieren. Das Ausschalten von Wi-Fi macht nur Sinn, wenn du den ESP32 in den Deep-Sleep-Modus versetzt und beim Aufwachen die Synchronisation neu startest.
Loop-Funktion
Die loop() Funktion gibt alle 10 Sekunden die aktuelle Zeit aus.
void loop() {
printTime();
delay(10000);
}
Beachte, dass die Synchronisation im Hintergrund (separater Thread) erfolgt. Es gibt keine Funktion, die du regelmäßig in der loop Funktion aufrufen musst, um den SNTP-Server abzufragen.
Beispielausgabe
Um den Code auszuprobieren, empfehle ich, das Synchronisationsintervall vorübergehend auf 30 Sekunden zu setzen (30 * 1000UL). Im Serial Monitor solltest du dann folgende Ausgaben sehen:

Zuerst eine „waiting“ Meldung, bis die Verbindung zum SNTP-Server hergestellt ist. Danach Zeit-Ausgaben alle 10 Sekunden, gefolgt von einer „synchronized“ Benachrichtigung alle 30 Sekunden.
Im nächsten Abschnitt verwenden wir im Wesentlichen denselben Code, um eine internet-synchronisierte Uhr zu bauen, die Zeit und Datum auf einem LCD anzeigt.
Anzeige der Internetzeit auf einem LCD
Ein LCD mit I2C-Schnittstelle an den ESP32 anzuschließen, um die Zeit anzuzeigen, ist einfach. Verbinde zuerst 5V mit VCC und G mit Ground. Dann verbinde SDA mit Pin 8 (gelbes Kabel) und SCL mit Pin 9 (oranges Kabel) am ESP32.

Wenn du detailliertere Informationen zu LCD-Displays brauchst, schau dir unsere Tutorials zu How to use a 16×2 character LCD with Arduino und Character I2C LCD with Arduino Tutorial (8 Examples) an.
Achte darauf, SDA und SCL korrekt anzuschließen. Wenn du ein anderes Board verwendest, solltest du die Pins wählen, die auf deinem Board die I2C-Schnittstelle unterstützen. Suche einfach nach den Pins mit der Bezeichnung SDA und SCL im Pinout. Hier ist das Pinout für den ESP32-C3 Supermini, den ich in diesem Projekt verwende:
Obwohl die Pins GPIO8 und GPIO9 eigentlich die nativen Pins für die I2C-Schnittstelle sind, musste ich sie im Code explizit setzen (Wire.begin(sda, scl)), damit es funktioniert. Für mehr Informationen zum SuperMini-Board schau dir das ESP32-C3 SuperMini Board Tutorial an.
Das folgende Bild zeigt die tatsächliche, fertige Verkabelung und die laufende Uhr auf dem LCD. Wie du siehst, sind es nur wenige Kabel, und der ESP32-C3 Supermini ist im Vergleich zum LCD wirklich „mini“.

Code für die internet-synchronisierte Uhr
Hier ist der komplette Code, um die interne Uhr eines ESP32 mit einem SNTP-Server zu synchronisieren und die aktuelle Zeit und das Datum auf einem LCD anzuzeigen.
// makerguides.com: synchronizing ESP32 time with SNTP server
#include "WiFi.h"
#include "esp_sntp.h"
#include "LiquidCrystal_I2C.h"
const int sdaPin = 8;
const int sclPin = 9;
const char* ssid = "SSID";
const char* password = "PASSWORD";
// https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
const char *timezone = "AEST-10AEDT,M10.1.0,M4.1.0/3"
LiquidCrystal_I2C lcd = LiquidCrystal_I2C(0x3F, 16, 2);
void initWiFi() {
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(100);
}
}
void initSNTP() {
sntp_set_sync_interval(60 * 60 * 1000UL); // 1 h
esp_sntp_setoperatingmode(ESP_SNTP_OPMODE_POLL);
esp_sntp_setservername(0, "pool.ntp.org");
esp_sntp_init();
setenv("TZ", timezone, 1);
tzset();
}
void wait4SNTP() {
lcd.setCursor(0, 0);
lcd.print("synchronizing...");
while (sntp_get_sync_status() != SNTP_SYNC_STATUS_COMPLETED) {
delay(100);
}
lcd.clear();
}
void initLCD() {
Wire.begin(sdaPin, sclPin); // I2C for ESP32-C3 Supermini
lcd.init();
lcd.backlight();
lcd.clear();
}
void lcdPrintTime() {
// https://cplusplus.com/reference/ctime/tm/
char buff[24];
struct tm t;
getLocalTime(&t);
sprintf(buff, "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
lcd.setCursor(4, 0);
lcd.print(buff);
sprintf(buff, "%02d-%02d-%04d ", t.tm_mday, t.tm_mon+1, t.tm_year+1900);
lcd.setCursor(3, 1);
lcd.print(buff);
}
void setup() {
initLCD();
initWiFi();
initSNTP();
wait4SNTP();
}
void loop() {
lcdPrintTime();
delay(1000);
}
Wie du siehst, ist das im Wesentlichen derselbe Code wie zuvor, mit der Ergänzung der initLCD() Funktion, die das LCD vorbereitet, und der lcdPrintTime() Funktion, die Zeit und Datum auf dem LCD anzeigt.
void lcdPrintTime() {
char buff[24];
struct tm t;
getLocalTime(&t);
sprintf(buff, "%02d:%02d:%02d ", t.tm_hour, t.tm_min, t.tm_sec);
lcd.setCursor(4, 0);
lcd.print(buff);
sprintf(buff, "%02d-%02d-%04d ", t.tm_mday, t.tm_mon, t.tm_year+1900);
lcd.setCursor(3, 1);
lcd.print(buff);
}
Die lcdPrintTime() Funktion ruft zuerst getLocalTime() auf, um die lokale Zeit der internen ESP32-Uhr zu erhalten, die wir mit dem SNTP-Server synchronisiert haben. Aus struct tm extrahieren wir dann die benötigten Zeit- und Datumsinformationen (Stunde, Minute, …, Jahr) und schreiben sie formatiert in den String-Puffer buff. Schließlich setzen wir den Cursor für das LCD und zeigen den Inhalt des Puffers auf dem LCD an. Deine Ausgabe auf dem LCD sollte so aussehen:

Und das war’s!
Jetzt hast du eine immer genaue Uhr auf dem ESP32, die ihre Zeit mit einem SNTP-Server synchronisiert.
Fazit
In diesem Tutorial hast du gelernt, wie man die interne Uhr eines ESP32 mit dem Zeitsignal eines SNTP-Servers synchronisiert. Das stellt sicher, dass die ESP32-Uhr immer genau ist und hat den zusätzlichen Vorteil, dass du die Uhr nie wieder einstellen musst.
Wir haben ESP32-spezifische Funktionen wie sntp_set_sync_interval, esp_sntp_setoperatingmode und esp_sntp_setservername verwendet, um die Kommunikation mit einem SNTP-Server zu konfigurieren.
Es ist jedoch gut zu wissen, dass es auch die Arduino core also provides two functions to set up an SNTP server Verbindung gibt. Diese sind configTime und configTzTime. Sie sind abstraktere Wrapper um dieselben Funktionen, die wir in initSNTP verwendet haben, funktionieren aber unverändert z.B. für ESP32 oder ESP8266. Du könntest also initSNTP durch einen Aufruf von configTzTime(timezone, "pool.ntp.org") ersetzen. Aber initSNTP gibt dir etwas mehr Kontrolle, z.B. das Einstellen des Synchronisationsintervalls.
Wenn du den ESP32 im Deep-Sleep-Modus betreiben und die Uhr beim Aufwachen synchronisieren möchtest oder zusätzliche Zeitinformationen brauchst, bevorzugst du vielleicht einen Internet-Zeitdienst wie WorldTimeAPI. Für mehr Informationen schau dir das Automatic Daylight Savings Time Clock, das Digital Clock with CrowPanel 3.5″ ESP32 Display oder das LED Ring Clock with WS2812 Tutorial an.
Viel Spaß beim Tüfteln ; )


