Uhr mit TFT 240×240 Pixeln



Zie­le des Projekts

  • mit der WiFi-Funk­ti­on eines ESP32 oder ESP8266 Mikro­con­trol­lers die aktu­el­le Zeit und das aktu­el­le Datum holen
  • ana­lo­ge Uhr auf einem run­den TFT anzeigen

Die Genau­ig­keit der Zeit­an­zei­ge hängt sehr vom ver­wen­de­ten Mikro­con­trol­ler ab. Die bes­ten Ergeb­nis­se habe ich mit den ESP32-Mikro­con­trol­lern erzielt. Beson­ders beim ESP32C6 ent­sprach die ange­zeig­te Zeit auch nach meh­re­ren Tagen genau der Internetzeit. 

Das Pro­gramm für ein TFT mit 320×240 Pixeln fin­dest du ⇒hier, das für ein TFT mit 480×320 Pixeln gibt es ⇒hier.

Das Pro­gramm im Film

Anwen­dungs­bei­spie­le

Dan­ke an Tho­mas Neuenburg
Uhr auf TFT mit 320×240 Pixeln
Uhr auf ESP32-2432S028R mit 320×240 Pixeln

Kon­fi­gu­ra­ti­on der Mikrocontroller

TFT mit 240×240 Pixeln

Bei­spiel­pro­gramm

Schalt­plan für ESP32-Wroom

RST (gelb) -> 4
CS (weiß) -> 5
DC ( grün) -> 2
SDA (blau) -> 23
SCL (braun) -> 18

Benö­tig­te Bibliothek

Biblio­thek Adafruit GFX Library

Die Biblio­thek Adafruit_GC9A01A ver­wen­det die Funk­tio­nen der Adafruit GFX Library.

Funk­tio­nen der Biblio­thek Adafruit_GFX_Library

Schlüs­sel­wortPara­me­terAkti­on
width();Bild­schirm­brei­te feststellen
height();Bild­schirm­hö­he feststellen
setRotation(Richtung);Rich­tung = 0 → nicht drehen
Rich­tung = 1 → 90° drehen
Rich­tung = 2 → 180° drehen
Rich­tung = 3 → 270 ° drehen
Bild­schirm ausrichten
fillScreen(Farbe);Bild­schirm­hin­ter­grund
drawLine(StartX, Star­tY, End­eX, EndeY, Farbe);Linie zeich­nen
drawFastHLine(StartX, Star­tY, Län­ge, Farbe);hori­zon­ta­le Linie zeichnen
drawFastVLine(StartX, Star­tY, Län­ge, Farbe);ver­ti­ka­le Linie zeichnen
drawRect(StartX, Star­tY,, Brei­te, Höhe, Farbe);Recht­eck zeichnen
drawRoundRect(StartX, Star­tY, Brei­te, Höhe, Ecken­ra­di­us, Farbe);abge­run­de­tes Recht­eck zeichnen
fillRoundRect(StartX, Star­tY, Brei­te, Höhe, Ecken­ra­di­us, Füllfarbe);aus­ge­füll­tes abge­run­de­tes Recht­eck zeichnen
fillRect(StartX, Star­tY, Brei­te, Höhe, Füllfarbe);aus­ge­füll­tes Recht­eck zeichnen
drawCircle(MittelpunktX, Mit­tel­punk­tY, Radi­us, Farbe);Kreis zeich­nen
fillCircle(MittelpunktX, Mit­tel­punk­tY, Radi­us, Füllfarbe);Aus­ge­füll­ten Kreis zeichnen
drawTriangle(x1, y1, x2, y2, x3, y3, Farbe);Drei­eck zeichnen:
x1, y1: 1.Punkt
x2, y2: 2.Punkt
x3, y3: 3.Punkt
fillTriangle(x1, y1, x2, y2, x3, y3, Füllfarbe);aus­ge­füll­tes Drei­eck zeichnen:
x1, y1: 1.Punkt
x2, y2: 2.Punkt
x3, y3: 3.Punkt
setCursor(x, y);Cur­sor setzen
setTextSize(Textgröße);Text­grö­ßeText­grö­ße bestimmen
setTextColor(Farbe);Text­far­be setzen
print("Text"); println("Text");Text schrei­ben
setTextWrap(true/false);fal­se → Text fließt über den Rand des TFTs hinaus
true → Text wird am Ende umgebrochen
Zei­len­um­bruch
invert­Dis­play();0 → Far­ben nicht tauschen
1 → Far­ben tauschen
color565(rot, grün, blau);rot: 0 - 255
grün: 0 - 255
blau: 0 - 255
belie­bi­ge Misch­far­ben erstellen
drawBitmap(PosX, PosY, Array, Array­GrößeX, Array­Grö­ßeY, Farbe);Array als Bild darstellen

