ESP32 mit 3,5 Zoll Dis­play (ESP32-3248S035)

Lese­zeit: 35 Minu­ten

Die Hard­ware

Beim Modul ESP32-3248S035 han­delt es sich um ein 3,5 Zoll gro­ßes TFT-Dis­­play mit 480×320 Pixeln. Auf der Rück­sei­te sind ein ESP32-Wroom und ein SD-Kar­ten-Modul ver­baut. Der ESP32-Wroom ver­fügt über WiFi und Blue­tooth. Mit JST-Ste­­ckern kön­nen Peri­phe­rie­ge­rä­te ange­schlos­sen werden.

Es gibt zwei Versionen:

  • mit Mini-USB-Anschluss
    JST-Ste­cker sind rechts ange­ord­net (vom USB-Ste­cker aus betrach­tet)
    I²C-Pins: SDA 22, SCL 21
  • mit USB-C-Anschluss
    JST-Ste­cker befin­den sich links (vom USB-Ste­cker aus betrach­tet)
    I²C-Pins: SDA 32, SCL 25

Bei­de Ver­sio­nen ver­wen­den den Gra­fik­trei­ber ST7796. 

Anschluss von Peripherie

I²C und Datenpins

Modul mit Mini-USB-Anschluss

I²C-Pins (Anschluss CN1)

SDA -> 22
SCL -> 21

Daten­pins
21 und 22

Modul mit USB-C-Anschluss

I²C-Pins (Anschluss I2C)

SDA -> 32
SCL -> 25

Daten­pins

25 und 32

Laut­spre­cher­an­schluss

Lau­t­spre­cher-Anschluss mit 2 Pin Mikro-JST-Ste­­cker
GND
26

int Lautsprecher = 26; 

void setup()  
{ 
  // kein setup-Teil notwendig
} 

void loop() 
{ 
  // tone(Ausgabepin, Frequenz, Zeit_in_Millisekunden)    
  tone(Lautsprecher, 1000, 100); 

  delay(500);

  // Lautsprecher ausschalten 
  noTone(Lautsprecher);
}

RGB-LED

Auf der Rück­sei­te (Modul mit Mini-USB-Anschluss) oder auf der Vor­der­sei­te (Modul mit USB-C-Anschluss befin­det sich eine RGB-LED.
Die ver­schie­de­nen Far­ben leuch­ten bei LOW und sind bei HIGH ausgeschaltet.

int RoteLED = 4;
int GrueneLED = 16;
int BlaueLED = 17;

void setup()  
{ 
  pinMode(RoteLED, OUTPUT);
  pinMode(GrueneLED, OUTPUT);
  pinMode(BlaueLED, OUTPUT);
}

void loop()   
{ 
  digitalWrite(RoteLED, LOW);
  delay(500);
  digitalWrite(RoteLED, HIGH);
  delay(500);
  digitalWrite(GrueneLED, LOW);
  delay(500);
  digitalWrite(GrueneLED, HIGH);
  delay(500);
  digitalWrite(BlaueLED, LOW);
  delay(500);
  digitalWrite(BlaueLED, HIGH);
  delay(500);
}

SD-Kar­ten­mo­dul

Das ⇒SD-Kar­ten­mo­dul nutzt den Stan­dard Hard­ware SPI-Bus (VSPI):

Pins VSPI
23 -> COPI (MOSI)
19 -> CIPO (MISO)
18 -> CLK
5 -> CS

Das Bei­spiel­pro­gramm liest den Inhalt der SD-Kar­te und zeigt die Datei­en im Seri­el­len Moni­tor an.

Benö­tig­te Bibliothek

#include "SdFat.h"
SdFs SD;

// 3 = FAT32
#define SD_FAT_TYPE 3

// SPI-Geschwindigkeit
#define SPI_SPEED SD_SCK_MHZ(4)

/*
  Pinbelegung:
  CIPO -> 19
  COPI -> 23
  SCK  -> 18
  CS   ->  5
*/

// CSPin der SD-Karte
int CSPin = 5;

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

  // auf serielle Verbindung warten
  while (!Serial);
  delay(1000);

  /*
     SD-Karte mit Angabe des CSPins starten
     wenn die Intialisierung fehlschlägt
     - keine SD-Karte vorhanden
     - falsche Pinbelegung
     -> es wird eine Fehlermeldung angezeigt
  */
  if (!SD.begin(CSPin, SPI_SPEED)) 
  {
    Serial.println("Initialisierung fehlgeschlagen!");
  } 
  else Serial.println("Initialisierung abgeschlossen");

  // Dateien im Verzeichnis anzeigen
  Serial.println(SD.ls(LS_DATE | LS_SIZE | LS_R));
}

void loop() 
{
  // bleibt leer, das Programm läuft nur einmal
}

Kon­fi­gu­ra­ti­on

Mikro­con­trol­ler

Benö­tig­te Bibliothek

User_Setup.h

Im Ver­zeich­nis /Arduino/libraries/TFT_eSPI (zu fin­den im aktu­el­len Benut­zer­ver­zeich­nis) muss die Datei User_Setup.h ange­passt werden.

// Treiber TFT
#define ST7796_DRIVER
#define TFT_WIDTH  320
#define TFT_HEIGHT 480
#define TFT_BACKLIGHT_ON HIGH

// SPI-Pins
#define TFT_MISO 12
#define TFT_MOSI 13
#define TFT_SCLK 14
#define TFT_CS   15
#define TFT_DC   2
#define TFT_RST  -1
#define TFT_BL   27

// Farbreihenfolge statt RGB BGR
#define TFT_RGB_ORDER TFT_BGR

// Farben sind invertiert: Schwarz = weiß
// #define TFT_INVERSION_ON

#define TFT_INVERSION_OFF

#define SPI_FREQUENCY  55000000
#define SPI_READ_FREQUENCY  20000000

// Touch
#define TOUCH_IRQ 36
#define TOUCH_MOSI 32
#define TOUCH_MISO 39
#define TOUCH_CLK 25
#define TOUCH_CS 33
#define SPI_TOUCH_FREQUENCY  2500000

// Schriftarten
// Font 2-4: Buchstaben, Zahlen und Satzzeichen, keine Umlaute
// Font 6-8: , und .
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define SMOOTH_FONT
#define LOAD_GFXFF

Touch­screen

Touch­screen kalibrieren

Bei den Touch­screens wer­den resis­ti­ve oder kapa­zi­ti­ve Tech­ni­ken ver­wen­det:
Ein resis­ti­ver Touch­screen reagiert auf Druck mit einem Fin­ger oder einem Stift, bei einem kapa­zi­ti­ver Touch­screen reicht eine Berüh­rung um eine Reak­ti­on aus­zu­füh­ren. In der Regel ist aus Kos­ten­grün­den ein resis­ti­ver Touch­screen verbaut.

Lei­der ver­bau­en die Her­stel­ler unter­schied­li­che Con­trol­ler für den Touch-Screen, daher funk­tio­niert das Pro­gramm nicht mit allen TFT-Modu­len. Beim Modul mit USB-C-Anschluss konn­te ich den Touch-Con­trol­ler der Biblio­thek TFT_eSPI verwenden.

Für jede gewünsch­te Rota­ti­on des Dis­plays muss das Pro­gramm sepe­rat aus­ge­führt werden.

// Original
// https://github.com/Bodmer/TFT_eSPI/blob/master/examples/Generic/Touch_calibrate/Touch_calibrate.ino
#include "TFT_eSPI.h"     

TFT_eSPI tft = TFT_eSPI();

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

  // gewünschte Rotation setzen
  tft.setRotation(0);

  // Kalibrierung starten
  touch_calibrate();

  tft.fillScreen(TFT_BLACK);
  tft.setTextFont(4);
  tft.println("Kalibrierung beendet!");
  tft.println("Verwende den im Seriellen ");
  tft.println("Monitor angezeigem Code.");
}


void loop() 
{
  // Koordinaten
  uint16_t x = 0, y = 0;

  // wenn der Bildschirm berührt wurde
  bool pressed = tft.getTouch(&x, &y);

  // Draw a white spot at the detected coordinates
  if (pressed) 
  {
    tft.fillCircle(x, y, 2, TFT_WHITE);
  }
}

