John Bradnam
Published © GPL3+

Domino Clock

An interesting representation of time using three dominos.

IntermediateFull instructions provided16 hours4,308
Domino Clock

Things used in this project

Hardware components

Arduino Nano R3
Arduino Nano R3
×1
DS3231MPMB1 Peripheral Module
Maxim Integrated DS3231MPMB1 Peripheral Module
Search for "DS3231 ZS042 AT24C32 IIC Module Precision RTC Real time Clock Memory For Arduino" on eBay
×1
WS2812B 5050 RGB heatsink
×42
Tactile Switch, SPST-NO
Tactile Switch, SPST-NO
Long shaft
×1
Resistor 10k ohm
Resistor 10k ohm
0805 SMD variant
×2
Through Hole Resistor, 330 kohm
Through Hole Resistor, 330 kohm
×1
Capacitor 470 µF
Capacitor 470 µF
10V 6.37mm diameter
×1
8mm flat-back glass cabochons
Search for "Transparent Clear Crystal Round Flat Back Glass Cabochon" on eBay. They are about $3 per 100.
×42
Coin Cell Battery Holder
Coin Cell Battery Holder
CR2032 SMD battery holder and battery
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
3D Printer (generic)
3D Printer (generic)

Story

Read more

Custom parts and enclosures

STL Files

3D print files for your slicing software

Schematics

Schematic (Switch Board)

PCB (Switch Board)

Eagle Files

Switch Board Schematic and PCB in Eagle format

Fritz drawing for wiring modules

Code

Domino_Clock_V1.ino

C/C++
/********************************************************
 * Domino Clock
 * Author: John Bradnam (jbrad2089@gmail.com)
 * Created: 2021-06-21
 * 
 * Hardware: Arduino Nano, DS3231 RTC
 * 
 * 2021-06-21 jbrad2089@gmail.com
 *  - Initial code base
 */

#include <Adafruit_NeoPixel.h>
#include <RTClib.h>

#define WS2812B  2
#define SWITCHES A0

//Domino LED wiring
// 6     8
// 5  7  9
// 4     10
//
// 3     11
// 2  0  12
// 1     13

#define BLANK 13
const uint16_t digits[14] PROGMEM = {
//  fedcba9876543210  
  0b0000000000000000,  //0
  0b0000000000000001,  //1
  0b0000100000000010,  //2
  0b0000100010000010,  //3
  0b0000100100010010,  //4
  0b0010000100011001,  //5
  0b0010100100011010,  //6
  0b0010100110011010,  //7
  0b0010110101011010,  //8
  0b0010110101011011,  //9
  0b0010110111011011,  //10
  0b0011110111011110,  //11
  0b0011111101111110,  //12
  0b0000000000000000   //Space
};

#define DOM_HOUR 28
#define DOM_MIN_TEN 14
#define DOM_MIN_UNIT 0

//Uncomment to show dominos in white
//#define MONOCHROME

//Uncomment to animate colors
#define ANIMATE

#ifdef MONOCHROME
  #define COLOR_HOUR strip.Color(127, 127, 127)
  #define COLOR_MIN_TEN strip.Color(127, 127, 127)
  #define COLOR_MIN_UNIT strip.Color(127, 127, 127)
#else
  #define COLOR_HOUR strip.Color(127, 0, 0)
  #define COLOR_MIN_TEN strip.Color(0, 127, 0)
  #define COLOR_MIN_UNIT strip.Color(0, 0, 127)
#endif
#define COLOR_BLACK strip.Color(0,0,0)

#define BRIGHTNESS 50 //Brightness level (0 to 255)
#define LED_COUNT 42
Adafruit_NeoPixel strip(LED_COUNT, WS2812B, NEO_GRB + NEO_KHZ800);

RTC_DS3231 rtc;

enum SwitchEnum { NONE, SET, NEXT };

int lastHours = -1;             //Used to test if hour changed
int lastMinutes = -1;           //Used to test if minute changed
int setH = 0;                   //Hour being set
int setMT= 0;                   //Tens of Minutes being set
int setMU= 0;                   //Units of Minutes being set

enum ClockEnum { SHOW_TIME, SET_HOUR, SET_MIN_TEN, SET_MIN_UNIT };
ClockEnum clockMode = SHOW_TIME;

#define FLASH_TIME 200          //Time in mS to flash digit being set
#define STEP_TIME 350           //Time in mS for auto increment time
#define COLOR_CHANGE 100        //Time between color changes on animation

