Skip to content

Technik Blog

Programmieren | Arduino | ESP32 | MicroPython | Python | Raspberry Pi | Raspberry Pi Pico

Menu
  • Smarthome
  • Gartenautomation
  • Mikrocontroller
    • Arduino
    • ESP32 & Co.
    • Calliope Mini
    • Raspberry Pi & Pico
  • Solo Mining
  • Deutsch
  • English
Menu

ESP32 Touch-GUI mit LVGL: RGB-LED per Slider und Buttons steuern

Veröffentlicht am 25. Januar 202623. Januar 2026 von Stefan Draeger

In meinem bereits veröffentlichten Beitrag zum Edelmetall-Monitor auf dem Cheap Yellow Display habe ich gezeigt, wie sich mit LVGL ein interaktives Dashboard auf dem ESP32 umsetzen lässt.
Dort lag der Fokus auf der Anzeige von Echtzeitdaten und der Gestaltung einer Touch-Oberfläche.

In diesem Beitrag gehen wir einen Schritt weiter:
Ich zeige, wie du eine RGB-LED direkt über das Touchdisplay steuerst – mit farbigen Buttons zum Ein- und Ausschalten der einzelnen Kanäle sowie einem Slider zur Regelung der Helligkeit.
Das Projekt eignet sich perfekt, um den Umgang mit LVGL-Events, Slidern und Button-Callbacks praxisnah zu vertiefen.

CYD - Buttons und Slider für RGB LED - Helligkeit 100 Prozent rot aktiv
CYD - Buttons und Slider für RGB LED
CYD - RGB LED Farbe blau
CYD - RGB LED Farbe gruen
CYD - RGB LED Farbe rot
CYD - RGB LED Farbe violet

Inhaltsverzeichnis

  • Wo bekommt man das Cheap Yellow Display (CYD)?
  • Aufbau des Cheap Yellow Display – ESP32-2432S028R
  • Voraussetzungen & benötigte Bibliotheken
  • Touchdisplay kalibrieren (XPT2046)
    • Vorgehensweise
    • Touch-Rohwerte auf Bildschirmkoordinaten abbilden
    • Typische Stolperfallen
  • Programmierung mit LVGL

Wo bekommt man das Cheap Yellow Display (CYD)?

Das sogenannte Cheap Yellow Display (CYD) ist kein reines TFT-Modul, sondern ein komplettes ESP32-Entwicklungsboard mit integriertem 2,8″-Touchdisplay (240 × 320 Pixel).
Der ESP32 ist bereits auf der Platine verbaut, sodass keine zusätzliche Display-Ansteuerung oder externe Verkabelung notwendig ist – ideal für Projekte mit LVGL und Touch-GUI.

Lieferumfang - ESP32 mit 2.8
Lieferumfang – ESP32 mit 2.8″ TFT-Display & Touch (ESP32-2432S028R)
ESP32 mit 2.8
ESP32 mit 2.8″ TFT-Display & Touch (ESP32-2432S028R)
Rückseite - ESP32-2432S028R
Rückseite – ESP32-2432S028R

Erhältlich ist das CYD unter Bezeichnungen wie:

  • ESP32-2432S028
  • ESP32-2432S028R
  • Cheap Yellow Display ESP32

Typische Bezugsquellen sind AliExpress, Amazon und eBay.
Auf AliExpress ist das Board oft für unter 15 € erhältlich, während es bei Amazon in der Regel etwas teurer, dafür aber schneller lieferbar ist.

ESP32-2432S028R auf Aliexpress
ESP32-2432S028R auf Aliexpress

Aufbau des Cheap Yellow Display – ESP32-2432S028R

Das Cheap Yellow Display (ESP32-2432S028R) ist ein kompaktes All-in-One-Entwicklungsboard, bei dem ein ESP32-Mikrocontroller, ein 2,8″-TFT-Display (240 × 320 Pixel) sowie ein resistiver Touchscreen bereits auf einer Platine integriert sind.
Dadurch eignet sich das Board besonders gut für Projekte mit LVGL und interaktiven Benutzeroberflächen, ohne dass zusätzliche Hardware notwendig ist.

Ein praktisches Detail ist die auf der Rückseite des Boards fest verbaute RGB-LED.
Diese ist direkt mit drei GPIO-Pins des ESP32 verbunden und kann ohne zusätzliche Verdrahtung angesteuert werden.

In diesem Projekt nutze ich dafür folgende Pins:

  • Rot: GPIO 4
  • Grün: GPIO 16
  • Blau: GPIO 17

Die genaue Belegung kann je nach Board-Variante leicht abweichen.
Im Zweifel hilft ein Blick auf das jeweilige Pinout oder ein kurzer Funktionstest per Code.

Aufbau - ESP32-2432S028R Cheap-Yellow-Display
Aufbau – ESP32-2432S028R Cheap-Yellow-Display

