ESP32 NTP Uhrzeit: Zeitzone, Sommerzeit, OLED-Uhr und RTC-Backup
ESP32 NTP Uhrzeit abrufen: configTzTime mit Zeitzone und Sommerzeit, getLocalTime, strftime, OLED-Uhr, DS3231-Backup, tägliche Aufgabe und ntptime am Pico W.
Die ESP32 NTP Uhrzeit abzurufen heißt, das Board mit Zeitservern im Internet zu verbinden und Datum und Uhrzeit ohne Uhrmodul auf wenige Dutzend Millisekunden genau zu kennen. Sobald der ESP32 im WLAN ist, holt ein einziger Aufruf von configTzTime() die Zeit, wendet deine Zeitzone samt Sommerzeit an, und danach zählt das Board selbstständig weiter. Für Tischuhren, zeitgesteuerte Bewässerung, Zeitstempel beim Datenloggen, einen Wecker am Morgen oder Licht, das zu einer festen Uhrzeit angeht, brauchst du kein separates RTC-Modul mehr.
In dieser Anleitung gehen wir Schritt für Schritt durch: wie NTP funktioniert, die richtige Zeitzone für Deutschland, Österreich und die Schweiz (MEZ/MESZ), das Datum mit getLocalTime und strftime beliebig formatieren, die Uhrzeit auf einem OLED-Display anzeigen, die Zeit mit einem DS3231-Modul halten, wenn WLAN oder Strom ausfällt, und eine Aufgabe jeden Tag zu einer festen Uhrzeit ausführen. Im letzten Abschnitt erledigen wir dasselbe auf dem Raspberry Pi Pico W mit dem MicroPython-Modul ntptime.

