WEATHERCLOCK CODE
Arduino Sketch + Custom Fonts
DISPLAY PREVIEW
ARDUINO SKETCH
// ============================================================================ // WEATHERCLOCK - Smartwatch-style Clock Display // Hardware: ESP32-S3 Super Mini + GC9A01 1.28" Round LCD (240x240) // Features: NTP time sync, minute progress ring, sensor gauges, LED indicators // ============================================================================ // --- Library Includes --- #include <WiFi.h> // WiFi connectivity for ESP32 #include "time.h" // Time functions for NTP sync #include <Wire.h> // I2C communication for sensors #include <Adafruit_GFX.h> // Graphics library #include <Adafruit_GC9A01A.h> // Driver for GC9A01 round LCD #include <Adafruit_AHTX0.h> // Driver for AHT20 temp/humidity sensor #include <Adafruit_BMP280.h> // Driver for BMP280 pressure sensor // --- Display Pin Definitions --- #define TFT_CS 12 // Chip Select #define TFT_DC 11 // Data/Command #define TFT_RST 10 // Reset #define TFT_MOSI 9 // SPI data line #define TFT_SCLK 8 // SPI clock line #define TFT_BL 13 // Backlight control // --- I2C Pin Definitions (for sensors) --- #define I2C_SDA 7 // I2C data line #define I2C_SCL 6 // I2C clock line // --- LED Pin Definitions (seconds indicators) --- #define LED1 5 // LED for seconds 0-14 #define LED2 4 // LED for seconds 15-29 #define LED3 3 // LED for seconds 30-44 #define LED4 2 // LED for seconds 45-59 // --- Color Definitions (RGB565 format) --- #define COLOR_BG 0x0000 // Black background #define COLOR_TEXT 0xFFFF // White text #define COLOR_ACCENT 0xFEA0 // Orange/gold accent #define COLOR_TICK_BG 0xBDF7 // Light grey tick background #define COLOR_TICK_15 0x4208 // Dark grey for 15-min marks #define COLOR_ELAPSED 0xF81F // Magenta for elapsed minutes #define COLOR_DATE 0x07FF // Teal for date text #define COLOR_RING_TEMP 0x07E0 // Green for temperature gauge #define COLOR_RING_HUM 0x5D9B // Blue for humidity gauge #define COLOR_RING_ALT 0xC618 // Gray for altitude gauge // --- Create Hardware Objects --- Adafruit_GC9A01A tft(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCLK, TFT_RST); Adafruit_AHTX0 aht; // Temperature/humidity sensor Adafruit_BMP280 bmp; // Pressure/altitude sensor // --- WiFi Credentials --- const char* ssid = "YOUR_WIFI_SSID"; const char* password = "YOUR_WIFI_PASSWORD"; // --- NTP Time Server Settings --- const char* ntpServer = "pool.ntp.org"; const long gmtOffset_sec = -6 * 3600; // CST = UTC-6 const int daylightOffset_sec = 3600; // DST +1 hour // --- Screen Layout Constants --- const int SCREEN_W = 240; const int SCREEN_H = 240; const int CX = SCREEN_W/2; // Center X (120) const int CY = SCREEN_H/2; // Center Y (120) const int OUTER_R = 110; // Outer ring radius const int SMALL_R = 22; // Sensor gauge radius // --- Custom Digital Fonts (place in Fonts folder) --- #include <Fonts/digital_740pt7b.h> // Large font for time #include <Fonts/digital_711pt7b.h> // Small font for date/sensors // --- Global Variables --- int last_minute = -1; int last_day = -1; float lastTemp=0, lastHum=0, lastAlt=0; char lastTimeStr[8] = ""; // ============================================================================ // SETUP // ============================================================================ void setup() { Serial.begin(115200); delay(200); // Initialize I2C and Sensors Wire.begin(I2C_SDA, I2C_SCL); aht.begin(); bmp.begin(0x77); bmp.setSampling(Adafruit_BMP280::MODE_NORMAL, Adafruit_BMP280::SAMPLING_X2, Adafruit_BMP280::SAMPLING_X16, Adafruit_BMP280::FILTER_X16, Adafruit_BMP280::STANDBY_MS_250); // Initialize Display pinMode(TFT_BL, OUTPUT); digitalWrite(TFT_BL, HIGH); tft.begin(); tft.setRotation(3); tft.fillScreen(COLOR_BG); // Initialize LEDs pinMode(LED1, OUTPUT); pinMode(LED2, OUTPUT); pinMode(LED3, OUTPUT); pinMode(LED4, OUTPUT); digitalWrite(LED1, HIGH); digitalWrite(LED2, HIGH); digitalWrite(LED3, HIGH); digitalWrite(LED4, HIGH); // Connect to WiFi WiFi.begin(ssid, password); unsigned long t0 = millis(); while (WiFi.status() != WL_CONNECTED && millis() - t0 < 15000) { delay(300); } // Configure NTP configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); // Initial Display drawFullFace(true); } // ============================================================================ // LOOP // ============================================================================ void loop() { struct tm ti; if (!getLocalTime(&ti)) return; if (ti.tm_min != last_minute) { if (ti.tm_min == 0) { drawFullFace(true); // New hour - full redraw } else { addMinuteTick(last_minute); updateTimeOnly(); } last_minute = ti.tm_min; } updateLEDs(); delay(200); } // ============================================================================ // LED CONTROL - Blinks one LED based on 15-second intervals // ============================================================================ void updateLEDs() { struct tm ti; if (!getLocalTime(&ti)) return; int sec = ti.tm_sec; digitalWrite(LED1, HIGH); digitalWrite(LED2, HIGH); digitalWrite(LED3, HIGH); digitalWrite(LED4, HIGH); int activePin; if (sec <= 14) activePin = LED1; else if (sec <= 29) activePin = LED2; else if (sec <= 44) activePin = LED3; else activePin = LED4; static unsigned long lastBlink = 0; static bool state = false; if (millis() - lastBlink > 500) { lastBlink = millis(); state = !state; digitalWrite(activePin, state ? LOW : HIGH); } } // ============================================================================ // SENSOR READING // ============================================================================ void readSensors() { sensors_event_t hum, temp; aht.getEvent(&hum, &temp); lastTemp = temp.temperature * 9.0 / 5.0 + 32.0; // Convert to Fahrenheit lastHum = hum.relative_humidity; lastAlt = bmp.readAltitude(1013.25); } // ============================================================================ // DISPLAY FUNCTIONS // ============================================================================ // Add single minute tick (no full redraw) void addMinuteTick(int minute) { if (minute < 0 || minute >= 60) return; const int THICKNESS = 6, HALF = 3; float a = (minute * 6 - 90) * PI / 180.0; int x1 = CX + (OUTER_R - HALF) * cos(a); int y1 = CY + (OUTER_R - HALF) * sin(a); int x2 = CX + (OUTER_R + HALF) * cos(a); int y2 = CY + (OUTER_R + HALF) * sin(a); for (int t = -HALF; t <= HALF; t++) { tft.drawLine( CX + (OUTER_R - HALF + t) * cos(a), CY + (OUTER_R - HALF + t) * sin(a), CX + (OUTER_R + HALF + t) * cos(a), CY + (OUTER_R + HALF + t) * sin(a), COLOR_ELAPSED); } tft.fillCircle(x1, y1, HALF+1, COLOR_ELAPSED); tft.fillCircle(x2, y2, HALF+1, COLOR_ELAPSED); } // Update just the time display void updateTimeOnly() { struct tm ti; if (!getLocalTime(&ti)) return; char newTime[8]; int hour12 = ti.tm_hour % 12; if (hour12 == 0) hour12 = 12; snprintf(newTime, sizeof(newTime), "%2d:%02d", hour12, ti.tm_min); // Erase old time in black tft.setFont(&digital_740pt7b); if (lastTimeStr[0] != '\0') { int16_t x1, y1; uint16_t w, h; tft.getTextBounds(lastTimeStr, 0, 0, &x1, &y1, &w, &h); tft.setTextColor(COLOR_BG); tft.setCursor(CX - w/2 - 12, CY + 42); tft.print(lastTimeStr); } // Draw new time in white int16_t x1, y1; uint16_t w, h; tft.getTextBounds(newTime, 0, 0, &x1, &y1, &w, &h); tft.setTextColor(COLOR_TEXT); tft.setCursor(CX - w/2 - 12, CY + 42); tft.print(newTime); // AM/PM indicator tft.setFont(&digital_711pt7b); tft.setTextColor(COLOR_RING_TEMP); tft.setCursor(CX + w/2 - 8, CY + 42); tft.print(ti.tm_hour >= 12 ? "PM" : "AM"); strcpy(lastTimeStr, newTime); } // Draw complete clock face void drawFullFace(bool full) { tft.fillScreen(COLOR_BG); drawOuterRing(); drawMiniCircle(CX-60, CY-40, SMALL_R, lastTemp, 14, 122, "F", COLOR_RING_TEMP); drawMiniCircle(CX, CY-70, SMALL_R, lastHum, 0, 100, "%", COLOR_RING_HUM); drawMiniCircle(CX+60, CY-40, SMALL_R, lastAlt, -50, 3000, "m", COLOR_RING_ALT); drawCentralTime(); drawDateLine(); } // Draw outer minute ring void drawOuterRing() { const int THICKNESS = 6, HALF = 3; struct tm ti; if (!getLocalTime(&ti)) return; // Light grey tick marks for all 60 minutes for (int i = 0; i < 360; i += 6) { float a = (i - 90) * PI / 180.0; int x1 = CX + (OUTER_R - HALF) * cos(a); int y1 = CY + (OUTER_R - HALF) * sin(a); int x2 = CX + (OUTER_R + HALF) * cos(a); int y2 = CY + (OUTER_R + HALF) * sin(a); tft.drawLine(x1, y1, x2, y2, COLOR_TICK_BG); } // 15-minute markers for (int m = 0; m < 60; m += 15) { float a = (m * 6 - 90) * PI / 180.0; int x1 = CX + (OUTER_R - HALF) * cos(a); int y1 = CY + (OUTER_R - HALF) * sin(a); int x2 = CX + (OUTER_R + HALF) * cos(a); int y2 = CY + (OUTER_R + HALF) * sin(a); tft.fillCircle(x1, y1, HALF+2, COLOR_TICK_15); tft.fillCircle(x2, y2, HALF+2, COLOR_TICK_15); } // Magenta marks for elapsed minutes for (int m = 0; m < ti.tm_min; m++) { float a = (m * 6 - 90) * PI / 180.0; for (int t = -HALF; t <= HALF; t++) { tft.drawLine( CX + (OUTER_R - HALF + t) * cos(a), CY + (OUTER_R - HALF + t) * sin(a), CX + (OUTER_R + HALF + t) * cos(a), CY + (OUTER_R + HALF + t) * sin(a), COLOR_ELAPSED); } } } // Draw sensor gauge circle void drawMiniCircle(int cx, int cy, int r, float value, float vmin, float vmax, const char* unit, uint16_t color) { int newR = r + 6; tft.fillCircle(cx, cy, newR, 0x1082); tft.drawCircle(cx, cy, newR, color); float ratio = constrain((value - vmin) / (vmax - vmin), 0, 1); int filled = (int)(ratio * 50); for (int i = 0; i <= filled; i++) { float a = (-90 + i * 7.2) * PI / 180.0; tft.drawLine(cx + (newR-7)*cos(a), cy + (newR-7)*sin(a), cx + (newR-2)*cos(a), cy + (newR-2)*sin(a), color); } char buf[16]; snprintf(buf, sizeof(buf), "%.0f%s", value, unit); tft.setFont(&digital_711pt7b); int16_t tx, ty; uint16_t w, h; tft.getTextBounds(buf, 0, 0, &tx, &ty, &w, &h); tft.setTextColor(COLOR_TEXT); tft.setCursor(cx - w/2, cy + h/3); tft.print(buf); } // Draw central time void drawCentralTime() { struct tm ti; if (!getLocalTime(&ti)) return; char buf[8]; int hour12 = ti.tm_hour % 12; if (hour12 == 0) hour12 = 12; snprintf(buf, sizeof(buf), "%2d:%02d", hour12, ti.tm_min); tft.setFont(&digital_740pt7b); int16_t x1, y1; uint16_t w, h; tft.getTextBounds(buf, 0, 0, &x1, &y1, &w, &h); tft.setTextColor(COLOR_TEXT); tft.setCursor(CX - w/2 - 12, CY + 42); tft.print(buf); tft.setFont(&digital_711pt7b); tft.setTextColor(COLOR_RING_TEMP); tft.setCursor(CX + w/2 - 8, CY + 42); tft.print(ti.tm_hour >= 12 ? "PM" : "AM"); strcpy(lastTimeStr, buf); } // Draw date line void drawDateLine() { struct tm ti; if (!getLocalTime(&ti)) return; const char *wdays[] = {"SUN","MON","TUE","WED","THU","FRI","SAT"}; char dstr[32]; snprintf(dstr, sizeof(dstr), "%s %02d/%02d/%04d", wdays[ti.tm_wday], ti.tm_mday, ti.tm_mon+1, ti.tm_year+1900); tft.setFont(&digital_711pt7b); int16_t x1, y1; uint16_t w, h; tft.getTextBounds(dstr, 0, 0, &x1, &y1, &w, &h); tft.setTextColor(COLOR_DATE); tft.setCursor(CX - w/2, CY + 70); tft.print(dstr); }
CUSTOM FONTS
Fonts/*.hDigital 7-segment style fonts required for the clock display
DOWNLOAD FONTS (22KB)SETUP INSTRUCTIONS
- WiFi: Replace
YOUR_WIFI_SSIDandYOUR_WIFI_PASSWORDwith your WiFi credentials. - Libraries: Install via Arduino Library Manager:
Adafruit GFX LibraryAdafruit GC9A01AAdafruit AHTX0(for AHT20 sensor)Adafruit BMP280(for BMP280 sensor)
- Board: Select "ESP32S3 Dev Module" in Arduino IDE.
- Sensors: Connect AHT20+BMP280 combo sensor to I2C pins (SDA=7, SCL=6).
- LEDs: Optional - connect 4 LEDs to GPIO 2, 3, 4, 5 for seconds indication.
- Timezone: Adjust
gmtOffset_secfor your timezone (CST = -6 hours = -21600 seconds).
DIGITAL FONT INSTALLATION
The clock display uses custom digital 7-segment fonts. Download and install them to your sketch folder:
- Download the fonts: digital-fonts.zip
- Extract the zip file
- Create a
Fontsfolder inside your Arduino sketch folder - Place both
.hfiles inside theFontsfolder
Folder structure should be:
weatherclock/
├── weatherclock.ino
└── Fonts/
├── digital_740pt7b.h
└── digital_711pt7b.h
DISPLAY ROTATION
To rotate the display, add tft.setRotation(n); in setup() after tft.begin();
tft.setRotation(0); // 0° - Default orientation
tft.setRotation(1); // 90° - Rotate clockwise
tft.setRotation(2); // 180° - Upside down
tft.setRotation(3); // 270° - Rotate counter-clockwise
Note: This rotates the entire display output. Use this if your display is mounted upside down or at an angle.
ARDUINO PROJECT SETUP
- Create Project Folder: Create a folder named
WeatherClockin your Arduino sketches directory (usuallyDocuments/Arduino/) - Main Sketch: Save the code as
WeatherClock.ino- filename MUST match folder name - Font Files: Copy the digital font .h files to the same folder (see Digital Font Installation above)
WeatherClock/ ├── WeatherClock.ino // Main sketch (copy the code above) ├── DSEG7Classic_Bold20.h // Font for time display ├── DSEG7Classic_Bold30.h // Font for time display └── DSEG7Classic_Bold40.h // Font for time display
COMPONENTS NEEDED
| ESP32-S3 Super Mini | Main microcontroller | AliExpress |
| GC9A01 Round Display | 240x240 1.28" TFT | Amazon |
| AHT20+BMP280 Module | Combined temp/humidity/pressure sensor | Amazon |
| 5mm LEDs (x4) | Quarter-minute indicators | Amazon |
| 330Ω Resistors (x4) | Current limiting for LEDs | Amazon |
| Dupont Wires | For connections | Amazon |
WIRING DIAGRAM
| GC9A01 Display | ESP32-S3 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SCL (SCK) | GPIO 8 |
| SDA (MOSI) | GPIO 9 |
| RES (RST) | GPIO 10 |
| DC | GPIO 11 |
| CS | GPIO 12 |
| BLK (Backlight) | GPIO 13 |
| AHT20+BMP280 Sensor | ESP32-S3 |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SDA | GPIO 7 |
| SCL | GPIO 6 |
| Quarter LEDs | ESP32-S3 |
|---|---|
| LED1 (+) → 330Ω (0-14 sec) | GPIO 5 |
| LED2 (+) → 330Ω (15-29 sec) | GPIO 4 |
| LED3 (+) → 330Ω (30-44 sec) | GPIO 3 |
| LED4 (+) → 330Ω (45-59 sec) | GPIO 2 |
| All LED (-) | GND |
REQUIRED LIBRARIES
Install via Arduino Library Manager (Sketch → Include Library → Manage Libraries):
| Adafruit GFX Library | Graphics primitives |
| Adafruit GC9A01A | Round display driver |
| Adafruit AHTX0 | AHT20 temperature/humidity sensor |
| Adafruit BMP280 | BMP280 pressure/altitude sensor |
ARDUINO IDE 3.x BOARD SETTINGS
| Board | ESP32S3 Dev Module |
| USB CDC On Boot | Enabled |
| Flash Size | 4MB (32Mb) |
| Partition Scheme | Default 4MB with spiffs |
| Upload Speed | 921600 |
ESP32 Board URL: Add this in File → Preferences → Additional Board Manager URLs:
https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json
TROUBLESHOOTING
- Upload fails / Guru Meditation Error: Hold BOOT button while clicking Upload, release after "Connecting..." appears.
- Display is white/blank: Check wiring, especially CS, DC, and RST pins. Verify 3.3V power.
- WiFi won't connect: Double-check SSID and password. ESP32 only supports 2.4GHz networks.
- Time shows 00:00: Check WiFi connection and NTP server. Verify timezone offset in code.
- Sensor shows 0 or NaN: Check I2C wiring (SDA to GPIO 7, SCL to GPIO 6). Run I2C scanner.
- LEDs not lighting: Check LED polarity (long leg = positive). Verify 330Ω resistors.
- Font not displaying: Ensure .h font files are in the same folder as the .ino sketch.
- Serial Monitor empty: Set baud rate to 115200. Enable "USB CDC On Boot" in board settings.
BUILD CHECKLIST
- □ Install Arduino IDE 3.x
- □ Add ESP32 board package URL and install ESP32 boards
- □ Install required libraries: Adafruit GFX, GC9A01A, AHTX0, BMP280
- □ Create project folder:
WeatherClock/ - □ Copy sketch as
WeatherClock.ino - □ Download and copy DSEG7 font .h files to project folder
- □ Edit code: Set WiFi SSID and password
- □ Edit code: Set timezone offset (
gmtOffset_sec) - □ Wire GC9A01 display to ESP32-S3
- □ Wire AHT20+BMP280 sensor (I2C)
- □ Wire 4 quarter LEDs with 330Ω resistors
- □ Connect ESP32-S3 via USB-C
- □ Select ESP32S3 Dev Module and correct COM port
- □ Upload sketch (hold BOOT if needed)
- □ Check Serial Monitor for WiFi/NTP connection
- □ Verify time and weather data display - Done!