Wie du das LCD Keypad Shield programmierst, habe ich dir bereits im Beitrag LCD Keypad Shield für Arduino: Einsteigerfreundliches Display mit Tastensteuerung ausführlich erläutert. In diesem Beitrag soll es nun darum gehen, wie du dir eine Weltzeituhr am Arduino UNO R4 programmierst. Der Arduino UNO R4 verfügt über einen ESP32 Chip und mit dem Formfaktor des alten UNO R3 ist dieser bestens geeignet für dieses Projekt.
Der Aufbau einer Schaltung ist recht einfach, denn das LCD Keypad Shield wird lediglich auf den Mikrocontroller gesteckt.
Das LCD Display verfüg über zwei Zeilen mit je 16 Zeichen, das ist für die Anzeige von Datum, Uhrzeit und der Zeitzone etwas wenig, daher habe ich das auf den Tag, die Uhrzeit und die Zeitzone in Kurzform komprimiert.
Inhaltsverzeichnis
- Benötigte Ressourcen für dieses kleine Projekt
- NTP Timeserver für die Abfrage von Zeiten
- Programmieren einer Weltzeituhr mit einem NTP Zeitserver
- Arbeiten mit dem Timestamp
Benötigte Ressourcen für dieses kleine Projekt
Wenn du das kleine Projekt nachbauen möchtest, dann benötigst du:
- einen Arduino UNO R4 WiFi*,
- ein USB-C Datenkabel*,
- ein LCD Keypad Shield*
Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!
NTP Timeserver für die Abfrage von Zeiten
Es gibt im Internet einige NTP Timeserver welche für dieses Projekt verwendet werden können, für dieses Projekt verwende ich ptbtime1.ptb.de von der Physikalisch-technische Bundesanstalt.
Wir müssen lediglich für die jeweilige Zeitzone noch Stunden dazu oder abzählen.
Liste mit Zeitzonen und TimeOffsets
Nachfolgend gebe ich dir eine Tabelle, aus welcher du entnehmen kannst, welchen Offset du für die Zeit verwenden musst.
Rechenbeispiele:
- für Pacific Standard Time (PST) musst du von der aktuellen Uhrzeit -8h rechnen,
- für Japan Standard Time (JST) musst du von der aktuellen Uhrzeit +9h rechnen
Kontinent | Zeitzone | UTC Zeit |
---|---|---|
Nordamerika | Eastern Standard Time (EST) | UTC-5 |
Central Standard Time (CST) | UTC-6 | |
Mountain Standard Time (MST) | UTC-7 | |
Pacific Standard Time (PST) | UTC-8 | |
Alaska Standard Time (AKST) | UTC-9 | |
Hawaii-Aleutian Standard Time (HAST) | UTC-10 | |
Südamerika | Brasília Time (BRT) | UTC-3 |
Argentina Standard Time (ART) | UTC-3 | |
Chile Standard Time (CLT) | UTC-4 | |
Venezuela Standard Time (VET) | UTC-4:30 | |
Europa | Greenwich Mean Time (GMT) | UTC+0 |
Central European Time (CET) | UTC+1 | |
Eastern European Time (EET) | UTC+2 | |
British Summer Time (BST) | UTC+1 | |
Afrika | West Africa Time (WAT) | UTC+1 |
Central Africa Time (CAT) | UTC+2 | |
East Africa Time (EAT) | UTC+3 | |
South Africa Standard Time (SAT) | UTC+2 | |
Asien | Indian Standard Time (IST) | UTC+5:30 |
China Standard Time (CST) | UTC+8 | |
Japan Standard Time (JST) | UTC+9 | |
Australian Eastern Standard Time (AEST) | UTC+10 | |
Australian Central Standard Time (ACST) | UTC+9:30 | |
Australian Western Standard Time (AWST) | UTC+8 | |
Ozeanien | New Zealand Standard Time (NZST) | UTC+12 |
Fiji Standard Time (FST) | UTC+12 | |
Tonga Standard Time (TST) | UTC+13 |
Programmieren einer Weltzeituhr mit einem NTP Zeitserver
Wie du dich mit einem Mikrocontroller zu einem WiFi Netzwerk verbindest, habe ich dir bereits in einigen Beiträgen ausführlich erläutert. Jedoch nehme ich dich in der nachfolgenden Schritt-für-Schritt-Anleitung an die Hand, sodass du alle Informationen in diesem Beitrag findest.
Schritt 1 – Installation der benötigten Bibliotheken
Für die Abfrage der Zeit aus dem Internet benötigen wir eine Bibliothek, ich verwende hier NTPClient welche als ZIP-Datei vom GitHub Repository arduino-libraries/NTPClient heruntergeladen werden kann.
Schritt 2 – Aufbau einer WiFi Verbindung
Im ersten richtigen Schritt programmieren wir die WiFi Verbindung, dieses ist recht einfach und mit wenigen Zeilen Code erledigt.
//Bibliotheken zum aufbau einer WiFi Verbindung #include <WiFi.h> #include <WiFiUdp.h> //Die Zugangsdaten zum WiFi-Netzwerk const char *ssid = "*****"; const char *password = "*****"; void setup() { //beginn der seriellen Kommunikation Serial.begin(115200); //Aufbau der WiFi Verbindung WiFi.begin(ssid, password); //solange noch keine Verbindung hergestellt wurde, soll //eine Pause von 500ms eingelegt werden, //ein Punkt auf der seriellen Schnittstelle ausgegeben werden while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } //Wenn die Verbindung erfolgreich aufgebaut wurde, dann //wird der nchfolgende Text angezeigt. Serial.print("Erfolgreich zu "); Serial.print(ssid); Serial.println(" verbunden!"); } void loop() { //bleibt leer }
Wenn die Verbindung zum Netzwerk hergestellt wurde, dann erscheint auf der seriellen Schnittstelle die Ausgabe vom Text „Erfolgreich zu <ssid> verbunden!“.
Schritt 3 – Abfrage einer Zeit von einem NTPServer
Wie erwähnt möchte ich die Daten vom Zeitserver der Physikalisch-technische Bundesanstalt abrufen. Die Adresse von diesem ist ptbtime1.ptb.de.
//Bibliotheken zum aufbau einer WiFi Verbindung #include <WiFi.h> #include <WiFiUdp.h> //Die Zugangsdaten zum WiFi-Netzwerk const char *ssid = "*****"; const char *password = "*****"; void setup() { //beginn der seriellen Kommunikation Serial.begin(115200); //Aufbau der WiFi Verbindung WiFi.begin(ssid, password); //solange noch keine Verbindung hergestellt wurde, soll //eine Pause von 500ms eingelegt werden, //ein Punkt auf der seriellen Schnittstelle ausgegeben werden while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } //Wenn die Verbindung erfolgreich aufgebaut wurde, dann //wird der nchfolgende Text angezeigt. Serial.print("Erfolgreich zu "); Serial.print(ssid); Serial.println(" verbunden!"); } void loop() { //bleibt leer }
Die Ausgabe auf der seriellen Schnittstelle ist wiefolgt:
- erste Zeile, der Hinweis das die WiFi-Verbindung aufgebaut wurde,
- zweite bis n-te Zeile, die Uhrzeit vom NTPServer
Wenn noch keine Verbindung zum Server hergestellt wurde, dann kann auch einmal die Uhrzeit mit 01:00:00 geliefert werden. Hier muss man lediglich ein paar Sekunden warten.
In der Grafik ist ersichtlich, dass die Uhrzeit um eine Stunde verschoben ist, bedingt durch die europäische Sommerzeit. Um dies anzupassen, wird die Funktion setTimeOffset am Objekt timeClient aufgerufen, welches in der setup-Funktion initialisiert wird. Dabei wird der Funktion einmalig ein Parameter von 7200 Sekunden übergeben, was zwei Stunden entspricht.
timeClient.setTimeOffset(7200);
Schritt 4 – Ausgeben der Daten auf dem LCD Display
Das LCD Display hat maximal 16 Zeichen für je zwei Zeilen zur Verfügung, anders als bei OLED Displays kann man hier nicht durch die Auswahl einer kleineren Schriftart für mehr Platz sorgen. Man muss quasi Abstriche machen.
Damit wir mit dem LCD Display kommunizieren können, importieren wir die LiquidCrystal Bibliothek welche bei der Arduino IDE 2.x dabei ist.
//Bibliotheken zum aufbau einer WiFi Verbindung #include <WiFi.h> #include <WiFiUdp.h> //Bibliothek zum kommunizieren mit einem Zeitserver #include <NTPClient.h> //Bibliothek zum kommunizieren mit dem LCD Display #include <LiquidCrystal.h> //Objekt vom Typ LiquidCrystal initialisieren LiquidCrystal lcd(8, 9, 4, 5, 6, 7); //Die Zugangsdaten zum WiFi-Netzwerk const char *ssid = "*****"; const char *password = "******"; WiFiUDP ntpUDP; NTPClient timeClient(ntpUDP, "ptbtime1.ptb.de", 3600, 60000); void setup() { //beginn der seriellen Kommunikation Serial.begin(115200); //Aufbau der WiFi Verbindung WiFi.begin(ssid, password); //solange noch keine Verbindung hergestellt wurde, soll //eine Pause von 500ms eingelegt werden, //ein Punkt auf der seriellen Schnittstelle ausgegeben werden while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } //Wenn die Verbindung erfolgreich aufgebaut wurde, dann //wird der nchfolgende Text angezeigt. Serial.print("Erfolgreich zu "); Serial.print(ssid); Serial.println(" verbunden!"); //beginn der Kommunikation mit dem Zeitserver timeClient.begin(); //Zeitoffset von 1h für die europäische Sommerzeit timeClient.setTimeOffset(7200); //Das verbaute LCD Display hat 16 Zeichen mit zwei Zeilen. //Die Bibliothek kann für viele weitere verwendet werden, daher müssen //diese Daten übergeben werden. lcd.begin(16, 2); } void loop() { //aktualisieren der Daten timeClient.update(); //Ausgeben eines formatierten Zeitstempels String formattedTime = timeClient.getFormattedTime(); Serial.println(formattedTime); //Daten im LCD Display leeren lcd.clear(); //Anzeigen der Uhrzeit lcd.print(formattedTime); //eine Pause von 1 Sekunde delay(1000); }
Die Uhrzeit wird nun nicht nur auf der seriellen Schnittstelle ausgegeben, sondern auch auf dem LCD Display.
Schritt 5 – Anzeigen von unterschiedlichen Weltzeiten auf dem Display
Im nächsten Schritt wollen wir jetzt eine Liste von Weltzeiten anlegen und diese nacheinander anzeigen lassen. In der zweiten noch leeren Zeile möchte ich die Zeitzone und die Differenz anzeigen lassen.
Am einfachsten geht dieses mit einem MenuItem welches nachfolgende Eigenschaften hat:
struct MenuItem { double timeOffset; //die Zeit welche abgezogen/addiert werden soll String timezone; //die Bezeichnung der Zeitzone };
Von diesem neuen Datentyp MenuItem können wir uns nun beliebig viele anlegen und in ein Array ablegen.
MenuItem item1 = { 1, "CEST (UTC +2)" }; //Central European Summer Time (CEST) MenuItem item2 = { 0, "GMT (UTC +0)" }; //Greenwich Mean Time (GMT) MenuItem item3 = { -4, "CLT (UTC -4)" }; //Chile Standard Time (CLT) MenuItem item4 = { 12, "NZST (UTC +12)" }; //New Zealand Standard Time (NZST) MenuItem item5 = { 9.5, "ACST (UTC +9:30)" }; //Australian Central Standard Time (ACST) MenuItem item6 = { -10, "HAST (UTC -10)" }; //Hawaii-Aleutian Standard Time (HAST) const int NUM_MENUITEMS = 6; MenuItem menu[NUM_MENUITEMS] = { item1, item2, item3, item4, item5, item6 };
Wir entnehmen ein MenuItem mit dem aktuellen index aus dem Array. Im Anschluss berechnen wir den Offset. Dieser Offset wird in Sekunden übergeben (1h = 3600 Sekunden).
Die nächsten Zeilen dienen dann lediglich um den Zeitstempel wie zuvor zu laden und auf dem Display anzuzeigen.
MenuItem item = menu[index]; double timeOffset = item.timeOffset * 3600; timeClient.setTimeOffset(timeOffset); //aktualisieren der Daten timeClient.update(); //Ausgeben eines formatierten Zeitstempels String formattedTime = timeClient.getFormattedTime(); Serial.println(formattedTime); //Daten im LCD Display leeren lcd.clear(); //Anzeigen der Uhrzeit lcd.print(formattedTime); //Anzeigen der Zeitzone lcd.setCursor(0, 1); lcd.print(item.timezone);
Die Weltzeiten sollen immer jeweils für 5 Sekunden angezeigt werden, dafür nutze ich eine bekannte Lösung mit einer Pause ohne Delay. Da ich auf die Werte im Array mit der Variable index zugreife erhöhe ich diese damit alle 5 Sekunden jedoch läuft das Programm im Hintergrund weiter.
long lastAction = -1; const long PAUSE = 3000; //Alle 5 Sekunden die Zeitzone wechseln long currentMillis = millis(); if (lastAction + PAUSE < currentMillis) { lastAction = currentMillis; if (index < NUM_MENUITEMS-1) { index++; } else { index = 0; } }
Arbeiten mit dem Timestamp
Von der Bibliothek erhalten wir mit der Funktion getEpochTime() einen Zeitstempel in Sekunden. Dieser repräsentiert die vergangenen Sekunden seit dem 01.01.1970. Aus diesem können wir zusätzlich auch das Datum berechnen und somit auf dem Display zusätzlich das Datum anzeigen. Ich habe hier bei der Uhrzeit die Sekunden abgeschnitten und das Jahr auf zweistellig gekürzt.
Das fertige Programm kannst du nachfolgend herunterladen:
// Funktion zur Umwandlung eines Unix-Timestamps in ein Datum String formatiereDatum(unsigned long timestamp) { unsigned long days = timestamp / 86400; // Tage seit dem Unix-Epoch unsigned long years = 1970; // Beginn des Unix-Epoch unsigned long remainingDays = days; // Jahr berechnen while (remainingDays >= 365) { if (istSchaltjahr(years)) { remainingDays -= 366; } else { remainingDays -= 365; } years++; } // Monat und Tag berechnen unsigned long months = 0; unsigned long monthLength = 0; while (remainingDays > monthLength) { monthLength = tageImMonat(months, years); remainingDays -= monthLength; months++; } return zweistellig(remainingDays + 1) + "." + zweistellig(months + 1) + "." + String(years % 100); } // Funktion zur Überprüfung, ob ein Jahr ein Schaltjahr ist bool istSchaltjahr(unsigned long year) { return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); } // Funktion zur Rückgabe der Anzahl der Tage in einem Monat unsigned long tageImMonat(unsigned long month, unsigned long year) { if (month == 1) { // Februar if (istSchaltjahr(year)) { return 29; } else { return 28; } } else if (month == 3 || month == 5 || month == 8 || month == 10) { // April, Juni, September, November return 30; } else { return 31; } } // Funktion zur Umwandlung eines Unix-Timestamps in eine Uhrzeit String formatiereUhrzeit(unsigned long timestamp) { unsigned long minutes = (timestamp % 3600) / 60; unsigned long hours = (timestamp % 86400) / 3600; return zweistellig(hours) + ":" + zweistellig(minutes); } // Funktion zur Formatierung von Zahlen auf zwei Stellen String zweistellig(unsigned long zahl) { if (zahl < 10) { return "0" + String(zahl); } else { return String(zahl); } }