void touch_calibrate()
{
  uint16_t calData[5];
  int calDataOK = 0;

  tft.fillScreen(TFT_BLACK);
  tft.setCursor(20, 0);
  tft.setTextFont(2);

  tft.setTextColor(TFT_WHITE, TFT_BLACK);

  tft.println("Ecken des Bildschirms werden angezeigt");
  tft.println("    Nacheinander auf die markierten Ecken zeigen");
  tft.setTextFont(1);
  tft.println();

  tft.calibrateTouch(calData, TFT_MAGENTA, TFT_BLACK, 15);

  Serial.println(); 
  Serial.println();
  Serial.println("Diese Kalibrierung verwenden:");
  Serial.println("void setup()");
  Serial.println("{");
  Serial.println("  tft.init();");
  Serial.println("  tft.setRotation(..);");
  Serial.print("  uint16_t calData[5] = ");
  Serial.print("{");

  for (int i = 0; i < 5; i++)
  {
    Serial.print(calData[i]);
    if (i < 4) Serial.print(", ");
  }

  Serial.println("};");
  Serial.println("  tft.setTouch(calData);");
  Serial.println("}");
  Serial.println();
}

Nach Abschluss des Pro­gramms wird das Ergeb­nis der Kali­brie­rung im Seri­el­len Moni­tor ange­zeigt.
Je nach­dem, wie genau du die Ecken des Bild­schirms "getrof­fen" hast, wer­den sich die Wer­te unterscheiden.

Touch­screen testen

Du musst die Daten aus der Kali­brie­rung im set­up-Teil eintragen.

#include "TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI();

#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

bool FarbwechselLinkerButton = false;
bool FarbwechselRechterButton = false;

uint16_t x, y;

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

  tft.init();
  tft.setRotation(1);

  // kalibrierte Daten verwenden
  uint16_t calData[5] = { 344, 3356, 242, 3616, 4 };
  tft.setTouch(calData);
  tft.fillScreen(SCHWARZ);

  // linker Button  
  tft.fillRect(50, 100, 100, 50, ROT);
  tft.drawRoundRect(50, 100, 100, 50, 5, WEISS);
  
  // rechter Button
  tft.fillRect(180, 100, 100, 50, GELB);
  tft.drawRoundRect(180, 100, 100, 50, 5, WEISS);
  tft.setTextSize(2);
}

void loop() 
{
  // wenn eine Berührung erkannt wurde
  if (tft.getTouch(&x, &y))
  {
    KoordinatenAnzeigen(x, y, tft.getTouchRawZ());

    delay(100);
    
    // linker Button Koordinaten abfragen
    if (x >= 50 && x <= 150 && y >= 100 && y <= 150) 
    {
      if (FarbwechselLinkerButton) 
      {
        tft.fillRect(50, 100, 100, 50, ROT);
        tft.drawRoundRect(50, 100, 100, 50, 5, WEISS);
      }
      
      else 
      {
        tft.fillRect(50, 100, 100, 50, BLAU);
        tft.drawRoundRect(50, 100, 100, 50, 5, WEISS);
      }
      FarbwechselLinkerButton = !FarbwechselLinkerButton;
    }

    // rechter Button Koordinaten abfragen
    if (x >= 180 && x <= 280 && y >= 100 && y <= 150) 
    {
      if (FarbwechselRechterButton) 
      {
        tft.fillRect(180, 100, 100, 50, MAGENTA);
        tft.drawRoundRect(180, 100, 100, 50, 5, WEISS);
      }

      else 
      {
        tft.fillRect(180, 100, 100, 50, GELB);
        tft.drawRoundRect(180, 100, 100, 50, 5, WEISS);
      }
      FarbwechselRechterButton = !FarbwechselRechterButton;
    }
  }
}

void KoordinatenAnzeigen(int x, int y, int z) 
{
  Serial.print("x-Koordinate = ");
  Serial.print(x);
  Serial.print(" | y-Koordinate = ");
  Serial.print(y);
  Serial.print(" | Druck = ");
  Serial.print(z);
  Serial.println();

  tft.fillRect(1, 1, tft.width(), 90, SCHWARZ);
  tft.setCursor(10, 10);

  // interne Schrift Größe 2
  tft.setTextFont(2);
  tft.println("x-Koordinate: " + String(x));
  tft.setCursor(10, 40);
  tft.print("y-Koordinate: " + String(y));
}

Gra­fi­sche Funktionen

Bei­spiel

#include "TFT_eSPI.h"

TFT_eSPI tft = TFT_eSPI();

// 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

void setup() 
{
  tft.init();
}

void loop()
{
  tft.fillScreen(SCHWARZ);

  tft.setTextSize(1);
  tft.setCursor(1, 5);
  tft.setTextColor(BLAU);
  tft.print("Text");
  delay(500);

  tft.setTextSize(2);
  tft.setCursor(1, 20);
  tft.setTextColor(ORANGE);
  tft.print("Text");
  delay(500);

  tft.setTextSize(3);
  tft.setCursor(1, 40);
  tft.setTextColor(GRUEN);
  tft.print("Text");
  delay(500);

  tft.setTextSize(5);
  tft.setCursor(1, 70);
  tft.setTextColor(ROT);
  tft.print("Text");
  delay(500);

  delay(2000);

  // zufällige Pixel
  tft.fillScreen(SCHWARZ);
  for  (int i = 0; i < 700; i++)
  {
    int PixelX = random(1, tft.width());
    int PixelY = random(1, tft.height());
    tft.drawPixel(PixelX, PixelY, tft.color565(random(255),random(255),random(255)));
    delay(5);
  }
  delay(2000);

  // Linien ziehen
  tft.fillScreen(SCHWARZ);
  for (int i = 1; i < tft.height(); i+=10)
  {
    tft.drawLine(1, i, tft.width(), i, ORANGE);
  }
  delay(2000);

  // Kreise vom Mittelpunkt zeichnen
  tft.fillScreen(SCHWARZ);
  for (int i = 1; i < tft.width() / 2; i+=10)
  {
    tft.fillCircle(tft.width() / 2, tft.height() / 2, tft.width() / 2 - i, tft.color565(random(255),random(255),random(255)));
    delay(50);
  }
  delay(2000);

  // Rechtecke zeichnen
  tft.fillScreen(SCHWARZ);
  for (int i = 1; i < tft.width(); i+=10)
  {
    tft.drawRect(tft.width() / 2 - i / 2, tft.height() / 2 - i / 2 , i, i, tft.color565(random(255),random(255),random(255)));
  }
  delay(2000);

   // ausgefüllte Rechtecke zeichnen
  tft.fillScreen(SCHWARZ);
  for (int i = 1; i < tft.width() / 2; i+=10)
  {
    tft.fillRect(i, i, i, i, tft.color565(random(ROT),random(GRUEN),random(BLAU)));
    delay(50);
  }
  delay(2000);

  // Dreiecke
  tft.fillScreen(SCHWARZ);
  for (int i = 1; i <tft.width(); i+=10)
  {
    tft.fillTriangle(i, i, 100, 100, 1, tft.width(), tft.color565(random(255),random(255),random(255)));
    delay(50);
  }
}

Bei­spiel­pro­gram­me

Tem­pe­ra­tur­an­zei­ge mit BMP280

Modul mit Mini-USB-Anschluss

grün -> GND
gelb -> SDA
schwarz -> SCL
rot -> VCC

Modul mit USB-C-Anschluss

rot -> VCC
schwarz -> SDA
gelb -> SCL
grün -> GND

Die HEX-Adres­se des BMP280 kannst du mit einem Pro­gramm her­aus­fin­den.
Du musst die SDA- und SCL-Pins anpas­sen. Die ange­zeig­te Adres­se musst du dem Auf­ruf von bmp.begin inner­halb der Klam­mern übergeben.

#include "Wire.h"

// Pins anpassen
// Mini-USB-Anschluss
// #define SDA_PIN 22
// #define SCL_PIN 21

// USB-C-Anschluss
#define SDA_PIN 25
#define SCL_PIN 32

void setup()
{
  Wire.begin(SDA_PIN, SCL_PIN);
  Serial.begin(9600);
  delay(500);
  Serial.print("I2C Scanner");
}