Wichtig:
Die auf dem CYD verbaute RGB-LED ist über eine gemeinsame Kathode angeschlossen.
Dadurch ist die PWM-Logik im Vergleich zu einer „normalen“ Ansteuerung invertiert:

  • PWM-Wert 0 → LED voll an
  • PWM-Wert 255 → LED aus

Für die Helligkeitsregelung muss der gewünschte Wert daher entsprechend umgerechnet werden (z. B. 255 - Helligkeit).
Darauf gehe ich im Code weiter unten konkret ein.

Voraussetzungen & benötigte Bibliotheken

Bevor wir das Touchdisplay kalibrieren und anschließend die RGB-LED über Buttons und Slider steuern, müssen zunächst die grundlegenden Software-Voraussetzungen erfüllt sein.

Für dieses Projekt benötigst du:

  • ein Cheap Yellow Display (ESP32-2432S028R*)
  • die Arduino IDE (empfohlen: Version 2.x)
  • folgende Bibliotheken:
    • lvgl
    • TFT_eSPI
    • XPT2046_Touchscreen

Die Bibliotheken kannst du direkt über den Bibliotheksverwalter der Arduino IDE installieren.

Zusätzlich muss die TFT_eSPI-Library einmalig auf das Cheap Yellow Display angepasst werden (z. B. über User_Setup.h oder User_Setup_Select.h).
Dabei werden unter anderem die Display-Pins, die Auflösung (240 × 320 Pixel) und der verwendete Controller festgelegt.

Diese Konfiguration ist sowohl für die spätere LVGL-GUI als auch für den Kalibrier-Sketch des Touchdisplays erforderlich.
Wie die Anpassung von TFT_eSPI im Detail funktioniert, habe ich bereits in meinem vorherigen Beitrag zum Edelmetall-Monitor auf dem Cheap Yellow Display beschrieben.

Hinweis von mir: Die mit einem Sternchen (*) markierten Links sind Affiliate-Links. Wenn du über diese Links einkaufst, erhalte ich eine kleine Provision, die dazu beiträgt, diesen Blog zu unterstützen. Der Preis für dich bleibt dabei unverändert. Vielen Dank für deine Unterstützung!

Touchdisplay kalibrieren (XPT2046)

Damit Touch-Eingaben auf dem Cheap Yellow Display zuverlässig an der richtigen Position ankommen, muss der resistive Touchcontroller (XPT2046) einmal kalibriert werden.
Der Controller liefert rohe Analogwerte, die je nach Board leicht variieren. Diese Rohwerte müssen später auf die tatsächliche Displayauflösung (240 × 320 Pixel) gemappt werden.

Für die Kalibrierung nutze ich einen kleinen Test-Sketch, der die Rohdaten der X- und Y-Koordinaten sowohl im seriellen Monitor als auch direkt auf dem Display ausgibt.

Vorgehensweise

  1. Kalibrier-Sketch auf das CYD flashen.
  2. Seriellen Monitor auf 115200 Baud öffnen.
  3. Nacheinander möglichst genau in die vier Ecken des Displays tippen:
    • oben links
    • oben rechts
    • unten links
    • unten rechts
  4. Die jeweils kleinsten und größten Werte notieren:
    • TS_X_MIN = kleinster gemessener X-Wert
    • TS_X_MAX = größter gemessener X-Wert
    • TS_Y_MIN = kleinster gemessener Y-Wert
    • TS_Y_MAX = größter gemessener Y-Wert

Diese vier Werte werden anschließend im Haupt-Sketch verwendet, um die Touch-Rohdaten korrekt auf die Display-Pixel zu skalieren:

#define TS_X_MIN 200
#define TS_X_MAX 3700
#define TS_Y_MIN 240
#define TS_Y_MAX 3800

Touch-Rohwerte auf Bildschirmkoordinaten abbilden

Im eigentlichen Projekt werden die Rohwerte dann mit map() auf die Displayauflösung umgerechnet:

int px = map(p.x, TS_X_MIN, TS_X_MAX, 1, TFT_HOR_RES);
int py = map(p.y, TS_Y_MIN, TS_Y_MAX, 1, TFT_VER_RES);

Damit erhält LVGL die korrekten Koordinaten für Buttons, Slider und andere GUI-Elemente.

Typische Stolperfallen

  • Achsen vertauscht oder gespiegelt:
    Je nach Board-Variante und ts.setRotation() kann es nötig sein, X/Y zu tauschen oder die Mapping-Richtung zu invertieren.
  • Ungenaue Werte an den Rändern:
    Die äußersten Pixel lassen sich mit resistiven Touchscreens oft nicht perfekt treffen.
    Deshalb sollte man die Ecken möglichst präzise antippen und die Werte leicht „abrunden“.

