Last Updated on 7. November 2026 by sfambachLink-Status: OK
Runde Displays sind einfach schick. Für Uhren, Tachos, Messinstrumente oder kleine Augen in Robotern gibts kaum was Besseres als das 1,28″-TFT mit dem GC9A01-Controller. Ich habe meins beim schnellen Ali für ein paar Euro bestellt, angeschlossen wird es per SPI.
In diesem Beitrag geht es um den Anschluss an ESP8266, ESP32 und ESP32-C3, die Libraries, die es dafür gibt, und ein paar Demos. Angefangen hat es mit einer Orakelkugel-Animation: Ihr kennt die schwarzen Wahrsagekugeln, die man schüttelt und die dann „Ja“ oder „Nein“ sagen. Genau das, nur ohne Schütteln. Dazu kamen dann noch Kreise, ein Radio-Tuner, ein Oszi und ein Radar.




Fakten
- Modul: ZJY_IPS128_8P (Aufdruck Zhongjingyuan)
- Display: 1,28″ IPS-TFT, rund, hoher Betrachtungswinkel
- Auflösung: 240 × 240 Pixel (der Speicher ist quadratisch, die Ecken sieht man halt nicht)
- Farben: 65K (RGB565)
- Controller: GC9A01 (bei Adafruit GC9A01A)
- Schnittstelle: SPI (4-Draht), nur Schreiben, kein MISO
- Sichtbarer Bereich: Ø 32,4 mm
- Panel: 35,6 × 38,1 × 1,5 mm
- Platine: 44 × 36 mm, vier Befestigungslöcher Ø 2 mm
- Arbeitsspannung: 2,8–3,3 V
- Anschluss: 8 Pins im 2,54-mm-Raster: GND, VCC, SCL, SDA, RES, DC, CS, BLK
- Arbeitstemperatur: −20 °C bis 70 °C, Luftfeuchtigkeit 5–95 % RH
Pinbelegung
| Pin | Signal | Farbe |
|---|---|---|
| 1 | GND | — |
| 2 | VCC | — |
| 3 | SCL | — |
| 4 | SDA | — |
| 5 | RES | — |
| 6 | DC | — |
| 7 | CS | — |
| 8 | BLK | — |
Anschluss an ESP8266, ESP32 und ESP32-C3
Nicht verwirren lassen: Die Pins heißen auf dem Modul SCL und SDA wie bei I²C, es ist aber SPI. SCL ist der Takt (SCK), SDA die Datenleitung (MOSI). Wichtig: Das Modul verträgt laut Händler nur 2,8–3,3 V, also VCC an 3V3 und nicht an 5 V.
Ich wollte die gleiche Verdrahtung für den Wemos D1 MiniLink-Status: OK (ESP8266) und den D1 Mini ESP32Link-Status: OK. Das geht, weil beide Boards den Hardware-SPI auf D5 (SCK) und D7 (MOSI) haben. Nur die GPIO-Nummern dahinter sind verschieden, die übersetzt der Sketch selbst.
| Display | D1-Pin | ESP8266 (D1 Mini, NodeMCU) | ESP32 (D1 Mini ESP32, DevKit) | C3 Mini | ESP32-C3 Super Mini | Funktion |
|---|---|---|---|---|---|---|
| VCC | 3V3 | 3,3 V | 3,3 V | 3,3 V | 3,3 V | Versorgung |
| GND | G | GND | GND | GND | GND | Masse |
| SCL | D5 | GPIO 14 | GPIO 18 | GPIO 1 | GPIO 4 | SPI-Takt (SCK) |
| SDA | D7 | GPIO 13 | GPIO 23 | GPIO 4 | GPIO 6 | SPI-Daten (MOSI) |
| CS | D8 | GPIO 15 | GPIO 5 | GPIO 5 | GPIO 7 | Chip Select |
| DC | D3 | GPIO 0 | GPIO 17 | GPIO 7 | GPIO 5 | Daten/Befehl |
| RES | D0 | GPIO 16 | GPIO 26 | GPIO 2 | GPIO 3 | Reset |
| BLK | – | 3,3 V | 3,3 V | 3,3 V | GPIO 10 | Hintergrundbeleuchtung |
Getestet habe ich nur mit dem ESP32-C3 Super Mini. Die Belegungen für D1 Mini (ESP8266), C3 Mini und ESP32 sind aus den Pinouts der Boards abgeleitet und von mir nicht selbst getestet.
Boot-Pins beim D1 Mini (ESP8266)
Das betrifft nur den ESP8266: D8 hat einen Pull-down und D3/D4 einen Pull-up, die alle drei beim Booten den richtigen Pegel haben müssen. An D8 (CS) und D3 (DC) hängen nur Eingänge des Displays, das stört nicht. BLK hängt direkt an 3V3, weil ein Pin, der beim Booten nach unten gezogen wird, den ESP8266 in den Flash-Modus schickt. D1 und D2 bleiben frei für I²C-Shields.
| Pin D1 Mini (ESP8266) | GPIO | Pin Display |
|---|---|---|
| 3V3 | 3,3 V | VCC |
| G | GND | GND |
| D5 | GPIO 14 | SCL |
| D7 | GPIO 13 | SDA |
| D0 | GPIO 16 | RES |
| D3 | GPIO 0 | DC |
| D8 | GPIO 15 | CS |
| 3V3 | 3,3 V | BLK |
C3 Mini (ESP32-C3)
Der Wemos C3 MiniLink-Status: OK hat das gleiche Format, also gleiche Steckplätze. Beim C3 liegt der SPI aber nicht von selbst auf D5/D7, der Sketch legt ihn per SPI.begin() dorthin, das geht beim C3 an jedem Pin. Zwei Kleinigkeiten: An D3 (GPIO 7) hängt auch die RGB-LED, die kann beim Zeichnen bunt mitzucken. D0 ist GPIO 2, ein Boot-Pin, am RES-Eingang stört der aber nicht.
| Pin C3 Mini | GPIO | Pin Display |
|---|---|---|
| 3V3 | 3,3 V | VCC |
| G | GND | GND |
| D5 | GPIO 1 | SCL |
| D7 | GPIO 4 | SDA |
| D0 | GPIO 2 | RES |
| D3 | GPIO 7 | DC |
| D8 | GPIO 5 | CS |
| 3V3 | 3,3 V | BLK |
ESP32-C3 Super Mini
Der ESP32-C3 Super MiniLink-Status: OK hat kein D1-Format, deshalb eigene Pins. Ich nehme die Standard-SPI-Pins des C3 (SCK 4, MOSI 6, CS 7) und lasse die Boot-Pins GPIO 2, 8 und 9 frei. Weil hier genug Pins übrig sind, schaltet GPIO 10 die Hintergrundbeleuchtung.

