Doug Domke
Published © GPL3+

A Big Self-Setting Clock

Use an ESP32, an RTC, and a flexible LED Matrix panel to build this large clock that gets NTP time from the Internet.

IntermediateFull instructions provided4 hours3,278

Things used in this project

Hardware components

Adafruit HUZZAH32 – ESP32 Feather Board
Adafruit HUZZAH32 – ESP32 Feather Board
×1
WS2812B RGB 8X32 256 Pixels LED Matrix Flexible Display
×1
DS3231 Real Time Clock Module
×1
LM2596 Buck Regulator Module
×1
Wall charger 5 Volt 2 Amp
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Hot glue gun (generic)
Hot glue gun (generic)
3D Printer (generic)
3D Printer (generic)
OPTIONAL

Story

Read more

Custom parts and enclosures

3D Print for Matrix Clock

.stl file for the case itself

Display Support

.stl file for a small piece to put behind the display for support and rigidity

Schematics

Clock Schematic

Code

Matrix Clock

Arduino
#include <NTPClient.h> //NTPClient by Fabrice Weinberg 3.2.1
#include <WiFi.h>
#include <WiFiUdp.h>
#include <Wire.h>
#include <RtcDS3231.h> // Requires Rtc by Makuna 2.3.5 specifically
#include "FastLED.h" // FastLED by Daniel Garcia 3.6.0

#define NUM_LEDS 256  // # of LEDs in the display
#define DATA_PIN 21 // pin controlling the display

// Your wifi credentials 
const char* ssid      = "Your Network";
const char* password  = "Your Password";
// Other User Settings
const int   GMToffset = -7;  // - 7 = US-MST;  set for your time zone)
const int   format = 12; // time format can be 12 or 24 hour
const char* ntpServer = "us.pool.ntp.org";  // NTP server

RtcDS3231<TwoWire> Rtc(Wire); // instance of RTC
WiFiUDP ntpUDP;  // instance of UDP
NTPClient timeClient(ntpUDP, ntpServer, GMToffset*3600);
CRGB leds[NUM_LEDS];
bool colon=false;
byte myArray[8][32];  // display buffer

byte num [10][8] = {  // our font for numbers 0-9
  { 0x38,  /* 001110   number 0 */
    0x44,  /* 010001 */
    0x4C,  /* 010011 */
    0x54,  /* 010101 */
    0x64,  /* 011001 */
    0x44,  /* 010001 */
    0x38,  /* 001110 */
    0x00},   /* 000000 */      
  { 0x10,  /* 000100   number 1 */
    0x30,  /* 001100 */
    0x10,  /* 000100 */
    0x10,  /* 000100 */
    0x10,  /* 000100 */
    0x10,  /* 000100 */
    0x38,  /* 001110 */
    0x00},   /* 000000 */
  { 0x38,  /* 001110   number 2  */
    0x44,  /* 010001 */
    0x04,  /* 000001 */
    0x18,  /* 000110 */
    0x20,  /* 001000 */
    0x40,  /* 010000 */
    0x7C,  /* 011111 */
    0x00},  /* 000000 */
  { 0x38,  /* 001110   number 3  */
    0x44,  /* 010001 */
    0x04,  /* 000001 */
    0x38,  /* 001110 */
    0x04,  /* 000001 */
    0x44,  /* 010001 */
    0x38,  /* 001110 */
    0x00},  /* 000000 */
  { 0x08,  /* 000010   number 4  */
    0x18,  /* 000110 */
    0x28,  /* 001010 */
    0x48,  /* 010010 */
    0x7C,  /* 011111 */
    0x08,  /* 000010 */
    0x08,  /* 000010 */
    0x00},  /* 000000 */
  { 0x7C,  /* 011111   number 5  */
    0x40,  /* 010000 */
    0x40,  /* 010000 */
    0x78,  /* 011110 */
    0x04,  /* 000001 */
    0x44,  /* 010001 */
    0x38,  /* 001110 */
    0x00},  /* 000000 */
  { 0x18,  /* 000110   number 6  */
    0x20,  /* 001000 */
    0x40,  /* 010000 */
    0x78,  /* 011110 */
    0x44,  /* 010001 */
    0x44,  /* 010001 */
    0x38,  /* 001110 */
    0x00},  /* 000000 */
  { 0x7C,  /* 011111   number 7  */
    0x04,  /* 000001 */
    0x08,  /* 000010 */
    0x10,  /* 000100 */
    0x20,  /* 001000 */
    0x20,  /* 001000 */
    0x20,  /* 001000 */
    0x00},  /* 000000 */
  { 0x38,  /* 001110   number 8  */
    0x44,  /* 010001 */
    0x44,  /* 010001 */
    0x38,  /* 001110 */
    0x44,  /* 010001 */
    0x44,  /* 010001 */
    0x38,  /* 001110 */
    0x00},  /* 000000 */
  { 0x38,  /* 001110   number 9  */
    0x44,  /* 010001 */
    0x44,  /* 010001 */
    0x3C,  /* 001111 */
    0x04,  /* 000001 */
    0x08,  /* 000010 */
    0x30,  /* 001100 */
    0x00}  /* 000000 */
};

