Подключение дисплея 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 |
| VCC | 3.3V |
| GND | GND |
| SCL / CLK | GPIO 14 |
| SDA / DIN | GPIO 12 |
| DC | GPIO 23 |
| CS | GPIO 15 |
| RST | GPIO 4 |
| BL | 3.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);
}
Комментарии к статье
Добавить комментарий