Arduino Flip Clock met 8×8 LED Matrix – DIY Real-Time Clock Project
Onderdelen en benodigdheden
| Arduino Nano R3 | × | 1 |
| Maxim Integrated DS3231M – ±5ppm, I2C Real-Time Clock | × | 1 |
| 8×8 LED Matrix met MAX7219 driver | × | 1 |
| RGB Diffused Common Cathode LED | × | 1 |
| Weerstand 220 ohm | × | 2 |
| Rotary Encoder met Push-Button | × | 1 |
Benodigde tools en machines
| Solderbout (generiek) |
Apps en online services
| Arduino IDE |
Over dit project
Ik bouwde deze 8×8 LED Matrix Module Clock, ontwikkeld door Adrian Jones. De tijd wordt weergegeven door te scrollen van links naar rechts, of – onder softwarematige controle – van boven naar beneden. Elk cijfer van de tijd glijdt achtereenvolgens van de rechterkant naar binnen en stopt momentaan in het midden, waarna het iets helderder wordt. Vervolgens scrollt het naar links terwijl het volgende cijfer in beeld komt. De cyclus herhaalt zich met een korte pauze tussen opeenvolgende weergaves.
Voor verticaal omvliegen (top to bottom) moet je de volgende regel wijzigen:
static boolean top2bottom = false;
naar:
static boolean top2bottom = true;
Zoals je in de commentaar van de code kunt zien, zijn veel parameters aanpasbaar: scrollsnelheid, aantal lege regels tussen karakters, 12/24-uurs weergave, milliseconden tussen karakters, helderheid, etc.
De tijd kan eenvoudig ingesteld worden met een rotary encoder. Een tweekleurige LED toont de instelmodus (minuten, uren of normaal). Ik plaatste de klok in een behuizing bestaande uit twee basisstukken, zodat de toegang tot het binnenste heel praktisch is.
Code
code Arduino
//*****************************************************************************************//
// 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();
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++) {
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) {
if(charHI) maxSingle(max7219_reg_intensity, 0x01);
newDelay(delay_char);
} else {
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){
clockwise = (!enc_A && enc_B)? true : false;
if(mode != 0) rotating = true;
}
enc_A_prev = enc_A;
}
// function swEncoder(): ISR called when encoder button pushed
void swEncoder(){
delay(1);
if(digitalRead (enc_Switch) != LOW) return;
delay(1);
if(digitalRead (enc_Switch) != LOW) return;
mode++;
mode = mode % 3;
digitalWrite(mode_Pin, !(mode == 1));
digitalWrite(min_Pin, !(mode == 2));
updateFlag = true;
}
// ********************************************************************************** //
// OPERATION ROUTINES
// ********************************************************************************** //
void newDelay (int dly) {
for (int z=1; z< dly; z++) {
delay(1);
if(rotating || updateFlag) break;
}
}
void initMatrix() {
maxSingle(max7219_reg_scanLimit, 0x07);
maxSingle(max7219_reg_decodeMode, 0x00);
maxSingle(max7219_reg_shutdown, 0x01);
maxSingle(max7219_reg_displayTest, 0x00);
for (int e=1; e<=8; e++) {maxSingle(e,0); }
maxSingle(max7219_reg_intensity, 0x00);
}
void adjTime(DateTime now, boolean dir) {
if(mode == 1) {
int adj_hrs = now.hour();
if(dir) {
if(++adj_hrs >= 25) adj_hrs = 1;
} else {
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) {
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() ));
}
}
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));
}
}
}
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) {
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) {
new_mess += hr;
}
if(mode == 2) {
if(mn < 10) new_mess += '0';
new_mess += mn;
}
return new_mess;
}
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++; }
return x;
}
void maxSingle(byte reg, byte col) {
digitalWrite(load, LOW);
putByte(reg);
putByte(col);
digitalWrite(load,HIGH);
}
void putByte(byte data) {
byte i = 8;
byte mask;
while(i > 0) {
mask = 0x01 << (i - 1);
digitalWrite(clock, LOW);
if (data & mask){
digitalWrite(dataIn, HIGH);
} else {
digitalWrite(dataIn, LOW);
}
digitalWrite(clock, HIGH);
--i;
}
}
Schema
Productieproces
- Arduino POV-klok: Bouw een Persistence-of-Vision LED-display
- Bouw een retro Numitron-klok met Arduino: Eenvoudig, betrouwbaar en energiezuinig
- Bouw een Aangepaste LED Hoofdklok met Alarm – Zichtbaar vanaf 12 Meter
- Bouw een Tetris-spel met Arduino Nano op een Zelfgemaakte 16×8 LED-matrix
- MotionSense: Slimme inbraakdetectie met Arduino en ESP8266
- Interactieve LED-matrix deurdisplay met PIR-bewegingsensor – Arduino vakantiegroet
- Bouw een Slimme Arduino Vierpotige Robot: Stap-voor-Stap Handleiding
- Naadloze LED-dimmer met Bolt IoT en Arduino UNO
- Web-IoT Meter Bouwen met Arduino, Yaler en IFTTT
- Arduino-gebaseerde Afstotende Magnetische Levitatie: Eenvoudige Schakeling & DIY Handleiding
- High-Performance Arduino Weerballon Datalogger