Was ist NTP und wie bekommt der ESP32 die Uhrzeit?
NTP (Network Time Protocol) ist ein seit den 1980er-Jahren genutztes Protokoll, mit dem Computer und Geräte ihre Uhren über das Internet synchronisieren. Die Server sind in Schichten (Stratum) angeordnet: Stratum-1-Server hängen direkt an einer Atomuhr oder an GPS, Stratum-2-Server beziehen ihre Zeit von ihnen und so weiter. Der ESP32 fragt die Zeit mit einem kleinen Paket über UDP-Port 123 an; der Server antwortet mit einem Zeitstempel, der die Sekunden seit 1900 samt Sekundenbruchteil enthält. Der ESP32 berücksichtigt die Laufzeit des Pakets und stellt seine eigene Uhr. Über einen Heimanschluss erreichst du so meist eine Genauigkeit von 10 bis 50 Millisekunden, mehr als genug für ein Uhrenprojekt.
Der lwIP-Netzwerkstack im ESP32 verwendet SNTP, eine vereinfachte Variante von NTP. Nach dem ersten Empfang führt der ESP32 die Zeit als Systemzeit und zählt mit einem vom Quarz abgeleiteten Timer weiter; er fragt nicht jede Sekunde im Internet nach. Im Hintergrund fragt er in festen Abständen erneut beim Server an (im Arduino-ESP32-Core standardmäßig alle 3 Stunden) und korrigiert kleine Abweichungen. Die Systemzeit wird in UTC geführt; die Ortszeit, die du siehst, wird daraus mit der Zeitzonenregel berechnet.
| Begriff | Wert / Erklärung |
|---|---|
| Protokoll und Port | SNTP, UDP 123 |
| Zeitstempel | Sekunden seit 1900 (der ESP32 rechnet sie in Unix-Zeit ab 1970 um) |
| Typische Genauigkeit | 10–50 ms über einen Heimanschluss |
| Neusynchronisation | Standard 3 Stunden im Arduino-ESP32-Core, änderbar mit sntp_set_sync_interval() (mindestens 15 s) |
| Maximale Serverzahl | 3 (die an configTzTime übergebenen Servernamen) |
| Datenverkehr | Anfrage und Antwort sind je 48 Byte große NTP-Pakete; nur wenige Kilobyte pro Tag |
| Voraussetzungen | ESP32, ein 2,4-GHz-WLAN und Internetzugang |
configTime und configTzTime: Zeitzone und Sommerzeit einstellen
Im Arduino-ESP32-Core gibt es zwei Wege, NTP zu starten. configTime(gmtOffset_sec, daylightOffset_sec, server1, server2, server3) erwartet den UTC-Versatz und den Sommerzeit-Versatz in Sekunden, zum Beispiel configTime(3600, 3600, "pool.ntp.org") für MEZ. Diese Methode ist einfach, kennt aber die Umstellungstermine deiner Region nicht; in Deutschland würde die Umstellung am falschen Tag passieren. Für Länder ohne Sommerzeit, etwa die Türkei mit ganzjährig UTC+3 (configTime(10800, 0, ...)), reicht sie aus.
Der bessere Weg ist configTzTime(TZ, server1, server2, server3). Diese Funktion erwartet eine Zeitzonen-Zeichenkette im POSIX-Format, die auch die Sommerzeitregeln enthält; der ESP32 stellt jedes Jahr selbst um. Für Deutschland, Österreich und die Schweiz lautet sie "CET-1CEST,M3.5.0,M10.5.0/3": CET ist die Winterzeit-Abkürzung, -1 bedeutet, dass man von der Ortszeit eine Stunde abzieht, um UTC zu erhalten, CEST ist die Sommerzeit, M3.5.0 heißt „letzter Sonntag im März“ und M10.5.0/3 „letzter Sonntag im Oktober um 3 Uhr“. Die Beispielsketche unten verwenden die türkische Zeitzone "<+03>-3"; ersetze TZ_INFO einfach durch den Wert aus der Tabelle.
| Region | TZ-Zeichenkette (configTzTime) | Entsprechung mit configTime |
|---|---|---|
| Türkei (ganzjährig UTC+3) | <+03>-3 | configTime(10800, 0, ...) |
| Deutschland, Österreich, Schweiz | CET-1CEST,M3.5.0,M10.5.0/3 | Ungeeignet, configTzTime verwenden |
| Großbritannien, Portugal | GMT0BST,M3.5.0/1,M10.5.0 | Ungeeignet, configTzTime verwenden |
| USA Ostküste (New York) | EST5EDT,M3.2.0,M11.1.0 | Ungeeignet, configTzTime verwenden |
| Aserbaidschan, Georgien (UTC+4) | <+04>-4 | configTime(14400, 0, ...) |
| UTC (für Zeitstempel) | UTC0 | configTime(0, 0, ...) |
Was du brauchst und Verdrahtung
Um die Uhrzeit im seriellen Monitor zu sehen, reichen ein ESP32 und ein WLAN. Später in der Anleitung nutzen wir ein 0,96-Zoll-OLED-Display mit SSD1306 für die Anzeige, ein DS3231-RTC-Modul gegen Stromausfälle und ein Relaismodul für das Beispiel mit der geplanten Aufgabe. OLED und DS3231 teilen sich denselben I2C-Bus, beide kommen also an dieselben zwei Pins. Die Pinbelegung des ESP32 findest du im Werkzeug Pinout.
| Bauteil-Pin | ESP32 | Raspberry Pi Pico W | Hinweis |
|---|---|---|---|
| OLED / DS3231 VCC | 3V3 | 3V3 (OUT) | Beide laufen mit 3,3 V |
| OLED / DS3231 GND | GND | GND | Gemeinsame Masse |
| OLED / DS3231 SDA | GPIO 21 | GP4 | I2C-Datenleitung |
| OLED / DS3231 SCL | GPIO 22 | GP5 | I2C-Taktleitung |
| Relaismodul IN | GPIO 26 | GP15 | Beispiel geplante Aufgabe |
| Relaismodul VCC | 5V (VIN) | VBUS | Die meisten Relaisspulen brauchen 5 V |
Schritt für Schritt: dein erster ESP32-NTP-Sketch
- Stelle sicher, dass das ESP32-Boardpaket in der Arduino IDE installiert ist, und wähle unter Werkzeuge ESP32 Dev Module. Wie das geht, steht im Beitrag Was ist der ESP32.
- Eine zusätzliche Bibliothek ist nicht nötig: WiFi.h und time.h gehören zum ESP32-Core.
- Ersetze WIFI_SSID und WIFI_PASS durch dein eigenes Netzwerk und TZ_INFO durch "CET-1CEST,M3.5.0,M10.5.0/3".
- Lade den Sketch hoch und öffne den seriellen Monitor mit 115200 Baud.
- Zuerst erscheinen WLAN-Punkte, nach ein paar Sekunden dann Datum und Uhrzeit, jede Sekunde aktualisiert.
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| Zeile | Was macht sie? |
|---|---|
| configTzTime(TZ_INFO, ...) | Startet den SNTP-Client, setzt Server und Zeitzone; kehrt sofort zurück, die Zeit kommt im Hintergrund |
| getLocalTime(&t) | Füllt eine struct tm mit der Ortszeit; ohne gültige Zeit wartet sie bis zu 5 Sekunden und gibt false zurück |
| Serial.println(&t, "...") | Die Print-Klasse des ESP32 kann eine struct tm direkt mit strftime-Format ausgeben |
| time(nullptr) | Liefert die Sekunden seit 1970 (Unix-Zeit, UTC) |
Ein wichtiges Detail: getLocalTime() entscheidet anhand des Jahres, ob die Zeit „gültig“ ist. Beim Start steht die Systemzeit auf dem 1. Januar 1970, und die Funktion liefert false, bis das Jahr nach 2016 liegt. Code, der Daten loggt oder Aufgaben ausführt, bevor NTP da ist, erzeugt also keine Einträge mit Datum 1970, sofern du den Rückgabewert prüfst. Der zweite Parameter ist die Wartezeit in Millisekunden; willst du loop() nicht blockieren, schreib getLocalTime(&t, 0), dann wird nur einmal geprüft.
struct tm: die Bestandteile der Uhrzeit
getLocalTime() schreibt die Zeit zerlegt in eine struct tm. Mit diesen Feldern vergleichst du Stunden, Tage oder Monate, wenn du geplante Aufgaben baust. Zwei Felder verwirren Einsteiger oft: Der Monat zählt ab 0, das Jahr ab 1900.
| Feld | Bereich | Beispiel (Montag, 16. November 2026, 14:05:09) |
|---|---|---|
| tm_sec | 0–59 (60 bei Schaltsekunde) | 9 |
| tm_min | 0–59 | 5 |
| tm_hour | 0–23 | 14 |
| tm_mday | 1–31 | 16 |
| tm_mon | 0–11 (0 = Januar) | 10 |
| tm_year | Jahre seit 1900 | 126 (1900 + 126 = 2026) |
| tm_wday | 0–6 (0 = Sonntag) | 1 |
| tm_yday | 0–365 (Tag im Jahr, ab 0) | 319 |
Datum und Uhrzeit mit strftime formatieren
strftime() schreibt die Zeit aus einer struct tm nach einem Muster in ein Zeichen-Array. Die Prozent-Codes im Muster werden durch den passenden Wert ersetzt, alle anderen Zeichen bleiben stehen. Das Array muss so groß sein wie die längste Ausgabe plus eins (für die abschließende Null): „14:05:09“ hat 8 Zeichen, also mindestens 9 Byte. Die wichtigsten Codes:
| Code | Bedeutung | Beispielausgabe |
|---|---|---|
| %H / %M / %S | Stunde (00–23) / Minute / Sekunde | 14 / 05 / 09 |
| %I und %p | 12-Stunden-Format und AM/PM | 02 PM |
| %d / %m / %Y | Tag / Monat (01–12) / vierstelliges Jahr | 16 / 11 / 2026 |
| %y | Zweistelliges Jahr | 26 |
| %A / %a | Wochentag lang / kurz (Englisch) | Monday / Mon |
| %B / %b | Monatsname lang / kurz (Englisch) | November / Nov |
| %j | Tag im Jahr (001–366) | 320 |
| %F / %T | Kurzformen für %Y-%m-%d / %H:%M:%S | 2026-11-16 / 14:05:09 |
| %Z | Zeitzonen-Abkürzung | +03 (bzw. CET / CEST) |
Die C-Bibliothek des ESP32 kennt nur englische Wochentags- und Monatsnamen; %A liefert immer „Monday“. Für ein deutsches Datum legst du die Namen in eigenen Arrays ab und nutzt tm_wday und tm_mon als Index. Das Beispiel unten macht das mit türkischen Namen; tausche sie einfach gegen „Sonntag, Montag, …“ und „Januar, Februar, …“. Da der serielle Monitor UTF-8 unterstützt, erscheinen Umlaute dort korrekt. Der Sketch erzeugt ein gut lesbares Datum und das sortierbare ISO-Format (2026-11-16 14:05:09) zum Loggen.
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}
34Uhrzeit und Datum auf einem OLED-Display anzeigen
Um aus der Ausgabe im seriellen Monitor eine echte Tischuhr zu machen, ist das 0,96-Zoll-OLED mit SSD1306 ideal: günstig, mit zwei Leitungen angeschlossen und mit gestochen scharfen großen Ziffern. Der Sketch unten zeigt oben Wochentag und Datum, in der Mitte groß die Uhrzeit und unten den WLAN-Status. Weil er nur neu zeichnet, wenn sich die Sekunde ändert, und nicht bei jedem Schleifendurchlauf, belastet er den I2C-Bus nicht unnötig. Installiere die Bibliotheken Adafruit SSD1306 und Adafruit GFX über den Bibliotheksverwalter; das Display selbst erklärt der Beitrag OLED-Display (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}
66Beachte, dass die Wochentage ohne Sonderzeichen geschrieben sind: Die Standardschrift von Adafruit GFX enthält keine Zeichen wie ä, ö, ü oder ß und zeigt stattdessen sinnlose Symbole. Bei Textgröße 2 ist jedes Zeichen 12 Pixel breit; „14:05:09“ mit 8 Zeichen belegt 96 Pixel und steht ab Pixel 16 genau mittig auf dem 128 Pixel breiten Display. Willst du lieber ein 16x2-Zeichen-LCD, gib dieselben strftime-Ausgaben mit lcd.print() aus; die Verdrahtung erklärt der Beitrag I2C-LCD-Display.
Was passiert bei WLAN-Ausfall? DS3231 als RTC-Backup
Die häufigste Frage: Ist mit dem WLAN auch die Uhrzeit weg? Nein. Hat der ESP32 die Zeit einmal per NTP bekommen, zählt er mit seinem eigenen Timer weiter; ein WLAN-Abbruch hält die Uhr nicht an. Da dieser Timer vom 40-MHz-Quarz kommt, driftet er etwa ein bis zwei Sekunden pro Tag, und die nächste Synchronisation korrigiert das, sobald das WLAN zurück ist. Das eigentliche Problem ist ein Stromausfall oder ein Neustart über die EN-Taste: Dann springt die Systemzeit auf 1970 zurück, und ohne WLAN kann der ESP32 die Uhrzeit nicht kennen.
| Situation | Bleibt die Zeit erhalten? | Was tun? |
|---|---|---|
| WLAN fällt eine Weile aus | Ja, der ESP32 zählt weiter | Nichts; NTP korrigiert, sobald WLAN zurück ist |
| Deep Sleep (Tiefschlaf) | Ja, der RTC-Timer zählt weiter | Bei langem Schlaf ab und zu nach dem Aufwachen per NTP abgleichen |
| Stromausfall oder EN-Taste | Nein, die Uhr springt auf 1970 | Ein batteriegepuffertes RTC-Modul wie den DS3231 ergänzen |
| Gar kein Internet (Feld, Lager) | NTP kommt nie an | Die Zeit nur aus einem RTC-Modul holen |
Im Tiefschlaf wird die Zeit nicht vom Hauptquarz, sondern von einem ungenaueren RC-Oszillator im Chip gezählt. Dieser ist temperaturabhängig, sodass die Uhr bei langem Schlaf um einige Sekunden oder mehr abweichen kann; bei batteriebetriebenen Projekten, die stundenlang schlafen, ist ein NTP-Abgleich alle paar Aufwachvorgänge eine gute Gewohnheit. Einzelheiten stehen im Beitrag ESP32 Deep Sleep.

