Dice Clock

After finishing the SPECTRALIZER, and adding some text features to it, I was intrigued with the idea of playing with a general purpose display panel stuffed with addressable LEDs.  So, I made one and its first ‘incarnation’ is as a DICE CLOCK, shown here displaying time in several slightly different modes.

Dice Clock with boxes around time sections

Dice Clock with boxes around each time section

Dice Clock with boxes around each mumeral

Dice Clock with boxes around each numeral

Dice Clock showing hours, minutes and seconds

Dice Clock showing hours, minutes and seconds

Dice Clock showing "domino" mode where hours, minutes and seconds are shown together

Dice Clock in “domino” mode with hours, minutes and seconds displayed like domino tiles

The time is displayed as pips on a dice; a pair of which are used for hours, minutes and optionally, seconds. Each dice can be programmed with a different colour and can be displayed in its own box, or with a box drawn around the time element. In the “domino” mode, each time element is displayed like the pips on a domino tile (so, for example, in the above image, the time is 16:44:40). For the hours and minutes only modes, a second counter runs along the bottom of the display although something more interesting is planned. As all of the displayed characters are located in the Arduino program memory there is lots of room to try out different characters and display modes.

The panel is constructed using meter-long lengths of LEDs, each containing 60 addressable RGB LEDs on a self-adhesive flexible substrate. They are arranged in 9 rows of 20 LEDs per row. The rows are centred on 0.7″ to match the LED separation (16.5mm, 0.64″) of the strip, and this makes anything displayed on the panel properly scaled. A large panel is made of three smaller circuit board panels, joined top and bottom with thin support rails, and the LED strips are stuck to this. Right-angled header pins are soldered to the GND, +5V and Data connections of the strips. Bus wires on the back side of the panels join the power connections and allow the daisy-chaining of the data signal, one row to the next. As with the SPECTRALIZER, the LEDs are organized as a continuous strip of 180 LEDs, starting at the bottom left (LED[0]) and ending at top right (LED[189]). A translucent fascia made from 1/8″ clear plastic is connected tot he panel assembly using nylon hardware.

The electronics to drive the panel is one of my Arduino clock boards which contains an ATmega328 chip, a DS3231 Real-Time Clock module and a header for a removable FITL USB interface. The board can be powered from an external 5v supply (wise, considering the power draw of the LEDs) or through a higher voltage supply and a on-board 5v regulator, with suitable heatsink. If you choose the latter, with a 9v supply, for example, watch out for how much power is dissipated by the regulator! A couple high-current diodes (silicon NOT schottky) in series would help lower the power dissipation to a more reasonable level.

Here’s the initial build software:

[codesyntax lang=”php” title=”Dice Clock” blockstate=”collapsed”]

/*******************************************************************************************
* /                                       DICE CLOCK 
* /                                  Adrian Jones, Nov. 2014
* /
/********************************************************************************************/

// 
// Build 1
//   r1 141203 - initial build 

//********************************************************************************************
#define build 1
#define revision 1
//********************************************************************************************

#include <avr/pgmspace.h>         // for memory storage in program space

#include "dicenums.h"             // dice numbers

#include "FastLED.h"
#define LED_COUNT 180             // #LEDs in strip
#define DATA_PIN 5                // Data transfer pin
CRGB leds[LED_COUNT];             // Define array of leds

#define hStart   80               // hue start 
#define sStart  255               // saturation start
#define bbright  30               // background brightness
#define hbright 120               // high brightness
#define sbright  60               // seconds brightness

int bright=bbright,hue=hStart,sat=sStart;                       //
int row,col,z;
int lnum,inc;
int n,p;  


#include <Wire.h>               // I2C-WIRE library
#include "RTClib.h"             // RTC-Library
RTC_DS1307 RTC;                 // Tiny RTC (DS1307) module (SDA - A4, SCL - A5)
DateTime now;                   // current time
byte ch,cm,cs;                  // current hours, mins, and seconds
byte lastsec;

// modes
#define modeWithBox  0           // display mode dice with surrounding box
#define modeNobBox   1           // display mode dice without box
#define modeMinsSecs 2           // display mode mins and secs

// display positions
int droff;                       // row offset for dice
#define sroff 0                  // row offset for seconds


boolean serOn = true;


//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//
void setup() {
  FastLED.addLeds<WS2812B, DATA_PIN, GRB>(leds, LED_COUNT);  // LED Strip 
  Wire.begin();

  RTC.begin();                                            // real-time clock
  if (! RTC.isrunning()) {   
    RTC.adjust(DateTime(__DATE__, __TIME__));
    Serial.println(F("RTC reset"));
  } else {
    Serial.println(F("RTC running"));
  }

  Serial.begin(57600);
  if(serOn) {
    Serial.println("DICE CLOCK: Adrian Jones, December 2014");
    Serial.print(F("Build ")); 
    Serial.print(build); 
    Serial.print(F(".")); 
    Serial.println(revision);
    Serial.print(F("Free RAM: "));  
    Serial.print(freeRam()); 
    Serial.println(F("B"));
  }

} 

//*****************************************************************************************//
//                                      MAIN LOOP
//*****************************************************************************************//
void loop() {
  getTime();                                  // get current time 
  if(cs != lastsec) {                         // every second
    doPrintTime();                            // to serial port
    doDisplayDice(modeMinsSecs);              // display dice mode
    lastsec = cs;                             // update seconds counter
  }  
}  

// ********************************************************************************** //
//                                      SUBROUTINES
// ********************************************************************************** //
void doDisplayDice(byte mode) {
  switch(mode) {
    case modeWithBox:
      droff=4;
      doDisplayDice5(ch/10,0,30);               // 10's hours
      doDisplayDice5(ch%10,5,60);               // 1's hours
      doDisplayDice5(cm/10,10,90);              // 10's mins
      doDisplayDice5(cm%10,15,160);             // 1's mins 
      doDisplaySecs(cs,200);                    // seconds     
      FastLED.show();
      break;
  case modeNobBox:
      droff=5;
      doDisplayDice3(ch/10,1,30);               // 10's hours
      doDisplayDice3(ch%10,5,60);               // 1's hours
      doDisplayDice3(cm/10,11,90);              // 10's mins
      doDisplayDice3(cm%10,16,160);             // 1's mins 
      doDrawBox(0,4,9,8,180);                   // draw box
      doDrawBox(10,4,19,8,180);                 // draw box
      doDisplaySecs(cs,200);                    // seconds     
      FastLED.show();
      break;
  case modeMinsSecs:
      droff=5;
      doDisplayDice3(ch/10,1,30);               // 10's hours
      doDisplayDice3(ch%10,5,60);               // 1's hours
      doDisplayDice3(cm/10,11,90);              // 10's mins
      doDisplayDice3(cm%10,16,160);             // 1's mins 
      doDrawBox(0,4,9,8,180);                   // draw box
      doDrawBox(10,4,19,8,180);                 // draw box
      droff=1;
      doDisplayDice3(cs/10,5,90);                // 10's secs
      doDisplayDice3(cs%10,11,160);              // 1's secs 
      doDrawBox(4,0,14,4,200);                   // draw box
      FastLED.show();
      break;
  }
  
}

void doDrawBox(byte x1,byte y1,byte x2, byte y2, byte color) {
  x2 = min(x2,19); y2 = min(y2,9);                           // protect limits (x:0-19, y:0-8)
  for(row = y1; row < (y2+1); row++) {
    for(col = x1; col < (x2+1); col++) {    
      lnum = (row%2)? (row*20) + 19 - col : row*20 + col;    // odd/even rows
      if( row==y1 || row==y2 || col==x1 || col==x2 ) {       // outline
         leds[lnum] = CHSV( color, sat, bbright );
      }
    }
  }  
}


void doPrintTime() {
  if(!serOn) return;
  Serial.print(F("T: "));
  if(ch < 10) Serial.print(F("0"));  Serial.print(ch,DEC); Serial.print(F(":"));
  if(cm < 10) Serial.print(F("0"));  Serial.print(cm,DEC); Serial.print(F(":"));
  if(cs < 10) Serial.print(F("0"));  Serial.print(cs,DEC); Serial.println(F(""));
}

void doDisplaySecs(int n, byte color) {
  n=n/3; 
  byte rpos = (sroff*20);
  leds[n+rpos] = CHSV( color, sat, sbright );
  if(n==0) leds[19+rpos] = CHSV( 0,0,0 ); 
  leds[(n-1)+rpos] = CHSV( 0,0,0 );
}

void doDisplayDice5(int n, int c, byte color) {
  for(int i=0; i<5; i++) {
    z = pgm_read_byte( &( dicenum5[n][i] ) );
    for(row=0; row<5; row++) {
      lnum = (!((row+droff)%2))? (row+droff)*20 + c + i : ((row+droff)*20) + 19 -  c - i;
      bright = ( row==0 || row==4 || i==0 || i==4 )? bbright : hbright;  
      leds[lnum] = (bitRead(z,row))? CHSV( color, sat, bright ) : CHSV(0,0,0);
    }   
  }
}

void doDisplayDice3(int n, int c, byte color) {
  bright = hbright;
  for(int i=0; i<3; i++) {
    z = pgm_read_byte( &( dicenum3[n][i] ) );
    for(row=0; row<3; row++) {
      lnum = (!((row+droff)%2))? (row+droff)*20 + c + i : ((row+droff)*20) + 19 -  c - i;
      leds[lnum] = (bitRead(z,row))? CHSV( color, sat, bright ) : CHSV(0,0,0);
    }   
  }
}


void getTime() {
   now = RTC.now();                                  // get current time
   ch=now.hour(); cm=now.minute(); cs=now.second();
}


// ********************************************************************************** //
//                              OPERATION ROUTINES
// ********************************************************************************** //
// FREERAM: Returns the number of bytes currently free in RAM  
int freeRam(void) {
  extern int  __bss_end, *__brkval; 
  int free_memory; 
  if((int)__brkval == 0) {
    free_memory = ((int)&free_memory) - ((int)&__bss_end); 
  } 
  else {
    free_memory = ((int)&free_memory) - ((int)__brkval); 
  }
  return free_memory; 
}

[/codesyntax]

Simple, huh?

Come on... leave a comment...