Project tutorial
Single LED Matrix Arduino Flip Clock

Single LED Matrix Arduino Flip Clock © GPL3+

A simple to build LED matrix flip clock.

  • 1,993 views
  • 0 comments
  • 13 respects

Components and supplies

Necessary tools and machines

09507 01
Soldering iron (generic)

Apps and online services

About this project

I build this 8x8 LED Matrix Module CLOCK developed by Adrian Jones.Time is displayed by scrolling from left to right, or, under software control, from top to bottom. Each digit of the time successively slides in from the right side and when centered, stops momentarily and brightens slightly. It then scrolls to the left while the next digit in the display scrolls into view. The cycle repeats with a short delay between successive displays.For "from top to bottom" vertical flipping you should change :

static boolean top2bottom = false;

to:

static boolean top2bottom = true;

As you can see in the comments of the code, many parameters can be changed, for example scroll spreed, number of blank lines between characters, 12/24 hour display, ms between characters, brightness etc..

Time can be easily set using a rotary encoder.A two-color diode displays the setting mode (minutes, hours or normal).I put the clock in a box made up of two basic pieces so the access to the inside of the box is very convenient and practical.

Code

codeArduino
//*****************************************************************************************//
//                                      MATRIX CLOCK
//                                 Adrian Jones, March 2014
//
//    - allows left-to-right and top-to-bottom scrolling
//
//*****************************************************************************************//

#include <Wire.h>             // I2C-WIRE library
#include <RTClib.h>           // RTC-Library

// define max7219 registers and control pins
#define max7219_reg_noop        0x00
#define max7219_reg_digit0      0x01
#define max7219_reg_digit1      0x02
#define max7219_reg_digit2      0x03
#define max7219_reg_digit3      0x04
#define max7219_reg_digit4      0x05
#define max7219_reg_digit5      0x06
#define max7219_reg_digit6      0x07
#define max7219_reg_digit7      0x08
#define max7219_reg_decodeMode  0x09
#define max7219_reg_intensity   0x0a
#define max7219_reg_scanLimit   0x0b
#define max7219_reg_shutdown    0x0c
#define max7219_reg_displayTest 0x0f
#define dataIn  12             // DIN
#define load    10             // CS 
#define clock   11             // CLK



