Community project

ESP32-C6 Hello World Display

zypan13

Published August 20, 2026

ESP32
Photo of ESP32-C6 Hello World Display

This project brings the ESP32-C6 to life with a colorful display and interactive interface. The build combines an ST7789 TFT screen, NeoPixel RGB LED, and WiFi connectivity to create a smart desktop companion that displays time, date, moon phases, and network information with animated eyes and customizable pages.

The guide provides a complete wiring diagram, parts list, and step-by-step assembly instructions to connect the display, LED, and microcontroller. Included firmware handles WiFi configuration, web UI control, Unicode text rendering, and automatic page rotation—everything needed to get the device running and displaying content right out of the box.

Wiring diagram

Interactive · read-only

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

Assembly

2 steps
  1. Подключите плату

    Подключите Waveshare ESP32-C6-LCD-1.47 к компьютеру качественным USB-C кабелем передачи данных. Внешние модули и перемычки не требуются: дисплей и RGB-светодиод встроены в плату.

    • Tip: Расположите плату экраном вверх, чтобы не давить на LCD.
    • Tip: Не используйте проводящие поверхности под платой.
    • Отключайте USB-C перед любой физической доработкой платы.
  2. Проверьте автономный DeskBuddy

    После Deploy на LCD в горизонтальной ориентации автоматически сменяются три страницы: анимированное лицо, счётчик времени после включения и сведения об устройстве. Каждая страница показывается примерно 6 секунд.

    • Tip: Анимация моргания обновляет только области глаз, а счётчик — только область времени.
    • Tip: Встроенный RGB-светодиод загорается приглушённым зелёно-бирюзовым цветом после запуска.
    • На данной модели нет встроенных тач-контроллера и IMU, поэтому касания, свайпы и реакция на наклон не предусмотрены.

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_ST7789.h>
#include <Adafruit_NeoPixel.h>
#include <U8g2_for_Adafruit_GFX.h>
#include "DeskBuddy.h"
#include "Settings.h"
#include "Services.h"
#include "WebUi.h"
#include "EveEyes.h"

#ifndef ST77XX_DARKGREY
#define ST77XX_DARKGREY 0x7BEF
#endif


// Forward declarations
bool online();
String clip(const String &s, uint8_t n);
static void selectUnicodeFont(uint8_t textSize);
void centerText(const String &s, int16_t y, uint8_t size, uint16_t color);
static void wrappedCenterText(const String &s, int16_t y, uint8_t size, uint16_t color, int16_t maxWidth);
void drawDots();
static void drawFacePage();
static void networkPage(const String &title, const String &a, const String &b, const String &c);
static void drawClock();
static void updateClock();
static void drawDate();
static void drawMoon();
void showPage();
static void connectWiFi();
static Page nextEnabledAutoPage(Page current);

static constexpr int TFT_CS_PIN = 14, TFT_DC_PIN = 15, TFT_RST_PIN = 21;
static constexpr int TFT_SCLK_PIN = 7, TFT_MOSI_PIN = 6, TFT_BL_PIN = 22, RGB_LED_PIN_NUM = 8;
Adafruit_ST7789 tft(TFT_CS_PIN, TFT_DC_PIN, TFT_RST_PIN);
Adafruit_NeoPixel statusLed(1, RGB_LED_PIN_NUM, NEO_GRB + NEO_KHZ800);
U8G2_FOR_ADAFRUIT_GFX unicodeFont;
WebServer server(80);
Page page = FACE_PAGE;
uint32_t pageStartedAt = 0, lastDataAt = 0;
static uint32_t lastClockSecond = UINT32_MAX, lastWiFiRetryAt = 0;

bool online() { return WiFi.status() == WL_CONNECTED; }
String clip(const String &s, uint8_t n) { return s.length() <= n ? s : s.substring(0, n - 1) + "..."; }