Das Pro­gramm

Das Pro­gramm kann auf viel­fäl­ti­ge Art und Wei­se ange­passt werden:

  • die Far­be der Zei­ger (Zeig­erfar­be)
    die Far­ben kannst du dem Kopf des Pro­gramms entnehmen
  • die Far­be des inne­ren Krei­ses (Kreis­far­be)
  • die Far­be der äuße­ren Umran­dung (Rand­far­be)
  • Anzei­ge des Datums (Datu­m­An­zei­gen)
    true: Datum anzei­gen, fal­se: Datum verbergen
  • Sekun­den­zei­ger voll­stän­dig oder nur als Kreis anzei­gen (Sekun­den­zei­ger­Kreis)
    true: nur den Kreis anzei­gen, fal­se: Sekun­den­zei­ger als Linie mit Kreis am Ende der Linie anzei­gen
    Wird true gewählt, ent­fällt die Aktua­li­sie­rung des Datums für 20 Sekun­den, wenn der Sekun­den­zei­ger sich dar­über befin­det.
    Das redu­ziert das kur­ze Fla­ckern der Anzei­ge des Datums.
  • die Anzei­ge der Stun­den­mar­kie­run­gen 12, 3, 6 und 9 (Zif­fern­an­zei­gen)
    true: Zif­fern anzei­gen, fal­se: Zif­fern verbergen

Der Zeit­ser­ver wird beim Start und danach nur alle 60 Sekun­den kon­tak­tiert. Daher kann es bis zu zwei Minu­ten dau­ern, bis die kor­rek­te Zeit und das rich­ti­ge Datum auf der Uhr ange­zeigt wird.

Du musst die SPI-Pins des ver­wen­de­ten Mikro­con­trol­lers anpassen.

#ifdef ESP8266
  #include "ESP8266WiFi.h"

#else 
  #include "WiFi.h"
#endif

#include "time.h"
#include "Adafruit_GC9A01A.h"

// Adafruit-Schriftart einbinden
#include "Fonts/FreeSans12pt7b.h"

// XIAO
// define TFT_CS D7
// #define TFT_RST D1
// #define TFT_DC D2

// Arduino Nano ESP 32
// #define TFT_CS       10
// #define TFT_RST       9
// #define TFT_DC        8

// SPI-Pins ESP32-C6
// #define TFT_CS       18
// #define TFT_RST       3
// #define TFT_DC        2

// ESP32-WROOM
// #define TFT_CS        5
// #define TFT_RST       4
// #define TFT_DC        2

Adafruit_GC9A01A tft(TFT_CS, TFT_DC);

// WiFi-Daten
char Router[] = "Router_SSID";
char Passwort[] = "xxxxxxxx";

// Variablen des TFTs (Höhe, Breite, Radius)
const int MitteHoehe = tft.height() / 2;
const int MitteBreite = tft.width() / 2;
const int Radius = tft.width() / 2;

// Multiplikatoren für x- y-Positionen der Stunden, Minuten und Sekunden
float SekundePosX = 0, SekundePosY = 0, MinutePosX = 0, MinutePosY = 0, StundePosX = 0, StundePosY = 0;
float GradSekunden = 0, GradMinuten = 0, GradStunden = 0;

// x- y-Koordinaten für die Anzeige Stunden, Minuten und Sekunden
int SekundenZeigerX = MitteHoehe, SekundenZeigerY = MitteHoehe;
int MinutenZeigerX = MitteHoehe, MinutenZeigerY = MitteHoehe;
int StundenZeigerX = MitteHoehe, StundenZeigerY = MitteHoehe; 

// Start wird nur beim ersten Start für den Aufbau des TFTs benötigt
bool Start = true;

// Variablen für die Markierungen und Punkte und Striche des Ziffernblatts
float PosX, PosY;
int PunktX, PunktY, PunktX1, PunktX2, PunktY1, PunktY2;