char alphanum[][8] ={{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},    // blank  Hex 20  Dec 32
                     {0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x10},    // ! 33
                     {0x00,0x28,0x28,0x28,0x00,0x00,0x00,0x00},    // " 34
                     {0x00,0x28,0x7C,0x28,0x7C,0x28,0x00,0x00},    // # 35
                     {0x10,0x38,0x50,0x38,0x14,0x54,0x38,0x10},    // $
                     {0x41,0xA2,0x44,0x08,0x10,0x22,0x45,0x82},    // %
                     {0x38,0x44,0x44,0x38,0x50,0x4A,0x44,0x3A},    // &
                     {0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00},    // '
                     {0x30,0x40,0x80,0x80,0x80,0x80,0x40,0x30},    // (  40
                     {0xC0,0x20,0x10,0x10,0x10,0x10,0x20,0xC0},    // )
                     {0x28,0x10,0xAA,0x54,0xAA,0x10,0x28,0x00},    // *
                     {0x00,0x10,0x10,0x10,0xFE,0x10,0x10,0x10},    // +
                     {0x00,0x00,0x00,0x00,0x00,0x08,0x08,0x10},    // ,
                     {0x00,0x00,0x00,0x00,0x7E,0x00,0x00,0x00},    // -
                     {0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18},    // .
                     {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80},    // /
                     {0x7E,0xC1,0xA1,0x91,0x89,0x85,0x83,0x7E},    // 0
                     {0x10,0x30,0x10,0x10,0x10,0x10,0x10,0x7C},    // 1
                     {0x38,0x44,0x82,0x04,0x18,0x20,0x40,0xFE},    // 2   50
                     {0x7C,0x82,0x02,0x3C,0x02,0x02,0x82,0x7C},    // 3
                     {0x08,0x18,0x28,0x48,0xFE,0x08,0x08,0x08},    // 4
                     {0xFE,0x80,0xF8,0x04,0x02,0x82,0x44,0x38},    // 5
                     {0x38,0x44,0x80,0xB8,0xC4,0x82,0x44,0x38},    // 6
                     {0xFE,0x02,0x04,0x08,0x10,0x20,0x20,0x20},    // 7
                     {0x7C,0x82,0x82,0x7C,0x82,0x82,0x82,0x7C},    // 8
                     {0x7C,0x82,0x82,0x7E,0x02,0x82,0x44,0x38},    // 9
                     {0x00,0x00,0x18,0x18,0x00,0x18,0x18,0x00},    // :
                     {0x00,0x00,0x18,0x18,0x00,0x18,0x18,0x30},    // ;
                     {0x00,0x10,0x20,0x40,0x80,0x40,0x20,0x10},    // <  60
                     {0x00,0x00,0x00,0x7E,0x00,0x7E,0x00,0x00},    // =
                     {0x00,0x80,0x40,0x20,0x10,0x20,0x40,0x80},    // >
                     {0x70,0x88,0x88,0x10,0x20,0x20,0x00,0x20},    // ?
                     {0x7E,0x81,0x99,0xA1,0xA1,0x9E,0x80,0x7E},    // @
                     {0x3C,0x42,0x81,0x81,0xFF,0x81,0x81,0x81},    // A
                     {0xFC,0x82,0x81,0xFE,0x81,0x81,0x82,0xFC},    // B
                     {0x3C,0x42,0x81,0x80,0x80,0x81,0x42,0x3C},    // C
                     {0xFC,0x82,0x81,0x81,0x81,0x81,0x82,0xFC},    // D
                     {0xFE,0x80,0x80,0xFC,0x80,0x80,0x80,0xFE},    // E
                     {0xFE,0x80,0x80,0xFC,0x80,0x80,0x80,0x80},    // F  70
                     {0x3C,0x42,0x81,0x80,0x87,0x81,0x42,0x3C},    // G
                     {0x81,0x81,0x81,0xFF,0x81,0x81,0x81,0x81},    // H
                     {0xFE,0x10,0x10,0x10,0x10,0x10,0x10,0xFE},    // I
                     {0xFF,0x08,0x08,0x08,0x08,0x88,0x88,0x70},    // J
                     {0x88,0x90,0xA0,0xC0,0xA0,0x90,0x88,0x84},    // K
                     {0x80,0x80,0x80,0x80,0x80,0x80,0x80,0xFE},    // L
                     {0x81,0xC3,0xA5,0x99,0x81,0x81,0x81,0x81},    // M
                     {0x81,0xC1,0xA1,0x91,0x89,0x85,0x83,0x81},    // N
                     {0x3C,0x42,0x81,0x81,0x81,0x81,0x42,0x3C},    // O
                     {0xFC,0x82,0x81,0x82,0xFC,0x80,0x80,0x80},    // P  80
                     {0x3C,0x42,0x81,0x81,0x81,0x85,0x42,0x3D},    // Q
                     {0xFC,0x82,0x81,0x82,0xFC,0x84,0x82,0x81},    // R
                     {0x3C,0x42,0x81,0x40,0x3E,0x81,0x42,0x3C},    // S
                     {0xFE,0x10,0x10,0x10,0x10,0x10,0x10,0x10},    // T
                     {0x82,0x82,0x82,0x82,0x82,0x82,0x44,0x38},    // U
                     {0x82,0x82,0x82,0x82,0x82,0x44,0x28,0x10},    // V
                     {0x81,0x81,0x81,0x81,0x99,0xA5,0xC3,0x81},    // W
                     {0x81,0x42,0x24,0x18,0x18,0x24,0x42,0x81},    // X
                     {0x82,0x44,0x28,0x10,0x10,0x10,0x10,0x10},    // Y
                     {0xFF,0x02,0x04,0x08,0x10,0x20,0x40,0xFF},    // Z  90
                     {0xE0,0x80,0x80,0x80,0x80,0x80,0x80,0xE0},    // [ 
                     {0x80,0x40,0x20,0x10,0x08,0x04,0x02,0x01},    // 
                     {0x07,0x01,0x01,0x01,0x01,0x01,0x01,0x07},    // ]
                     {0xE0,0xA0,0xE0,0xA0,0xAA,0x15,0x15,0x11},    // am (coded as '^'
                     {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7E},    // _
                     {0x10,0x08,0x00,0x00,0x00,0x00,0x00,0x00},    // '
                     {0x00,0x00,0x38,0x04,0x3C,0x44,0x48,0x34},    // a
                     {0x00,0x40,0x40,0x40,0x78,0x44,0x44,0x38},    // b
                     {0x00,0x00,0x18,0x24,0x40,0x40,0x24,0x18},    // c
                     {0x00,0x04,0x04,0x04,0x3C,0x44,0x44,0x38},    // d  100
                     {0x00,0x00,0x38,0x44,0x7C,0x40,0x44,0x38},    // e
                     {0x00,0x18,0x20,0x20,0x78,0x20,0x20,0x20},    // f
                     {0x00,0x38,0x44,0x44,0x38,0x04,0x44,0x38},    // g
                     {0x00,0x40,0x40,0x40,0x78,0x44,0x44,0x44},    // h
                     {0x00,0x00,0x40,0x00,0x40,0x40,0x40,0x40},    // i
                     {0x00,0x08,0x00,0x08,0x08,0x08,0x48,0x30},    // j
                     {0x00,0x40,0x40,0x48,0x50,0x60,0x50,0x48},    // k 
                     {0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x20},    // l
                     {0x00,0x00,0x00,0x28,0x54,0x44,0x44,0x44},    // m
                     {0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x44},    // n  110
                     {0x00,0x00,0x00,0x38,0x44,0x44,0x44,0x38},    // o 
                     {0x00,0x00,0x70,0x48,0x48,0x70,0x40,0x40},    // p
                     {0x00,0x00,0x30,0x48,0x48,0x38,0x08,0x08},    // q
                     {0x00,0x00,0x00,0x30,0x48,0x40,0x40,0x40},    // r
                     {0x00,0x30,0x48,0x40,0x30,0x08,0x48,0x30},    // s
                     {0x00,0x20,0x70,0x20,0x20,0x20,0x28,0x10},    // t
                     {0x00,0x00,0x44,0x44,0x44,0x44,0x44,0x38},    // u
                     {0x00,0x00,0x44,0x44,0x44,0x44,0x28,0x10},    // v
                     {0x00,0x00,0x82,0x82,0x82,0x92,0x54,0x28},    // w
                     {0x00,0x00,0x84,0x48,0x30,0x30,0x48,0x84},    // x  120
                     {0x00,0x48,0x48,0x48,0x38,0x08,0x48,0x30},    // y 
                     {0x00,0x00,0x00,0x7C,0x08,0x10,0x20,0x7C},    // z
                     {0x00,0x30,0x40,0x40,0x80,0x40,0x40,0x30},    // {
                     {0x00,0x20,0x20,0x20,0x20,0x20,0x20,0x20},    // |
                     {0x00,0x60,0x10,0x10,0x08,0x10,0x10,0x60},    // }
                     {0xE0,0xA0,0xE0,0x80,0x8A,0x15,0x15,0x11}     // pm codes as '~'  Hex 7E, Dec 126
                   };