| Pin ESP32-C3 Super Mini | GPIO | Pin Display |
|---|---|---|
| 3.3 | 3,3 V | VCC |
| G | GND | GND |
| 4 | GPIO 4 | SCL |
| 6 | GPIO 6 | SDA |
| 3 | GPIO 3 | RES |
| 5 | GPIO 5 | DC |
| 7 | GPIO 7 | CS |
| 10 | GPIO 10 | BLK |
D1 Mini ESP32 und andere ESP32-Boards
Beim ESP32 liegen D3 (GPIO 17) und D0 (GPIO 26) nicht auf Boot-Pins. D8 ist GPIO 5, der wird beim Booten zwar abgefragt, am CS-Eingang des Displays stört das aber nicht. Hier könnte BLK also auch an einen freien GPIO, der Einheitlichkeit wegen bleibt er an 3V3.
| Pin D1 Mini ESP32 | GPIO | Pin Display |
|---|---|---|
| 3V3 | 3,3 V | VCC |
| G | GND | GND |
| D5 | GPIO 18 | SCL |
| D7 | GPIO 23 | SDA |
| D0 | GPIO 26 | RES |
| D3 | GPIO 17 | DC |
| D8 | GPIO 5 | CS |
| 3V3 | 3,3 V | BLK |
Wer ein normales ESP32-Board (DevKit) nimmt, verdrahtet nach der ESP32-Spalte. Ausnahme: Auf Boards mit WROVER-Modul belegt das PSRAM GPIO 16 und 17, dann DC auf einen anderen Pin legen und im Sketch anpassen.
Libraries
Für das Display gibt es eine ganze Reihe Libraries. Alle können Text, Linien, Kreise und Bilder, die Unterschiede liegen in Geschwindigkeit, Konfiguration und darin, wie gut sie mit dem aktuellen ESP32-Core 3.x klarkommen.
| Library | Konfiguration | Tempo | ESP32-Core 3.x | Gut für |
|---|---|---|---|---|
| TFT_eSPILink-Status: OK (Bodmer) | User_Setup.h im Library-Ordner | sehr schnell, DMA, Sprites | Abstürze bzw. Compile-Fehler berichtet, Workaround Core 2.0.x | bestehende Projekte, riesige Beispielsammlung |
| LovyanGFXLink-Status: OK | im Sketch (Klasse) | sehr schnell, DMA, Sprites | läuft | ESP32-Projekte mit Animationen, viele Fonts mit dabei |
| Adafruit GC9A01ALink-Status: OK | zwei Zeilen im Sketch | langsamer, kein DMA | läuft | schneller Test, Adafruit-GFX-Welt |
| GFX Library for ArduinoLink-Status: OK (Arduino_GFX) | wenige Zeilen im Sketch | schnell, Canvas | läuft | viele Displays und Busse, auch andere Controller |
| LVGLLink-Status: OK | braucht einen der Treiber oben | – | läuft | Oberflächen: Zeiger, Skalen, Buttons |
Standard im Testsketch ist Adafruit GC9A01ALink-Status: OK, LovyanGFX ist per Schalter in der Konfiguration wählbar. Die Library ist klein, läuft auf ESP8266 und ESP32 ohne Anpassung, und die Konfiguration sind genau drei Pins im Konstruktor. Dazu kommen Adafruit GFXLink-Status: OK (Grafik und Fonts) und Adafruit BusIOLink-Status: OK, beides holt der Library-Manager gleich mit. Für eine Animation in einem Teilbereich reicht das Tempo locker.
Wer mehr Tempo braucht, etwa für Vollbild-Animationen oder Bilder, schaltet um auf LovyanGFXLink-Status: OK: DMA, Sprites und viele Fonts, die Konfiguration steht im Sketch. TFT_eSPILink-Status: OK ist der Klassiker, aber ein ziemlicher Brocken, die Konfiguration liegt in der Library selbst, und mit dem ESP32-Core 3.x gibt es viele Berichte über einen Absturz in tft.begin(), siehe Issue 3346Link-Status: OK. Für das Display gibt es dort das fertige Setup Setup46_GC9A01_ESP32.h.
Wer lieber in Python bastelt: Für MicroPython gibt es den Treiber gc9a01_mpyLink-Status: OK von Russ Hughes.
Testsketche
Der Sketch zeigt auf schwarzem Grund nacheinander „Ja“, „Nein“ und „Vielleicht“. Jede Antwort taucht wie bei der Kugel langsam aus dem Dunkel auf: Die Schrift wird in drei Sekunden von Schwarz zu Weiß, bleibt kurz stehen, verblasst wieder, und dann ist das Display dunkel. Danach kommt die nächste Antwort.
Damit nichts flackert, wird jedes Bild erst im RAM gezeichnet und dann zum Display geschickt. Ein Vollbild bräuchte 115 KB, das hat der ESP8266 nicht. Deshalb zeichnet der Sketch nur den Textbereich, und das in zwei Streifen à 36 Zeilen. Ein Streifen braucht 16 KB, das passt auf alle Boards.



