Позвонить Telegram Viber
График работы: пн–пт 9:00–18:00

Підключення GC9A01 до ESP32-S3 — робоче налаштування TFT_eSPI та годинник по Wi-Fi

DIY32

Час збірки: 30 хв

Складність: низька

Компоненти

  • Display GC9A01
  • ESP32 S3 WROOM 30pin
  • DuPont

Підключення дисплея GC9A01 до ESP32-S3 — робоче налаштування TFT_eSPI та годинник по Wi-Fi

Використовуємо:

Arduino IDE та бібліотека TFT_eSPI

 Круглі TFT дисплеї GC9A01 з роздільною здатністю 240×240 — популярне рішення для годинників, міні-панелей, погодних станцій та інтерфейсів на ESP32. Найчастіше такі дисплеї продаються як “1.28 IPS GC9A01 SPI display” і чудово підходять для проєктів з Wi-Fi та анімацією.

 У моєму випадку спочатку планувалося підключення до ESP32-S2 Mini, оскільки в нього достатньо вільних GPIO. Однак із підключеним дисплеєм плата поводилася нестабільно — були проблеми з прошивкою та завантаженням.

 Після переходу на ESP32-S3 WROOM все запрацювало нормально без додаткових маніпуляцій.

  •  Наприкінці статті — робоча конфігурація TFT_eSPI та готова логіка годинника по Wi-Fi.

Що вийшло в результаті

За підсумками налаштування ви отримаєте:

  • повністю робочий дисплей GC9A01 на ESP32-S3
  • коректну роботу через TFT_eSPI
  • підключення до Wi-Fi
  • автоматичну синхронізацію часу через інтернет
  • перемикання між аналоговим та цифровим годинником
  • готову базу для DIY годинника або smart display проєкту
  • Налаштування TFT_eSPI для GC9A01

Для роботи знадобиться бібліотека TFT_eSPI. Після встановлення бібліотеки відкрийте файл та налаштуйте конфігураційний файл:

C:\Users\Name\Documents\Arduino\libraries\TFT_eSPI\User_Setup.h

У User_Setup.h вкажіть наступні параметри:

#define GC9A01_DRIVER

#define TFT_WIDTH  240
#define TFT_HEIGHT 240

#define TFT_CS     15  // Chip Select
#define TFT_DC     23  // Data/Command
#define TFT_RST    4   // Reset
#define TFT_MOSI   12  // SPI Data
#define TFT_SCLK   14  // SPI Clock

#define LOAD_GLCD
#define LOAD_FONT2
#define LOAD_FONT4
#define LOAD_FONT6
#define LOAD_FONT7
#define LOAD_FONT8
#define LOAD_GFXFF

#define SPI_FREQUENCY  27000000

#define USER_SETUP_ID 931


Відповідність пінів: GC9A01 | ESP32-S3 (продубльовано на зображенні)

Висновок дисплеяПідключення до ESP32‑S3
VCC3.3V
GNDGND
SCL / CLKGPIO 14
SDA / DINGPIO 12
DCGPIO 23
CSGPIO 15
RSTGPIO 4
BL3.3V

Підсвічування BL краще одразу підключити до 3.3V, інакше екран може залишатися повністю чорним навіть при успішній ініціалізації SPI.

Важливий нюанс із ESP32-S2

Під час тестування ESP32-S2 Mini працював нестабільно при підключеному дисплеї.

Симптоми були такі:

  • плата не завжди прошивалася
  • іноді зависало завантаження
  • COM-порт міг зникати після підключення дисплея

Точної причини визначити не вдалося, але після переходу на ESP32-S3 проблема повністю зникла.

Якщо дисплей заважає прошивці:

спробуйте тимчасово вимкнути живлення дисплея

перевірте GPIO boot strap піни

або використовуйте ESP32-S3

Логіка роботи проєкту

Дисплей використовується як Wi-Fi годинник з двома режимами відображення:

  • аналоговий годинник
  • цифровий годинник
  • Час автоматично синхронізується через інтернет за NTP.
  • На GPIO 22 можна підключити кнопку:
  • при подачі LOW рівня відображення перемикається між цифровим та аналоговим інтерфейсом.

Чому відмовився від зображення циферблата

Спочатку була спроба використати готове зображення циферблата у вигляді масиву bitmap, але з’явилися проблеми:

  • сильна пікселізація
  • великий обсяг масиву
  • незручно змінювати дизайн
  • зайва витрата пам’яті

