WEATHERCLOCK CODE

Arduino Sketch + Custom Fonts

CAPTIVE PORTAL VERSION →

DISPLAY PREVIEW

TIME CONTROLS

SENSOR VALUES

Ready - Click RUN BOOT SEQUENCE

ARDUINO SKETCH

weatherclock.ino
// ============================================================================
// 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/*.h

Digital 7-segment style fonts required for the clock display

DOWNLOAD FONTS (22KB)

SETUP INSTRUCTIONS

  • WiFi: Replace YOUR_WIFI_SSID and YOUR_WIFI_PASSWORD with your WiFi credentials.
  • Libraries: Install via Arduino Library Manager:
    • Adafruit GFX Library
    • Adafruit GC9A01A
    • Adafruit 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_sec for 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:

  1. Download the fonts: digital-fonts.zip
  2. Extract the zip file
  3. Create a Fonts folder inside your Arduino sketch folder
  4. Place both .h files inside the Fonts folder

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 WeatherClock in your Arduino sketches directory (usually Documents/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

  1. □ Install Arduino IDE 3.x
  2. □ Add ESP32 board package URL and install ESP32 boards
  3. □ Install required libraries: Adafruit GFX, GC9A01A, AHTX0, BMP280
  4. □ Create project folder: WeatherClock/
  5. □ Copy sketch as WeatherClock.ino
  6. □ Download and copy DSEG7 font .h files to project folder
  7. □ Edit code: Set WiFi SSID and password
  8. □ Edit code: Set timezone offset (gmtOffset_sec)
  9. □ Wire GC9A01 display to ESP32-S3
  10. □ Wire AHT20+BMP280 sensor (I2C)
  11. □ Wire 4 quarter LEDs with 330Ω resistors
  12. □ Connect ESP32-S3 via USB-C
  13. □ Select ESP32S3 Dev Module and correct COM port
  14. □ Upload sketch (hold BOOT if needed)
  15. □ Check Serial Monitor for WiFi/NTP connection
  16. □ Verify time and weather data display - Done!