Konfiguration
Library und Board werden in der Datei config.h per Präprozessor gewählt. DISPLAY_LIBRARY schaltet zwischen Adafruit GC9A01A (Standard) und LovyanGFX um, TARGET_BOARD wählt das Board. Bleibt es auf BOARD_AUTO, nimmt der Sketch das Standard-Board zum Chip, der in der IDE eingestellt ist: ESP8266 → ESP8266 dev, ESP32 → ESP32 dev, ESP32-C3 → C3 Super Mini. Für den C3 Mini also BOARD_LOLIN_C3_MINI eintragen. Die Pins selbst stehen in boards.h.
#pragma once
// Graphics library
#define LIBRARY_ADAFRUIT 1 // Adafruit GC9A01A + Adafruit GFX: small, easy
#define LIBRARY_LOVYANGFX 2 // LovyanGFX: faster, DMA on ESP32
#define DISPLAY_LIBRARY LIBRARY_ADAFRUIT
// Board, BOARD_AUTO takes the default board of the chip selected in the Arduino IDE:
// ESP8266 -> BOARD_ESP8266_DEV, ESP32 -> BOARD_ESP32_DEV, ESP32-C3 -> BOARD_ESP32C3_SUPER_MINI
#define BOARD_AUTO 0
#define BOARD_ESP8266_DEV 1 // NodeMCU, Wemos D1 Mini and other ESP8266 boards
#define BOARD_ESP32_DEV 2 // ESP32 DevKit, D1 Mini ESP32
#define BOARD_ESP32C3_SUPER_MINI 3
#define BOARD_LOLIN_C3_MINI 4 // Wemos/LOLIN C3 Mini (D1 Mini form factor)
#define TARGET_BOARD BOARD_AUTO
Installation
- In der Arduino IDE das Boardpaket installieren: „esp8266Link-Status: OK“ für den ESP8266, „esp32Link-Status: OK“ von Espressif für ESP32 und ESP32-C3.
- Im Library-Manager „Adafruit GC9A01A“ installieren und Adafruit GFX und Adafruit BusIO mitinstallieren lassen. Für die andere Variante „LovyanGFX“.
- Den Ordner
OracleBallaus dem GitHub-RepositoryLink-Status: OK herunterladen undOracleBall.inoöffnen. - In
config.hLibrary und ggf. Board wählen, Board in der IDE einstellen, hochladen, fertig.
Sketch
Quellcode OracleBall.ino (GitHub: esp_round_display_demoLink-Status: OK). Die Pin-Tabelle boards.h und die beiden Library-Anpassungen display_adafruit.h und display_lovyangfx.h liegen dort mit im Ordner.
// Oracle ball demo for the 1.28" round GC9A01 TFT (240x240, SPI).
// Answers fade in from black to white: Ja -> Nein -> Vielleicht -> dark, endlessly.
// Library and board are chosen in config.h, the pins live in boards.h.
//
// Copyright (c) 2026 Stefan Fambach (SteFam), https://www.fambach.net
// License: PolyForm Noncommercial 1.0.0, https://polyformproject.org/licenses/noncommercial/1.0.0/
#include "config.h"
#include "boards.h"
#if DISPLAY_LIBRARY == LIBRARY_ADAFRUIT
#include "display_adafruit.h"
#elif DISPLAY_LIBRARY == LIBRARY_LOVYANGFX
#include "display_lovyangfx.h"
#else
#error "Unknown DISPLAY_LIBRARY in config.h"
#endif
const char* const ANSWERS[] = { "Ja", "Nein", "Vielleicht" };
constexpr int ANSWER_COUNT = sizeof(ANSWERS) / sizeof(ANSWERS[0]);
// Only the text area is redrawn, in horizontal bands: flicker-free like a frame
// buffer, but 2 x 16 KB instead of 115 KB, so it also fits into the ESP8266.
constexpr int AREA_X = 10, AREA_Y = 84, AREA_W = 220, AREA_H = 72;
constexpr int BAND_H = 36;
constexpr int TEXT_X = AREA_W / 2; // area coordinates
constexpr int TEXT_Y = AREA_H / 2;
constexpr int TEXT_MAX_W = 200;
constexpr uint32_t FADE_IN_MS = 3000;
constexpr uint32_t HOLD_MS = 2000;
constexpr uint32_t FADE_OUT_MS = 1500;
constexpr uint32_t DARK_MS = 1000;
constexpr uint32_t CYCLE_MS = FADE_IN_MS + HOLD_MS + FADE_OUT_MS + DARK_MS;
static RoundDisplay display(AREA_W, BAND_H);
static int answerFont[ANSWER_COUNT];
static float smooth(float t) {
return t * t * (3.0f - 2.0f * t);
}
static int largestFittingFont(const char* text) {
for (int font = 0; font < RoundDisplay::FONT_COUNT - 1; ++font) {
if (display.textWidth(text, font) <= TEXT_MAX_W) {
return font;
}
}
return RoundDisplay::FONT_COUNT - 1;
}
static void drawAnswer(int answer, float level) {
const uint8_t brightness = static_cast<uint8_t>(255 * level);
const uint16_t textColor = display.color(brightness, brightness, brightness);
for (int bandTop = 0; bandTop < AREA_H; bandTop += BAND_H) {
display.clearBand();
display.drawCenteredText(ANSWERS[answer], answerFont[answer], TEXT_X, TEXT_Y - bandTop, textColor);
display.pushBand(AREA_X, AREA_Y + bandTop);
}
}
void setup() {
Serial.begin(115200);
if (PINS.backlight >= 0) {
pinMode(PINS.backlight, OUTPUT);
digitalWrite(PINS.backlight, HIGH);
}
if (!display.begin()) {
Serial.println("Not enough RAM for the drawing band");
while (true) {
delay(1000);
}
}
for (int answer = 0; answer < ANSWER_COUNT; ++answer) {
answerFont[answer] = largestFittingFont(ANSWERS[answer]);
}
}
void loop() {
static int lastAnswer = -1;
const uint32_t now = millis();
const int answer = (now / CYCLE_MS) % ANSWER_COUNT;
const uint32_t phaseTime = now % CYCLE_MS;
if (answer != lastAnswer) {
lastAnswer = answer;
Serial.println(ANSWERS[answer]);
}
float level = 0.0f;
if (phaseTime < FADE_IN_MS) {
level = smooth(static_cast<float>(phaseTime) / FADE_IN_MS);
} else if (phaseTime < FADE_IN_MS + HOLD_MS) {
level = 1.0f;
} else if (phaseTime < FADE_IN_MS + HOLD_MS + FADE_OUT_MS) {
level = 1.0f - smooth(static_cast<float>(phaseTime - FADE_IN_MS - HOLD_MS) / FADE_OUT_MS);
}
drawAnswer(answer, level);
delay(10); // also feeds the ESP8266 watchdog
}
Die Zeiten für Einblenden, Stehen, Ausblenden und Dunkelphase stehen als Konstanten oben im Sketch. Wer es echter haben will, hängt einen Erschütterungssensor oder einen Taster dran und würfelt die Antwort mit random() aus.
Enhanced-Variante
Wer es näher am Original mag: Im Repository liegt zusätzlich OracleBallEnhanced. Da steigt die Antwort auf einem blauen Dreieck wie auf dem Würfel in der Kugel ein Stück nach oben und blendet dabei ein. Konfiguration und Verdrahtung sind gleich.