void loop()
{
  byte Fehler, Adresse;
  int Geraete = 0;
  Serial.println("Starte Scanvorgang");

  for (Adresse = 1; Adresse < 127; Adresse++ )
  {
    // Übertragung starten
    Wire.beginTransmission(Adresse);

    // wenn die Übertragung beendet wird
    Fehler = Wire.endTransmission();

    if (Fehler == 0)
    {
      Serial.print("I2C Gerät gefunden - Adresse: 0x");
      if (Adresse < 16) Serial.print("0");
      Serial.print(Adresse, HEX);
      Serial.println("");
      Geraete++;
    }
  }
  if (Geraete == 0) Serial.println("Keine I2C Geräte gefunden\n");
  else Serial.println("Scanvorgang abgeschlossen");

  delay(10000);
}

Die im Pro­gramm ver­wen­de­te Biblio­thek U8g2_for_TFT_eSPI kann nicht über die Biblio­theks­ver­wal­tung instal­liert wer­den, sie muss heruntergeladen

https://​git​hub​.com/​B​o​d​m​e​r​/​U​8​g​2​_​f​o​r​_​T​F​T​_​e​SPI

und mit

Sketch -> Biblio­thek ein­bin­den -> zip-Biblio­­thek hinzufügen

instal­liert werden.

Zusätz­lich benö­tig­te Bibliothek:

#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"

// Objekt für Schriften von U8g2 (u8g2Schriften)
U8g2_for_TFT_eSPI u8g2Schriften;

TFT_eSPI tft = TFT_eSPI();

#include "Adafruit_BMP280.h"

Adafruit_BMP280 bmp;

// Pins anpassen
// Mini-USB-Anschluss
#define SDA_PIN 22
#define SCL_PIN 21

// USB-C-Anschluss
// define SDA_PIN 25
// #define SCL_PIN 32

// 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

void setup() 
{
  // Sensor starten
  Wire.begin(SDA_PIN, SCL_PIN);

  // bmp.begin(0x77);
  bmp.begin(0x76);

  tft.init();
  tft.setRotation(1);
  tft.fillScreen(SCHWARZ);

  // Schriften von u8g2 tft zuordnen
  u8g2Schriften.begin(tft); 
}

void loop()
{
  DatenAnzeigen();
  delay(10000);
}

void DatenAnzeigen()
{
  // Temperatur lesen 
  String Temperatur = String(bmp.readTemperature());

  // replace -> . durch , ersetzen
  Temperatur.replace(".", ",");

  String Luftdruck = String(bmp.readPressure() / 100);
  Luftdruck.replace(".", ",");

  // Bereich für die Messwerte löschen
  tft.fillRect(1, 40, tft.width(), 200, SCHWARZ);

  u8g2Schriften.setCursor(10, 100);
  u8g2Schriften.setForegroundColor(BLAU);   
  u8g2Schriften.setFont(u8g2_font_logisoso58_tf);   
  u8g2Schriften.print(Luftdruck + " hPa");

  u8g2Schriften.setCursor(10, 250);
  u8g2Schriften.setForegroundColor(ROT);
  u8g2Schriften.print(Temperatur + "°C");
}

Tem­pe­ra­tur­an­zei­ge DHT mit Touch-Bedienung

(nur Modul mit USB-C-Anschluss)

Bit­maps erstellen

#include "TFT_eSPI.h"
TFT_eSPI tft = TFT_eSPI();
#include "U8g2_for_TFT_eSPI.h"
#include "DHT.h"

// Objekt für Schriften von U8g2 (u8g2Schriften)
U8g2_for_TFT_eSPI u8g2Schriften;

#include "DHT.h"

// Pin anpassen
int SENSOR_DHT = 25;

#define SensorTyp DHT22

// Sensor einen Namen zuweisen
DHT dht(SENSOR_DHT, SensorTyp); 

#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 Messwerte/Piktogramm
#define FarbeLuftfeuchtigkeit BLAU
#define FarbeTemperatur ROT

// Arrays Piktogramme
const unsigned char Thermometer [] PROGMEM = {
	// 'Thermometer, 34x70px
	0x00, 0x07, 0xf0, 0x00, 0x00, 0x00, 0x0f, 0xfc, 0x00, 0x00, 0x00, 0x3f, 0xfe, 0x00, 0x00, 0x00, 
	0x7c, 0x0f, 0x00, 0x00, 0x00, 0x70, 0x07, 0x80, 0x00, 0x00, 0xe0, 0x03, 0xc0, 0x00, 0x00, 0xe0, 
	0x01, 0xc0, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x01, 0xc0, 0x01, 
	0xc0, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x0f, 0xc0, 0x01, 0xc0, 0x00, 0x0f, 0xc0, 0x01, 0xc0, 
	0x00, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x00, 
	0x00, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x00, 0x0f, 0xc0, 0x01, 0xc0, 0x00, 0x0f, 
	0xc0, 0x01, 0xc0, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc0, 
	0x01, 0xc0, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc0, 0x01, 0xc0, 0x00, 0x0f, 0xc0, 0x01, 
	0xc0, 0x00, 0x0f, 0xc0, 0x01, 0xc0, 0x00, 0x01, 0xc0, 0x01, 0xc0, 0x00, 0x00, 0xc3, 0xe1, 0xc0, 
	0x00, 0x00, 0xc3, 0xf1, 0xc0, 0x00, 0x00, 0xc3, 0xf1, 0xc0, 0x00, 0x0f, 0xc3, 0xf1, 0xc0, 0x00, 
	0x0f, 0xc3, 0xf1, 0xc0, 0x00, 0x0f, 0xc3, 0xf1, 0xc0, 0x00, 0x00, 0xc3, 0xf1, 0xc0, 0x00, 0x00, 
	0xc3, 0xf1, 0xc0, 0x00, 0x00, 0xc3, 0xf1, 0xc0, 0x00, 0x00, 0xc3, 0xf1, 0xc0, 0x00, 0x00, 0xc3, 
	0xf1, 0xc0, 0x00, 0x00, 0xc3, 0xf1, 0xc0, 0x00, 0x03, 0xc3, 0xf1, 0xf0, 0x00, 0x07, 0xc3, 0xf0, 
	0xf8, 0x00, 0x0f, 0x03, 0xf0, 0x7c, 0x00, 0x0e, 0x03, 0xe0, 0x3c, 0x00, 0x1c, 0x07, 0xf0, 0x1e, 
	0x00, 0x3c, 0x1f, 0xfc, 0x0f, 0x00, 0x38, 0x3f, 0xfe, 0x0f, 0x00, 0x78, 0x7f, 0xff, 0x07, 0x80, 
	0x70, 0x7f, 0xff, 0x87, 0x80, 0x70, 0x7f, 0xff, 0x83, 0x80, 0xf0, 0xff, 0xff, 0x83, 0xc0, 0xf0, 
	0xff, 0xff, 0xc3, 0xc0, 0xf0, 0xff, 0xff, 0xc3, 0xc0, 0xf0, 0xff, 0xff, 0xc3, 0xc0, 0xf0, 0xff, 
	0xff, 0xc3, 0xc0, 0xf0, 0xff, 0xff, 0x83, 0xc0, 0x70, 0x7f, 0xff, 0x83, 0x80, 0x70, 0x7f, 0xff, 
	0x87, 0x80, 0x78, 0x3f, 0xff, 0x07, 0x80, 0x38, 0x3f, 0xfe, 0x0f, 0x00, 0x3c, 0x0f, 0xfc, 0x0f, 
	0x00, 0x1e, 0x03, 0xe0, 0x1e, 0x00, 0x1f, 0x00, 0x00, 0x3e, 0x00, 0x0f, 0x00, 0x00, 0x7c, 0x00, 
	0x07, 0xe0, 0x01, 0xf8, 0x00, 0x03, 0xf8, 0x07, 0xf0, 0x00, 0x00, 0xff, 0xff, 0xc0, 0x00, 0x00, 
	0x7f, 0xff, 0x80, 0x00, 0x00, 0x1f, 0xfe, 0x00, 0x00, 0x00, 0x03, 0xf0, 0x00, 0x00
};

