Community project

Round TFT Dashboard

Tom Gorman

Published July 28, 2026

ESP321 component4 assembly steps
Remix this project
Photo of Round TFT Dashboard

Build a charming round dashboard powered by an ESP32 and a 1.28-inch GC9A01 circular TFT display. This project combines WiFi connectivity and real-time clock synchronization to create a Minecraft-themed visual display that shows the current time with pixel-art clouds, terrain, and a golden sun on a 240x240 round screen.

This guide provides a complete parts list, wiring diagram for connecting the display to the ESP32, step-by-step assembly instructions, and ready-to-use firmware. Follow the assembly outline to verify power and signal connections, then configure the clock via WiFi to bring your dashboard to life.

Wiring diagram

Interactive · read-only
Wiring diagram for Round TFT Dashboard

Pan and zoom to explore the wiring. Remix the project to edit it in your own workspace.

Parts list

Bill of materials
ComponentQtyNotes
GC9A01 Round TFT LCD 1.28 inch 240x2401.28 in, 240×24011.28 inch round IPS TFT LCD display module with GC9A01/GC9A01A driver IC. 240x240 RGB resolution, 4-wire SPI interface, and 3.3V/5V module input. Module-side labels are VCC, GND, DIN, CLK, CS, DC, RST, and BL/BLK. The display is write-only over SPI, so no MISO line is required for the LCD. Supported by Adafruit GC9A01A and TFT_eSPI libraries in Arduino-compatible firmware projects.

Assembly

4 steps
  1. Disconnect USB power

    Unplug the ESP32-C3 SuperMini before touching or moving any of the display wires.

    • Tip: Keep the six signal wires short, as in your photo, to help the SPI display run reliably.
    • The ESP32-C3 GPIO pins are 3.3 V only; never connect any display signal to 5 V.
  2. Check power wiring

    Confirm the GC9A01 VCC wire goes to the ESP32 3V3 pin and the display GND wire goes to ESP32 GND.

    • Tip: The display needs a common ground with the ESP32.
    • Do not use the ESP32 5V pin for the display VCC connection shown in this project.
  3. Check the screen signal connections

    Confirm: display RST to GPIO 0, CS to GPIO 1, DC to GPIO 10, SDA/DIN/MOSI to GPIO 3, and SCL/CLK/SCK to GPIO 4. Leave MISO unconnected because this display does not send data back to the ESP32.

    • Tip: On this display, SDA means SPI data (MOSI), and SCL means SPI clock (SCK); they are not I2C wires.
    • Tip: If your module has BL or BLK, it may be left as its module default if the backlight already lights; otherwise connect BL to 3V3 for always-on backlight.
    • Inspect for adjacent solder bridges, especially around GPIO 0 and GPIO 1.
  4. Power and configure the clock

    Plug the ESP32 into USB power and use Schematik’s Deploy button. On its first run, connect a phone or computer to the Wi-Fi network named BlockClock-Setup and follow the captive page to give it your home Wi-Fi details. The display then obtains time from the internet in UTC.

    • Tip: The scene is deliberately original block-style pixel art: grass, dirt, stone, clouds, a square sun, and redstone-coloured accents.
    • Tip: After initial Wi-Fi setup, it reconnects automatically when that network is available.
    • If the screen stays blank, unplug power first and recheck VCC is 3.3 V, not 5 V.

Pin assignments

Board wiring reference
PinConnectionType
3V3gc9a01_display VCCpower
GNDgc9a01_display GNDground
GPIO 0gc9a01_display RSTdigital
GPIO 1gc9a01_display CSspi
GPIO 10gc9a01_display DCdigital
GPIO 3gc9a01_display MOSIspi
GPIO 4gc9a01_display SCKspi

Firmware

ESP32
main.cppDeploy to device
#include <Arduino.h>
#include <SPI.h>
#include <WiFi.h>
#include <WiFiManager.h>
#include <time.h>
#include <Adafruit_GFX.h>
#include <Adafruit_GC9A01A.h>

#define TFT_RST 0
#define TFT_CS 1
#define TFT_DC 10
#define TFT_MOSI 3
#define TFT_SCK 4


// Forward declarations
void block(int x, int y, int w, int h, uint16_t color);
void cloud(int x, int y);
void drawWorld();
void drawTime(int hour, int minute);
void drawSecondMarker(int second);
void statusLine(const char *message, uint16_t color);
void connectWiFi();
void synchroniseTime();

Adafruit_GC9A01A tft(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);