Kreise
Das dritte Programm im Repository heißt Circles. Bunte Ringe erscheinen vom Rand nach innen, bis die Mitte erreicht ist, danach verschwinden sie von der Mitte nach außen wieder. Abstand, Dicke und Farbe der Ringe werden aus der Anzahl RING_COUNT und der Displaygröße berechnet, nichts ist fest eingetragen. Passt gut zum runden Display.

Radio-Tuner
Das vierte Programm, RadioTuner, sieht aus wie die Skala eines alten UKW-Radios, nur rund. Ein Marker gleitet von Sender zu Sender (FFH, Radio Bob, hr3, hr1), die Frequenz zählt mit, und ist der Sender eingestellt, blendet sein Name in der Mitte ein. Die Sender stehen samt Frequenz und Farbe in einer Liste oben im Sketch. Die Frequenzen sind Beispielwerte für den Raum Frankfurt, die tragt ihr am besten für eure Gegend ein.

Oszilloskop
Oscilloscope macht aus dem Display ein altes Oszi mit grüner Röhre: Raster mit Teilstrichen, darauf läuft eine Sinuskurve mit leichtem Nachleuchten, die Amplitude atmet langsam. Teilung, Wellenlänge und Tempo sind Konstanten oben im Sketch.

Radar
Radar zeigt einen drehenden Radarstrahl mit nachglühender Spur über Entfernungsringen. Ab und an taucht ein zufälliger Feindkontakt auf. Er leuchtet rot auf, sobald der Strahl drüberfährt, glimmt bis zur nächsten Runde nach, driftet langsam und verschwindet irgendwann wieder. Unten stehen Peilung und Entfernung des stärksten Kontakts.