// Variablen für die Zeit
int Stunden, Minuten, Sekunden;

// Farben
#define SCHWARZ     0x0000
#define WEISS       0xFFFF
#define BLAU        0x001F
#define ROT         0xF800
#define GRUEN       0x07E0
#define CYAN        0x07FF
#define MAGENTA     0xF81F
#define GELB        0xFFE0
#define BRAUN       0x9A60
#define GRAU        0x7BEF
#define GRUENGELB   0xB7E0
#define DUNKELCYAN  0x03EF
#define ORANGE      0xFDA0
#define PINK        0xFE19
#define BORDEAUX    0xA000
#define HELLBLAU    0x867D
#define VIOLETT     0x915C
#define SILBER      0xC618
#define GOLD        0xFEA0

// Farben innerer Kreis, Randfarbe und Zeigerfarbe
// die Farben der Zeiger können aber auch individuell gesetzt werden
const int Kreisfarbe = SCHWARZ;
const int Zeigerfarbe = WEISS;
const int Randfarbe = BORDEAUX;

// true -> Datum anzeigen
// false -> Datum nicht anzeigen
bool DatumAnzeigen = true;

// true -> Sekundenzeiger nur als Kreis
bool SekundenzeigerKreis = true;

// Ziffern 12 3 6 9 anzeigen/nicht anzeigen
bool Ziffernanzeigen = true;

// NTP-Server aus dem Pool
#define Zeitserver "de.pool.ntp.org"

/*
  Liste der Zeitzonen
  https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
  Zeitzone CET = Central European Time -1 -> 1 Stunde zurück
  CEST = Central European Summer Time von
  M3 = März, 5.0 = Sonntag 5. Woche, 02 = 2 Uhr
  bis M10 = Oktober, 5.0 = Sonntag 5. Woche 03 = 3 Uhr
*/
#define Zeitzone "CET-1CEST,M3.5.0/02,M10.5.0/03"

// time_t enthält die Anzahl der Sekunden seit dem 1.1.1970 0 Uhr
time_t aktuelleZeit;

/* 
  Struktur tm
  tm_hour -> Stunde: 0 bis 23
  tm_min -> Minuten: 0 bis 59
  tm_sec -> Sekunden 0 bis 59
  tm_mday -> Tag 1 bis 31
  tm_mon -> Monat: 0 (Januar) bis 11 (Dezember)
  tm_year -> Jahre seit 1900
  tm_yday -> vergangene Tage seit 1. Januar des Jahres
  tm_isdst -> Wert > 0 = SoMinutenerzeit (dst = daylight saving time)
*/
tm Zeit;

unsigned long Zeitmessung = 0; 