const uint16_t SKY = 0x5DDF;
const uint16_t GRASS = 0x3D86;
const uint16_t DIRT = 0x8A42;
const uint16_t STONE = 0x7BEF;
const uint16_t DEEPSLATE = 0x3186;
const uint16_t WHITE = 0xFFFF;
const uint16_t GOLD = 0xFEA0;
const uint16_t REDSTONE = 0xF800;
const uint16_t OBSIDIAN = 0x1082;

bool timeReady = false;
int lastMinute = -1;
int lastHour = -1;

void block(int x, int y, int w, int h, uint16_t color) {
  tft.fillRect(x, y, w, h, color);
}

void cloud(int x, int y) {
  block(x, y + 8, 40, 8, WHITE);
  block(x + 8, y, 24, 24, WHITE);
  block(x + 32, y + 8, 16, 8, WHITE);
}

void drawWorld() {
  tft.fillScreen(SKY);
  cloud(22, 45);
  cloud(170, 64);
  block(184, 27, 24, 24, GOLD);
  block(190, 21, 12, 36, GOLD);

  block(0, 166, 240, 10, GRASS);
  block(0, 176, 240, 28, DIRT);
  block(0, 204, 240, 36, DEEPSLATE);
  for (int x = 4; x < 240; x += 31) {
    block(x, 183 + ((x / 31) % 2) * 7, 12, 7, 0xA343);
    block(x + 12, 211 + ((x / 31) % 3) * 6, 14, 7, STONE);
  }

  tft.drawRoundRect(18, 78, 204, 80, 8, OBSIDIAN);
  tft.drawRoundRect(19, 79, 202, 78, 8, WHITE);
  tft.setTextWrap(false);
  tft.setTextColor(OBSIDIAN);
  tft.setTextSize(1);
  tft.setCursor(77, 88);
  tft.print("BLOCK TIME");
  tft.setCursor(76, 148);
  tft.print("NTP CLOCK");
  block(20, 80, 8, 8, REDSTONE);
  block(212, 80, 8, 8, REDSTONE);
  block(20, 149, 8, 8, REDSTONE);
  block(212, 149, 8, 8, REDSTONE);
}

void drawTime(int hour, int minute) {
  char text[6];
  snprintf(text, sizeof(text), "%02d:%02d", hour, minute);
  tft.fillRect(42, 101, 156, 42, WHITE);
  tft.setTextSize(5);
  tft.setTextColor(OBSIDIAN);
  tft.setCursor(46, 105);
  tft.print(text);
}

void statusLine(const char *message, uint16_t color) {
  tft.fillRect(43, 216, 154, 14, DEEPSLATE);
  tft.setTextSize(1);
  tft.setTextColor(color);
  tft.setCursor(50, 219);
  tft.print(message);
}

void connectWiFi() {
  WiFiManager wm;
  wm.setConfigPortalTimeout(180);
  statusLine("JOIN BLOCKCLOCK-SETUP", GOLD);
  if (wm.autoConnect("BlockClock-Setup")) {
    statusLine("WIFI CONNECTED", GRASS);
  } else {
    statusLine("WIFI OFFLINE", REDSTONE);
  }
}

void synchroniseTime() {
  setenv("TZ", "UTC0", 1);
  tzset();
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  struct tm now;
  timeReady = getLocalTime(&now, 12000);
  statusLine(timeReady ? "TIME SYNCED (UTC)" : "WAITING FOR NTP", timeReady ? GRASS : GOLD);
}

void setup() {
  Serial.begin(115200);
  tft.begin();
  tft.setRotation(0);
  drawWorld();
  statusLine("STARTING...", GOLD);
  connectWiFi();
  if (WiFi.status() == WL_CONNECTED) synchroniseTime();
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) {
    static uint32_t lastRetry = 0;
    if (millis() - lastRetry > 30000) {
      lastRetry = millis();
      WiFi.reconnect();
    }
    delay(50);
    return;
  }

  struct tm now;
  if (!getLocalTime(&now, 50)) {
    if (!timeReady) synchroniseTime();
    delay(250);
    return;
  }
  timeReady = true;

  // The pixel-art world remains untouched; only the numeric time changes.
  if (now.tm_min != lastMinute || now.tm_hour != lastHour) {
    drawTime(now.tm_hour, now.tm_min);
    lastMinute = now.tm_min;
    lastHour = now.tm_hour;
  }
  delay(50);
}

“Deploy to device” opens this project in Schematik, where you can flash it to your board over USB.

Remix this project

Make it yours in one click

Open a full copy of this project in your own Schematik workspace — diagram, code, parts, and assembly steps included. Swap the sensor, add features, or redesign the whole thing with AI. The author's original stays untouched.

Open in Schematik