#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);
}
}
Comments