Probleme
Farben invertiert
Schwarz wird weiß: Die Module sind nicht alle gleich. Bei Adafruit in display_adafruit.h nach tft.begin() ein tft.invertDisplay(false); einfügen, bei LovyanGFX in display_lovyangfx.h invert auf false stellen.
Display bleibt schwarz
Verdrahtung von SCL/SDA prüfen, das sind SCK und MOSI, nicht I²C. BLK muss an 3V3 (beim Super Mini an GPIO 10), sonst bleibt die Hintergrundbeleuchtung aus. Bei TFT_eSPI mit Core 3.x siehe oben.
C3 Mini und C3 Super Mini verwechselt
Die beiden heißen fast gleich, haben aber völlig andere Pins. Der C3 Mini hat das D1-Mini-Format, der Super Mini nicht. Wer nach der falschen Spalte verkabelt, bekommt nur die Hintergrundbeleuchtung zu sehen. Also in der Tabelle genau die richtige Spalte nehmen und im Zweifel in der config.h das Board fest eintragen.
ESP8266 startet nicht mehr
Hängt am D1 Mini etwas an D3, D4 oder D8, das den Pegel beim Booten verbiegt, bleibt er im Flash-Modus oder startet gar nicht. Display-Pins genau nach Tabelle stecken, BLK nicht an D3/D4.
„Not enough RAM for the drawing band“
Ein anderer Teil des Programms frisst den Speicher. BAND_H kleiner machen, dann wird in mehr, aber kleineren Streifen gezeichnet.
Fazit
Für ein paar Euro ist das runde Display ein richtig schickes Teil für Uhren, Anzeigen und Spielereien. Am ESP32-C3 Super Mini laufen alle Demos, angeschlossen mit acht Kabeln auf dem Steckbrett, die Videos zeigen es. Die anderen Boards habe ich nicht selbst getestet.
Mit Adafruit GC9A01A bleibt der Sketch klein und läuft auf allen Boards. Wer mehr Tempo will, schaltet in der config.h auf LovyanGFX um. Ob die Kugel mit ihren Antworten recht hat, müsst ihr allerdings selbst entscheiden 😉
Lizenz
- Hardware: kommerzielles Modul (Handelsware), keine offene Lizenz angegeben
- Software: Testsketch © Stefan Fambach, PolyForm Noncommercial 1.0.0Link-Status: Kaputt (frei, aber keine kommerzielle Nutzung ohne Einwilligung)
- Libraries: jeweils eigene Open-Source-Lizenz, siehe die verlinkten Repositories
- Marken: Namen und Marken gehören den jeweiligen Inhabern. Die Orakelkugel-Demo hat keine Verbindung zum Produkt „Magic 8 Ball“ von Mattel, Inc.
KI-Hinweis: Bei Recherche und Text hat KI mitgewirkt (Claude, Anthropic). Geprüft und verantwortet von Stefan Fambach.
Quellen
- Adafruit BusIOLink-Status: OK
- Adafruit GC9A01ALink-Status: OK
- Adafruit GFXLink-Status: OK
- AliExpress – rundes 1,28″-TFT GC9A01 (Bezugsquelle)Link-Status: OK
- Arduino-Forum – Adafruit GC9A01 läuft, TFT_eSPI nichtLink-Status: OK
- D1 Mini ESP32Link-Status: OK
- DroneBot Workshop – GC9A01 Round DisplaysLink-Status: OK
- esp32Link-Status: OK
- ESP32-C3 Super MiniLink-Status: OK
- esp8266Link-Status: OK
- esp_round_display_demoLink-Status: OK
- gc9a01_mpyLink-Status: OK
- GFX Library for ArduinoLink-Status: OK
- Issue 3346Link-Status: OK
- LovyanGFXLink-Status: OK
- LVGLLink-Status: OK
- PolyForm Noncommercial 1.0.0Link-Status: Kaputt
- TFT_eSPILink-Status: OK
- TFT_eSPI – Diskussion GC9A01 und ESP32-S3Link-Status: OK
- Voltlog #349 – GC9A01 mit Arduino und ESP32Link-Status: OK
- Wemos C3 MiniLink-Status: OK
- Wemos D1 MiniLink-Status: OK
| Datum | Änderung | Autor |
|---|---|---|
| 07.11.2026 | Beitrag erstellt | sfambach |