// define RTC operation
RTC_DS1307 RTC;               // Tiny RTC (DS1307) module (SDA - A4, SCL - A5)

// rotary encoder, switch and LED control
#define enc_PinA   2          // encoder A to pin 2  (interrupt 0)
#define enc_PinB   4          // encoder B to pin 4
#define enc_Switch 3          // encoder switch to pin 3 (interrupt 1)
#define mode_Pin   8          // mode LED pin
#define min_Pin    9          // minute LED pin

unsigned char enc_A, enc_B, enc_A_prev=0;

static boolean rotating  = false;
static boolean clockwise = false;
static boolean updateFlag= false;
static int mode = 0;          // 0 - nothing, 1 - hour set, 2 - min set

// define display strings
#define max_array_size 100
char ac[max_array_size] = {};
byte rc[8] = {};
String display_message = "";
int arraylen;


// operational parameters
#define delay_line 75       // ms between line shifts
#define delay_char 400       // ms between characters
#define delay_mess 500       // ms between messages
#define cblanks 1            // number of blank lines between characters
#define eblanks 0            // number of additional blank lines (above 8) at the end of the message

// display features
static boolean top2bottom  = false;      // display direction (top to bottom, or right to left
static boolean hour24      = false;      // 24 hour display?
static boolean charHI      = true;       // highlight whole character