static void selectUnicodeFont(uint8_t textSize) {
  int level = fontPreset; if (textSize >= 4) level = 2;
  if (level == 0) unicodeFont.setFont(u8g2_font_6x12_t_cyrillic);
  else if (level == 1) unicodeFont.setFont(u8g2_font_unifont_t_cyrillic);
  else unicodeFont.setFont(u8g2_font_10x20_t_cyrillic);
  unicodeFont.setFontMode(1);
}
void centerText(const String &s, int16_t y, uint8_t size, uint16_t color) {
  tft.setTextWrap(false);
  if (russianLanguage) {
    selectUnicodeFont(size); unicodeFont.setForegroundColor(color);
    uint16_t w = unicodeFont.getUTF8Width(s.c_str());
    int16_t baseline = constrain((size <= 1 ? 11 : size == 2 ? 15 : 19) + fontPreset - 1, 11, 19);
    unicodeFont.setCursor(max<int16_t>(0, (SCREEN_WIDTH - w) / 2), y + baseline); unicodeFont.print(s); return;
  }
  tft.setTextSize(scaledTextSize(size)); tft.setTextColor(color, ST77XX_BLACK);
  int16_t x, yy; uint16_t w, h; tft.getTextBounds(s, 0, y, &x, &yy, &w, &h);
  tft.setCursor((SCREEN_WIDTH - w) / 2, y); tft.print(s);
}
static void wrappedCenterText(const String &s, int16_t y, uint8_t size, uint16_t color, int16_t maxWidth) {
  if (!russianLanguage) { centerText(clip(s, 40), y, size, color); return; }
  selectUnicodeFont(size); if (unicodeFont.getUTF8Width(s.c_str()) <= maxWidth) { centerText(s, y, size, color); return; }
  int split = -1;
  for (uint16_t i = 0; i < s.length(); ++i) if (s.charAt(i) == ' ') { String part = s.substring(0, i); if (unicodeFont.getUTF8Width(part.c_str()) <= maxWidth) split = i; else break; }
  if (split < 1) { centerText(s, y, size, color); return; }
  centerText(s.substring(0, split), y, size, color); centerText(s.substring(split + 1), y + (fontPreset == 2 ? 24 : 20), size, color);
}
void drawDots() { for (uint8_t i = 0; i < PAGE_COUNT; i++) tft.fillCircle(112 + i * 16, 164, 3, i == page ? ST77XX_CYAN : ST77XX_DARKGREY); }