void setup() 
{
  Serial.begin(9600);

  // Zeitzone: Parameter für die zu ermittelnde Zeit
  configTzTime(Zeitzone, Zeitserver);

  // WiFi starten
  WiFi.mode(WIFI_STA);
  WiFi.begin(Router, Passwort);

  Serial.println("------------------------");

  while (WiFi.status() != WL_CONNECTED) 
  {
    delay(200);
    Serial.print(".");
  }
  
  Serial.println();
  Serial.print("Verbunden mit ");
  Serial.println(Router);
  Serial.print("IP über DHCP: ");
  Serial.println(WiFi.localIP());

  time(&aktuelleZeit);

  // localtime_r -> Zeit in die lokale Zeitzone setzen
  localtime_r(&aktuelleZeit, &Zeit);

  // Zeit in Stunden, Minuten und Sekunden
  Stunden = Zeit.tm_hour, Minuten = Zeit.tm_min, Sekunden = Zeit.tm_sec;
  
  tft.begin();
  tft.setRotation(0);
  tft.fillScreen(Kreisfarbe);

  // 4 Pixel breiter äußerer Rand, Farbe au der Farbpalette wählen
  tft.drawCircle(MitteHoehe, MitteBreite, Radius - 1, Randfarbe);
  tft.drawCircle(MitteHoehe, MitteBreite, Radius - 2, Randfarbe);
  tft.drawCircle(MitteHoehe, MitteBreite, Radius - 3, Randfarbe);
  tft.drawCircle(MitteHoehe, MitteBreite, Radius - 4, Randfarbe);

  // innere Fläche bis auf den Rand von 4 Pixeln vollständig löschen
  // wenn andere Farbe als äußerer Rand gewählt wird ergibt sich ein schmaler Rand
  tft.fillCircle(MitteHoehe, MitteBreite, Radius - 4, Kreisfarbe);

 /*
    alle 30° Linie am Rand als Stundenmarkierung zeichnen
    DEG_TO_RAD (= PI/180 = 0.0174532925) -> Winkel in Bogenmaß umrechnen
    sin/cos berechnen die x-/y-Kordinaten des Punktes auf der Kreislinie
  */
  for (int i = 0; i < 360; i += 30) 
  {
    PosX = cos((i - 90) * DEG_TO_RAD);
    PosY = sin((i - 90) * DEG_TO_RAD);

    // kurze Linien zeichnen, von 114 bis 100 vom äußeren Rand aus
    // Farbe individuell wählbar
    int PunktX1 = PosX * 110 + Radius;
    int PunktY1 = PosY * 110 + Radius;
    int PunktX2 = PosX * 100 + Radius;
    int PunktY2 = PosY * 100 + Radius;

    tft.drawLine(PunktX1, PunktY1, PunktX2, PunktY2, Zeigerfarbe);
    
    // keine Striche an der Position der Zahlen
    if (Ziffernanzeigen)
    {
      if (PunktX1 == 10 || PunktX1 == 120 || PunktX1 == 230)
      {
        tft.drawLine(PunktX1, PunktY1, PunktX2, PunktY2, Kreisfarbe);
      }  
    }  
  }

  // alle 6 Grad Punkte als Sekundenmarkierung zeichnen
  for (int i = 0; i < 360; i += 6) 
  {
    PosX = cos((i - 90) * DEG_TO_RAD);
    PosY = sin((i - 90) * DEG_TO_RAD);

    // Positionen der Punkte
    // 108 -> Abstand vom Mittelpunkt
    PunktX = PosX * 108 + Radius;
    PunktY = PosY * 108 + Radius;
    tft.drawPixel(PunktX, PunktY, Zeigerfarbe);  
  }

  // Markierung 12 3 6 9
  if (Ziffernanzeigen)
  {
    tft.setFont(&FreeSans12pt7b);
    tft.setTextColor(Zeigerfarbe);
    tft.setCursor(tft.height() / 2 - 15, 25);
    tft.print("12");
    
    tft.setCursor(10, tft.height() / 2 + 7);
    tft.print("9");
    
    tft.setCursor(220, tft.height() / 2 + 10);
    tft.print("3");
  
    tft.setCursor(tft.height() / 2 - 7, 230);
    tft.print("6");
  }

  if (DatumAnzeigen)
  {
    ZeigeDatum();
  }

  Zeitmessung = millis() + 1000; 
}

