Dzielę się działającym przykładem podłączenia okrągłego wyświetlacza GC9A01 (240×240 px) do mikrokontrolera ESP32-S3 WROOM (30 pinów). Początkowo chciałem użyć mojego ulubionego ESP32-S2 Mini, ponieważ ma wiele wyprowadzeń, ale z tym układem mi się nie udało (prawdopodobnie nie wgrywał się z podłączonym wyświetlaczem), więc przeszedłem na ESP32-S3 — i wszystko od razu zadziałało jak należy.
Zakładam, że masz już zainstalowane Arduino IDE. Do pracy z wyświetlaczem potrzebna będzie biblioteka TFT_eSPI. Po jej zainstalowaniu otwórz plik konfiguracyjny User_Setup.h, który znajduje się w ścieżce:
C:\Users\You\Documents\Arduino\libraries\TFT_eSPI
W tym pliku ustaw następujące parametry:
#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
Próbowałem wgrać obraz tarczy zegara jako tablicę, ale jakość była zbyt pikselowa. Ostatecznie zdecydowałem się rysować grafikę bezpośrednio w kodzie projektu — wyszło elastycznie i schludnie.
Poniżej — schemat podłączenia:

Podaję również kod podłączenia mikrokontrolera do Twojej sieci Wi-Fi i pobierania czasu z internetu. W kodzie należy podać swój SSID i hasło, a w razie potrzeby zmienić strefę czasową.
Do GPIO 22 można podłączyć przycisk — po podaniu niskiego poziomu na ten pin wyświetlanie czasu przełącza się między wersją analogową a cyfrową.
#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);
}