У підсумку виявилося простіше і краще малювати інтерфейс прямо в коді:

  • лінії,
  • стрілки,
  • кола,
  • текст.

Так зображення виглядає охайніше і легше масштабується під різні проєкти.

Перевірка після прошивки

Після завантаження скетчу перевірте:

  • чи з’являється зображення
  • чи працює підсвічування
  • чи підключається Wi-Fi
  • чи оновлюється час
  • чи реагує кнопка на GPIO 22

Можливі проблеми

Чорний екран

Найчастіше:

  • не підключено BL
  • помилка в SPI пінах
  • поганий контакт Dupont

Білий екран

Зазвичай:

  • проблема з ініціалізацією SPI
  • невірне підключення DC або CS
  • Не прошивається ESP32

Можливий конфлікт boot GPIO при підключеному дисплеї.

Артефакти або сміття на екрані

Підсумок

GC9A01 — один із найзручніших круглих SPI дисплеїв для ESP32 проєктів.

Що важливо враховувати:

  • ESP32-S3 працює з ним помітно стабільніше
  • TFT_eSPI повністю вирішує завдання
  • підсвічування BL обов’язкове
  • графіку зручніше малювати кодом, а не bitmap масивами

Головне правило:

Якщо дисплей визначається, але екран порожній — спочатку перевіряйте BL, SPI піни та частоту SPI, а не сам код.

Code for Arduino ADE
#include <wifi.h>
#include <time.h>
#include <tft_espi.h>

TFT_eSPI tft = TFT_eSPI();

// Настройки часов
#define CENTER_X 120
#define CENTER_Y 120
#define RADIUS 100
#define DOT_RADIUS 3
#define TRACK_RADIUS 110

// Пины
#define PIN_BUTTON 22
#define DEBOUNCE_DELAY 200

// Wi-Fi настройки
const char* ssid = "YouWiFi";
const char* password = "YouPASS";
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 3 * 3600;
const int daylightOffset_sec = 0;

// Переменные времени
struct tm timeinfo;
uint8_t last_second = 61;
bool showAnalogClock = true;

// Позиции стрелок
int16_t last_hx, last_hy, last_mx, last_my;

// Прототипы функций
void drawClockFace();
void updateAnalogClock(uint8_t prevSecond);
void drawDotAtSecond(uint8_t sec, uint16_t color);
void displayDigitalTime();

void setup() {
  Serial.begin(115200);
  pinMode(PIN_BUTTON, INPUT_PULLUP);

  tft.init();
  tft.setRotation(2);
  tft.fillScreen(TFT_BLACK);

  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.setTextSize(2);
  tft.drawString("CONNECTING...", CENTER_X, CENTER_Y);

  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }

  tft.fillRect(CENTER_X - 80, CENTER_Y - 10, 160, 20, TFT_BLACK);
  tft.drawString("CONNECTED!", CENTER_X, CENTER_Y);
  delay(1000);

  configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
  while (!getLocalTime(&timeinfo)) {
    Serial.println("Waiting for time...");
    delay(500);
  }

  drawClockFace();
}

void loop() {
  static uint32_t lastUpdate = 0;

  if (digitalRead(PIN_BUTTON) == LOW) {
    delay(DEBOUNCE_DELAY);
    if (digitalRead(PIN_BUTTON) == LOW) {
      showAnalogClock = !showAnalogClock;
      tft.fillScreen(TFT_BLACK);
      if (showAnalogClock) drawClockFace();
      while (digitalRead(PIN_BUTTON) == LOW) delay(10);
    }
  }

  if (millis() - lastUpdate >= 1000) {
    lastUpdate = millis();

    if (getLocalTime(&timeinfo)) {
      if (timeinfo.tm_sec != last_second) {
        uint8_t prev_second = last_second;
        last_second = timeinfo.tm_sec;

        if (showAnalogClock) {
          updateAnalogClock(prev_second);
        } else {
          displayDigitalTime();
        }
      }
    }
  }
}