static boolean doSerial = true;           // serial output?


//*****************************************************************************************//
//                                      Initial Setup
//*****************************************************************************************//

void setup () {
  Wire.begin();
  
  Serial.begin(57600);
  if(doSerial) Serial.print("MATRIX Clock - Adrian Jones, Mar. 2014");
  
  // 8x8 LED matrix control pins
  pinMode(dataIn, OUTPUT);
  pinMode(clock,  OUTPUT);
  pinMode(load,   OUTPUT);
  initMatrix();                            // initialize LED matrix

  // LED pins
  pinMode(mode_Pin, OUTPUT);               // mode pin
  digitalWrite(mode_Pin, 1);
  pinMode(min_Pin, OUTPUT);                // minute pin
  digitalWrite(min_Pin, 1);
  
  // encoder control
  pinMode(enc_PinA, INPUT_PULLUP);  digitalWrite(enc_PinA,   HIGH);      // rotary encoder pin A
  pinMode(enc_PinB, INPUT_PULLUP);  digitalWrite(enc_PinB,   HIGH);      // rotary encoder pin B
  pinMode(enc_Switch, INPUT_PULLUP);digitalWrite(enc_Switch, HIGH);      // encoder switch
  
  attachInterrupt(0, rotEncoder, CHANGE);    // time setting
  attachInterrupt(1, swEncoder,  CHANGE);    // mins / hours

  // RTC  
  RTC.begin();
  if (! RTC.isrunning()) {   
    RTC.adjust(DateTime(__DATE__, __TIME__));
    if(doSerial) Serial.println("  (RTC reset)");
  } else {
    if(doSerial) Serial.println("  (RTC running)");
  }
  
}  

//*****************************************************************************************//
//                                      Main Loop
//*****************************************************************************************//

void loop () {

    DateTime now = RTC.now();   // 
    show_time_and_date(now);                    // display time
    display_message = createMessage(now);
    arraylen = initDisplayString(display_message);
    
    if(updateFlag) {
      show_time_and_date(now);
      updateFlag = false;  
    }

    while(rotating) {
      delay(1);                                   // debounce
      adjTime(now, clockwise); 
      show_time_and_date( RTC.now() );
      display_message = createMessage(now);
      arraylen = initDisplayString(display_message);
      delay(1);
      rotating = false;       // Reset the interrupt flag back to false
    }
    delay(5);

    for (int i = 0; i < (arraylen-7); i++) {                       // loops through message array, advancing one byte at a time
      for (int j = 1; j < 9; j++) { maxSingle(j,ac[i+8-j]);  }     // row 1 gets ac[i+8], row 2 gets ac[i+7] etc... row 8 gets ac[i+0]
      if(i%(8+cblanks) == 0) {                                     // when there is a complete character on the display...
          if(charHI) maxSingle(max7219_reg_intensity, 0x01);       // ... increase brightness and temporary halt
          newDelay(delay_char);
      } else {                                                     // normal brightness
          maxSingle(max7219_reg_intensity, 0x00);
          newDelay(delay_line);
      }
    }
    if(mode == 0) newDelay(delay_mess);
}