Die beste Lösung für eine ausfallsichere Uhr kombiniert beides: Beim Start sofort die Zeit aus dem DS3231 holen, sie per NTP korrigieren, sobald WLAN da ist, und den DS3231 bei jeder NTP-Synchronisation aktualisieren. Im Sketch unten speichert der DS3231 UTC statt Ortszeit; so steckt die Zeitzonenregel an einer Stelle (der TZ-Zeichenkette), und die Sommerzeitumstellung bringt die RTC nie durcheinander. Um die NTP-Synchronisation mitzubekommen, wird mit sntp_set_time_sync_notification_cb() eine Callback-Funktion registriert. Weil sie im Netzwerk-Task läuft, passiert dort keine I2C-Arbeit; sie setzt nur ein Flag, und loop() schreibt in den DS3231. Der Sketch nutzt die Bibliothek 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}
53Jeden Tag zu einer festen Uhrzeit eine Aufgabe ausführen
Am häufigsten wird die NTP-Zeit für geplante Aufgaben genutzt: um 07:30 die Bewässerungspumpe 10 Minuten laufen lassen, um 19:00 die Gartenbeleuchtung einschalten, jede volle Stunde einen Messwert senden. Das Prinzip ist einfach: In loop() die Zeit lesen und die Aufgabe starten, wenn Stunde und Minute dem Ziel entsprechen. Die eigentliche Feinheit ist, zu verhindern, dass die Aufgabe innerhalb derselben Minute hunderte Male auslöst. Dazu speicherst du den Tag, an dem sie lief (tm_yday), in einer Variablen; am selben Tag läuft sie dann kein zweites Mal. Der Sketch unten schaltet an Werktagen jeden Morgen um 07:30 das Relais an GPIO 26 für 10 Minuten ein; weil er die Dauer mit millis() misst, blockiert der Code nirgends.
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- Auf Sekunden prüfen: Eine Bedingung wie t.tm_sec == 0 überspringt die Aufgabe, wenn die Schleife genau diese Sekunde verpasst (beim WLAN-Neuverbinden oder während eines langen delay). Minute und Tag zu prüfen ist sicherer.
- Gültige Zeit prüfen: Keine Aufgaben ausführen, solange getLocalTime() false liefert, sonst triffst du direkt nach dem Start falsche Entscheidungen auf Basis von 1970.
- Verpasste Aufgabe: Fällt der Strom um 07:29 aus und kommt um 07:35 zurück, läuft die Aufgabe an diesem Tag nicht. Ist das wichtig, formuliere die Bedingung als „nach 07:30 und heute noch nicht gelaufen“ und speichere sonGun mit Preferences dauerhaft.
- Mehrere Aufgaben: Stunde, Minute und Dauer in einem Array halten und für jede Aufgabe eine eigene Variable für den letzten Lauftag verwenden.
- Lasten mit hohem Strom: Pumpe oder Lampe über ein Relaismodul oder einen MOSFET schalten, nie direkt am Pin.
Synchronisationsintervall ändern und NTP überwachen
Das Standardintervall von 3 Stunden passt für die meisten Projekte. Um häufiger abzugleichen (etwa draußen bei starken Temperaturschwankungen) oder Datenverkehr zu sparen, übergibst du sntp_set_sync_interval() das neue Intervall in Millisekunden. Rufe die Funktion vor configTzTime() auf, denn das neue Intervall gilt erst beim nächsten Start oder wenn das laufende Intervall abläuft. Laut Protokoll darf es nicht kürzer als 15 Sekunden sein. sntp_get_sync_status() liefert nach einer abgeschlossenen Synchronisation einmal SNTP_SYNC_STATUS_COMPLETED und setzt sich dann zurück, so erkennst du jeden neuen Abgleich.
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}
22Welchen NTP-Server solltest du nehmen?
Die gängigste Wahl ist pool.ntp.org: ein Pool aus tausenden Servern, die Freiwillige weltweit betreiben, und der dir bei jeder Anfrage einen Server in deiner Nähe nennt. Länder-Subdomains (de.pool.ntp.org, at.pool.ntp.org, ch.pool.ntp.org) holen den Server noch näher heran. configTzTime() nimmt bis zu drei Servernamen; antwortet der erste nicht, geht es mit dem nächsten weiter.
| Server | Eigenschaft | Hinweis |
|---|---|---|
| pool.ntp.org | Freiwilligen-Pool, nächster Server | Erste Wahl für den allgemeinen Einsatz |
| de.pool.ntp.org / europe.pool.ntp.org | Länder- oder Kontinent-Pool | Etwas geringere Latenz |
| time.cloudflare.com | Infrastruktur eines großen Anbieters | Gut als Ersatzserver |
| time.google.com | Verschmiert Schaltsekunden (Leap Smear) | Nicht mit anderen Servern mischen |
| IP-Adresse deines Routers | Manche Router (z. B. FRITZ!Box) bieten lokales NTP | Nützlich im lokalen Netz ohne Internet |
NTP am Raspberry Pi Pico W: ntptime
Auf dem Raspberry Pi Pico W (und dem Pico 2 W) übernimmt das MicroPython-Modul ntptime diese Aufgabe. ntptime.settime() fragt den NTP-Server und stellt die interne RTC des Pico auf UTC. MicroPython kennt keine Zeitzonen-Zeichenkette wie der ESP32; für die Ortszeit addierst du den Versatz in Sekunden zu UTC (im Beispiel 3 × 3600 für die Türkei, für MEZ 3600 und im Sommer 7200). ntptime holt die Zeit nur einmal und gleicht nicht automatisch neu ab; bei langlaufenden Projekten beseitigt ein- bis zweimal täglich settime() die Drift. Auch die RTC des Pico W wird bei Stromausfall zurückgesetzt, das DS3231-Backup gilt also auch hier. Wie sich der Pico W mit dem WLAN verbindet, erklärt der Beitrag Pico W WLAN und Webserver.
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)
36time.localtime() liefert in MicroPython ein Tupel mit acht Elementen: Jahr, Monat (1–12), Tag, Stunde, Minute, Sekunde, Wochentag und Tag im Jahr. Zwei Unterschiede zur struct tm des ESP32: Der Monat beginnt bei 1, und der Wochentag zählt 0 = Montag. ntptime.settime() wirft bei einer Zeitüberschreitung im Netzwerk einen OSError; deshalb hat der Code eine Schleife mit fünf Versuchen.
Projektideen mit der ESP32-NTP-Uhr
- WLAN-Tischuhr: Uhrzeit, Datum und Raumtemperatur auf einem OLED-Display zeigen und nachts die Helligkeit senken.
- Große LED-Uhr: Die NTP-Zeit als Lauftext auf einer MAX7219-LED-Matrix anzeigen.
- Zeitgesteuerte Pflanzenbewässerung: Jeden Morgen die Bodenfeuchte prüfen und bei Bedarf eine Mini-Wasserpumpe für eine feste Zeit laufen lassen.
- Datenlogger mit Zeitstempel: Sensormesswerte mit ISO-Datum auf eine SD-Karte schreiben.
- Uhr mit Wetter: Neben der Uhrzeit das Wetter aus dem Internet zeigen; der Beitrag ESP32 Wetter setzt hier an.
- Morgenbericht: Jeden Tag um 08:00 die Raumtemperatur und die nachts erkannten Bewegungen per Telegram aufs Handy schicken.
Häufige Fehler und Lösungen
| Symptom | Mögliche Ursache | Lösung |
|---|---|---|
| Zeit steht auf 1970 oder kommt nie | WLAN nicht verbunden oder NTP-Port (UDP 123) gesperrt | Zuerst WiFi.status() prüfen; in Schul- und Firmennetzen ein anderes Netz oder einen Handy-Hotspot testen |
| Uhr geht genau 1 bzw. 2 Stunden nach | Keine Zeitzone gesetzt, es wird UTC angezeigt | configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", ...) |
| Uhr liegt um den doppelten Versatz daneben | Umgekehrtes Vorzeichen in der TZ-Zeichenkette ("CET+1") | "CET-1CEST,..." bzw. "<+03>-3" schreiben |
| Monat einen zu wenig, Jahr 126 | tm_mon zählt ab 0, tm_year ab 1900 | tm_mon + 1, tm_year + 1900 oder strftime verwenden |
| Aufgabe löst in derselben Minute mehrfach aus | Kein Wiederholungsschutz | Den Lauftag (tm_yday) speichern und prüfen |
| Schleife hängt bei jedem Durchlauf 5 Sekunden | Ohne gültige Zeit wartet getLocalTime() standardmäßig 5000 ms | In loop() getLocalTime(&t, 0) verwenden |
| Seltsame Symbole im Wochentag auf dem OLED | Die Schrift hat keine Umlaute | Wochentage in ASCII schreiben oder zur Bibliothek U8g2 wechseln |
ESP32 NTP ist der günstigste und bequemste Weg, einem Projekt eine genaue Uhrzeit zu geben: Mit wenigen Codezeilen holt er die Zeit auf die Sekunde genau, verwaltet Zeitzone und Sommerzeit selbst und übersteht mit einem zusätzlichen DS3231 auch Stromausfälle. In der BlueGrays-App findest du Projekte zu Echtzeit per ESP32 NTP, einer Digitaluhr mit DS1307-RTC, einer Wetteranzeige auf dem OLED und einem Temperaturlogger mit Zeitstempel, jeweils Schritt für Schritt mit Schaltplan und Code.
Häufige Fragen
Kann der ESP32 die Uhrzeit ohne Internet halten?
Ja, sobald er die Zeit einmal per NTP erhalten hat: Der ESP32 zählt die Systemzeit mit seinem eigenen Timer weiter, und ein WLAN-Ausfall hält die Uhr nicht an. Fällt aber der Strom aus oder startet das Board neu, springt die Zeit auf 1970. Für Orte ohne Internet oder eine ausfallsichere Uhr ergänzt du ein batteriegepuffertes RTC-Modul wie den DS3231.
Warum geht meine ESP32-NTP-Uhr eine Stunde falsch?
Ohne Zeitzone zeigt der ESP32 UTC an, im Winter also eine Stunde und im Sommer zwei Stunden hinter der deutschen Zeit. Verwende configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"). Liegt sie um den doppelten Versatz daneben, ist das Vorzeichen in der TZ-Zeichenkette vertauscht.
Wie stelle ich beim ESP32 die Sommerzeit ein?
Am einfachsten mit configTzTime und einer POSIX-Zeitzone, die die Umstellungsregeln enthält. Für Deutschland, Österreich und die Schweiz lautet sie "CET-1CEST,M3.5.0,M10.5.0/3"; der ESP32 stellt dann am letzten Sonntag im März und im Oktober automatisch um. configTime mit festem Sommerzeit-Versatz kennt die Termine nicht.
Wie oft synchronisiert der ESP32 mit NTP?
Im Arduino-ESP32-Core fragt SNTP standardmäßig alle 3 Stunden erneut beim Server an und hält die Zeit dazwischen mit seinem eigenen Timer. Das Intervall änderst du mit sntp_set_sync_interval() in Millisekunden, laut Protokoll aber nicht unter 15 Sekunden.
Brauche ich beim ESP32 ein RTC-Modul?
Nicht, wenn das Gerät dauerhaft Strom hat und ins WLAN kommt; NTP ist genauer. Brauchst du nach einem Stromausfall sofort die richtige Zeit, bevor das Internet wieder da ist, oder läuft das Gerät ohne Internet, ergänzt du ein batteriegepuffertes RTC-Modul wie den DS3231.
Wie bekomme ich am Raspberry Pi Pico W die NTP-Zeit?
In MicroPython reicht nach der WLAN-Verbindung import ntptime und ntptime.settime(); damit wird die interne RTC des Pico auf UTC gestellt. Für die deutsche Zeit rechnest du time.localtime(time.time() + 3600) im Winter und + 7200 im Sommer. Da ntptime nicht automatisch neu abgleicht, solltest du settime() ein- bis zweimal am Tag wiederholen.
Projekte, die du danach bauen kannst
Alle Projekte ansehen
ESP32: Echtzeit aus dem Internet per NTP
Der ESP32 verbindet sich mit dem WLAN und ruft die Echtzeit mit Millisekunden-Genauigkeit von NTP-Servern (Network Time Protocol) ab. Dank configTzTime() wird die türkische Zeitzone (TRT, UTC+3) automatisch angewendet, und der DST-Wechsel wird per POSIX-TZ-String verwaltet. Die Uhrzeit wird auf dem Serial Monitor und einem optionalen I2C-LCD ausgegeben. configTzTime() startet den SNTP-Dienst; bei verfügbarem Netzwerkzugang erfolgt regelmäßig eine erneute Synchronisierung, und zwischen den Sy

Digitale Uhr mit ESP32 und RTC DS1307 (I2C + LCD)
In diesem Projekt zeigen wir Uhrzeit und Datum auf einem 16x2-I2C-LCD-Display mithilfe des DS1307-Echtzeituhrmoduls (RTC) an. Das DS1307 ist ein batteriegestütztes RTC, das die Uhrzeit auch dann beibehält, wenn das ESP32 ausgeschaltet ist. Beide Module teilen sich denselben I2C-Bus (SDA/SCL), sodass wir mit nur zwei Signalkabeln die Steuerung mehrerer Geräte erlernen. So lernen Sie das I2C-Protokoll und die Funktionsweise von RTC-Modulen in der Praxis kennen.

ESP32-WLAN-STA-Verbindung (mit LCD-Display)
In diesem Projekt verbinden wir den ESP32 im Station-Modus (STA) sicher und fehlertolerant mit dem heimischen WLAN. Wir lagern SSID und Passwort in eine separate secrets.h-Datei aus, um Leaks zu Git zu verhindern, richten einen Auto-Reconnect-Mechanismus ein und zeigen RSSI-Signalpegel und IP-Adresse auf einem I2C-LCD-Display an. Die interne LED dient als Statusanzeige. Ein Verbindungsmanager auf Produktionsniveau.