Nach dieser einmaligen Kalibrierung funktionieren Touch-Events auf dem CYD deutlich zuverlässiger – und Buttons sowie Slider lassen sich exakt dort bedienen, wo sie angezeigt werden.

Quellcode
/*
  CYD Touch Rohdaten / Kalibrier-Helfer
  -----------------------------------
  Zweck:
  - Rohwerte (X/Y) des XPT2046 Touchcontrollers ausgeben (Serial + Label)
  - Damit kannst du die Kalibrierwerte TS_X_MIN/MAX und TS_Y_MIN/MAX ermitteln

  Wichtig:
  - Dieser Sketch gibt aktuell die ROHWERTE p.x/p.y aus (nicht gemappt!)
  - Für LVGL "Pointer Device" sollte man data->state und data->point setzen.
    In diesem Sketch ist das absichtlich nicht nötig, weil wir nur Rohdaten loggen.
  - Dennoch: data->state wird bei Touch aktuell nicht auf PRESSED gesetzt.
    Das ist ok, solange du nicht willst, dass LVGL wirklich "Klicks" verarbeitet.
*/

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

/* -------------------- Touch-Pins (CYD Standard) --------------------
   XPT2046 ist ein resistiver Touch-Controller, der per SPI angebunden ist.
   Diese Pins sind typisch für ESP32-2432S028R (CYD).
*/
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

SPIClass touchSPI(HSPI);                      // HSPI-Bus wird für Touch genutzt
XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);

/* -------------------- Display-Auflösung / Rotation --------------------
   TFT-Auflösung: 240 x 320 Pixel
   LVGL Rotation: abhängig davon, wie dein Display physisch "steht"
*/
#define TFT_HOR_RES 240
#define TFT_VER_RES 320
#define TFT_ROTATION LV_DISPLAY_ROTATION_270

/* UI-Farben */
#define COLOR_ORANGE lv_color_hex(0xFB8C00)
#define COLOR_BG     lv_color_hex(0x000000)

/* -------------------- LVGL Draw Buffer --------------------
   LVGL rendert in einen Buffer und TFT_eSPI gibt ihn auf das Display aus.
*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];

/* Label, in das wir live die Koordinaten schreiben */
lv_obj_t *koordinaten;

/* -------------------- Touch-Kalibrierwerte --------------------
   Diese Werte werden im eigentlichen Projekt für map(...) genutzt.
   In diesem Rohdaten-Sketch sind sie nicht notwendig, da du Rohwerte anzeigen willst.
   -> Du kannst sie hier später mit den gemessenen Eckwerten aktualisieren.
*/
#define TS_X_MIN 200
#define TS_X_MAX 3700
#define TS_Y_MIN 240
#define TS_Y_MAX 3800

/* -------------------- LVGL Input Callback --------------------
   LVGL fragt hier regelmäßig den Touch ab.
   Wir prüfen:
   - IRQ aktiv? (tirqTouched)
   - Touch wirklich gedrückt? (touched)
   Dann lesen wir die Rohwerte per getPoint().

   Hinweis: getPoint() liefert Rohwerte (typisch ca. 0..4095).
*/
static void my_touch_read(lv_indev_t *indev, lv_indev_data_t *data) {
  (void)indev;

  if (ts.tirqTouched() && ts.touched()) {
    TS_Point p = ts.getPoint();

    // Rohwerte in die serielle Konsole ausgeben
    Serial.print("Touch: X = ");
    Serial.print(p.x);
    Serial.print(" | Y = ");
    Serial.println(p.y);

    // Rohwerte als Text auf dem Display anzeigen
    char coord[25];
    sprintf(coord, "X=%d Y=%d", p.x, p.y);
    lv_label_set_text(koordinaten, coord);

    // Für diesen Sketch optional:
    // data->state = LV_INDEV_STATE_PRESSED;
    // (Wenn du willst, dass LVGL es als "echten Touch" erkennt.)
  } else {
    // Kein Touch
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

void setup() {
  Serial.begin(115200);
  Serial.println("Touch-Koordinaten Test gestartet");

  // LVGL starten
  lv_init();

  // Touch SPI initialisieren und Touch Controller starten
  touchSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  ts.begin(touchSPI);

  // Rotation für Touchcontroller (nicht identisch mit Displayrotation!)
  // Je nach Board kann hier 0..3 nötig sein.
  ts.setRotation(2);

  // Display für LVGL erstellen
  lv_display_t *disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, sizeof(draw_buf));
  lv_display_set_rotation(disp, TFT_ROTATION);

  // Touch in LVGL registrieren
  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  lv_indev_set_read_cb(indev, my_touch_read);

  // Screen Styling
  lv_obj_t *scr = lv_screen_active();
  lv_obj_set_style_bg_color(scr, COLOR_BG, 0);
  lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);

  // Überschrift
  lv_obj_t *lbl = lv_label_create(scr);
  lv_label_set_text(lbl, "Rohdaten");
  lv_obj_set_style_text_color(lbl, COLOR_ORANGE, 0);
  lv_obj_set_style_text_font(lbl, &lv_font_montserrat_22, 0);
  lv_obj_align(lbl, LV_ALIGN_TOP_MID, 0, 90);

  // Label für Koordinatenanzeige
  koordinaten = lv_label_create(scr);
  lv_obj_set_style_text_color(koordinaten, COLOR_ORANGE, 0);
  lv_obj_set_style_text_font(koordinaten, &lv_font_montserrat_22, 0);
  lv_obj_align(koordinaten, LV_ALIGN_TOP_MID, 0, 120);
}

