//*****************************************************************************************//
// 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
}
}
Comments