static void drawFacePage() {
  eveEyesEnter(faceMood, randomEmotions, wifiEyeMotion, online() ? WiFi.RSSI() : -90, online());
  drawDots();
}
static void networkPage(const String &title, const String &a, const String &b, const String &c) {
  tft.fillScreen(ST77XX_BLACK); centerText(title, 14, 1, ST77XX_CYAN); wrappedCenterText(a, 46, 2, ST77XX_WHITE, 300); wrappedCenterText(b, 92, 1, ST77XX_WHITE, 300); wrappedCenterText(c, 132, 1, ST77XX_DARKGREY, 300); drawDots();
}
static void drawClock() { tft.fillScreen(ST77XX_BLACK); centerText(tr("CLOCK", "ЧАСЫ"), 18, 1, ST77XX_CYAN); lastClockSecond = UINT32_MAX; drawDots(); }
static void updateClock() {
  time_t now = time(nullptr); if (now < 100000) { centerText(tr("Syncing time...", "Синхронизация..."), 75, 2, ST77XX_WHITE); return; }
  tm ti; localtime_r(&now, &ti); if ((uint32_t)ti.tm_sec == lastClockSecond) return; lastClockSecond = ti.tm_sec;
  char text[12]; strftime(text, sizeof(text), "%H:%M:%S", &ti); tft.fillRect(25, 58, 270, 44, ST77XX_BLACK); centerText(text, 62, 4, ST77XX_WHITE); centerText(online() ? tr("NTP synchronized", "NTP синхронизирован") : tr("Clock running", "Часы работают"), 122, 1, ST77XX_DARKGREY);
}
static void drawDate() {
  tft.fillScreen(ST77XX_BLACK); centerText(tr("DATE", "ДАТА"), 18, 1, ST77XX_CYAN); time_t now = time(nullptr);
  if (now < 100000) centerText(tr("Waiting for time sync", "Ожидание времени"), 80, 1, ST77XX_WHITE);
  else {
    static const char *const weekdaysEn[] = {"Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"};
    static const char *const weekdaysRu[] = {"Воскресенье","Понедельник","Вторник","Среда","Четверг","Пятница","Суббота"};
    static const char *const monthsEn[] = {"January","February","March","April","May","June","July","August","September","October","November","December"};
    static const char *const monthsRu[] = {"января","февраля","марта","апреля","мая","июня","июля","августа","сентября","октября","ноября","декабря"};
    tm ti; localtime_r(&now, &ti); String weekday = russianLanguage ? weekdaysRu[ti.tm_wday] : weekdaysEn[ti.tm_wday]; String date = String(ti.tm_mday) + " " + (russianLanguage ? monthsRu[ti.tm_mon] : monthsEn[ti.tm_mon]) + " " + String(ti.tm_year + 1900); centerText(weekday, 62, 2, ST77XX_WHITE); centerText(date, 96, 1, ST77XX_CYAN);
  } drawDots();
}
static void drawMoon() {
  time_t now = time(nullptr); if (now < 100000) { networkPage(tr("MOON", "ЛУНА"), tr("Waiting for time", "Ожидание времени"), "", ""); return; }
  double phase = fmod(difftime(now, 947182440) / 86400.0, 29.53058867) / 29.53058867; if (phase < 0) phase += 1;
  String name = phase < .03 || phase > .97 ? tr("New Moon", "Новолуние") : phase < .22 ? tr("Waxing Crescent", "Растущий серп") : phase < .28 ? tr("First Quarter", "Первая четверть") : phase < .47 ? tr("Waxing Gibbous", "Растущая луна") : phase < .53 ? tr("Full Moon", "Полнолуние") : phase < .72 ? tr("Waning Gibbous", "Убывающая луна") : phase < .78 ? tr("Last Quarter", "Последняя четверть") : tr("Waning Crescent", "Убывающий серп");
  networkPage(tr("MOON", "ЛУНА"), name, tr("Illumination ", "Освещённость ") + String((int)(100 * (1 - fabs(2 * phase - 1)))) + "%", tr("Local calculation", "Локальный расчёт"));
}
static Page nextEnabledAutoPage(Page current) {
  // Search once around the seven pages; the mask always contains at least one page.
  for (uint8_t step = 1; step <= PAGE_COUNT; ++step) {
    Page candidate = (Page)(((uint8_t)current + step) % PAGE_COUNT);
    if (autoSlideMask & (1U << (uint8_t)candidate)) return candidate;
  }
  return FACE_PAGE;
}
void showPage() {
  if (page == FACE_PAGE) drawFacePage(); else if (page == CLOCK_PAGE) drawClock(); else if (page == DATE_PAGE) drawDate(); else if (page == WEATHER_PAGE) networkPage(tr("WEATHER", "ПОГОДА"), isnan(temperatureC) ? "—" : String(temperatureC, 1) + " C", weatherText, city); else if (page == MOON_PAGE) drawMoon(); else if (page == STOCK_PAGE) networkPage(tr("STOCK", "АКЦИИ"), stockSymbol, stockText, online() ? (stockProvider == "moex" ? "MOEX · 10 min" : "Yahoo · 10 min") : tr("Wi-Fi required", "Нужен Wi-Fi")); else networkPage(tr("GITHUB", "ГИТХАБ"), githubUser.length() ? "@" + githubUser : tr("Set a username", "Укажите имя"), githubText, online() ? tr("GitHub API · 10 min", "GitHub API · 10 мин") : tr("Wi-Fi required", "Нужен Wi-Fi"));
}
static void connectWiFi() { WiFiManager manager; manager.setConfigPortalTimeout(180); manager.setConnectTimeout(20); bool ok = manager.autoConnect("DeskBuddy-Setup", "deskbuddy"); statusLed.setPixelColor(0, ok ? statusLed.Color(0,20,4) : statusLed.Color(20,8,0)); statusLed.show(); }
void setup() {
  Serial.begin(115200); ledcAttach(TFT_BL_PIN, 5000, 8); statusLed.begin(); statusLed.setBrightness(20); statusLed.setPixelColor(0, statusLed.Color(0,0,18)); statusLed.show();
  SPI.begin(TFT_SCLK_PIN, -1, TFT_MOSI_PIN, TFT_CS_PIN); SPI.setFrequency(27000000); tft.init(172, 320); tft.setRotation(1); unicodeFont.begin(tft); loadSettings(); ledcWrite(TFT_BL_PIN, brightness); eveEyesBegin(); connectWiFi(); startServer(); if (online()) refreshData(); showPage(); pageStartedAt = millis();
}
void loop() {
  uint32_t now = millis(); server.handleClient();
  if (!online() && now - lastWiFiRetryAt >= WIFI_RETRY_MS) { lastWiFiRetryAt = now; WiFi.reconnect(); }
  if (online() && now - lastDataAt >= DATA_REFRESH_MS) refreshData();
  if (autoSlideshow && now - pageStartedAt >= pageMs) { page = nextEnabledAutoPage(page); pageStartedAt = now; showPage(); }
  if (page == FACE_PAGE) eveEyesUpdate(faceMood, randomEmotions, wifiEyeMotion, online() ? WiFi.RSSI() : -90, online());
  if (page == CLOCK_PAGE) updateClock();
}

“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