const unsigned char Regen [] PROGMEM = {
	// 'Regen, 60x49px
	0x00, 0x00, 0x00, 0x08, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x02, 0x08, 0x21, 0x04, 0x10, 0x00, 0x00, 0x00, 0x06, 0x18, 0x63, 0x0c, 0x30, 0x80, 0x00, 
	0x00, 0x06, 0x10, 0x63, 0x0c, 0x31, 0x80, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x20, 0x84, 0x10, 0x41, 0x08, 0x20, 0x00, 0x00, 0x61, 0x8c, 0x30, 0xc3, 0x18, 0x60, 0x00, 
	0x08, 0xc1, 0x8c, 0x30, 0xc3, 0x18, 0x61, 0x00, 0x00, 0x41, 0x04, 0x20, 0x82, 0x08, 0x40, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x30, 0x86, 0x18, 0x43, 0x04, 0x30, 0x80, 
	0x18, 0x71, 0x86, 0x18, 0xc3, 0x0c, 0x31, 0x80, 0x18, 0x21, 0x84, 0x10, 0xc3, 0x0c, 0x21, 0x80, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x82, 0x18, 0x21, 0x04, 0x10, 0x42, 0x18, 0x40, 
	0x86, 0x18, 0x63, 0x0c, 0x30, 0xc6, 0x18, 0xc0, 0x86, 0x10, 0x63, 0x0c, 0x30, 0xc6, 0x18, 0xc0, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x18, 0x21, 0x04, 0x10, 0xc2, 0x0c, 0x21, 0x00, 0x30, 0x63, 0x0c, 0x31, 0xc6, 0x0c, 0x63, 0x00, 
	0x30, 0x63, 0x0c, 0x31, 0x86, 0x18, 0x63, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x06, 0x10, 0x43, 0x08, 0x20, 0x86, 0x10, 0x00, 0x06, 0x30, 0xc3, 0x18, 0x61, 0x86, 0x30, 0x00, 
	0x04, 0x30, 0xc2, 0x18, 0x61, 0x8c, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x08, 0x00, 0x80, 0x00, 0x00, 0x0c, 0x30, 0xc6, 0x18, 0x61, 0x00, 0x00, 
	0x00, 0x00, 0x61, 0xc6, 0x18, 0x60, 0x00, 0x00, 0x00, 0x00, 0x60, 0x86, 0x10, 0x60, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

uint16_t x, y;

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

  // Sensor starten
  dht.begin();

  tft.init();
  tft.setRotation(1);
  
  // kalibrierter Touchscreen
  uint16_t calData[5] = { 262, 3608, 244, 3481, 7 };
  tft.setTouch(calData);
  tft.fillScreen(SCHWARZ);

  // Schriften von u8g2 tft zuordnen
  u8g2Schriften.begin(tft); 
  
  // Button
  tft.fillRect(20, 220, 330, 70, ROT);
  tft.drawRoundRect(20, 220, 330, 70, 5, WEISS);
  tft.drawRoundRect(19, 219, 329, 69, 5, WEISS);
  u8g2Schriften.setCursor(30, 268);
  u8g2Schriften.setForegroundColor(WEISS);
  u8g2Schriften.setBackgroundColor(ROT);
  u8g2Schriften.setFont(u8g2_font_fub35_tf);
  u8g2Schriften.print("Aktualisieren");
  
  /*
    Piktogramme anzeigen
    Parameter: 
    Position x-/y-Koordinate
    Name des Arrays
    Bildgröße in Pixel x/y
    Farbe
  */
  tft.drawBitmap(1, 30, Regen, 60, 49, FarbeLuftfeuchtigkeit);
  tft.drawBitmap(10, 100, Thermometer, 34, 70, FarbeTemperatur);

  TemperaturAnzeigen();
}

void loop() 
{
  // wenn eine Berührung erkannt wurde
  if (tft.getTouch(&x, &y))
  {
    //KoordinatenAnzeigen(x, y, tft.getTouchRawZ());
     
     if (x >= 20 && x <= 350 && y >= 220 && y <= 300) 
    {
      TemperaturAnzeigen();
      delay(100);
    }
  }
}

void TemperaturAnzeigen()
{
  // Temperatur lesen 
  String Temperatur = String(dht.readTemperature());

  // replace -> . durch , ersetzen
  Temperatur.replace(".", ",");

  // Luftfeuchtigkeit lesen 
  String Luftfeuchtigkeit = String(dht.readHumidity());

  // replace -> . durch , ersetzen
  Luftfeuchtigkeit.replace(".", ",");

  // Bereich für die Messwerte löschen
  tft.fillRect(65, 10, 270, 150, SCHWARZ);

  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(70, 80);
  u8g2Schriften.setForegroundColor(FarbeLuftfeuchtigkeit);   
  u8g2Schriften.setFont(u8g2_font_logisoso58_tf);   
  u8g2Schriften.print(Luftfeuchtigkeit + "%");

  u8g2Schriften.setCursor(70, 160);
  u8g2Schriften.setForegroundColor(FarbeTemperatur);   
  u8g2Schriften.setForegroundColor(FarbeTemperatur);
  u8g2Schriften.print(Temperatur + "°C");
}

void KoordinatenAnzeigen(int x, int y, int z) 
{
  Serial.print("x-Koordinate = ");
  Serial.print(x);
  Serial.print(" | y-Koordinate = ");
  Serial.print(y);
  Serial.print(" | Druck = ");
  Serial.print(z);
  Serial.println();
}

Ana­lo­ge Uhr

Das Pro­gramm kann 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 anzeigen
  • 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
  • Das Pro­gramm ver­wen­det für die Anzei­ge des Datums die Schrift­ar­ten von ⇒u8g2.
    Eini­ge Bei­spie­le für Schrif­ten:
    14pt: u8g2_font_luRS14_tf
    16pt: u8g2_fnt_logisoso16_tf
    18pt: u8g2_font_luBS18_tf
    20pt: u8g2_font_fub20_tf
    22pt: u8g2_font_logisoso22_tf
    24pt: u8g2_font_helvB24_tf
    26pt: u8g2_font_logisoso26_tf
    28pt: u8g2_font_logisoso28_tf
    Die Schrif­ten wer­den mit setFont(Name_der_Schrift) definiert.

Beim Start des Pro­gramms zei­gen die Mel­dun­gen ob Datum und Zeit kor­rekt sind. Wenn in 90 Sekun­den kei­ne Ver­bin­dung zu einem Zeit­ser­ver her­ge­stellt wer­den konn­te, wird das Pro­gramm been­det. Nach einem erneu­ten Hoch­la­den kommt zumeist die Ver­bin­dung schnell zustande.

#include "WiFi.h"
#include "time.h"
#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"

// Objekt für Schriften von U8g2 (u8g2Schriften)
U8g2_for_TFT_eSPI u8g2Schriften;

TFT_eSPI tft = TFT_eSPI();

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

// Variablen des TFTs (Höhe, Breite, Radius)
const int MitteHoehe = 160;
const int MitteBreite = 160;
const int Radius = 160;

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

unsigned long Zeitmessung = 0;  

// 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 = false;