void loop() {
  // LVGL "arbeiten lassen"
  lv_timer_handler();
  lv_tick_inc(5);
  delay(5);
}

Programmierung mit LVGL

Da ich in meinem vorherigen Beitrag bereits ausführlich gezeigt habe, wie das Cheap Yellow Display mit ESP32 und LVGL in Betrieb genommen und grundlegend konfiguriert wird, steigen wir hier direkt in die Praxis ein.

Ziel ist es, eine einfache Touch-Oberfläche zu erstellen, mit der sich eine RGB-LED über drei Buttons (Rot, Grün, Blau) ein- und ausschalten lässt.
Zusätzlich kommt ein Slider zum Einsatz, über den die Helligkeit aller aktiven Kanäle geregelt wird.

Dabei zeige ich unter anderem:

  • wie Buttons und Slider mit LVGL erstellt werden
  • wie Event-Callbacks für Touch-Eingaben funktionieren
  • wie sich die GUI-Elemente mit der Hardware (RGB-LED) verknüpfen lassen
  • und worauf man bei PWM-Ansteuerung und Helligkeitsregelung achten sollte

Wenn du das CYD bereits lauffähig hast, kannst du den folgenden Code direkt übernehmen und an deine eigenen GPIO-Pins anpassen.

/*
  ESP32-2432S028R (Cheap Yellow Display / CYD) + LVGL
  ---------------------------------------------------
  Ziel:
  - Touch-GUI mit 3 Buttons (Rot/Grün/Blau) zum Ein/Aus der RGB-LED Kanäle
  - Slider zur Helligkeitsregelung (PWM) für aktive Kanäle
  - Die RGB-LED ist auf dem Board fest verbaut und besitzt eine gemeinsame Kathode,
    deshalb ist die PWM-Logik invertiert: 0 = voll an, 255 = aus.

  Hardware:
  - TFT 240x320 mit TFT_eSPI
  - Resistiver Touch XPT2046 (SPI)
  - RGB-LED Pins: R=GPIO4, G=GPIO16, B=GPIO17 (kann je nach Board variieren)
*/

#include <lvgl.h>
#include <TFT_eSPI.h>
#include <XPT2046_Touchscreen.h>

/* ----------------------------- Touch (XPT2046) Pins -----------------------------
   Diese Pinbelegung entspricht dem typischen CYD-Board.
   Wichtig: Touch läuft über SPI (hier HSPI), getrennt vom Display.
*/
#define XPT2046_IRQ 36
#define XPT2046_MOSI 32
#define XPT2046_MISO 39
#define XPT2046_CLK 25
#define XPT2046_CS 33

SPIClass touchSPI(HSPI);                      // Wir nutzen den HSPI-Bus für Touch
XPT2046_Touchscreen ts(XPT2046_CS, XPT2046_IRQ);

/* ----------------------------- Display Einstellungen -----------------------------
   TFT-Auflösung und LVGL-Rotation (abhängig von deinem Setup)
*/
#define TFT_HOR_RES 240
#define TFT_VER_RES 320
#define TFT_ROTATION LV_DISPLAY_ROTATION_270

/* Forward Declarations (Funktionsprototypen)
   - Damit kann die Funktion später im Code definiert werden, aber vorher schon genutzt werden.
*/
static void on_btn_press(lv_event_t *e);     // (wird in deinem Code aktuell nicht genutzt)
static void slider_event_cb(lv_event_t *e);
static void refreshBrightness();

/* ----------------------------- LVGL Draw Buffer -----------------------------
   LVGL rendert in einen Draw Buffer (Teilstücke), der dann an TFT_eSPI ausgegeben wird.
   Faustregel: größerer Buffer = flüssiger, aber mehr RAM-Verbrauch.
   Hier: 1/10 des Screens. Bei LV_COLOR_DEPTH = 16 braucht LVGL entsprechend Speicher.
*/
#define DRAW_BUF_SIZE (TFT_HOR_RES * TFT_VER_RES / 10 * (LV_COLOR_DEPTH / 8))
uint32_t draw_buf[DRAW_BUF_SIZE / 4];        // uint32_t Array => "in 32-bit Blöcken"