void loop() 
{
  // Sekunden weiter zählen
  if (Zeitmessung < millis()) 
  {
    Zeitmessung += 1000;
    Sekunden++;  

    if (Sekunden == 60) 
    { 
 
      // Sekunden = 0; 
      Minuten++; 

     if (DatumAnzeigen) ZeigeDatum();

      // aktuelle Zeit holen
      time(&aktuelleZeit);

      // localtime_r -> Zeit in die lokale Zeitzone setzen
      localtime_r(&aktuelleZeit, &Zeit);

      // Zeit in Stunden, Minuten und Sekunden
      Stunden = Zeit.tm_hour, Minuten = Zeit.tm_min, Sekunden = Zeit.tm_sec;

      if (Minuten > 59) 
      {
        Minuten = 0;
        Stunden++;  
        if (Stunden > 23) Stunden = 0;
      }
    }

    // Datum anzeigen/nicht anzeigen
    // Datumsanzeige nur erneuern, wenn der Sekundenzeiger
    // vollständig als Strich und Kreis angezeigt wird
    if (DatumAnzeigen && !SekundenzeigerKreis)
    {
      // Anzeige des Datums nur aktualisieren,
      // wenn sich der Sekundenzeiger darüber befindet
      if (Sekunden > 20 & Sekunden < 40)
      {
        ZeigeDatum();
      }
    }

    // Vorausberechnung der x- und y-Koordinaten
    // alle 6° eine Sekunde vorwärts
    GradSekunden = Sekunden * 6;    
  
    // alle 6° eine Minute vorwärts     
    GradMinuten = Minuten * 6; 

    // alle 30° eine Stunde vorwärts
    // 30 / 3600 = 0.0833333
    // sorgt dafür, dass der Stundenzeiger entsprechend 
    // der Anzahl der Minuten weiter "wandert"
    GradStunden = Stunden * 30 + GradMinuten * 0.0833333; 
    StundePosX = cos((GradStunden - 90) * DEG_TO_RAD);
    StundePosY = sin((GradStunden - 90) * DEG_TO_RAD);

    MinutePosX = cos((GradMinuten - 90) * DEG_TO_RAD) ;
    MinutePosY = sin((GradMinuten - 90) * DEG_TO_RAD);

    SekundePosX = cos((GradSekunden - 90) * DEG_TO_RAD);
    SekundePosY = sin((GradSekunden - 90) * DEG_TO_RAD);

    // nach jeder Minute Minuten-/Stundenzeiger löschen
    // oder einmalig beim Start der Anzeige
    if (Sekunden == 0 || Start) 
    {
      Start = false;
      tft.drawLine(StundenZeigerX, StundenZeigerY, MitteHoehe, MitteHoehe + 1, Kreisfarbe);  

      // 62 Pixel -> Länge des Stundenzeigers
      // Mittelpunkt + 1 -> Mittelpunkt soll nicht gelöscht werden
      StundenZeigerX = StundePosX * 62 + MitteHoehe + 1;
      StundenZeigerY = StundePosY * 62 + MitteHoehe + 1;
      tft.drawLine(MinutenZeigerX, MinutenZeigerY, MitteHoehe, MitteHoehe + 1, Kreisfarbe);

      // 84 Pixel -> Länge des Minutenzeigers
      // Mittelpunkt + 1 -> Mittelpunkt soll nicht gelöscht werden
      MinutenZeigerX = MinutePosX * 84 + MitteHoehe;
      MinutenZeigerY = MinutePosY * 84 + MitteHoehe + 1;
    }

    // Sekundenzeiger löschen
    if (!SekundenzeigerKreis) tft.drawLine(SekundenZeigerX, SekundenZeigerY, MitteHoehe, MitteHoehe + 1, Kreisfarbe);

    // Kreis am Sekundenzeiger löschen, Radius 5
    tft.fillCircle(SekundenZeigerX, SekundenZeigerY, 5, Kreisfarbe);

    // 85 Pixel -> Länge des Sekundenzeigers
    SekundenZeigerX = SekundePosX * 85 + MitteHoehe + 1;
    SekundenZeigerY = SekundePosY * 85 + MitteHoehe + 1;

    // Zeiger neu zeichnen
    // Sekunden Linie nur anzeigen wenn SekundenzeigerKreis false
    if (!SekundenzeigerKreis) tft.drawLine(SekundenZeigerX, SekundenZeigerY, MitteHoehe, MitteHoehe + 1, ROT);
    
    // Minuten
    tft.drawLine(MinutenZeigerX, MinutenZeigerY, MitteHoehe, MitteHoehe + 1, Zeigerfarbe);

    // Stunden
    tft.drawLine(StundenZeigerX, StundenZeigerY, MitteHoehe, MitteHoehe + 1, Zeigerfarbe);

    // Kreis an der Spitze des Sekundenzeigers, Radius 5
    tft.fillCircle(SekundenZeigerX, SekundenZeigerY, 5, ROT);

    // Mittelpunkt zeichnen
    tft.fillCircle(MitteHoehe, MitteHoehe + 1, 3, Zeigerfarbe); 
  }
}

void ZeigeDatum()
{
  tft.setFont(&FreeSans12pt7b);  
  tft.setCursor(65, 170);
  tft.setTextColor(GRUEN);
      
  // Bildschirmbereich für das Datum löschen
  tft.fillRect(60, 150, 125, 25, Kreisfarbe);
  if (Zeit.tm_mday < 10) tft.print("0");
  tft.print(Zeit.tm_mday);
  tft.print(".");

  // Monat: führende 0 ergänzen
  if (Zeit.tm_mon < 9) tft.print("0");
    
  // Zählung beginnt mit 0 -> +1
  tft.print(Zeit.tm_mon + 1);
  tft.print(".");

  // Anzahl Jahre seit 1900
  tft.print(Zeit.tm_year + 1900);
}

Quel­len


Letzte Aktualisierung: Juli 8, 2025 @ 17:36