// 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 = Sommerzeit (dst = daylight saving time)
*/
tm Zeit;

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

  // Schriften von u8g2 tft zuordnen
  u8g2Schriften.begin(tft);

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

  WiFi.mode(WIFI_STA);
  
  // WiFi starten
  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());

  // Zeit holen
  time(&aktuelleZeit);

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

  // beim Start entspricht das Datum der Unixtime: 1.1.1970
  // Datum/Kalender sollen erst angezeigt werden, wenn das Datum korrekt ist
  String Jahr = String(Zeit.tm_year + 1900); 
  int Zaehler = 0;

  // String Jahr nach "1970" durchsuchen
  int Suche = Jahr.indexOf("1970");         

  Serial.println("-------------------------"); 
  Serial.println("Datum und Zeit holen (maximal 90 Sekunden)...");
  
  // solange die Suche nicht erfolgreich ist
  while (Suche != -1) 
  {
    // aktuelle Zeit holen
    time(&aktuelleZeit);

    // localtime_r -> Zeit in die lokale Zeitzone setzen
    localtime_r(&aktuelleZeit, &Zeit);
    Jahr = String(Zeit.tm_year + 1900); 
    
    // String Jahr nach "1970" durchsuchen
    Suche = Jahr.indexOf("1970");

    // Zeit in Stunden, Minuten und Sekunden
    Stunden = int(Zeit.tm_hour), Minuten = int(Zeit.tm_min), Sekunden = int(Zeit.tm_sec);
    delay(1000);
    Zaehler ++;

    if (Zaehler >= 90)
    {
      Serial.println();
      Serial.println("Datum und Zeit konnte innerhalb von " + String(Zaehler) + " Sekunden nicht geholt werden");
      Serial.println("Programm wird beendet");

      // Programm beenden
      while(1);
    }

    Serial.print(".");
  }   

  Serial.println();

  // Datum/Zeit erfolgreich synchronisiert
  if (Suche == -1) 
  {
    Serial.println("-------------------------");
    Serial.println("Datum/Zeit erfolgreich synchronisiert ...");
    
    if (Zeit.tm_mday < 10) Serial.print("0");
    Serial.print(Zeit.tm_mday);
    Serial.print(".");

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

    // Anzahl Jahre seit 1900
    Serial.println(Zeit.tm_year + 1900);

    if (Zeit.tm_hour < 10) Serial.print("0");
    Serial.print(Zeit.tm_hour);
    Serial.print(":");
      
    if (Zeit.tm_min < 10) Serial.print("0");
    Serial.println(Zeit.tm_min);
    Serial.println("-------------------------");
  }

  // 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 * 155 + Radius;
    int PunktY1 = PosY * 155 + Radius;
    int PunktX2 = PosX * 145 + Radius;
    int PunktY2 = PosY * 145 + Radius;
    tft.drawLine(PunktX1, PunktY1, PunktX2, PunktY2, Zeigerfarbe);
    
    // keine Striche an der Position der Zahlen
    if (Ziffernanzeigen)
    {

      if (PunktX1 == 160 || PunktX1 == 315 || PunktX1 == 5)
      {
        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 * 150 + Radius;
    PunktY = PosY * 150 + Radius;
    tft.drawPixel(PunktX, PunktY, Zeigerfarbe);  
  }

  // Markierung 12 3 6 9
  if (Ziffernanzeigen)
  {
    tft.setTextSize(2);
    tft.setTextColor(Zeigerfarbe);
    tft.setCursor(145, 10);
    tft.print("12");
    
    tft.setCursor(10, 153);
    tft.print("9");
    
    tft.setCursor(300, 153);
    tft.print("3");
  
    tft.setCursor(155, 300);
    tft.print("6");
  }

  if (DatumAnzeigen)
  {
    ZeigeDatum();
  }

  Zeitmessung = millis() + 1000; 
}

void loop() 
{
  // Sekunden weiter zählen
  if (Zeitmessung < millis()) 
  {
    Zeitmessung += 1000;
    Sekunden++;  
  
    if (Sekunden == 60) 
    { 
      // Zeit jede Minute mit Zeitserver synchronisieren
      // aktuelle Zeit holen
      time(&aktuelleZeit);

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

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

      // Mitternacht 
      // -> Wechsel des Datums anzeigen
      if (Stunden == 0 && Minuten == 0) 
      {
        if (DatumAnzeigen) ZeigeDatum();
      }
      
      if (Minuten > 59) 
      {
        Minuten = 0;
        Stunden++;  
        if (Stunden > 23) 
        {
          Stunden = 0;
        }
      }
    }

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

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

      // 125 Pixel -> Länge des Minutenzeigers
      // Mittelpunkt + 1 -> Mittelpunkt soll nicht gelöscht werden
      MinutenZeigerX = MinutePosX * 125 + MitteHoehe;
      MinutenZeigerY = MinutePosY * 125 + 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);

    // 125 Pixel -> Länge des Sekundenzeigers
    SekundenZeigerX = SekundePosX * 125 + MitteHoehe + 1;
    SekundenZeigerY = SekundePosY * 125 + 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()
{
  u8g2Schriften.setForegroundColor(GRUEN);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 380);
  u8g2Schriften.setFont(u8g2_font_fub35_tf);
      
  // Bildschirmbereich für das Datum löschen
  tft.fillRect(10, 320, tft.width(), tft.height(), SCHWARZ);

   // Wochentag anzeigen
  switch (Zeit.tm_wday) 
  {
    case 0:
      u8g2Schriften.print("Sonntag");
      break;
    case 1:
      u8g2Schriften.print("Montag");
      break;
    case 2:
      u8g2Schriften.print("Dienstag");
      break;
    case 3:
      u8g2Schriften.print("Mittwoch");
      break;
    case 4:
      u8g2Schriften.print("Donnerstag");
      break;
    case 5:
      u8g2Schriften.print("Freitag");
      break;
    case 6:
      u8g2Schriften.print("Samstag");
      break;
  }

  u8g2Schriften.setCursor(10, 450);
  if (Zeit.tm_mday < 10) u8g2Schriften.print("0");
  u8g2Schriften.print(Zeit.tm_mday);
  u8g2Schriften.print(".");

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

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

Ana­lo­ge Uhr Tem­pe­ra­tur­an­zei­ge mit DHT

#include "WiFi.h"
#include "time.h"
#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"
#include "DHT.h"

int SENSOR_DHT = 22;

#define SensorTyp DHT22

// Sensor einen Namen zuweisen
DHT dht(SENSOR_DHT, SensorTyp); 

// Objekt für Schriften von U8g2 (u8g2Schriften)
U8g2_for_TFT_eSPI u8g2Schriften;

TFT_eSPI tft = TFT_eSPI();

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

// Variablen des TFTs (Höhe, Breite, Radius)
const int MitteHoehe = 160;
const int MitteBreite = 160;
const int Radius = 160;

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

unsigned long Zeitmessung = 0;  

// 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 = false;

// 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 = Sommerzeit (dst = daylight saving time)
*/
tm Zeit;

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

  // Sensor starten
  dht.begin();

  // Schriften von u8g2 tft zuordnen
  u8g2Schriften.begin(tft);

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

  WiFi.mode(WIFI_STA);
  
  // WiFi starten
  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());

  // Zeit holen
  time(&aktuelleZeit);

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

  // beim Start entspricht das Datum der Unixtime: 1.1.1970
  // Datum/Kalender sollen erst angezeigt werden, wenn das Datum korrekt ist
  String Jahr = String(Zeit.tm_year + 1900); 
  int Zaehler = 0;

  // String Jahr nach "1970" durchsuchen
  int Suche = Jahr.indexOf("1970");         

  Serial.println("-------------------------"); 
  Serial.println("Datum und Zeit holen (maximal 90 Sekunden)...");
  
  // solange die Suche nicht erfolgreich ist
  while (Suche != -1) 
  {
    // aktuelle Zeit holen
    time(&aktuelleZeit);

    // localtime_r -> Zeit in die lokale Zeitzone setzen
    localtime_r(&aktuelleZeit, &Zeit);
    Jahr = String(Zeit.tm_year + 1900); 
    
    // String Jahr nach "1970" durchsuchen
    Suche = Jahr.indexOf("1970");

    // Zeit in Stunden, Minuten und Sekunden
    Stunden = int(Zeit.tm_hour), Minuten = int(Zeit.tm_min), Sekunden = int(Zeit.tm_sec);
    delay(1000);
    Zaehler ++;

    if (Zaehler >= 90)
    {
      Serial.println();
      Serial.println("Datum und Zeit konnte innerhalb von " + String(Zaehler) + " Sekunden nicht geholt werden");
      Serial.println("Programm wird beendet");

      // Programm beenden
      while(1);
    }

    Serial.print(".");
  }   

  Serial.println();

  // Datum/Zeit erfolgreich synchronisiert
  if (Suche == -1) 
  {
    Serial.println("-------------------------");
    Serial.println("Datum/Zeit erfolgreich synchronisiert ...");
    
    if (Zeit.tm_mday < 10) Serial.print("0");
    Serial.print(Zeit.tm_mday);
    Serial.print(".");

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

    // Anzahl Jahre seit 1900
    Serial.println(Zeit.tm_year + 1900);

    if (Zeit.tm_hour < 10) Serial.print("0");
    Serial.print(Zeit.tm_hour);
    Serial.print(":");
      
    if (Zeit.tm_min < 10) Serial.print("0");
    Serial.println(Zeit.tm_min);
    Serial.println("-------------------------");
  }

  // 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 * 155 + Radius;
    int PunktY1 = PosY * 155 + Radius;
    int PunktX2 = PosX * 145 + Radius;
    int PunktY2 = PosY * 145 + Radius;
    tft.drawLine(PunktX1, PunktY1, PunktX2, PunktY2, Zeigerfarbe);
    
    // keine Striche an der Position der Zahlen
    if (Ziffernanzeigen)
    {

      if (PunktX1 == 160 || PunktX1 == 315 || PunktX1 == 5)
      {
        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 * 150 + Radius;
    PunktY = PosY * 150 + Radius;
    tft.drawPixel(PunktX, PunktY, Zeigerfarbe);  
  }

  // Markierung 12 3 6 9
  if (Ziffernanzeigen)
  {
    tft.setTextSize(2);
    tft.setTextColor(Zeigerfarbe);
    tft.setCursor(145, 10);
    tft.print("12");
    
    tft.setCursor(10, 153);
    tft.print("9");
    
    tft.setCursor(300, 153);
    tft.print("3");
  
    tft.setCursor(155, 300);
    tft.print("6");
  }

  if (DatumAnzeigen)
  {
    ZeigeDatum();
  }

  Zeitmessung = millis() + 1000; 
}