/* ----------------------------- Farben für UI -----------------------------
   lv_color_hex(...) erzeugt LVGL-Farbwerte.
*/
#define COLOR_BLACK lv_color_hex(0x000000)
#define COLOR_GOLD  lv_color_hex(0xD4AF37)
#define COLOR_SILVER lv_color_hex(0xD9D9D9)
#define COLOR_WHITE lv_color_hex(0xFFFFFF)

/* Achtung: COLOR_BLUE ist doppelt definiert (einmal oben, einmal unten)
   -> Das ist nicht kritisch, aber unsauber. Die zweite Definition "gewinnt".
*/
#define COLOR_BLUE lv_color_hex(0x1E88E5)

#define COLOR_RED      lv_color_hex(0xE53935)
#define COLOR_RED_DARK lv_color_hex(0x8C2A27)

#define COLOR_GREEN      lv_color_hex(0x43A047)
#define COLOR_GREEN_DARK lv_color_hex(0x1B331C)

#define COLOR_BLUE      lv_color_hex(0x1E88E5)
#define COLOR_BLUE_DARK lv_color_hex(0x104675)

/* ----------------------------- Touch Kalibrierung -----------------------------
   TS_X_MIN/MAX und TS_Y_MIN/MAX sind Rohwerte des XPT2046 Controllers.
   Diese Werte kommen aus einer Kalibrierung: Touch an Ecken -> Rohwerte messen.
*/
#define TS_X_MIN 200
#define TS_X_MAX 3700
#define TS_Y_MIN 240
#define TS_Y_MAX 3800

/* ----------------------------- RGB LED Pins -----------------------------
   Auf dem CYD ist die RGB-LED fest verbaut.
   Diese GPIOs sind typische Belegungen bei ESP32-2432S028R.
*/
#define LED_RED   4
#define LED_GREEN 16
#define LED_BLUE  17

/* ----------------------------- Status Flags -----------------------------
   Merken, ob der jeweilige Farbkanal aktiv (true) oder aus (false) ist.
*/
static bool statusLedRed = false;
static bool statusLedGreen = false;
static bool statusLedBlue = false;

/* ----------------------------- LVGL Objekte (globale Zeiger) -----------------------------
   Werden in setup() erzeugt und später in Callbacks genutzt.
*/
lv_obj_t *btn_red;
lv_obj_t *btn_green;
lv_obj_t *btn_blue;

lv_obj_t *slider;
lv_obj_t *slider_label;

/* Helligkeit 0..255 (logisch "wie hell soll es sein?")
   Da LED invertiert ist, wird im PWM später 255 - brightness verwendet.
*/
int32_t brightness = 0;

/* ----------------------------- LVGL Touch Read Callback -----------------------------
   LVGL fragt regelmäßig den Input-Device-Treiber ab.
   Hier:
   - Wenn Touch gedrückt: Rohwerte lesen, in Pixel umrechnen, LVGL Koordinate setzen
   - Wenn nicht gedrückt: RELEASED
*/
static void my_touch_read(lv_indev_t *indev, lv_indev_data_t *data) {
  (void)indev;  // Parameter wird nicht genutzt (verhindert Warnungen)

  // Prüfen, ob Touch-IRQ aktiv ist UND Touch tatsächlich anliegt
  if (ts.tirqTouched() && ts.touched()) {
    TS_Point p = ts.getPoint();  // Rohwerte vom Touchcontroller (X/Y)

    // Rohwerte -> Pixel (Mapping anhand Kalibrierwerte)
    // Hinweis: je nach Rotation/Board kann X/Y getauscht oder invertiert sein!
    int px = map(p.x, TS_X_MIN, TS_X_MAX, 1, TFT_HOR_RES);
    int py = map(p.y, TS_Y_MIN, TS_Y_MAX, 1, TFT_VER_RES);

    // Sicherheit: innerhalb Bildschirmgrenzen bleiben
    if (px < 0) px = 0;
    if (px > TFT_HOR_RES - 1) px = TFT_HOR_RES - 1;
    if (py < 0) py = 0;
    if (py > TFT_VER_RES - 1) py = TFT_VER_RES - 1;

    // LVGL mitteilen: Touch gedrückt + Koordinate
    data->state = LV_INDEV_STATE_PRESSED;
    data->point.x = px;
    data->point.y = py;
  } else {
    // Nicht berührt
    data->state = LV_INDEV_STATE_RELEASED;
  }
}