// ********************************************************************************** //
//                                      INTERRUPT ROUTINES
// ********************************************************************************** //

// function rotEncoder(): ISR called when encoder rotated
void rotEncoder(){
  delay(1);
  enc_A = digitalRead(enc_PinA); 
  enc_B = digitalRead(enc_PinB);
  if(!enc_A && enc_A_prev){      // change of state
    clockwise = (!enc_A && enc_B)? true : false;
    if(mode != 0) rotating = true;
  } 
  enc_A_prev = enc_A;     // Store value of A for next time    
}

// function swEncoder(): ISR called when encoder button pushed
void swEncoder(){
  delay(1);
  if(digitalRead (enc_Switch) != LOW) return;  // if switch depressed
  delay(1);                                    // debounce  
  if(digitalRead (enc_Switch) != LOW) return;  // if switch still depressed
  mode++; mode = mode % 3;                     // increment mode
  digitalWrite(mode_Pin, !(mode == 1));        // hour adjust LED
  digitalWrite(min_Pin,  !(mode == 2));        // minute adjust LED
  updateFlag = true;
}

// ********************************************************************************** //
//                                      OPERATION ROUTINES
// ********************************************************************************** //

// function newDelay
void newDelay (int dly) {
  for (int z=1; z< dly; z++) {
    delay(1);
    if(rotating || updateFlag) break;
  }
}



// function initMatrix()  :   initialization of the MAX7219 registers
void initMatrix() {
      maxSingle(max7219_reg_scanLimit, 0x07);       // all 8 columns being used
      maxSingle(max7219_reg_decodeMode, 0x00);      // set to LED matrix (not 7 seg. digit)
      maxSingle(max7219_reg_shutdown, 0x01);        // not in shutdown mode
      maxSingle(max7219_reg_displayTest, 0x00);     // not in display test
      for (int e=1; e<=8; e++) {maxSingle(e,0); }   // clear LED registers (turn all LEDs off)
      maxSingle(max7219_reg_intensity, 0x00);       // set intensity. Range: 0x00 to 0x0f 
}

// function adjTime(): increments/decrements (based encoder direction) hours/mins (depending on mode)
void adjTime(DateTime now, boolean dir) {
  if(mode == 1) {    // adjust hours
    int adj_hrs = now.hour();
    if(dir) {        // increment
       if(++adj_hrs >= 25) adj_hrs = 1;
    } else {         // decrement
      if(adj_hrs == 0) adj_hrs = 24;
      if(--adj_hrs <= 0) adj_hrs = 24;
    }
    RTC.adjust(DateTime(now.year(), now.month(), now.day(), adj_hrs, now.minute(), now.second() ));
  } 
  
  if(mode == 2) {    // adjust minutes
    int adj_mins = now.minute();
    if(dir) {   
      if(++adj_mins >= 60) adj_mins = 0; 
    } else {
      if(--adj_mins < 0) adj_mins = 59; 
    }
    RTC.adjust(DateTime(now.year(), now.month(), now.day(), now.hour(), adj_mins, now.second() ));
  }
}


// function rotChar(char): for character char, transposes bits 90 deg. (top - bottom ==> left - right)
// and stores results in rc[0] - rc[7].
byte rotChar(char inLetter) {
   int ind = int(inLetter) - 0x20;
   for (int col = 0; col < 8; col++) {
     byte mask = 0x01 << (7-col);
     for (int row = 0; row < 8 ; row++) { bitWrite(rc[col], 7-row,(alphanum[ind][row] & mask)); }
   }
}  