void loop() 
{
  // Sekunden weiter zählen
  if (Zeitmessung < millis()) 
  {
    Zeitmessung += 1000;
    Sekunden++;  
  
    if (Sekunden == 60) 
    { 
      // Zeit jede Minute mit Zeitserver synchronisieren
      // aktuelle Zeit holen
      time(&aktuelleZeit);

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

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

      // Mitternacht 
      // -> Wechsel des Datums anzeigen
      if (Stunden == 0 && Minuten == 0) 
      {
        Serial.println("neues Datum");
        if (DatumAnzeigen) 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) 
    {
      TemperaturAnzeigen();

      Start = false;

      tft.drawLine(StundenZeigerX, StundenZeigerY, MitteHoehe, MitteHoehe + 1, Kreisfarbe);  

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

      // 120 Pixel -> Länge des Minutenzeigers
      // Mittelpunkt + 1 -> Mittelpunkt soll nicht gelöscht werden
      MinutenZeigerX = MinutePosX * 120 + MitteHoehe;
      MinutenZeigerY = MinutePosY * 120 + 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);

    // 125 Pixel -> Länge des Sekundenzeigers
    SekundenZeigerX = SekundePosX * 125 + MitteHoehe + 1;
    SekundenZeigerY = SekundePosY * 125 + 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()
{
  u8g2Schriften.setForegroundColor(GRUEN);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 350);
  u8g2Schriften.setFont(u8g2_font_fub20_tf);
      
  // Bildschirmbereich für das Datum löschen
  tft.fillRect(10, 320, tft.width(), tft.height(), SCHWARZ);

   // Wochentag anzeigen
  switch (Zeit.tm_wday) 
  {
    case 0:
      u8g2Schriften.print("Sonntag");
      break;
    case 1:
      u8g2Schriften.print("Montag");
      break;
    case 2:
      u8g2Schriften.print("Dienstag");
      break;
    case 3:
      u8g2Schriften.print("Mittwoch");
      break;
    case 4:
      u8g2Schriften.print("Donnerstag");
      break;
    case 5:
      u8g2Schriften.print("Freitag");
      break;
    case 6:
      u8g2Schriften.print("Samstag");
      break;
  }

  u8g2Schriften.print(", ");
  if (Zeit.tm_mday < 10) u8g2Schriften.print("0");
  u8g2Schriften.print(Zeit.tm_mday);
  u8g2Schriften.print(".");

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

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

void TemperaturAnzeigen()
{
  // Temperatur lesen 
  String Temperatur = String(dht.readTemperature());

  // replace -> . durch , ersetzen
  Temperatur.replace(".", ",");

  // Luftfeuchtigkeit lesen 
  String Luftfeuchtigkeit = String(dht.readHumidity());

  // replace -> . durch , ersetzen
  Luftfeuchtigkeit.replace(".", ",");

  // Anzeigebereich löschen
  tft.fillRect(1, 365, tft.width(), tft.height(), SCHWARZ);
  u8g2Schriften.setForegroundColor(BLAU);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 405);
  u8g2Schriften.setFont(u8g2_font_fub35_tf);
  u8g2Schriften.print(Luftfeuchtigkeit + "%");

  u8g2Schriften.setCursor(10, 470);
  u8g2Schriften.setForegroundColor(ROT);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);

  u8g2Schriften.print(Temperatur + "°C");
}

Ana­lo­ge Uhr Tem­pe­ra­tur­an­zei­ge mit BMP280

#include "WiFi.h"
#include "time.h"
#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"
#include "Adafruit_BMP280.h"

Adafruit_BMP280 bmp;

// Pins anpassen
// Mini-USB-Anschluss
#define SDA_PIN 22
#define SCL_PIN 21

// USB-C-Anschluss
// define SDA_PIN 25
// #define SCL_PIN 32

// Objekt für Schriften von U8g2 (u8g2Schriften)
U8g2_for_TFT_eSPI u8g2Schriften;

TFT_eSPI tft = TFT_eSPI();

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

// Variablen des TFTs (Höhe, Breite, Radius)
const int MitteHoehe = 160;
const int MitteBreite = 160;
const int Radius = 160;

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

unsigned long Zeitmessung = 0;  

// 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 = false;

// 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 = Sommerzeit (dst = daylight saving time)
*/
tm Zeit;

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

  // Wire mit I2C-Pins starten
  Wire.begin(SDA_PIN, SCL_PIN);

  // bmp.begin(0x77);
  bmp.begin(0x76);

  // Schriften von u8g2 tft zuordnen
  u8g2Schriften.begin(tft);

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

  WiFi.mode(WIFI_STA);
  
  // WiFi starten
  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());

  // Zeit holen
  time(&aktuelleZeit);

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

  // beim Start entspricht das Datum der Unixtime: 1.1.1970
  // Datum/Kalender sollen erst angezeigt werden, wenn das Datum korrekt ist
  String Jahr = String(Zeit.tm_year + 1900); 
  int Zaehler = 0;

  // String Jahr nach "1970" durchsuchen
  int Suche = Jahr.indexOf("1970");         

  Serial.println("-------------------------"); 
  Serial.println("Datum und Zeit holen (maximal 90 Sekunden)...");
  
  // solange die Suche nicht erfolgreich ist
  while (Suche != -1) 
  {
    // aktuelle Zeit holen
    time(&aktuelleZeit);

    // localtime_r -> Zeit in die lokale Zeitzone setzen
    localtime_r(&aktuelleZeit, &Zeit);
    Jahr = String(Zeit.tm_year + 1900); 
    
    // String Jahr nach "1970" durchsuchen
    Suche = Jahr.indexOf("1970");

    // Zeit in Stunden, Minuten und Sekunden
    Stunden = int(Zeit.tm_hour), Minuten = int(Zeit.tm_min), Sekunden = int(Zeit.tm_sec);
    delay(1000);
    Zaehler ++;

    if (Zaehler >= 90)
    {
      Serial.println();
      Serial.println("Datum und Zeit konnte innerhalb von " + String(Zaehler) + " Sekunden nicht geholt werden");
      Serial.println("Programm wird beendet");

      // Programm beenden
      while(1);
    }

    Serial.print(".");
  }   

  Serial.println();

  // Datum/Zeit erfolgreich synchronisiert
  if (Suche == -1) 
  {
    Serial.println("-------------------------");
    Serial.println("Datum/Zeit erfolgreich synchronisiert ...");
    
    if (Zeit.tm_mday < 10) Serial.print("0");
    Serial.print(Zeit.tm_mday);
    Serial.print(".");

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

    // Anzahl Jahre seit 1900
    Serial.println(Zeit.tm_year + 1900);

    if (Zeit.tm_hour < 10) Serial.print("0");
    Serial.print(Zeit.tm_hour);
    Serial.print(":");
      
    if (Zeit.tm_min < 10) Serial.print("0");
    Serial.println(Zeit.tm_min);
    Serial.println("-------------------------");
  }

  // 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 * 155 + Radius;
    int PunktY1 = PosY * 155 + Radius;
    int PunktX2 = PosX * 145 + Radius;
    int PunktY2 = PosY * 145 + Radius;
    tft.drawLine(PunktX1, PunktY1, PunktX2, PunktY2, Zeigerfarbe);
    
    // keine Striche an der Position der Zahlen
    if (Ziffernanzeigen)
    {

      if (PunktX1 == 160 || PunktX1 == 315 || PunktX1 == 5)
      {
        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 * 150 + Radius;
    PunktY = PosY * 150 + Radius;
    tft.drawPixel(PunktX, PunktY, Zeigerfarbe);  
  }

  // Markierung 12 3 6 9
  if (Ziffernanzeigen)
  {
    tft.setTextSize(2);
    tft.setTextColor(Zeigerfarbe);
    tft.setCursor(145, 10);
    tft.print("12");
    
    tft.setCursor(10, 153);
    tft.print("9");
    
    tft.setCursor(300, 153);
    tft.print("3");
  
    tft.setCursor(155, 300);
    tft.print("6");
  }

  if (DatumAnzeigen)
  {
    ZeigeDatum();
  }

  Zeitmessung = millis() + 1000; 
}