/* ----------------------------- Arduino Setup ----------------------------- */
void setup() {
  Serial.begin(9600);

  // RGB LED Pins als Output
  pinMode(LED_RED, OUTPUT);
  pinMode(LED_GREEN, OUTPUT);
  pinMode(LED_BLUE, OUTPUT);

  // Startzustand: LED aus.
  // Invertierte Logik (gemeinsame Kathode): 255 = aus
  analogWrite(LED_RED, 255);
  analogWrite(LED_GREEN, 255);
  analogWrite(LED_BLUE, 255);

  // LVGL initialisieren
  lv_init();

  // Touch SPI initialisieren + Touch Controller starten
  touchSPI.begin(XPT2046_CLK, XPT2046_MISO, XPT2046_MOSI, XPT2046_CS);
  ts.begin(touchSPI);

  // Rotation des Touchcontrollers (nicht identisch mit Display-Rotation!)
  ts.setRotation(2);

  // Display Driver für LVGL erstellen (TFT_eSPI glue)
  lv_display_t *disp = lv_tft_espi_create(TFT_HOR_RES, TFT_VER_RES, draw_buf, sizeof(draw_buf));
  lv_display_set_rotation(disp, TFT_ROTATION);

  // Input Device (Touch) in LVGL registrieren
  lv_indev_t *indev = lv_indev_create();
  lv_indev_set_type(indev, LV_INDEV_TYPE_POINTER);
  lv_indev_set_read_cb(indev, my_touch_read);

  /* ----------------------------- UI Aufbau ----------------------------- */

  // Aktiven Screen holen und Grund-Styles setzen
  lv_obj_t *scr = lv_screen_active();
  lv_obj_set_style_bg_color(scr, COLOR_BLACK, 0);
  lv_obj_set_style_bg_opa(scr, LV_OPA_COVER, 0);
  lv_obj_set_style_pad_all(scr, 12, 0);
  lv_obj_set_scrollbar_mode(scr, LV_SCROLLBAR_MODE_OFF);

  // Toolbar Container oben (Flex Row)
  lv_obj_t *toolbar = lv_obj_create(scr);
  lv_obj_remove_style_all(toolbar);  // komplett "nacktes" Objekt ohne Default-Stile
  lv_obj_set_style_pad_column(toolbar, 10, 0);
  lv_obj_set_height(toolbar, LV_SIZE_CONTENT);
  lv_obj_set_style_pad_all(toolbar, 0, 0);
  lv_obj_set_width(toolbar, LV_PCT(100));
  lv_obj_set_style_pad_row(toolbar, 4, 0);

  // Flex Layout: Buttons nebeneinander
  lv_obj_set_flex_flow(toolbar, LV_FLEX_FLOW_ROW);
  lv_obj_set_flex_align(toolbar,
                        LV_FLEX_ALIGN_CENTER,
                        LV_FLEX_ALIGN_CENTER,
                        LV_FLEX_ALIGN_CENTER);
  lv_obj_align(toolbar, LV_ALIGN_TOP_MID, 0, 0);

  /* --- Button Rot --- */
  btn_red = lv_btn_create(toolbar);
  lv_obj_set_size(btn_red, 60, 60);
  lv_obj_set_pos(btn_red, 0, 0);

  // Event: PRESSED toggelt sofort beim "Runterdrücken".
  // Bei Touch kann das je nach Treiber mehrfach triggern -> CLICKED ist oft stabiler.
  lv_obj_add_event_cb(btn_red, on_btn_red_press, LV_EVENT_PRESSED, NULL);

  lv_obj_set_style_bg_color(btn_red, COLOR_RED, 0);
  lv_obj_set_style_border_width(btn_red, 1, 0);
  lv_obj_set_style_border_color(btn_red, COLOR_SILVER, 0);

  /* --- Button Grün --- */
  btn_green = lv_btn_create(toolbar);
  lv_obj_set_size(btn_green, 60, 60);
  lv_obj_set_pos(btn_green, 0, 0);
  lv_obj_add_event_cb(btn_green, on_btn_green_press, LV_EVENT_PRESSED, NULL);

  lv_obj_set_style_bg_color(btn_green, COLOR_GREEN, 0);
  lv_obj_set_style_border_width(btn_green, 1, 0);
  lv_obj_set_style_border_color(btn_green, COLOR_SILVER, 0);

  /* --- Button Blau --- */
  btn_blue = lv_btn_create(toolbar);
  lv_obj_set_size(btn_blue, 60, 60);
  lv_obj_set_pos(btn_blue, 0, 0);
  lv_obj_add_event_cb(btn_blue, on_btn_blue_press, LV_EVENT_PRESSED, NULL);

  lv_obj_set_style_bg_color(btn_blue, COLOR_BLUE, 0);
  lv_obj_set_style_border_width(btn_blue, 1, 0);
  lv_obj_set_style_border_color(btn_blue, COLOR_SILVER, 0);

  /* --- Slider für Helligkeit --- */
  slider = lv_slider_create(scr);
  lv_obj_center(slider); // mittig auf dem Screen
  lv_obj_add_event_cb(slider, slider_event_cb, LV_EVENT_VALUE_CHANGED, NULL);
  lv_slider_set_range(slider, 0, 255); // logischer Wertebereich

  /* Label unter dem Slider */
  slider_label = lv_label_create(scr);
  char buf[17];
  lv_snprintf(buf, sizeof(buf), "Helligkeit: %d", brightness);
  lv_label_set_text(slider_label, buf);

  lv_obj_set_style_text_color(slider_label, COLOR_WHITE, 0);
  lv_obj_set_style_text_font(slider_label, &lv_font_montserrat_18, 0);

  // Position: unter dem Slider
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);
}