void setup () {
  // Connect to WiFi and get NTP time
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
  }
  timeClient.begin();  // Start NTP Time Client
  delay(2000);
  timeClient.update();
  // NTP/Epoch time starts in 1970.  RTC library starts in 2000. 
  // So subtract the 30 extra yrs (946684800UL).
  unsigned long NTPtime = timeClient.getEpochTime()-946684800UL;
  Rtc.Begin();
  Rtc.SetDateTime(NTPtime); // Set RTC to NTP time
  // setup display
  FastLED.setBrightness(15); // even 5 is pretty bright!
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, NUM_LEDS);  // configure for WS2812B progrmmable LEDs
  FastLED.clear();  //clear the display
  FastLED.show();  
} 

void loop () {
  RtcDateTime currTime = Rtc.GetDateTime();
  int myhour = currTime.Hour();
  if (format==12) {  // if we are using a 12 hour format
    myhour = myhour%12; 
    if (myhour==0)myhour=12;
  }
  setNum(myhour/10,0);  // set first digit of hour
  setNum(myhour%10,1); // set second digit of hour
  int myminute = currTime.Minute();
  setNum(myminute/10,2); // set first digig of minute
  setNum(myminute%10,3);  // set second digit of minute
  int mysecs= currTime.Second();
  setColon(1); // turn colon on
  refreshDisplay(myHue(mysecs));
  delay(500); // wait 1/2 sec
  setColon(0); // turn colon off
  refreshDisplay(myHue(mysecs));
  while (mysecs == currTime.Second()){ // now wait until seconds change
    delay(10); 
    currTime = Rtc.GetDateTime();
  }
  memset(myArray, 0, sizeof myArray);  // clear the display buffer
}

// Sets the LED display to match content of display buffer at a specified color (myHue).
// Takes into account the zig-zap order of the array up and down from right to left
void refreshDisplay(byte myHue) {
  int myCount = 0;
  FastLED.clear();
  for (int x = 31; x > -1; x -= 2) {
    for (int y = 7; y > -1; y--) {
      if (myArray[y][x]) leds[myCount] = CHSV(myHue, 255, 255);
      myCount++;
    }
    for (int y = 0; y < 8; y++) {
      if (myArray[y][x - 1] == 1) leds[myCount] = CHSV(myHue, 255, 255);
      myCount++;
    }
  }
  FastLED.show();
}

// put numbers onto the display using our num[][] font in positions 0-3. 
void setNum(byte number, byte pos) {
  if (pos==0) pos=3;   // change pos to the real location in the array
  else if (pos==1) pos=9;
  else if (pos==2) pos=18;
  else if (pos==3) pos=24;
  for (int x = 0; x < 6; x++) {  // now place the digit's 6x8 font in the display buffer
    for (int y = 0; y < 8; y++) {
     if (num[number][y] & (0b01000000 >> x)) myArray[y][pos+ x] = 1;
    }
  }
}

void setColon(int onOff){  // turn the colon on (1) or off (0)
  myArray[2][16]=onOff;
  myArray[1][16]=onOff;
  myArray[5][16]=onOff;
  myArray[4][16]=onOff;
  myArray[2][15]=onOff;
  myArray[1][15]=onOff;
  myArray[5][15]=onOff;
  myArray[4][15]=onOff;
}

byte myHue(int mySeconds) {     // make display color change from green 
  if (mySeconds<2) return 85;   // through blue to red as seconds advance
  else if (mySeconds > 57)return 0;  // by returning the correct hue
  else return 85 + mySeconds*3; 
}

Credits

Doug Domke

Doug Domke

24 projects • 96 followers

Comments