TFT par­al­le­les LCD-Shield 3,5 Zoll am ESP32-Wroom

Ziel des Projekts

UNO TFT Shield

Für die Ardui­no-Welt gibt es ver­schie­de­ne TFT-Dis­plays, die als par­al­le­les LCD-Shield aus­ge­führt sind. Es wird auf den Ardui­no UNO gesteckt und nutzt die ent­spre­chen­den Pins.


Ziel des Pro­jekts ist es eine Ver­bin­dung zwi­schen dem LCD-Shield und einem ESP32-Wroom her­zu­stel­len. Auf dem TFT sol­len eine ana­lo­ge Uhr und die Mess­da­ten von Tem­pe­ra­tur­sen­so­ren dar­ge­stellt werden.

Kon­fi­gu­ra­ti­on des Mikrocontrollers

Pin­be­le­gung am ESP32-Wroom

linksPin ESP32-WroomrechtsPin ESP32-Wroom
LCD_RST32LCD_D226
LCD_CS33LCD_D325
LCD_RS15LCD_D417 (RX2)
LCD_WR4LCD_D516 (TX2)
LCD_RD2LCD_D714
GNDLCD_D023
5VLCD_D113
3V3

Biblio­thek installieren

Biblio­thek TFT_eSPI

Steu­er­da­tei User_Setup.h

Die Biblio­thek TFT_eSPI ver­wen­det eine Steu­er­da­tei, in der Trei­ber, Bild­schirm­ma­ße in Pixeln und die ver­wen­de­ten Pins notiert werden.

#define ESP32_PARALLEL

#define ILI9488_DRIVER
#define TFT_WIDTH 320
#define TFT_HEIGHT 480

#define TFT_CS   33
#define TFT_DC   15  // Beschriftung LDC_RS
#define TFT_RST  32
#define TFT_WR    4
#define TFT_RD    2
#define TFT_D0   23
#define TFT_D1   13
#define TFT_D2   26
#define TFT_D3   25
#define TFT_D4   17
#define TFT_D5   16
#define TFT_D6   27
#define TFT_D7   14

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

#define SMOOTH_FONT

Das Kom­pi­lie­ren führt zu einer Fehlermeldung:

Arduino/libraries/TFT_eSPI/Processors/TFT_eSPI_ESP32.c:114:9: error: 'gpio_input_get' was not declared in this scope;

Im Board­ma­na­ger esp32 ab Ver­si­on 3 wur­de die Funk­ti­on gpio_input_get' ent­fernt, in der Ver­si­on 2 tritt der Feh­ler nicht auf.

Die Datei Arduino/libraries/TFT_eSPI/Processors/TFT_eSPI_ESP32.c muss ab Zei­le 110 geän­dert werden

#if defined (TFT_PARALLEL_8_BIT)
  RD_L;
  uint32_t reg;           // Read all GPIO pins 0-31
  // reg = gpio_input_get(); // Read three times to allow for bus access time
  // reg = gpio_input_get();
  // reg = gpio_input_get(); // Data should be stable now
  RD_H;

Mög­li­cher­wei­se wird das Pro­blem in einer zukünf­ti­gen Ver­si­on beho­ben, ansons­ten muss nach einem Update die Ände­rung erneut durch­ge­führt werden.

Außer­dem erscheint eine War­nung, dass die Touch­pins nicht defi­niert sind, sie kann igno­riert werden.

Bei­spiel­pro­gram­me

Die in den Pro­gram­men 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.

Gra­fi­sche Funktionen

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

Tem­pe­ra­tur und Luft­druck mit BMP280 anzeigen

Der BMP280 wird am ⇒I²C-Bus ange­schlos­sen. Der Sen­sor arbei­tet mit unter­schied­li­chen HEX-Adres­sen, sie kön­nen mit die­sem ⇒Pro­gramm ermit­telt werden.

LCD mit Temperaturanzeige BMP280

Zusätz­li­che Biblio­thek installieren:

Das Pro­gramm

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

#define SDA_PIN 21
#define SCL_PIN 22

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

  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, 300, 150, SCHWARZ);

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

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

Ana­lo­ge Uhr

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

  // 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, 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();
  }

  Zeitmessung = millis() + 1000; 
}

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

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

      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;

      // Datum anzeigen/nicht anzeigen
      if (DatumAnzeigen) ZeigeDatum();

      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, 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 mit Tem­pe­ra­tur-/Luft­druck­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
#define SDA_PIN 21
#define SCL_PIN 22

// 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[] = "xxxxxxx";

// 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() 
{
  // Wire mit I2C-Pins starten
  Wire.begin(SDA_PIN, SCL_PIN);

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

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

  // 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, 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();
  }

  DatenAnzeigen();

  Zeitmessung = millis() + 1000; 
}

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

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

      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;

      // Datum anzeigen/nicht anzeigen
      if (DatumAnzeigen) ZeigeDatum();
      
      DatenAnzeigen();
      
      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, 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 DatenAnzeigen()
{
  // Temperatur lesen 
  String Temperatur = String(bmp.readTemperature());

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

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

  u8g2Schriften.setForegroundColor(BLAU);   
  u8g2Schriften.setBackgroundColor(SCHWARZ);
  u8g2Schriften.setCursor(10, 410);
  u8g2Schriften.setFont(u8g2_font_logisoso38_tf);
  u8g2Schriften.print(Luftdruck + " hPa");

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

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

Quel­len

Letzte Aktualisierung: Juli 8, 2025 @ 17:37