long flashTimeout = 0;          //Flash timeout when setting clock or alarm
bool flashOn = false;           //Used to flash display when setting clock or alarm
long stepTimeout = 0;           //Set time speed for auto increment or decrement of time

uint32_t colorH = COLOR_HOUR;       //Used to store active hour color
uint32_t colorMT = COLOR_MIN_TEN;   //Used to store active tens of minutes color
uint32_t colorMU = COLOR_MIN_UNIT;  //Used to store active units of minutes color
int wheelH = 85;                    //Start hours with RED
int wheelMT = 255;                  //Start tens of minutes with GREEN
int wheelMU = 170;                  //Start tens of minutes with BLUE
long colorTimeout = 0;          //Color timeout when animating colors

//---------------------------------------------------------------------------
// Hardware setup
void setup() 
{
  Serial.begin(9600);
  strip.begin();                    // INITIALIZE NeoPixel strip object (REQUIRED)
  strip.show();                     // Turn OFF all pixels ASAP
  strip.setBrightness(BRIGHTNESS);  // Set BRIGHTNESS to about 1/5 (max = 255)

  rtc.begin();
  if (rtc.lostPower()) {
    //Serial.println("RTC lost power, lets set the time!");
    rtc.adjust(DateTime(2021, 6, 21, 14, 55, 0));
  }
}

//---------------------------------------------------------------------------
// Main program loop
void loop() 
{
  bool updateTime = handleSetButton();
  
  switch (clockMode)
  {
    case SET_HOUR: hourMode(); break;
    case SET_MIN_TEN: tenMinuteMode(); break;
    case SET_MIN_UNIT: unitMinuteMode(); break;
  }
  
  if (clockMode == SHOW_TIME)
  {
    DateTime newTime = rtc.now();
    int hours = newTime.hour();
    int minutes = newTime.minute();

#ifdef ANIMATE
    if (millis() > colorTimeout)
    {
      wheelH = max((wheelH + 1) & 0xFF, 1);
      wheelMT = max((wheelMT + 1) & 0xFF, 1);
      wheelMU = max((wheelMU + 1) & 0xFF, 1);
      colorH = Wheel(wheelH);
      colorMT = Wheel(wheelMT);
      colorMU = Wheel(wheelMU);
      colorTimeout = millis() + COLOR_CHANGE;
      updateTime = true;
    }
#endif    
    
    if (updateTime || hours != lastHours || minutes != lastMinutes)
    {
      Serial.println("h="+String(hours)+", m="+String(minutes));
      lastHours = hours;
      lastMinutes = minutes;
      displayTime(hours, minutes);
    }
  }
}

//----------------------------------------------------------------------
// Test SET button and set the mode
bool handleSetButton()
{  
  bool updateTime = false;
  if (getButtonState() == SET)
  {
    delay(10);
    if (getButtonState() == SET)
    {
      DateTime t = rtc.now();
      clockMode = (clockMode == SET_MIN_UNIT) ? SHOW_TIME : (ClockEnum)((int)clockMode + 1);
      switch (clockMode)
      {
        case SET_HOUR: 
          setH = t.hour();
          setMT = t.minute() / 10; 
          setMU = t.minute() % 10; 
          flashTimeout = millis() + FLASH_TIME;
          flashOn = false;
          displayDomino(DOM_HOUR,normaliseHour(setH),COLOR_HOUR);
          displayDomino(DOM_MIN_TEN,setMT,COLOR_MIN_TEN);
          displayDomino(DOM_MIN_UNIT,setMU,COLOR_MIN_UNIT);
          break;

        case SET_MIN_TEN:
          displayDomino(DOM_HOUR,normaliseHour(setH),COLOR_HOUR);
          flashTimeout = millis() + FLASH_TIME;
          flashOn = false;
          displayDomino(DOM_MIN_TEN,setMT,COLOR_MIN_TEN);
          break;

        case SET_MIN_UNIT:
          displayDomino(DOM_MIN_TEN,setMT,COLOR_MIN_TEN);
          flashTimeout = millis() + FLASH_TIME;
          flashOn = false;
          displayDomino(DOM_MIN_UNIT,setMU,COLOR_MIN_UNIT);
          break;

        case SHOW_TIME:
          displayDomino(DOM_MIN_UNIT,setMU,COLOR_MIN_UNIT);
          //Set RTC
          rtc.adjust(DateTime(t.year(),t.month(),t.day(),setH,setMT*10+setMU,0));
          //force update
          updateTime = true;
          break;
      }
      //Wait until button is released
      while (getButtonState())
      {
        delay(10);
      }
    }
  }
  return updateTime;
}  

