ESP32 is a very versatile microcontroller with lots of computing power for lots of application. Here’s a fun project using RTC and LED matrix to build a hi-tech analog clock.
About the circuit
If you are not yet familiar with RTC module, it is used basically for time keeping.


LED MATRIX and ESP32 connection:
#define R1_PIN 19
#define G1_PIN 13
#define B1_PIN 18
#define R2_PIN 5
#define G2_PIN 12
#define B2_PIN 17
#define A_PIN 16
#define B_PIN 14
#define C_PIN 4
#define D_PIN 27
#define E_PIN 25 //–> required for 1/32 scan panels, like 64x64px. Any available pin would do, i.e. IO32.
#define LAT_PIN 26
#define OE_PIN 15
#define CLK_PIN 2
Here is the pinout of the HUB75E LED Matrix module.

Just follow along. I do not have time to draw the schematic for now. π
For the RTC module, it is simply connected to I2c line and 3.3V power. The RTC module have built-in battery so it can still track time even when the system has no power.

I have not time yet to put these schematic together. If I do, I will update this page so you can visit it on later date.
Arduino Code
Here’s the code that I have written. It is not yet that clean but at least it works. π
//>>>>>>>>>>>>>>>>>>>>>>>>>>>> tataylino.com <<<<<<<<<<<<<<<<<<<<<<<<<
// Dot matrix clock using RTC module, ESP32, and LED Matrix module
// by: tataylino.com
// Reference:
// https://randomnerdtutorials.com/esp32-ds3231-real-time-clock-arduino/
// https://github.com/mrcodetastic/ESP32-HUB75-MatrixPanel-DMA
//------ Including the libraries ----------
#include <ESP32-HUB75-MatrixPanel-I2S-DMA.h>
#include "time.h"
#include "RTClib.h"
RTC_DS3231 rtc;
char daysOfTheWeek[7][12] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
//----------------------------------------Defines the connected PIN between P5 and ESP32.
#define R1_PIN 19
#define G1_PIN 13
#define B1_PIN 18
#define R2_PIN 5
#define G2_PIN 12
#define B2_PIN 17
#define A_PIN 16
#define B_PIN 14
#define C_PIN 4
#define D_PIN 27
#define E_PIN 25 //--> required for 1/32 scan panels, like 64x64px. Any available pin would do, i.e. IO32.
#define LAT_PIN 26
#define OE_PIN 15
#define CLK_PIN 2
//----------------------------------------
//----------------------------------------Defines the P5 Panel configuration.
#define PANEL_RES_X 64 //--> Number of pixels wide of each INDIVIDUAL panel module.
#define PANEL_RES_Y 64 //--> Number of pixels tall of each INDIVIDUAL panel module.
#define PANEL_CHAIN 1 //--> Total number of panels chained one to another
//----------------------------------------
// Initialize MatrixPanel_I2S_DMA as "dma_display".
MatrixPanel_I2S_DMA *dma_display = nullptr;
//----------------------------------------Variable for color.
// color565(0, 0, 0); --> RGB color code. Use the "color picker" to use or find another color code.
uint16_t myBLACK = dma_display->color565(0, 0, 0);
uint16_t myWHITE = dma_display->color565(255, 255, 255);
uint16_t myRED = dma_display->color565(255, 0, 0);
uint16_t myGREEN = dma_display->color565(0, 255, 0);
uint16_t myBLUE = dma_display->color565(0, 0, 255);
uint16_t clock_bg = dma_display->color565(10, 10, 0);
// ************* Clock Colors ************
// value is from 0 to 15 only
// Second hand
uint8_t s_hand_r = 12;
uint8_t s_hand_g = 1;
uint8_t s_hand_b = 1;
// Minute Hand
uint8_t m_hand_r = 1;
uint8_t m_hand_g = 1;
uint8_t m_hand_b = 12;
// hour hand
uint8_t h_hand_r = 1;
uint8_t h_hand_g = 12;
uint8_t h_hand_b = 1;
// numbers
uint8_t num_r = 1;
uint8_t num_g = 10;
uint8_t num_b = 10;
// dots
uint8_t dot_r = 10;
uint8_t dot_g = 10;
uint8_t dot_b = 1;
// Screen Brightness
uint8_t led_brightness = 50; // 0 to 255
//----------------------------------------
#define clear() matrix.fillScreen(0)
#define show() matrix.swapBuffers(false)
#define WIDTH 64
#define HEIGHT 64
const byte CENTRE_X = 32;
const byte CENTRE_Y = 32;
uint8_t second;
uint8_t second_h;
uint8_t minute =1;
uint8_t minute_h =1;
uint8_t hour;
uint8_t hour_h;
void drawclockNB() {
// Draw clock Numbers
dma_display->setTextColor(dma_display->color444(num_r, num_g, num_b));
dma_display->setTextSize(1);
dma_display->setCursor(26, 4);
dma_display->println("12");
dma_display->setCursor(43, 8);
dma_display->println("1");
dma_display->setCursor(51, 15);
dma_display->println("2");
dma_display->setCursor(56, 29);
dma_display->println("3");
dma_display->setCursor(51, 40);
dma_display->println("4");
dma_display->setCursor(43, 49);
dma_display->println("5");
dma_display->setCursor(30, 54);
dma_display->println("6");
dma_display->setCursor(17, 49);
dma_display->println("7");
dma_display->setCursor(8, 40);
dma_display->println("8");
dma_display->setCursor(4, 29);
dma_display->println("9");
dma_display->setCursor(7, 17);
dma_display->println("10");
dma_display->setCursor(14, 8);
dma_display->println("11");
}
void drawclockDot() {
// Draw clock dots
float radians, angle;
for (int i = 0; i < 60; i+=5) {
//uint16_t color = MyColor[(co0+i*5)%92];
angle = 360 - i * 6;
radians = radians(angle);
int x0 = CENTRE_X + 30 * sin(radians);
int y0 = CENTRE_Y + 30 * cos(radians);
//matrix.fillCircle(x0, y0, 1, color);
dma_display->drawCircle(x0, y0, 1, dma_display->color444(dot_r, dot_g, dot_b));
}
}
void draw_hand(uint8_t val, uint8_t lenght, uint8_t r, uint8_t g, uint8_t b ){
float radians, angle;
val = val + 30; // offset
angle = 360 - val * 6;
radians = radians(angle);
int x0 = CENTRE_X + lenght * sin(radians);
int y0 = CENTRE_Y + lenght * cos(radians);
dma_display->drawLine(CENTRE_X, CENTRE_Y, x0, y0, dma_display->color444(r, g, b));
}
void update_time(void){
dma_display->clearScreen();
dma_display->fillScreen(clock_bg);
drawclockNB();
drawclockDot();
//draw_minute(minute);
draw_hand(second_h, 22, s_hand_r, s_hand_g, s_hand_b); // Second Hand
draw_hand(minute_h, 18, m_hand_r, m_hand_g, m_hand_b); // Minute Hand
draw_hand(hour_h, 12, h_hand_r, h_hand_g, h_hand_b); // Hour Hand
}
//________________________________________________________________________________VOID SETUP()
void setup() {
Serial.begin(115200);
Serial.println("tataylino.com - Starting clock");
delay(1000);
// Initialize the connected PIN between Panel P5 and ESP32.
HUB75_I2S_CFG::i2s_pins _pins={R1_PIN, G1_PIN, B1_PIN, R2_PIN, G2_PIN, B2_PIN, A_PIN, B_PIN, C_PIN, D_PIN, E_PIN, LAT_PIN, OE_PIN, CLK_PIN};
delay(10);
//----------------------------------------Module configuration.
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, //--> module width.
PANEL_RES_Y, //--> module height.
PANEL_CHAIN, //--> Chain length.
_pins //--> pin mapping.
);
delay(10);
//----------------------------------------
// Set I2S clock speed.
mxconfig.i2sspeed = HUB75_I2S_CFG::HZ_10M; // I2S clock speed, better leave as-is unless you want to experiment.
delay(10);
//----------------------------------------Display Setup.
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
dma_display->begin();
dma_display->setBrightness8(led_brightness); //--> 0-255.
//----------------------------------------
dma_display->clearScreen();
dma_display->fillScreen(myGREEN);
delay(500);
dma_display->fillScreen(myBLUE);
delay(500);
dma_display->clearScreen();
delay(100);
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
while (1) delay(10);
}
if (rtc.lostPower()) {
Serial.println("RTC lost power, let's set the time!");
// When time needs to be set on a new device, or after a power loss, the
// following line sets the RTC to the date & time this sketch was compiled
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
//rtc.adjust(DateTime(2014, 1, 21, 3, 0, 0));
}
// When time needs to be re-set on a previously configured device, the
// following line sets the RTC to the date & time this sketch was compiled
//rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
// This line sets the RTC with an explicit date & time, for example to set
// January 21, 2014 at 3am you would call:
//rtc.adjust(DateTime(2025, 6, 22, 20, 20, 0));
Serial.println("Initialization done!");
}
//________________________________________________________________________________
//________________________________________________________________________________VOID LOOP()
void loop() {
//unsigned long currentMillis = millis();
// Get the current time from the RTC
DateTime now = rtc.now();
// Getting each time field in individual variables
// And adding a leading zero when needed;
String yearStr = String(now.year(), DEC);
String monthStr = (now.month() < 10 ? "0" : "") + String(now.month(), DEC);
String dayStr = (now.day() < 10 ? "0" : "") + String(now.day(), DEC);
String hourStr = (now.hour() < 10 ? "0" : "") + String(now.hour(), DEC);
String minuteStr = (now.minute() < 10 ? "0" : "") + String(now.minute(), DEC);
String secondStr = (now.second() < 10 ? "0" : "") + String(now.second(), DEC);
String dayOfWeek = daysOfTheWeek[now.dayOfTheWeek()];
// Complete time string
String formattedTime = dayOfWeek + ", " + yearStr + "-" + monthStr + "-" + dayStr + " " + hourStr + ":" + minuteStr + ":" + secondStr;
// Print the complete formatted time
//Serial.println(formattedTime);
//Serial.println(now.hour());
//Serial.println(now.minute());
//Serial.println(now.second());
// initial settings
second = now.second();
minute = now.minute();
hour = now.hour();
if(hour>12) hour = hour - 12;
//compute hands position
second_h = second;
minute_h = minute;
hour_h = (hour * 5) + (minute/12);
update_time();
dma_display->drawCircle(CENTRE_X, CENTRE_Y, 33, dma_display->color444(10, 0, 0));
dma_display->drawCircle(CENTRE_X, CENTRE_Y, 1, dma_display->color444(10, 0, 0));
// Getting temperature
//Serial.print(rtc.getTemperature());
//Serial.println("ΒΊC");
//Serial.println();
delay(1000);
}
//________________________________________________________________________________
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Take note that you will need to install the RTC and ESP32-HUB75-MatrixPanel-I2S-DMA libraries to make it work.
Youtube Video: