The three displays are with I2C communication protocol and are controlled by a single microcontroller, but with the help of an I2C multiplexer module.
In this video I want to present you another unusual clock to add to my large collection of such DIY devices. Characteristic for this project is the fact that are used three displays on which appears different content.
The three displays are with I2C communication protocol and are controlled by a single microcontroller, but with the help of an I2C multiplexer module. I used similar hardware in one of my previous projects, but now instead of a BME280 module I use a Real Time Clock module.

The device is really simple to operate and consists of only a few components:
- LGT8F328P microcontroller board (here I would like to emphasize that without any changes in the code in the project, you can use Arduino Nano or Arduino UNO board)
- TCA9548A I2C multiplexer module
- Three OLED displays SSD1306 (Specifically in this project I am using a Blue-Yellow display, but another color or combination of colors can be used without modification.)
- and DS3231 Realtime clock module

This time I also use the case from the previous project, which, unlike then, is now placed in a horizontal position.
Now I propose to see how the clock works in real conditions. After switching on, the text "three display clock" appears sequentially on the three displays, an appropriate animation when writing the text, as well as rising and falling bars on the sides. Let's see how it works.

After this, the clock starts working. On the first display, in the blue field, the hour is displayed, on the second the minutes and on the third the seconds, which are marked on the top with "H", "M" and "S". On the bottom blue part the tens are displayed and below them the units respectively.

On the left yellow part of the screen there are three bars. The first one displays the elapsed time of the current Day in Hours (from 0 to 24), then the second one displays the elapsed time of the current Hour in Minutes (from 0 to 60 minutes), and similarly the third bar displays the elapsed time of the current Minute in Seconds (also from 0 to 60). Unfortunately, on video it's hard to capture the original image, which is crystal clear and without any flickering.
Let me mention that the exact time is downloaded from the personal computer automatically when uploading the code, but there is also an option to enter it into the code yourself.
And now a few words about the code.

It is relatively simple and uses only two libraries, one is support for OLED displays, and the other controls the Realtime Clock module.
At the beginning, the three displays are initialized and the exact time is downloaded from the PC. Then we have the showIntro function, which is intended for displaying the intro at the beginning. And finally the basic drawDisplay function, which draws the content of the three displays. Here we can easily change the parameters such as the font, size and borders, so the visual part of the clock can be easily modified.
And finally a short conclusion. This DIY clock successfully uses an I2C multiplexer to show the time across three OLED displays, complete with clever progress bars that visually track the elapsed time for the current minute, hour, and day. Code can be easy modified to customize the visual look of the OLED screens.

#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <RTClib.h>
Adafruit_SSD1306 display(128, 64, &Wire, 4);
RTC_DS3231 rtc;
int hours, minutes, seconds;
#define YELLOW_ZONE 16
void TCA9548A(uint8_t bus)
{
Wire.beginTransmission(0x70);
Wire.write(1 << bus);
Wire.endTransmission();
}
void setup()
{
// Initialize RTC on channel 1
TCA9548A(1);
rtc.begin();
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// --- OPTIONAL MANUAL TIME SET (only enable when needed) ---
// To set time manually, remove the comment marks from the lines below,
// adjust the DateTime parameters (Year, Month, Day, Hour, Minute, Second),
// upload once, then comment them out again to prevent overwriting the RTC each boot.
// rtc.adjust(DateTime(2025, 10, 22, 12, 15, 00)); // YYYY, MM, DD, HH, MM, SS
// Initialize displays with rotated orientation
// Display 1: Seconds (leftmost) - Channel 2
TCA9548A(2);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setRotation(1); // Rotate 90 degrees for portrait mode
display.clearDisplay();
display.display();
// Display 2: Minutes (middle) - Channel 3
TCA9548A(3);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setRotation(1); // Rotate 90 degrees for portrait mode
display.clearDisplay();
display.display();
// Display 3: Hours (rightmost) - Channel 4
TCA9548A(4);
display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
display.setRotation(1); // Rotate 90 degrees for portrait mode
display.clearDisplay();
display.display();
delay(500);
showIntro();
// delay(1000);
}
void showIntro() {
int screenH = 128;
const char* words[3] = {"CLOCK", "DISPL", "THREE"};
// --- STEP 1: Loading animation + falling letters ---
for (int h = 0; h <= 120; h += 6) {
for (int idx = 0; idx < 3; idx++) {
int ch = 2 + idx;
TCA9548A(ch);
display.clearDisplay();
// --- Yellow frame (16x128, 2-px border) ---
for (int i = 0; i < 2; i++) {
display.drawRect(i, i, YELLOW_ZONE - 2 * i, screenH - 2 * i, WHITE);
}
// --- Filling bar ---
int fillX = 4;
int fillW = YELLOW_ZONE - 8;
int fillY = 124 - h;
display.fillRect(fillX, fillY, fillW, h, WHITE);
// --- Falling letters ---
display.setTextColor(WHITE);
display.setTextSize(2);
int textX = YELLOW_ZONE + 20;
int textY = 12;
const char* w = words[idx];
// Determine how many letters to show depending on progress
int totalLetters = strlen(w);
int visibleLetters = map(h, 0, 120, 0, totalLetters);
if (visibleLetters > totalLetters) visibleLetters = totalLetters;
// Draw visible letters vertically, one by one
for (int k = 0; k < visibleLetters; k++) {
display.setCursor(textX, textY + k * 20);
display.write(w[k]);
}
display.display();
}
delay(80);
}
delay(1000); // pause 1 s at full bar
// --- STEP 2: Fade-out animation (bars emptying downward) ---
for (int h = 120; h >= 0; h -= 6) {
for (int idx = 0; idx < 3; idx++) {
int ch = 2 + idx;
TCA9548A(ch);
display.clearDisplay();
// --- Yellow frame ---
for (int i = 0; i < 2; i++) {
display.drawRect(i, i, YELLOW_ZONE - 2 * i, screenH - 2 * i, WHITE);
}
// --- Emptying bar ---
int fillX = 4;
int fillW = YELLOW_ZONE - 8;
int fillY = 124 - h;
display.fillRect(fillX, fillY, fillW, h, WHITE);
// --- Keep all letters visible during fade ---
display.setTextColor(WHITE);
display.setTextSize(2);
int textX = YELLOW_ZONE + 20;
int textY = 12;
const char* w = words[idx];
for (int k = 0; w[k]; k++) {
display.setCursor(textX, textY + k * 20);
display.write(w[k]);
}
display.display();
}
delay(60);
}
// --- STEP 3: Clear all displays ---
for (int ch = 2; ch <= 4; ch++) {
TCA9548A(ch);
display.clearDisplay();
display.display();
}
}
void loop()
{
// Read time from RTC
TCA9548A(1);
DateTime now = rtc.now();
hours = now.hour();
minutes = now.minute();
seconds = now.second();
// Update Seconds Display (Left) - Channel 2
TCA9548A(2);
display.clearDisplay();
drawDisplay(seconds, 'S', 60, seconds);
display.display();
// Update Minute Display (Middle) - Channel 3
TCA9548A(3);
display.clearDisplay();
drawDisplay(minutes, 'M', 60, minutes);
display.display();
// Update Hours Display (Right) - Channel 4
TCA9548A(4);
display.clearDisplay();
drawDisplay(hours, 'H', 24, hours);
display.display();
delay(200); // Update 5 times per second
}
void drawDisplay(int value, char unit, int maxValue, int barValue) {
// NOTE: In portrait rotation, display.width() = 64, display.height() = 128
int screenW = 64;
int screenH = 128;
// --- Bargraph container ---
int barX = 0;
int barW = YELLOW_ZONE; // 16 px
int barY = 0;
int barH = screenH;
// Outer rectangle (frame, 2-px thick)
for (int i = 0; i < 2; i++) {
display.drawRect(barX + i, barY + i, barW - 2 * i, barH - 2 * i, WHITE);
}
// --- Calculate bar height (usable area = 120 px: from y=4 to y=124) ---
int innerTop = 4;
int innerBottom = screenH - 4;
int usableHeight = innerBottom - innerTop; // 120 px
int barHeight = map(barValue, 0, maxValue, 0, usableHeight);
// --- Fill the bar inside the container ---
int fillX = barX + 2 + 2; // 2-px frame + 2-px gap
int fillW = barW - 8; // 2+2 margin each side → 8 total
int fillY = innerBottom - barHeight; // fill upward from bottom
display.fillRect(fillX, fillY, fillW, barHeight, WHITE);
// --- Blue field dimensions (right side) ---
int frameX1 = YELLOW_ZONE;
int frameW = screenW - YELLOW_ZONE;
int frameY1 = 0;
int frameH = screenH;
// --- Outer frame (3 px) ---
for (int i = 0; i < 3; i++) {
display.drawRect(frameX1 + i, frameY1 + i, frameW - 2 * i, frameH - 2 * i, WHITE);
}
// --- Divider between letter and digits ---
int dividerY = 40;
display.fillRect(frameX1 + 3, dividerY, frameW - 6, 3, WHITE);
// --- Text ---
display.setTextColor(WHITE);
display.setTextSize(3);
// Upper: unit letter (H / M / S)
int unitX = frameX1 + 17;
int unitY = 11;
display.setCursor(unitX, unitY);
display.print(unit);
display.setTextSize(4);
// Lower: digits
int tens = value / 10;
int units = value % 10;
int tensX = frameX1 + 13;
int tensY = 52;
display.setCursor(tensX, tensY);
display.print(tens);
int unitsX = frameX1 + 13;
int unitsY = 87;
display.setCursor(unitsX, unitsY);
display.print(units);
// --- Inner rounded frames ---
// Top rectangle (letters)
int innerTopX = frameX1 + 4;
int innerTopY = 4;
int innerTopW = frameW - 8;
int innerTopH = dividerY - innerTopY - 2; // 1px gap to divider
for (int i = 0; i < 2; i++) {
display.drawRoundRect(innerTopX + i, innerTopY + i, innerTopW - 2 * i, innerTopH - 2 * i, 4, WHITE);
}
// Bottom rectangle (digits)
int innerBotX = frameX1 + 4;
int innerBotY = dividerY + 4; // 1px gap below divider
int innerBotW = frameW - 8;
int innerBotH = frameH - innerBotY - 4;
for (int i = 0; i < 2; i++) {
display.drawRoundRect(innerBotX + i, innerBotY + i, innerBotW - 2 * i, innerBotH - 2 * i, 4, WHITE);
}
}