ESP32 HTTPClient + Wetter-API (mit SSD1306-OLED-Display)
In diesem Projekt führen wir mit dem ESP32 über das Internet mithilfe von HTTPClient.h einen REST-API-Aufruf an die Wetter-API (wttr.in) durch. Im Gegensatz zur vorherigen I2C-LCD-Version verwenden wir hier ein SSD1306-OLED-Display (128×64) – dies ermöglicht große Schriftarten, Vektor-Symbole und eine reichhaltige visuelle Darstellung auf einem einzigen Bildschirm. Für die Wetteranzeige werden 7 verschiedene Vektor-Symbole (sonnig, teilweise bewölkt, bewölkt, regnerisch, schneereich, stürmisch

ESP32 HTTPClient + Wetter-API (mit LCD-Display)
In diesem Projekt führen wir mit dem ESP32 über das Internet mithilfe von HTTPClient.h einen REST-API-Aufruf an die Wetter-API (wttr.in) durch. Mit ArduinoJson v7 analysieren wir die zurückgegebenen JSON-Daten und zeigen Informationen zu Stadt, Temperatur, Luftfeuchtigkeit, Wetter und Wind sowohl auf Türkisch als auch auf Englisch auf einem I2C-LCD-Display an. wttr.in ist kostenlos und erfordert keinen API-Schlüssel – Sie können den aktuellen Wetterbericht einer beliebigen Stadt abrufen, indem

ESP32 Deep Sleep – Batteriesparmodus (OLED + RTC-RAM-Zähler)
Der Deep-Sleep-Modus des ESP32 senkt den Stromverbrauch auf etwa 10 µA, indem er die CPU und die meisten Peripheriegeräte abschaltet, und ist für batteriebetriebene IoT-Geräte von entscheidender Bedeutung. In diesem Projekt verwenden wir drei verschiedene Weckquellen: den Timer (15 s), einen externen Pin (EXT0 – Taste GPIO 33) und die Berührungserkennung (T0/GPIO 4). Mit RTC_DATA_ATTR markierte Variablen bleiben während des Deep-Sleep-Modus erhalten – der Boot-Zähler, der Timer-Wake-Zähler und

ESP32 Temperatur-Datenlogger (DHT22 + DS1307 RTC + microSD + OLED)
Baue einen zeitgestempelten Temperaturlogger mit ESP32! Lese Temperatur/Feuchtigkeit vom DHT22 jede Minute, hole die aktuelle Zeit vom DS1307 RTC und protokolliere auf microSD-Karte als CSV. Das OLED zeigt live Temperatur (große Schrift), Luftfeuchtigkeit, Zeitstempel und Logzähler. Die /temp_log.csv-Datei auf der SD-Karte öffnet sich in Excel, Google Sheets oder Python pandas zur Analyse — perfekt für Gewächshausüberwachung, Raumtemperaturverfolgung, Laboraufzeichnung, Lebensmittellageru

ESP32 I2C-LCD-Display (16×2)
In diesem Projekt zeigen wir Text an, indem wir ein 16x2-Zeichen-LCD-Display über das I2C-Protokoll an den ESP32 anschließen. Dank I2C können wir das Display mit nur zwei Datenpins (SDA und SCL) steuern; die Verschwendung von 6+ Pins bei einer klassischen parallelen Verbindung entfällt. Der Code schreibt in die erste Zeile eine feste Meldung und in die zweite Zeile einen Zähler, der sich jede Sekunde erhöht. Außerdem lassen wir zu Beginn den I2C-Scanner laufen und geben die Adresse des Displays
Weitere Artikel
BlueGrays-App
Diese Schaltung Schritt für Schritt auf dem Handy nachbauen
Schaltplan, Code und Teileliste auf einem Bildschirm. Über 200 Arduino-, ESP32- und Pico-Projekte zum Nachbauen.
Mit dem Handy scannen
Richte die Kamera auf den Code, dann öffnet sich der App-Store deines Handys.
oder im Store öffnen

