Retro-Style Uhr mit TFT-Display und ESP32
In diesem Blogbeitrag geht es um die Umsetzung einer Retro-Style Uhr, die auf einem ESP32 Mikrocontroller basiert und ein rundes TFT-Display zur Darstellung nutzt. Mit der Möglichkeit, die Uhrzeit über NTP (Network Time Protocol) automatisch zu synchronisieren, kann auf ein RTC-Modul verzichtet werden.
Hardware
Da wir eine Retro-Uhr auf dem Display darstellen wollen, ist das runde LCD Display prädestiniert dafür. Um Displays anzusteuern, benötigt man einen Mikrocontroller mit ausreichend RAM, dafür ist das ESP32 mini S2 Board mit 2MB RAM die ideale Lösung, da dieser sogar über WLAN-Anbindung verfügt.
Benötigte Komponenten:
ESP32 Mikrocontroller
TFT-Display (GC9A01A)
Verbinden sie die Bauteile wie in folgender Skizze:
RST |
GPIO 3 |
CS |
GPIO 5 |
DC |
GPIO 7 |
SDA |
GPIO 9 |
SCL |
GPIO 11 |
GND |
GND |
VCC |
3V3 |
Software
Falls Sie das erste Mal mit einem ESP32 programmieren, kopieren Sie folgenden Link in die Arduino IDE unter File->Preferences -> Additional boards manager URLs: https://dl.espressif.com/dl/package_esp32_index.json
und installieren Sie in der Boardverwaltung das ESP32 Packet in der Version V2.0.11.
Wählen Sie zum Flashen des Boards das „ESP32S2 Dev Module“ aus. Halten Sie den Button „0“ gedrückt und drücken Sie einmal den Reset-Knopf. Dadurch ist das Board im Bootmodus und ein COM-Port sollte sichtbar werden.
Benötigte Library: TFT-eSPI
Die Installation der Bibliothek erfolgt wie gewohnt in der Arduino IDE unter: Sketch -> Include Library -> Add .ZIP Library
Wenn die Bibliothek installiert ist, muss nur noch die User_Setup Datei für den Displaytreiber im Verzeichnis der Library (Sketchbook Verzeichnis/libraries/TFT_eSPI/User_Setup.h) angepasst werden:
Kopieren Sie zuerst die vorhandene User_Setup.h, löschen Sie den Inhalt und kopieren Sie folgende Zeilen in die Datei:
#define USER_SETUP_INFO "User_Setup" #define GC9A01_DRIVER #define TFT_WIDTH 240 #define TFT_HEIGHT 240 #define TFT_MOSI 9 #define TFT_SCLK 11 #define TFT_CS 5 #define TFT_DC 7 #define TFT_RST 3 #define LOAD_GLCD // Font 1. Original Adafruit 8 pixel font needs ~1820 bytes in FLASH #define SPI_FREQUENCY 27000000 #define SPI_READ_FREQUENCY 20000000
Alternativ können Sie die Datei auch hier herunterladen.
Wenn in der Arduino IDE im Menü Werkzeuge das Board ESP32S2 ausgewählt wurde, muss im gleichen Menü unter dem Punkt Upload Mode von UART0 auf internal USB umgestellt werden (falls noch nicht geschehen)!
Code
Zuerst binden wir die Bibliotheken ein, die für die SPI-Kommunikation, das TFT-Display und für die Netzwerkverbindung benötigt werden:
#include "SPI.h" #include "TFT_eSPI.h" #include <WiFi.h> #include "time.h"
Als Nächstes definieren wir einige Variablen, die die Position des Zentrums der Uhr, sowie die Uhrzeit (Stunden, Minuten, Sekunden) und weitere Konfigurationen, wie die Adresse des NTP-Server, enthalten. Der Pi-Wert wird benötigt, um die Positionen der Zeiger im Kreis zu berechnen, da die Koordinaten der Zeiger auf einer Kreisbahn verlaufen. Die Konstanten ssid und password müssen auf ihre Netzwerk-Zugangsdaten geändert werden.
int clock_center_y = 120; int clock_center_x = 120; int minutes = 45; int hours = 6; int seconds = 45; const char* ssid = ""; const char* password = ""; const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = 0; double pi = 3.14159; TFT_eSPI tft = TFT_eSPI(); time_t now; tm tm;
Im setup() wird die WiFi-Verbindung aufgebaut und die Zeit wird mithilfe des NTP-Servers abgerufen. Danach wird das Zifferblatt gezeichnet.
void setup() { WiFi.begin(ssid, password); while (WiFi.status() != WL_CONNECTED) { delay(500); } configTime(3600, 3600, ntpServer); // Zeit wird synchronisiert struct tm tm; if (!getLocalTime(&tm)) { return; } seconds = tm.tm_sec; minutes = tm.tm_min; hours = tm.tm_hour; tft.init(); tft.fillScreen(0x000000); // Display schwarz füllen draw_clock_face(); // Zifferblatt zeichnen }
Um die Uhr zu visualisieren, müssen wir natürlich auch ein Zifferblatt mit den Stunden-Markierungen erstellen. Hier zeichnen wir die Stundenmarkierungen (1 bis 12) auf dem Zifferblatt. Dies geschieht durch Berechnung der x- und y-Koordinaten für jeden Punkt anhand der Kreisgeometrie.
void drawClockFace(){ for (int i = 1; i < 12; i++) { y = (120 * cos(pi - (2 * pi) / 12 * i)) + clock_center_y; x = (120 * sin(pi - (2 * pi) / 12 * i)) + clock_center_x; y_1 = (110 * cos(pi - (2 * pi) / 12 * i)) + clock_center_y; x_1 = (110 * sin(pi - (2 * pi) / 12 * i)) + clock_center_x; tft.drawLine(x_1, y_1, x, y, TFT_WHITE); // Stundenmarkierungen zeichnen } tft.setTextSize(2); tft.setTextColor(TFT_WHITE); tft.setCursor(clock_center_x - 10, 0); tft.println(F("12")); // "12" oben hinzufügen }
redrawClockFace() zeichnet die 12-Uhr-Markierung und den Kreis in der Mitte neu, da beim Löschen der einzelnen Zeiger auch Teile der Markierungen gelöscht werden.
void redrawClockFaceElements(){ tft.drawCircle(clock_center_x, clock_center_y,3, TFT_WHITE); tft.fillCircle(clock_center_x, clock_center_y,3, TFT_WHITE); tft.setCursor(clock_center_x-10, 0); tft.setTextColor(TFT_WHITE); tft.setTextSize(2); tft.println(F("12")); }
Nun kommen die eigentlichen Uhrzeiger, die auf der Grundlage der aktuellen Zeit angezeigt werden. Die draw_hour()- und die draw_minute()-Methoden zeichnen die Stunden- und Minutenzeiger auf dem Zifferblatt. Der Parameter mode legt fest, ob der Zeiger gezeichnet oder gelöscht wird.
void drawHour(int hour, int minute, int mode){ int l = 70; y= (l*cos(pi-(2*pi)/12*hour-(2*PI)/720*minute))+clock_center_y; x =(l*sin(pi-(2*pi)/12*hour-(2*PI)/720*minute))+clock_center_x; if (mode==1){ tft.drawLine(clock_center_x,clock_center_y,x,y,TFT_ORANGE); tft.drawLine(clock_center_x+1,clock_center_y+1,x+1,y+1,TFT_ORANGE); } else{ tft.drawLine(clock_center_x,clock_center_y,x,y,TFT_BLACK); tft.drawLine(clock_center_x+1,clock_center_y+1,x+1,y+1,TFT_BLACK); } }
void drawMinute(int minute, int mode){ int l = 110; y= (l*cos(pi-(2*pi)/60*minute))+clock_center_y; x =(l*sin(pi-(2*pi)/60*minute))+clock_center_x; if (mode==1)tft.drawLine(clock_center_x,clock_center_y,x,y,TFT_CYAN); else tft.drawLine(clock_center_x,clock_center_y,x,y,TFT_BLACK); }
Der Sekundenzeiger wird etwas anders dargestellt – als kleiner Kreis.
void drawSecond(int second, int mode){ int l = 100; double rad = pi-(2*pi)/60*second; y= (l*cos(rad))+clock_center_y; x =(l*sin(rad))+clock_center_x; if (mode==1) tft.drawCircle(x, y, 3, TFT_WHITE); else tft.drawCircle(x, y, 3, TFT_BLACK); }
Die Methode date() zeigt das aktuelle Datum je nach Position des Stundenzeigers über, beziehungsweise unter dem Mittelpunkt an.
void date() { struct tm tm; while(!getLocalTime(&tm)){ return; } if((tm.tm_hour == 3 || tm.tm_hour == 9 || tm.tm_hour == 15 || tm.tm_hour == 21) && (tm.tm_min == 0 && tm.tm_sec <= 3)) tft.fillScreen(0x000000); if((tm.tm_hour > 3 && tm.tm_hour < 9) || (tm.tm_hour > 15 && tm.tm_hour < 21)) { //datum oben -> zeiger unten tft.fillRect(70,90,110,25,TFT_BLACK); tft.setCursor(60,97); tft.setTextColor(TFT_PINK); tft.setTextSize(2); tft.printf("%02d.%02d.%04d ", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900); } else { tft.fillRect(70,130,110,25,TFT_BLACK); tft.setCursor(60,137); tft.setTextColor(TFT_PINK); tft.setTextSize(2); tft.printf("%02d.%02d.%04d ", tm.tm_mday, tm.tm_mon + 1, tm.tm_year + 1900); } }
In der loop()-Funktion wird die Uhrzeit kontinuierlich aktualisiert und die Zeiger entsprechend neu gezeichnet. Erst wird die vorherige Zeigerstellung gelöscht, um das Löschen des kompletten Displays zu vermeiden. Danach wird die aktuelle Uhrzeit angezeigt.
void loop() { struct tm tm; while (!getLocalTime(&tm)) { return; } drawSecond(seconds, 0); drawMinute(minutes, 0); drawHour(hours, minutes, 0); seconds = tm.tm_sec; minutes = tm.tm_min; hours = tm.tm_hour; drawSecond(seconds, 1); drawMinute(minutes, 1); drawHour(hours, minutes, 1); redrawClockFaceElements(); date(); delay(500); }
Ergebnis
Das Projekt kann natürlich auch für den langfristigen Betrieb auf eine Platine gelötet, oder in ein 3D-gedrucktes Gehäuse eingebaut werden, hier sind der Kreativität keine Grenzen gesetzt. Des Weiteren können ganz einfach die Farben der Uhrzeiger geändert, oder anstatt der Uhrzeit andere Informationen angezeigt werden. Da für die Synchronisation der Uhrzeit der NTP Time Server über das Internet abgefragt wird, muss eine WLAN Verbindung (2,4GHz) vorhanden sein.
Viel Spaß beim Nachbauen :)
28 commenti
Siggi
@Matze: Welches Board hast du? Das ESP32 S2 Mini von AZ?
Hast du mal geprüft, auf welche user_setup.h deine Arduino IDE zugreift?
Ich hatte verschiedene Versionen installiert und einmal war die Datei in dem library Verzeichnis direkt im Sketch Ordner, bei einer anderen Installation im Arduino Programm Ordner. Eventuell hast du die „falsche“ Datei angepasst?
Einfach mal die user_setup.h umbenennen und schauen, ob die IDE beim kompilieren meckert (Datei nicht vorhanden).
Gruß
Matze
Hallo Leutz
Habe alles so gemacht wie Siggi es beschrieben hat.
Mit der gezeigten Verkabelung von AZ-De..
die esp32 von espressif habe ich auf Version 2.0.11 zurück gesetzt.
Habe auch ein Testprogramm mal probiert mit gleicher Verkabelung…
aber es will einfach nicht.
Bitte um Hilfe was kann ich noch machen
LG Matze
Siggi
Hallo nochmal an alle Verzweifelten.
Ich hatte die gleichen Probleme wie viele hier und nun läuft es dann doch noch.
Was habe ich gemacht:
Wie Tom geschrieben hat, habe ich den Board Core zurück auf eine alte Version gesetzt, allerdings nicht den ESP32 Arduino Core V2.0.11(da gibt es laut IDE gar keine Version 3.0), sondern die esp32 von espressif habe ich auf Version 2.0.11 downgegradet. Ist eventuell in dem anderen Projekt zur Luftqualitätsmessung falsch beschrieben?
Dann habe ich noch das Board auf Lolin S2 Mini gesetzt (statt ESP32 S2 Mini) – hatte ich mal irgendwo gelesen bei einer S2 Mini Beschreibung, dass man dieses Board auswählen soll. Danach hat es funktioniert und zwar auch ohne diese GPIO Reset Meldung, etc.
Hoffe, das hilft dem einen oder anderen.
Gruß
Siggi
Bastian Brumbi
Update:
Die Kommunikation mit dem Display funktioniert nur unter ESP32 Paket Version 2.0.11.
Der Upload auf das Board sollte aber unter allen Versionen funktionieren!
Bastian Brumbi
Hallo zusammen,
@Siegfried Pappert
Die DC und CS Pin können auf beliebige GPIO im User_Setup.h gelegt werden.
@Thomas
Beim Upload entsteht die Fehlermeldung, da die Arduino IDE das Board automatisch zurücksetzen möchte, dies ist aber nicht via USB möglich. Das Board sollte nach einem Zurücksetzen über den RST-Taster funktionieren.
@Siggi
Bei ESP32 Boards können die SPI Pins auf beliebige IO im User_Setup.h gelegt werden.
Das Board Paket sollte versionsunabhängig funktionieren. (Ich benutze 3.0.7)
Grüße,
Bastian
Siegfried Pappert
at Bernd Steffen: Könntest du mal bitte das PINout von deinem ESP32 D1 Mini hier posten?
Die Bezeichnungen der Anschlüsse vom Display zum ESP 32 sind doch recht unterschiedlich. Wenn ich das richtig sehe, dann ist SDA → MOSI, VCC, GRD klar! MIt was hast du CS und DC verbunden?
Gruß Siggi
Thomas
Danke Tom für deine Lösung. Leider kommt jetzt eine andere Fehlermeldong nach Abschluß des Kompilierens:
WARNING: ESP32-S2FNR2 (revision v0.0) chip was placed into download mode using GPIO0.
esptool.py can not exit the download mode over USB. To run the app, reset the chip manually.
To suppress this note, set —after option to ‘no_reset’.
Fehlgeschlagenes Hochladen: Hochladefehler: exit status 1
Never ending story…
Siggi
Hallo Tom,
danke für den Hinweis. Leider funktioniert es bei mir bei beiden Projekten nach dem downgrade auf ESP32 Arduino Core V2.0.11 immer noch nicht.
Der upload funktioniert wohl (ESP32 taucht im WLAN mit einer IP auf).
Aus meiner Sicht hängt das Ganze mit den SPI ports zusammen. Wenn man sich das PIN out des ESP32 S2 mini bei AZ anschaut, dann passt das doch gar nicht mit der Zuordnung in der user_setup.h zusammen:
#define TFT_MOSI 9
#define TFT_SCLK 11
#define TFT_CS 5
#define TFT_DC 7
#define TFT_RST 3
Laut Pinout müssten die SPI pins doch eher ab PIN 34 oder so losgehen.
Ich werde das mal ausprobieren und schauen, was dann passiert.
Ich habe auch kein anderes ESP32 S2 board ausfindig machen können, wo die PINS 3-11 für SPI wären. @Tom: Welches Boards genau verwendest du?
Gruß Siggi
Tom
Moin!
Nach Tagen wilder Fummelei am PC läuft die Uhr bei mir (Einsteiger) jetzt.
In der Anleitung zur “Luftqualitätsüberwachung”, die ich gleichzeitig aufbaute und welche die gleiche Controller/Display-Kombi verwendet, steht man solle den ESP32 Arduino Core V2.0.11 verwenden, da das Projekt nicht mit V3 getestet wurde.
Also alte Version drauf und beide Aufbauten liefen sofort bei mir.
Hier mal der Link zum Nachlesen:
https://www.az-delivery.de/products/luftverschmuzung-projekt?pos=1&psq=luftqualit%C3%A4ts%C3%BCber&ss=e&v=1.0
Gruss Tom
Thomas
Hallo miteinander, auch bei mir ist es so wie bei vielen Anderen. Der gleiche Fehler wie bereits geschildert. Kann nicht doch einer der Insider das Problem aufgreifen und vielen Leuten eine Freude machen zu Weihnachten…. danke 👍
OE6LME
Hallo Andreas Wolter
Habe die gleichen Fehler wie alle anderen. Du hast ja den Artikel geschrieben und auch das Programm mit den oben angeführten Komponenten in Betrieb genommen. Warum funktioniert es bei dir ? Hast du den GPIO0 Fehler nicht? warum nicht ?
Was hast du genau gemacht das es funktioniert. Mit den heruntergeladenen Programm und den S2 Chip geht das nicht. Also was hast du genau gemacht. Bitte um genaue Anleitung.
Bedanke mich im Voraus für deine Hilfe.
lg. Manfred
Bastian Brumbi
Bei Problemen mit dem Upload des Programms überprüfen Sie, dass das richtige Board (ESP32S2) am richtigen Port ausgewählt ist. Des weiteren muss die upload Method auf internal USB gestellt werden.
Bei Problemen mit dem Display überprüfen Sie die UserSetup Datei in der Library und die verkabelung zum Display
Ich hoffe ich konnte ihnen weiterhelfen.
Bernd-Steffen
Hallo zusammen, ich hab das Ganze mit dem GC9A01A-TFT-Display aufgebaut, aber als Controller einen ESP32 Mini D1 verwendet, programmiert mit der Arduino IDE als ESP 32_WROOM-DA-Board. Der hat etwas andere GPIO rausgeführt – z.B. 7, 9, 11 gibt es da nicht auf dem Breakout, das ich hier habe. Nach ein wenig suchen nach den Einstellungen hab ich die Anpassung dazu in der Datei “User-Setup.h” gefunden, die die IDE parallel zum Sketch in einem separaten Ordner aufmacht. Dort die richtigen Anschlüsse eingetragen, (bei mir DC an GPIO17, SDA an GPIO21, SCL an GPIO22, RST an GPIO23, CS an GPIO05 kann so bleiben) und schon funktionierts. Freu! Viele Grüße von Bernd-Steffen
Frank Beutler
Bei denen wo es nicht geht in den Einstellungen insbesondere die Pin’s für den SPI-Bus prüfen.
Ich verwende aktuell ein ESP32DevKit und habe in der User_Setup.h in der Library TFT_eSPI folgende Konfiguration:
#define USER_SETUP_INFO “User_Setup”
#define GC9A01_DRIVER
#define TFT_HEIGHT 240
#define TFT_WIDTH 240
#define TFT_MISO 19 // Daten vom SPI-Device – hier ungenutzt
#define TFT_MOSI 23 // Daten zum SPI-Device, oft SDA benannt
#define TFT_SCLK 18
#define TFT_CS 2
#define TFT_DC 4
#define TFT_RST 0
#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF
#define SMOOTH_FONT
#define SPI_FREQUENCY 27000000
#define SPI_READ_FREQUENCY 20000000
DIe GPIO’s für SPI sind von Board zu Board unterschiedlich …
Arduino-Studio verwendet meines Wissens globale Librarys. Ändert man dort was schlägt das auf alle Projekte durch. PlatformIO nutzt projektspezifische Librarys so das man da freier ist.
Alternativ die Konfiguration im Kopf der .ini bzw .cpp definieren.
Programm läuft – nach ein paar kleinen Optimierungen – super, geht aber auch mit dem vorgestellten Quellcode.
Thomas
Hallo zusammen,
Leiter läuft es bei mir gar nicht :-(
Getestet habe ich das Display mit einem 8266 > Display ist i.O,
Den Sketch kann ich hochladen und nach dem Reset verbindet sich der esp32 mit dem Router (Protokoll des Routers). Die Hintergrund Erleuchtung des Displays ist an. Habe den esp32 auch schon neben den Router gestellt um Verbindungsprobleme zu vermeiden. Verkabelung zig mal geprüft. Gibt es in dieser Konfiguration an andere Möglichkeit das Display zu prüfen?
Heiend
Hallo Stefan,
ich muss bei mir den Button 0 nicht drücken um den Code flashen zu können, die Software regelt das alles irgendwie selbst. Dann wirft die Arduino IDE bei mir auch nicht mehr den Fehler und alles läuft wie erwartet.
Manfred Lampl
Hallo
Hab das gleiche Problem wie Stefan. Finde auch keine Einstellungen im “Werkzeug” wo dann der Fehler GPIO0 behoben ist. Weiters habe ich es mit mehreren ES32 Modulen Probiert. Der ESP32 bootet nach dem flashen ständig. Habe herausgefunden das die tft.init(); Anweisung Schuld daran ist. Anscheinent gibts Problem mit der Libaray.
Fazit: Es funktioniert leider nicht und man muss schon ein Profi sein und das Projekt zum laufen zu bringen. Vielleicht gibt es einen User der Tips geben kann und es tatsächlich geschafft hat die Uhr zum laufen zu bringen. Würde mich über Tips freuen. Einen schönen tag noch an alle
Matti
Ich habe das gleiche Problem wie Stefan, gibt es inzwischen eine Lösung?
Sascha
Habe das gleiche Problem wie Stefan, der Upload zählt durch bis 100%, dann kommt die Meldung “chip was placed into download mode using GPIO0” und “Fehlgeschlagenes Hochladen”. Habe schon viele ESP32-Boards geflasht, das Problem kenne ich bisher nicht. Hat jemand eine Idee ?
Manfred
Habe den gleichen Fehler wie Stefan. Hat jemand die richtigen Einstellungen im “Werkzeug” gefunden? Bitte um Info. Wäre schade wenn die Uhr nicht funktionieren sollte.
Danke im voraus für die Hilfe
Frank Beutler
Schönes Beispielprogramm. Leider in der englischen Version massig Übersetzungsfehler.
Mit ein paar Anpassungen weitestgehend “flickerfree” …
Thomas
Leider bekomme ich es nicht ans laufen.
Der Code ist hochgeladen, der ESP verbindet sich auch mit dem Router, das Display bleibt schwarz nach dem Reset :-(
Verkabelung nochmals geprüft. Der ESP ist wohl auch richtig in der Arduino IDE konfiguriert. Das Beispiel mit der blinkenden LED läuft prompt.
Andreas Wolter
@Wouters Marc: not at this moment, sry
@Jürgen: ich würde es einfach ausprobieren. Eventuell müssten für SDA und SCL andere Pins angeschlossen werden.
@Udo Brendel: danke für den Hinweis. Ich habe die .INO Datei korrigiert.
@Stefan: eventuell muss in den Board-Einstellungen unter WERKZEUGE etwas angepasst werden. Ansonsten interpretiere ich die Meldung so, dass etwas an GPIO0 angeschlossen ist, was den Upload verhindert. Manche Boards brauchen auf bestimmten Pins einen bestimmten Pegel (HIGH oder LOW), damit der Upload funktioniert. Der Zustand könnte sich ändern, wenn etwas an diese Pins angeschlossen ist. Dafür muss man nur das entsprechende Kabel für den Upload entfernen.
@oe1kaw: can you tell me what you have changed? I have corrected the mistake that Udo Brendel told us about. The compilation process works for me without errors now.
Grüße, best regards,
Andreas Wolter
AZ-Delivery Blog
Jürgen
Hallo Bastian,
kann ich anstelle eines ESP32-S2 auch einen ESP32-D1 verwenden?
Den hätte ich bereits hier herumzuliegen.
Danke
Stefan
Die “Uhr” geht leider nicht, weil nach dem kompilieren folgende Fehlermeldung kommt:
Leaving…
WARNING: ESP32-S2FNR2 (revision v0.0) chip was placed into download mode using GPIO0.
esptool.py can not exit the download mode over USB. To run the app, reset the chip manually.
To suppress this note, set —after option to ‘no_reset’.
Fehlgeschlagenes Hochladen: Hochladefehler: exit status 1
Kann mir jemand weiterhelfen?
Udo Brendel
für Bastler gibt es hier immer mal wieder ein schönes Projekt so auch diese Uhr.
Der Sketch zum Herunterladen enthält leider einen Fehler im Setup Teil, Zeile 41 da sollte folgendes stehen: drawClockFace();
oe1kaw
Nice project, but there are some TYPOs and missing declarations in the sketch. Very easy to correct, even for beginners
Wouters Marc
Hello,
Does there exist also the program in Python ?
Thanks !
Regards, Marc