Skip to Content

Wie man die ESP32-Uhr mit einem SNTP-Server synchronisiert

Wie man die ESP32-Uhr mit einem SNTP-Server synchronisiert

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 wire set

Dupont-Kabelsatz

Half_breadboard56a

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.

ESP32 polls NTP Servers
ESP32 fragt NTP-Server ab

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.

Hierarchy of the NTP server service
Hierarchie des NTP-Server-Dienstes (source)

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.

Active NTP servers as of Aug 2024
Aktive NTP-Server im August 2024 (source)

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:

SNTP synchronized time output on Serial Monitor
Ausgabe der Zeitsynchronisation im Serial Monitor

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.

Connecting LCD display via I2C to ESP32
Anschluss des LCD-Displays via I2C an den 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:

Pinout for the ESP32-C3 Supermini
Pinout für den ESP32-C3 Supermini

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“.

ESP32 synchronized clock on LCD
Internet-synchronisierte Uhr auf LCD

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:

Time output on LCD
Zeitausgabe auf dem LCD

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 ; )