void drawClockFace() {
  tft.fillScreen(TFT_BLACK);
  tft.drawCircle(CENTER_X, CENTER_Y, RADIUS, TFT_WHITE);

  // Цифры 12, 3, 6, 9
  const char* labels[] = {"12", "3", "6", "9"};
  int angles[] = {270, 0, 90, 180};  // поправлено

  tft.setTextSize(2);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);

  for (int i = 0; i < 4; i++) {
    float angle = angles[i] * PI / 180.0;
    int x = CENTER_X + (RADIUS - 25) * cos(angle);
    int y = CENTER_Y + (RADIUS - 25) * sin(angle);
    tft.drawString(labels[i], x, y);
  }

  // Минутные метки
  for (int i = 0; i < 60; i++) {
    float angle = i * 6 * PI / 180;
    int length = (i % 5 == 0) ? 12 : 6;
    int x1 = CENTER_X + (RADIUS - length) * cos(angle);
    int y1 = CENTER_Y + (RADIUS - length) * sin(angle);
    int x2 = CENTER_X + RADIUS * cos(angle);
    int y2 = CENTER_Y + RADIUS * sin(angle);
    tft.drawLine(x1, y1, x2, y2, (i % 5 == 0) ? TFT_WHITE : TFT_DARKGREY);
  }
}

void updateAnalogClock(uint8_t prevSecond) {
  // Стираем старые стрелки и точку
  tft.drawLine(CENTER_X, CENTER_Y, last_hx, last_hy, TFT_BLACK);
  tft.drawLine(CENTER_X, CENTER_Y, last_mx, last_my, TFT_BLACK);
  drawDotAtSecond(prevSecond, TFT_BLACK);

  // Часовая стрелка
  float hourAngle = (timeinfo.tm_hour % 12 + timeinfo.tm_min / 60.0) * PI / 6;
  last_hx = CENTER_X + RADIUS * 0.5 * sin(hourAngle);
  last_hy = CENTER_Y - RADIUS * 0.5 * cos(hourAngle);

  // Минутная стрелка
  float minAngle = timeinfo.tm_min * PI / 30;
  last_mx = CENTER_X + RADIUS * 0.7 * sin(minAngle);
  last_my = CENTER_Y - RADIUS * 0.7 * cos(minAngle);

  // Рисуем часовую и минутную стрелки
  tft.drawLine(CENTER_X, CENTER_Y, last_hx, last_hy, TFT_WHITE);
  tft.drawLine(CENTER_X, CENTER_Y, last_mx, last_my, TFT_WHITE);

  // Рисуем текущую точку-секунду
  drawDotAtSecond(timeinfo.tm_sec, TFT_RED);
}

void drawDotAtSecond(uint8_t sec, uint16_t color) {
  float angle = sec * PI / 30;
  int x = CENTER_X + TRACK_RADIUS * sin(angle);
  int y = CENTER_Y - TRACK_RADIUS * cos(angle);
  tft.fillCircle(x, y, DOT_RADIUS, color);
}

void displayDigitalTime() {
  static char timeStr[9];
  sprintf(timeStr, "%02d:%02d:%02d", timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);

  tft.setTextSize(3);
  tft.setTextColor(TFT_WHITE, TFT_BLACK);
  tft.setTextDatum(MC_DATUM);
  tft.drawString(timeStr, CENTER_X, CENTER_Y);
}

З цим купують:

  • Круглий TFT-дисплей 1.28" GC9A01 для Arduino, ESP32, STM32.

    Дисплей 1.28" TFT LCD GC9A01 — купити для Arduino, ESP32, STM32 | IPS, SPI, 240×240
    320 грн
    ДЕТАЛЬНІШЕ
  • Кабель DuPont мама-мама 20 см (2.54 мм) для Arduino та макетних плат

    Кабель DuPont мама-мама 20 см (2.54 мм) для Arduino та макетних плат
    2 грн
    ДЕТАЛЬНІШЕ
  • Кабель DuPont тато > мама 20 см (2.54 мм)

    Кабель DuPont тато > мама 20 см (2.54 мм)
    2 грн
    ДЕТАЛЬНІШЕ
  • Корпус для ESP32 WROOM 32D 30 pin (з доступом до пінів)

    Корпус для ESP32 WROOM 32D 30 pin (з доступом до пінів)
    60 грн
    ДЕТАЛЬНІШЕ

Коментарі до статті

Артем22.05.2026
Не заработало пока не догадался указать настройки своей Wi Fi в скетче :)
Модератор- 22.05.2026
Дякуємо за відгук!

Додати коментар