/* ----------------------------- Arduino Loop -----------------------------
   LVGL benötigt:
   - lv_timer_handler() regelmäßig (arbeitet Events/Animations/Redraws ab)
   - lv_tick_inc(ms) zur Zeitbasis

   Hinweis: Üblicherweise ruft man lv_tick_inc() in einem Timer/Interrupt auf.
   Hier ist es im Loop – ok für einfache Demos.
*/
void loop() {
  lv_timer_handler();
  lv_tick_inc(5);
  delay(5);
}

/* ----------------------------- Button Callbacks -----------------------------
   Jeder Button toggelt seinen Status und ruft refreshBrightness() auf,
   um PWM + UI-Farbe zu aktualisieren.
*/
static void on_btn_red_press(lv_event_t *e) {
  (void)e; // Event wird nicht ausgewertet
  statusLedRed = !statusLedRed;
  refreshBrightness();
}

static void on_btn_green_press(lv_event_t *e) {
  (void)e;
  statusLedGreen = !statusLedGreen;
  refreshBrightness();
}

static void on_btn_blue_press(lv_event_t *e) {
  (void)e;
  statusLedBlue = !statusLedBlue;
  refreshBrightness();
}

/* ----------------------------- PWM Hilfsfunktion -----------------------------
   Invertierte PWM wegen gemeinsamer Kathode:
   - brightness = 0   -> PWM 255 (aus)
   - brightness = 255 -> PWM 0   (voll an)

   Du arbeitest "logisch" mit brightness 0..255, und wandelst nur beim Output um.
*/
static inline int pwmValue(int brightness) {
  brightness = constrain(brightness, 0, 255);
  return 255 - brightness;
}

/* ----------------------------- LED + UI aktualisieren -----------------------------
   - Rechnet PWM-Werte
   - setzt Pins per analogWrite()
   - ändert Button-Farbe (hell = aus / dunkel = an), damit der Status sichtbar ist
*/
static void refreshBrightness() {
  // Debug-Ausgabe: zeigt Status der drei Kanäle
  Serial.print("R=");
  Serial.print(statusLedRed);
  Serial.print(" G=");
  Serial.print(statusLedGreen);
  Serial.print(" B=");
  Serial.println(statusLedBlue);

  // PWM für "an" und "aus" vorbereiten
  int pwmOn  = pwmValue(brightness);
  int pwmOff = pwmValue(0);  // -> 255 (aus) wegen invertierter Logik

  /* Rot */
  if (statusLedRed) {
    Serial.println("LED_ROT");
    analogWrite(LED_RED, pwmOn);
    lv_obj_set_style_bg_color(btn_red, COLOR_RED_DARK, 0);
  } else {
    analogWrite(LED_RED, pwmOff);
    lv_obj_set_style_bg_color(btn_red, COLOR_RED, 0);
  }

  /* Grün */
  if (statusLedGreen) {
    analogWrite(LED_GREEN, pwmOn);
    lv_obj_set_style_bg_color(btn_green, COLOR_GREEN_DARK, 0);
  } else {
    analogWrite(LED_GREEN, pwmOff);
    lv_obj_set_style_bg_color(btn_green, COLOR_GREEN, 0);
  }

  /* Blau */
  if (statusLedBlue) {
    analogWrite(LED_BLUE, pwmOn);
    lv_obj_set_style_bg_color(btn_blue, COLOR_BLUE_DARK, 0);
  } else {
    analogWrite(LED_BLUE, pwmOff);
    lv_obj_set_style_bg_color(btn_blue, COLOR_BLUE, 0);
  }
}

/* ----------------------------- Slider Callback -----------------------------
   Wird ausgelöst, sobald sich der Slider-Wert ändert.
   - Liest den aktuellen Slider-Wert (0..255)
   - Aktualisiert das Label
   - Ruft refreshBrightness() auf, damit aktive Kanäle die neue Helligkeit übernehmen
*/
static void slider_event_cb(lv_event_t *e) {
  (void)e;

  // Aktuellen Slider-Wert abfragen
  // (Hier nutzt du die globale slider-Variable - das ist ok, aber event-target wäre sauberer.)
  brightness = lv_slider_get_value(slider);

  // Label updaten
  char buf[17];
  lv_snprintf(buf, sizeof(buf), "Helligkeit: %d", brightness);
  lv_label_set_text(slider_label, buf);
  lv_obj_align_to(slider_label, slider, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);

  // LED aktualisieren, falls Kanäle aktiv sind
  refreshBrightness();
}

