image 1 e1772034722175 Saravati

Como criar seu próprio relógio digital com ESP32, display redondo e RTC

Já pensou em criar o seu próprio relógio, entendendo exatamente como cada parte funciona do hardware ao código? Neste tutorial, vou mostrar passo a passo como construí um relógio digital funcional usando:

Ao final, você terá um relógio que: mostra hora, minuto e segundo tem segundos “ticando” de verdade mantém a hora mesmo desligado da energia e usa um layout moderno, estilo smartwatch.

Conceito do projeto:

A ideia aqui não é comprar um smartwatch pronto, mas sim:

  • entender como um relógio realmente funciona
  • aprender comunicação SPI e I²C
  • trabalhar com gráficos em display
  • usar um RTC profissional (DS3231)

Esse tipo de projeto é excelente para quem quer sair do básico e começar a desenvolver sistemas eletrônicos reais.

image Saravati

Componentes utilizados

  • ESP32-S3 (ou ESP32 comum)
  • Display redondo GC9A01 (240×240, SPI)
  • RTC DS3231
  • Protoboard e jumpers
  • Cabo USB
  • Bateria CR2032 ou LIR2032 (para o RTC)

Ligações do hardware

Display GC9A01 → ESP32
Display ESP32
VCC 3V3
GND GND
SCK / SCL GPIO 12
SDA / MOSI GPIO 11
CS GPIO 10
DC GPIO 9
RST GPIO 8

Importante: o display usa SPI, mesmo que alguns pinos estejam escritos como “SCL” e “SDA”.

RTC DS3231 → ESP32

Neste projeto, o RTC é alimentado por um GPIO configurado como saída HIGH, pois não
havia outro ponto 3V3 disponível.

RTC ESP32
VCC GPIO 6
GND GND

SDA GPIO 4
SCL GPIO 5

Bibliotecas necessárias

Instale as seguintes bibliotecas pelo Gerenciador da IDE Arduino:

  • Adafruit GFX Library
  • Adafruit GC9A01A
  • RTClib (Adafruit)

Lógica do funcionamento

O projeto funciona assim:

  1. O RTC DS3231 fornece a hora real (HH:MM:SS)
  2. O ESP32 lê a hora via I²C
  3. O display GC9A01 é controlado via SPI
  4. A tela é atualizada somente quando o segundo muda
  5. Um “tick” visual mostra o avanço dos segundos no aro do relógio

Isso garante: animação suave; zero atraso; menor consumo de processamento

Código completo do projeto

IMPORTANTE: A linha rtc.adjust(…) deve ser usada apenas uma vez para ajustar a hora
inicial. Depois disso, comente essa linha.

include <SPI.h>

include <Wire.h>

include <Adafruit_GFX.h>

include <Adafruit_GC9A01A.h>

include <RTClib.h>

include <math.h>

// ================== PINOS DO DISPLAY ==================

define PIN_SCK 12

define PIN_MOSI 11

define PIN_CS 10

define PIN_DC 9

define PIN_RST 8

// ======================================================
// ================== RTC (DS3231) ======================

define I2C_SDA 4

define I2C_SCL 5

define RTC_VCC_PIN 6

// ======================================================
Adafruit_GC9A01A tft(PIN_CS, PIN_DC, PIN_RST);
RTC_DS3231 rtc;
static const int CX = 120;
static const int CY = 120;
static int lastSecond = -1;

define C_BG 0x0000

define C_WHITE 0xFFFF

define C_LGREY 0xC618

define C_DGREY 0x7BEF

define C_RED 0xF800

int prevTickX = -1, prevTickY = -1;
void drawBezel() {
tft.fillScreen(C_BG);
tft.drawCircle(CX, CY, 119, C_DGREY);
tft.drawCircle(CX, CY, 118, C_DGREY);
tft.drawCircle(CX, CY, 117, C_DGREY);

for (int i = 0; i < 60; i++) {
float a = (i * 6.0f - 90.0f) * DEG_TO_RAD;
int r1 = (i % 5 == 0) ? 96 : 102;
int r2 = 110;
int x1 = CX + cos(a) * r1;
int y1 = CY + sin(a) * r1;
int x2 = CX + cos(a) * r2;
int y2 = CY + sin(a) * r2;
tft.drawLine(x1, y1, x2, y2,
(i % 5 == 0) ? C_LGREY : C_DGREY);
}
tft.fillRoundRect(28, 86, 184, 72, 16, C_BG);
tft.drawRoundRect(28, 86, 184, 72, 16, C_DGREY);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(1);
tft.setTextColor(C_LGREY, C_BG);
tft.drawString("RTC TIME", CX, 92);
}
void drawTime(int hh, int mm, int ss) {
tft.fillRect(28, 100, 184, 56, C_BG);
char buf[12];
snprintf(buf, sizeof(buf), "%02d:%02d:%02d", hh, mm, ss);
tft.setTextDatum(MC_DATUM);
tft.setTextColor(C_WHITE, C_BG);
tft.setTextSize(3);
tft.drawString(buf, CX, CY + 8);
}
void erasePrevTick() {
if (prevTickX >= 0) {
tft.fillCircle(prevTickX, prevTickY, 4, C_BG);
}
}
void drawTickDot(int ss) {
float a = (ss * 6.0f - 90.0f) * DEG_TO_RAD;
int r = 112;
int x = CX + cos(a) * r;
int y = CY + sin(a) * r;
erasePrevTick();

tft.fillCircle(x, y, 4, C_RED);
prevTickX = x;
prevTickY = y;
}
void setup() {
Serial.begin(115200);
pinMode(RTC_VCC_PIN, OUTPUT);
digitalWrite(RTC_VCC_PIN, HIGH);
Wire.begin(I2C_SDA, I2C_SCL);
rtc.begin();
// Ajuste inicial (use uma vez)
// rtc.adjust(DateTime(2026, 1, 26, 17, 57, 0));
SPI.begin(PIN_SCK, -1, PIN_MOSI, PIN_CS);
tft.begin();
tft.setRotation(0);
drawBezel();
DateTime now = rtc.now();
drawTime(now.hour(), now.minute(), now.second());
drawTickDot(now.second());
lastSecond = now.second();
}
void loop() {
DateTime now = rtc.now();
if (now.second() != lastSecond) {
lastSecond = now.second();
drawTime(now.hour(), now.minute(), now.second());
drawTickDot(now.second());
}
}

Próximos passos

Com essa base, você pode evoluir facilmente para:

  • cronômetro
  • modo corrida
  • contagem de passos (sensor)
  • sincronização por Wi-Fi (NTP)
  • múltiplos watchfaces

Saiba mais sobre a parceria Saravati e Sara Educação

Este “Guia de Montagem” é uma colaboração especial entre a Saravati e a Sara Educação, criado pelo Professor Felipe Rosa. Nosso objetivo é enriquecer a comunidade de entusiastas da eletrônica, IoT e automação com recursos educacionais de alta qualidade. Através dessa parceria, buscamos inspirar e capacitar criadores em seus projetos, disponibilizando esses guias em nosso blog e nas redes sociais.

@saravatirobotica no Instagram e Tiktok

@sara.educacao no Instagram e Tiktok