void loop() 
{
  // Sekunden weiter zählen
  if (Zeitmessung < millis()) 
  {
    Zeitmessung += 1000;
    Sekunden++;  
  
    if (Sekunden == 60) 
    { 
      // Zeit jede Minute mit Zeitserver synchronisieren
      // aktuelle Zeit holen
      time(&aktuelleZeit);

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

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

      // Mitternacht 
      // -> Wechsel des Datums anzeigen
      if (Stunden == 0 && Minuten == 0) 
      {
        Serial.println("neues Datum");
        if (DatumAnzeigen) 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) 
    {
      TemperaturAnzeigen();

      Start = false;

      tft.drawLine(StundenZeigerX, StundenZeigerY, MitteHoehe, MitteHoehe + 1, Kreisfarbe);  

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

      // 120 Pixel -> Länge des Minutenzeigers
      // Mittelpunkt + 1 -> Mittelpunkt soll nicht gelöscht werden
      MinutenZeigerX = MinutePosX * 120 + MitteHoehe;
      MinutenZeigerY = MinutePosY * 120 + 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);

    // 125 Pixel -> Länge des Sekundenzeigers
    SekundenZeigerX = SekundePosX * 125 + MitteHoehe + 1;
    SekundenZeigerY = SekundePosY * 125 + 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()
{
  u8g2Schriften.setForegroundColor(GRUEN);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 350);
  u8g2Schriften.setFont(u8g2_font_fub20_tf);
      
  // Bildschirmbereich für das Datum löschen
  tft.fillRect(10, 320, tft.width(), tft.height(), SCHWARZ);

   // Wochentag anzeigen
  switch (Zeit.tm_wday) 
  {
    case 0:
      u8g2Schriften.print("Sonntag");
      break;
    case 1:
      u8g2Schriften.print("Montag");
      break;
    case 2:
      u8g2Schriften.print("Dienstag");
      break;
    case 3:
      u8g2Schriften.print("Mittwoch");
      break;
    case 4:
      u8g2Schriften.print("Donnerstag");
      break;
    case 5:
      u8g2Schriften.print("Freitag");
      break;
    case 6:
      u8g2Schriften.print("Samstag");
      break;
  }

  u8g2Schriften.print(", ");
  if (Zeit.tm_mday < 10) u8g2Schriften.print("0");
  u8g2Schriften.print(Zeit.tm_mday);
  u8g2Schriften.print(".");

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

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

void TemperaturAnzeigen()
{
  // Temperatur lesen 
  String Temperatur = String(bmp.readTemperature());

  // replace -> . durch , ersetzen
  Temperatur.replace(".", ",");

  String Luftdruck = String(bmp.readPressure() / 100);
  Luftdruck.replace(".", ",");

  // Anzeigebereich löschen
  tft.fillRect(10, 365, tft.width(), tft.height(), SCHWARZ);
  u8g2Schriften.setForegroundColor(BLAU);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 405);
  u8g2Schriften.setFont(u8g2_font_logisoso38_tf);
  u8g2Schriften.print(Luftdruck + " hPa");

  u8g2Schriften.setCursor(10, 470);
  u8g2Schriften.setForegroundColor(ROT);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);

  u8g2Schriften.print(Temperatur + "°C");
}

Wecker

(nur Modul mit USB-C-Anschluss)

#include "WiFi.h"
#include "time.h"
#include "TFT_eSPI.h"
#include "U8g2_for_TFT_eSPI.h"

// Objekt für Schriften von U8g2 (u8g2Schriften)
U8g2_for_TFT_eSPI u8g2Schriften;

TFT_eSPI tft = TFT_eSPI();

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

// Variablen des TFTs (Höhe, Breite, Radius)
const int MitteHoehe = 160;
const int MitteBreite = 160;
const int Radius = 160;

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

unsigned long Zeitmessung = 0;  

// 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 = false;

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

// Pin Lautsprecher
int Lautsprecher = 26; 

// Koordinaten Touchscreen
uint16_t x, y;

// Variablen Wecker
bool WeckerStatus = false;
int StundeAnzeigen = 6;
int MinuteAnzeigen = 0;
int WeckzeitStunde = 6;
int WeckzeitMinute = 0;

// 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 = Sommerzeit (dst = daylight saving time)
*/
tm Zeit;

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

  // Schriften von u8g2 tft zuordnen
  u8g2Schriften.begin(tft);

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

  WiFi.mode(WIFI_STA);
  
  // WiFi starten
  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());

  // Zeit holen
  time(&aktuelleZeit);

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

  // beim Start entspricht das Datum der Unixtime: 1.1.1970
  // Datum/Kalender sollen erst angezeigt werden, wenn das Datum korrekt ist
  String Jahr = String(Zeit.tm_year + 1900); 
  int Zaehler = 0;

  // String Jahr nach "1970" durchsuchen
  int Suche = Jahr.indexOf("1970");         

  Serial.println("-------------------------"); 
  Serial.println("Datum und Zeit holen (maximal 90 Sekunden)...");
  
  // solange die Suche nicht erfolgreich ist
  while (Suche != -1) 
  {
    // aktuelle Zeit holen
    time(&aktuelleZeit);

    // localtime_r -> Zeit in die lokale Zeitzone setzen
    localtime_r(&aktuelleZeit, &Zeit);
    Jahr = String(Zeit.tm_year + 1900); 
    
    // String Jahr nach "1970" durchsuchen
    Suche = Jahr.indexOf("1970");

    // Zeit in Stunden, Minuten und Sekunden
    Stunden = int(Zeit.tm_hour), Minuten = int(Zeit.tm_min), Sekunden = int(Zeit.tm_sec);
    delay(1000);
    Zaehler ++;

    if (Zaehler >= 90)
    {
      Serial.println();
      Serial.println("Datum und Zeit konnte innerhalb von " + String(Zaehler) + " Sekunden nicht geholt werden");
      Serial.println("Programm wird beendet");

      // Programm beenden
      while(1);
    }

    Serial.print(".");
  }   

  Serial.println();

  // Datum/Zeit erfolgreich synchronisiert
  if (Suche == -1) 
  {
    Serial.println("-------------------------");
    Serial.println("Datum/Zeit erfolgreich synchronisiert ...");
    
    if (Zeit.tm_mday < 10) Serial.print("0");
    Serial.print(Zeit.tm_mday);
    Serial.print(".");

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

    // Anzahl Jahre seit 1900
    Serial.println(Zeit.tm_year + 1900);

    if (Zeit.tm_hour < 10) Serial.print("0");
    Serial.print(Zeit.tm_hour);
    Serial.print(":");
      
    if (Zeit.tm_min < 10) Serial.print("0");
    Serial.println(Zeit.tm_min);
    Serial.println("-------------------------");

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

  // kalibrierte Daten verwenden
  uint16_t calData[5] = {242, 3496, 262, 3622, 4};
  tft.setTouch(calData);

  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 * 155 + Radius;
    int PunktY1 = PosY * 155 + Radius;
    int PunktX2 = PosX * 145 + Radius;
    int PunktY2 = PosY * 145 + Radius;
    tft.drawLine(PunktX1, PunktY1, PunktX2, PunktY2, Zeigerfarbe);
    
    // keine Striche an der Position der Zahlen
    if (Ziffernanzeigen)
    {

      if (PunktX1 == 160 || PunktX1 == 315 || PunktX1 == 5)
      {
        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 * 150 + Radius;
    PunktY = PosY * 150 + Radius;
    tft.drawPixel(PunktX, PunktY, Zeigerfarbe);  
  }

  // Markierung 12 3 6 9
  if (Ziffernanzeigen)
  {
    tft.setTextSize(2);
    tft.setTextColor(Zeigerfarbe);
    tft.setCursor(145, 15);
    tft.print("12");
    
    tft.setCursor(10, 153);
    tft.print("9");
    
    tft.setCursor(300, 153);
    tft.print("3");
  
    tft.setCursor(155, 300);
    tft.print("6");
  }

  if (DatumAnzeigen)
  {
    ZeigeDatum();
  }

 // linker Button  
  tft.fillRect(10, 400, 90, 50, HELLBLAU);
  tft.drawRoundRect(10, 400, 90, 50, 5, SCHWARZ);
  u8g2Schriften.setForegroundColor(SCHWARZ);
  u8g2Schriften.setBackgroundColor(HELLBLAU);
  u8g2Schriften.setCursor(45, 435);
  u8g2Schriften.print(StundeAnzeigen);
  
  // mittlerer Button
  tft.fillRect(120, 400, 90, 50, WEISS);
  tft.drawRoundRect(120, 400, 90, 50, 5, SCHWARZ);
  u8g2Schriften.setForegroundColor(SCHWARZ);
  u8g2Schriften.setBackgroundColor(WEISS);
  u8g2Schriften.setCursor(155, 435);
  u8g2Schriften.print(MinuteAnzeigen);

  // rechter Button
  tft.fillRect(230, 400, 90, 50, GELB);
  tft.drawRoundRect(230, 400, 90, 50, 5, SCHWARZ);
  u8g2Schriften.setForegroundColor(SCHWARZ);
  u8g2Schriften.setBackgroundColor(GELB);
  u8g2Schriften.setCursor(250, 435);
  u8g2Schriften.print("aus");

  Zeitmessung = millis() + 1000; 
}

void loop() 
{
  TouchAbfragen();
  
  // Sekunden weiter zählen
  if (Zeitmessung < millis()) 
  {
    Zeitmessung += 1000;
    Sekunden++;  
  
    if (Sekunden == 60) 
    { 
      // Zeit jede Minute mit Zeitserver synchronisieren
      // aktuelle Zeit holen
      time(&aktuelleZeit);

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

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

      // Mitternacht 
      // -> Wechsel des Datums anzeigen
      if (Stunden == 0 && Minuten == 0) 
      {
        Serial.println("neues Datum");
        if (DatumAnzeigen) ZeigeDatum();
      }

      // Wecker eingeschaltet
      if (WeckerStatus)
      {  
        if (Stunden == WeckzeitStunde && Minuten == WeckzeitMinute)
        {
          tone(Lautsprecher, 1000, 100); 
          delay(500);
          noTone(Lautsprecher);
          Serial.println("Weckzeit: " + String(WeckzeitStunde) + ":" + String(WeckzeitMinute));
        }
      }
    }

    // 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 * 85 + MitteHoehe + 1;
      StundenZeigerY = StundePosY * 85 + 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 * 120 + MitteHoehe;
      MinutenZeigerY = MinutePosY * 120 + 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 * 120 + MitteHoehe + 1;
    SekundenZeigerY = SekundePosY * 120 + 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()
{
  u8g2Schriften.setForegroundColor(GRUEN);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 370);
  u8g2Schriften.setFont(u8g2_font_fub20_tf);
      
  // Bildschirmbereich für das Datum löschen
  tft.fillRect(10, 340, tft.width(), 50, SCHWARZ);

   // Wochentag anzeigen
  switch (Zeit.tm_wday) 
  {
    case 0:
      u8g2Schriften.print("Sonntag");
      break;
    case 1:
      u8g2Schriften.print("Montag");
      break;
    case 2:
      u8g2Schriften.print("Dienstag");
      break;
    case 3:
      u8g2Schriften.print("Mittwoch");
      break;
    case 4:
      u8g2Schriften.print("Donnerstag");
      break;
    case 5:
      u8g2Schriften.print("Freitag");
      break;
    case 6:
      u8g2Schriften.print("Samstag");
      break;
  }

  u8g2Schriften.print(", ");
  if (Zeit.tm_mday < 10) u8g2Schriften.print("0");
  u8g2Schriften.print(Zeit.tm_mday);
  u8g2Schriften.print(".");

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

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

void TouchAbfragen()
{
  uint16_t x, y;

  if (tft.getTouch(&x, &y))
  {
    // linker Button Koordinaten abfragen
    if (x >= 10 && x <= 90 && y >= 400 && y <= 450) 
    {
      if (StundeAnzeigen == 23) StundeAnzeigen = 0;
      else StundeAnzeigen ++;
      tft.fillRect(10, 400, 90, 50, HELLBLAU);
      tft.drawRoundRect(10, 400, 90, 50, 5, SCHWARZ);
      u8g2Schriften.setForegroundColor(SCHWARZ);
      u8g2Schriften.setBackgroundColor(HELLBLAU);
      u8g2Schriften.setCursor(45, 435);
      u8g2Schriften.print(StundeAnzeigen);
      WeckzeitStunde = StundeAnzeigen;
    }

    // mittlerer Button Koordinaten abfragen
    if (x >= 120 && x <= 210 && y >= 400 && y <= 450) 
    {
      if (MinuteAnzeigen == 59) MinuteAnzeigen = 0;
      else MinuteAnzeigen ++;
      tft.fillRect(120, 400, 90, 50, WEISS);
      tft.drawRoundRect(120, 400, 90, 50, 5, SCHWARZ);
      u8g2Schriften.setForegroundColor(SCHWARZ);
      u8g2Schriften.setBackgroundColor(WEISS);
      u8g2Schriften.setCursor(155, 435);
      u8g2Schriften.print(MinuteAnzeigen);
      WeckzeitMinute = MinuteAnzeigen;
    }

    // rechter Button Koordinaten abfragen
    if (x >= 230 && x <= 320 && y >= 400 && y <= 450) 
    {
      WeckerStatus = !WeckerStatus;
    }

    // Status des Weckers anzeigen
    if (WeckerStatus) 
    { 
      tft.fillRect(230, 400, 90, 50, GELB);
      tft.drawRoundRect(230, 400, 90, 50, 5, SCHWARZ);
      u8g2Schriften.setForegroundColor(SCHWARZ);
      u8g2Schriften.setBackgroundColor(GELB);
      u8g2Schriften.setCursor(250, 435);
      u8g2Schriften.print("an");
    }

    else
    {
      tft.fillRect(230, 400, 90, 50, GELB);
      tft.drawRoundRect(230, 400, 90, 50, 5, SCHWARZ);
      u8g2Schriften.setForegroundColor(SCHWARZ);
      u8g2Schriften.setBackgroundColor(GELB);
      u8g2Schriften.setCursor(250, 435);
      u8g2Schriften.print("aus");    
    }
    
    KoordinatenAnzeigen(x, y, tft.getTouchRawZ());

    delay(150);
  }
}

void KoordinatenAnzeigen(int x, int y, int z) 
{
  Serial.print("x-Koordinate = ");
  Serial.print(x);
  Serial.print(" | y-Koordinate = ");
  Serial.print(y);
  Serial.print(" | Druck = ");
  Serial.print(z);
  Serial.println();
}

Quel­len


Letzte Aktualisierung: Nov. 10, 2025 @ 12:18