// function show_time_and_date:  print out time string & bytes
void show_time_and_date(DateTime datetime){
  if(doSerial) {
    int minutes = datetime.minute();
    int hours   = datetime.hour(); if(hours==0) hours=24;
    int seconds = datetime.second();
    char delim = '/'; char dend = ' ';
    String te = "Current date/time: ";
    te = te + datetime.year() + delim + datetime.month() + delim + datetime.day() + dend;

    Serial.print(te);
    if(hours<10) Serial.print(0); Serial.print(hours,DEC);
    Serial.print(":");
    if(minutes<10) Serial.print(0); Serial.print(minutes,DEC);
    Serial.print(":");
    if(seconds < 10) Serial.print(0); Serial.print(seconds,DEC);
    Serial.println("");
  } 
}


String createMessage(DateTime datetime) {
  String new_mess = " ";
  int hr = datetime.hour()%24;    if(hr == 0) hr = 24;
  int mn = datetime.minute();
  if(mode == 0) {                  // Normal mode
    if(hour24) { 
      new_mess += hr;
    } else {
      new_mess += (hr > 12)? hr - 12 : hr;
    }
    new_mess += ':';
    if(mn < 10) new_mess += '0'; new_mess += mn;
    if(!hour24) new_mess += (hr > 12)? "~" : "^";
  }
  
  if(mode == 1) {                  // Adjusting hours
    new_mess += hr;
  }  
  
  if(mode == 2) {                 // Adjusting minutes
    if(mn < 10) new_mess += '0'; new_mess += mn;
  }
  return new_mess;
}


// function initDisplayString()  :   creates array of message string with blanks between characters and at end
int initDisplayString(String message) {
  int x = 0;
  for (int y = 0; y < message.length(); y++ ){
    char thisCh = message.charAt(y);
    int ind = int(thisCh) - 0x20;
    if(!top2bottom) rotChar(thisCh);
    for (int row = 0; row < 8 + cblanks; row++) {
      if (row <= 7) {
        ac[x] = (top2bottom)? alphanum[ind][7-row] : rc[row];
      } else {
         ac[x] = 0;
      }
      x++;
    }
  }

  for(int y = 0; y < 7+eblanks; y++) {ac[x] = 0; x++; }    // end blanks
  return x;
}


// function maxSingle()  :   loads data into register 
void maxSingle(byte reg, byte col) {      
  digitalWrite(load, LOW);       // begin    
  putByte(reg);                    // specify register
  putByte(col);                    // ((data & 0x01) * 256) + data >> 1); // put data  
  digitalWrite(load,HIGH);
}


// function putByte()  :   loads data to matrix, MSB to LSB 
void putByte(byte data) {
  byte i = 8;
  byte mask;
  while(i > 0) {                 // MSB to LSB
    mask = 0x01 << (i - 1);      // create bitmask
    digitalWrite(clock, LOW);   // tick
    if (data & mask){            // choose bit
      digitalWrite(dataIn, HIGH);// send 1
    } else {
      digitalWrite(dataIn, LOW); // send 0
    }
    digitalWrite(clock, HIGH);   // tock
    --i;                         // move to lesser bit
   }
}

Schematics

schematic
Single matrix clock bb rovsddsz3e

Comments

Similar projects you might like

Tiny Calendar and Clock Featuring Moon Phase in a LED Matrix

Project in progress by LAGSILVA

  • 6,360 views
  • 6 comments
  • 35 respects

Arduino Infinity Mirror LED Clock

Project tutorial by Mirko Pavleski

  • 1,102 views
  • 2 comments
  • 1 respect

Simple Clock with LED Matrix

Project tutorial by LAGSILVA

  • 496 views
  • 1 comment
  • 4 respects

Analog Clock with LED Matrix and Arduino

Project in progress by LAGSILVA

  • 13,056 views
  • 10 comments
  • 45 respects

Amazing Binary Clock using LED Matrix Module

Project in progress by LAGSILVA

  • 9,821 views
  • 12 comments
  • 57 respects

Digital And Binary Clock With Two LED Matrix And RTC

Project in progress by LAGSILVA

  • 6,986 views
  • 23 comments
  • 32 respects
Add projectSign up / Login