Letzte Aktualisierung am: 23. Januar 2026

Foto von Stefan Draeger
Über den Autor

Stefan Draeger — Entwickler & Tech-Blogger

Ich zeige praxisnah, wie du Projekte mit Arduino, ESP32 und Smarthome-Komponenten umsetzt – Schritt für Schritt, mit Code und Schaltplänen.

Mehr Artikel von Stefan →

Schreibe einen Kommentar Antwort abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert

Fragen oder Feedback?

Du hast eine Idee, brauchst Hilfe oder möchtest Feedback loswerden?
Support-Ticket erstellen

Newsletter abonnieren

Bleib auf dem Laufenden: Erhalte regelmäßig Updates zu neuen Projekten, Tutorials und Tipps rund um Arduino, ESP32 und mehr – direkt in dein Postfach.

Jetzt Newsletter abonnieren

Unterstütze meinen Blog

Wenn dir meine Inhalte gefallen, freue ich mich über deine Unterstützung auf Tipeee.
So hilfst du mit, den Blog am Leben zu halten und neue Beiträge zu ermöglichen.

draeger-it.blog auf Tipeee unterstützen

Vielen Dank für deinen Support!
– Stefan Draeger

Kategorien

Tools

  • QR-Code Generator
  • Unix-Zeitstempel-Rechner
  • ASCII Tabelle
  • Spannung, Strom, Widerstand und Leistung berechnen
  • Widerstandsrechner
  • 8×8 LED Matrix Tool
  • 8×16 LED Matrix Modul von Keyestudio
  • 16×16 LED Matrix – Generator

Links

Blogverzeichnis Bloggerei.de TopBlogs.de das Original - Blogverzeichnis | Blog Top Liste Blogverzeichnis trusted-blogs.com

Stefan Draeger
Königsberger Str. 13
38364 Schöningen
Tel.: 015565432686
E-Mail: info@draeger-it.blog

Folge mir auf

link zu Fabook
link zu LinkedIn
link zu YouTube
link zu TikTok
link zu Pinterest
link zu Instagram
  • Impressum
  • Datenschutzerklärung
  • Disclaimer
  • Cookie-Richtlinie (EU)
©2026 Technik Blog | Built using WordPress and Responsive Blogily theme by Superb
Cookie-Zustimmung verwalten
Wir verwenden Technologien wie Cookies, um Geräteinformationen zu speichern und/oder darauf zuzugreifen. Wir tun dies, um das Surferlebnis zu verbessern und um personalisierte Werbung anzuzeigen. Wenn Sie diesen Technologien zustimmen, können wir Daten wie das Surfverhalten oder eindeutige IDs auf dieser Website verarbeiten. Wenn Sie Ihre Zustimmung nicht erteilen oder zurückziehen, können bestimmte Funktionen beeinträchtigt werden.
Funktional Immer aktiv
Die technische Speicherung oder der Zugang ist unbedingt erforderlich für den rechtmäßigen Zweck, die Nutzung eines bestimmten Dienstes zu ermöglichen, der vom Teilnehmer oder Nutzer ausdrücklich gewünscht wird, oder für den alleinigen Zweck, die Übertragung einer Nachricht über ein elektronisches Kommunikationsnetz durchzuführen.
Vorlieben
Die technische Speicherung oder der Zugriff ist für den rechtmäßigen Zweck der Speicherung von Präferenzen erforderlich, die nicht vom Abonnenten oder Benutzer angefordert wurden.
Statistiken
Die technische Speicherung oder der Zugriff, der ausschließlich zu statistischen Zwecken erfolgt. Die technische Speicherung oder der Zugriff, der ausschließlich zu anonymen statistischen Zwecken verwendet wird. Ohne eine Vorladung, die freiwillige Zustimmung deines Internetdienstanbieters oder zusätzliche Aufzeichnungen von Dritten können die zu diesem Zweck gespeicherten oder abgerufenen Informationen allein in der Regel nicht dazu verwendet werden, dich zu identifizieren.
Marketing
Die technische Speicherung oder der Zugriff ist erforderlich, um Nutzerprofile zu erstellen, um Werbung zu versenden oder um den Nutzer auf einer Website oder über mehrere Websites hinweg zu ähnlichen Marketingzwecken zu verfolgen.
  • Optionen verwalten
  • Dienste verwalten
  • Verwalten von {vendor_count}-Lieferanten
  • Lese mehr über diese Zwecke
Einstellungen anzeigen
  • {title}
  • {title}
  • {title}