//----------------------------------------------------------------------
// set hours
void hourMode()
{
  if (millis() > flashTimeout)
  {
    flashTimeout = millis() + FLASH_TIME;
    flashOn = !flashOn;
    displayDomino(DOM_HOUR,normaliseHour(setH),(flashOn) ? COLOR_BLACK : COLOR_HOUR);
  }
  if (millis() > stepTimeout && getButtonState() == NEXT)
  {
    setH = (setH + 1) % 12;
    displayDomino(DOM_HOUR,normaliseHour(setH),(flashOn) ? COLOR_BLACK : COLOR_HOUR);
    stepTimeout = millis() + STEP_TIME;
  }
}

//----------------------------------------------------------------------
// set tens of minutes
void tenMinuteMode()
{
  if (millis() > flashTimeout)
  {
    flashTimeout = millis() + FLASH_TIME;
    flashOn = !flashOn;
    displayDomino(DOM_MIN_TEN,setMT,(flashOn) ? COLOR_BLACK : COLOR_MIN_TEN);
  }
  if (millis() > stepTimeout && getButtonState() == NEXT)
  {
    setMT = (setMT + 1) % 6;
    displayDomino(DOM_MIN_TEN,setMT,(flashOn) ? COLOR_BLACK : COLOR_MIN_TEN);
    stepTimeout = millis() + STEP_TIME;
    Serial.println("h="+String(setH)+", m="+String(setMT * 10 + setMU));
  }
}

//----------------------------------------------------------------------
// set units of minutes
void unitMinuteMode()
{
  if (millis() > flashTimeout)
  {
    flashTimeout = millis() + FLASH_TIME;
    flashOn = !flashOn;
    displayDomino(DOM_MIN_UNIT,setMU,(flashOn) ? COLOR_BLACK : COLOR_MIN_UNIT);
  }
  if (millis() > stepTimeout && getButtonState() == NEXT)
  {
    setMU = (setMU + 1) % 10;
    displayDomino(DOM_MIN_UNIT,setMU,(flashOn) ? COLOR_BLACK : COLOR_MIN_UNIT);
    stepTimeout = millis() + STEP_TIME;
    Serial.println("h="+String(setH)+", m="+String(setMT * 10 + setMU));
  }
}

//---------------------------------------------------------------------------
// Display the given time on the dominos
// h = hours (0 to 23);
// m = minutes (0 to 59);
void displayTime(int h, int m)
{
  displayDomino(DOM_HOUR,normaliseHour(h),colorH);
  displayDomino(DOM_MIN_TEN,m/10,colorMT);
  displayDomino(DOM_MIN_UNIT,m%10,colorMU);
}

//---------------------------------------------------------------------------
// Convert hour to 1 to 12
// h = hours (0 to 23);
// returns 1 to 12;
int normaliseHour(int h)
{
  if (h > 11)
  {
    h = h - 12;
  }
  if (h == 0)
  {
    h = 12;
  }
  return h;
}

//---------------------------------------------------------------------------
// Display a number on the given domino
// d = domino (0 - most right, 14 - middle, 28 - left most);
// n = number to display (0 to 12)
// c = color to display
void displayDomino(int d, int n, uint32_t c)
{
  uint16_t bits = pgm_read_word(&digits[n]);
  uint16_t mask = 0b0000000000000001;
  for (int i = 0; i < 14; i++)
  {
    strip.setPixelColor(i + d, (bits & mask) ? c : COLOR_BLACK);
    mask = mask << 1;
  }
  strip.show();
}

//-----------------------------------------------------------------------------------
// Return current button state
SwitchEnum getButtonState()
{
  //SET - 0V 0
  //NEXT - 2.5V 512
  SwitchEnum result = NONE;
  int value = analogRead(SWITCHES);
  if (value < 450)
  {
    result = NEXT;
  }
  else if (value < 650)
  {
    result = SET;
  }
  return result;
}

//-------------------------------------------------------------------------
// Input a value 0 to 255 to get a color value.
// The colours are a transition r - g - b - back to r.
uint32_t Wheel(byte WheelPos) 
{
  if (WheelPos == 0) 
  {
    return 0;
  } 
  else if (WheelPos < 85) 
  {
    return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
  } 
  else if (WheelPos < 170) 
  {
    WheelPos -= 85;
    return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
  } 
  else 
  {
    WheelPos -= 170;
    return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
  }
}

Credits

John Bradnam
151 projects • 194 followers
Contact

Comments

Please log in or sign up to comment.