Reloj flip Arduino con matriz LED 8×8: proyecto DIY de reloj en tiempo real
Componentes y materiales
| Componente | Cantidad |
|---|---|
| Arduino Nano R3 | 1 |
| Maxim Integrated DS3231M - RTC I2C ±5ppm | 1 |
| Matriz LED 8x8 con driver MAX7219 | 1 |
| LED RGB difuso cátodo común | 1 |
| Resistencia 220 ohm | 2 |
| Encoder rotativo con pulsador | 1 |
Herramientas necesarias
- Soldador (genérico)
Aplicaciones y servicios online
- Arduino IDE
Sobre este proyecto
He construido este reloj con módulo de matriz LED 8x8 desarrollado por Adrian Jones. La hora se muestra desplazándose de izquierda a derecha, o bajo control por software, de arriba a abajo. Cada dígito de la hora se desliza sucesivamente desde el lado derecho y, cuando se centra, se detiene momentáneamente y se ilumina ligeramente. Luego se desplaza hacia la izquierda mientras el siguiente dígito aparece en pantalla. El ciclo se repite con un breve retraso entre visualizaciones sucesivas.
Para el volteo vertical "de arriba a abajo", cambia:
static boolean top2bottom = false;
a:
static boolean top2bottom = true;
Como se ve en los comentarios del código, muchos parámetros son ajustables: velocidad de desplazamiento, número de líneas en blanco entre caracteres, visualización 12/24 horas, milisegundos entre caracteres, brillo, etc.
La hora se configura fácilmente mediante un encoder rotativo. Un diodo de dos colores indica el modo de ajuste (minutos, horas o normal). He colocado el reloj en una caja formada por dos piezas básicas, lo que permite un acceso muy cómodo y práctico al interior.
Código
code (Arduino)
//*****************************************************************************************// // MATRIX CLOCK // Adrian Jones, March 2014 // // - allows left-to-right and top-to-bottom scrolling // //*****************************************************************************************// #include <Wire.h> // librería I2C-WIRE #include <RTClib.h> // librería RTC // definir registros max7219 y pines de control #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 (codificado como '^' {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 codificado como '~' Hex 7E, Dec 126 }; // definir operación RTC RTC_DS1307 RTC; // Módulo Tiny RTC (DS1307) (SDA - A4, SCL - A5) // control de encoder rotativo, pulsador y LED #define enc_PinA 2 // encoder A al pin 2 (interrupción 0) #define enc_PinB 4 // encoder B al pin 4 #define enc_Switch 3 // pulsador encoder al pin 3 (interrupción 1) #define mode_Pin 8 // pin LED modo #define min_Pin 9 // pin LED minutos 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 - nada, 1 - ajuste hora, 2 - ajuste minutos // definir cadenas de visualización #define max_array_size 100 char ac[max_array_size] = {}; byte rc[8] = {}; String display_message = ""; int arraylen; // parámetros operativos #define delay_line 75 // ms entre desplazamientos de línea #define delay_char 400 // ms entre caracteres #define delay_mess 500 // ms entre mensajes #define cblanks 1 // número de líneas en blanco entre caracteres #define eblanks 0 // número de líneas en blanco adicionales (más de 8) al final del mensaje // características de visualización static boolean top2bottom = false; // dirección de visualización (arriba-abajo o derecha-izquierda) static boolean hour24 = false; // visualización 24 horas? static boolean charHI = true; // resaltar carácter completo static boolean doSerial = true; // salida serial? //*****************************************************************************************// // Configuración inicial //*****************************************************************************************// void setup () { Wire.begin(); Serial.begin(57600); if(doSerial) Serial.print("MATRIX Clock - Adrian Jones, Mar. 2014"); // pines de control matriz LED 8x8 pinMode(dataIn, OUTPUT); pinMode(clock, OUTPUT); pinMode(load, OUTPUT); initMatrix(); // inicializar matriz LED // pines LED pinMode(mode_Pin, OUTPUT); // pin modo digitalWrite(mode_Pin, 1); pinMode(min_Pin, OUTPUT); // pin minutos digitalWrite(min_Pin, 1); // control encoder pinMode(enc_PinA, INPUT_PULLUP); digitalWrite(enc_PinA, HIGH); // pin A encoder rotativo pinMode(enc_PinB, INPUT_PULLUP); digitalWrite(enc_PinB, HIGH); // pin B encoder rotativo pinMode(enc_Switch, INPUT_PULLUP);digitalWrite(enc_Switch, HIGH); // pulsador encoder attachInterrupt(0, rotEncoder, CHANGE); // ajuste hora attachInterrupt(1, swEncoder, CHANGE); // min / horas // RTC RTC.begin(); if (! RTC.isrunning()) { RTC.adjust(DateTime(__DATE__, __TIME__)); if(doSerial) Serial.println(" (RTC reset)"); } else { if(doSerial) Serial.println(" (RTC running)"); } } //*****************************************************************************************// // Bucle principal //*****************************************************************************************// void loop () { DateTime now = RTC.now(); // show_time_and_date(now); // mostrar hora display_message = createMessage(now); arraylen = initDisplayString(display_message); if(updateFlag) { show_time_and_date(now); updateFlag = false; } while(rotating) { delay(1); // antirrebote adjTime(now, clockwise); show_time_and_date( RTC.now() ); display_message = createMessage(now); arraylen = initDisplayString(display_message); delay(1); rotating = false; // Resetear bandera de interrupción } delay(5); for (int i = 0; i < (arraylen-7); i++) { // recorre array de mensaje, avanzando un byte cada vez for (int j = 1; j < 9; j++) { maxSingle(j,ac[i+8-j]); } // fila 1 obtiene ac[i+8], fila 2 ac[i+7]... fila 8 ac[i+0] if(i%(8+cblanks) == 0) { // cuando hay un carácter completo en pantalla... if(charHI) maxSingle(max7219_reg_intensity, 0x01); // ... aumentar brillo y pausa temporal newDelay(delay_char); } else { // brillo normal maxSingle(max7219_reg_intensity, 0x00); newDelay(delay_line); } } if(mode == 0) newDelay(delay_mess); } // ********************************************************************************** // // RUTINAS DE INTERRUPCIÓN // ********************************************************************************** // // función rotEncoder(): ISR llamada al rotar encoder void rotEncoder(){ delay(1); enc_A = digitalRead(enc_PinA); enc_B = digitalRead(enc_PinB); if(!enc_A && enc_A_prev){ // cambio de estado clockwise = (!enc_A && enc_B)? true : false; if(mode != 0) rotating = true; } enc_A_prev = enc_A; // Guardar valor de A para próxima vez } // función swEncoder(): ISR llamada al pulsar botón encoder void swEncoder(){ delay(1); if(digitalRead (enc_Switch) != LOW) return; // si pulsador no presionado delay(1); // antirrebote if(digitalRead (enc_Switch) != LOW) return; // si sigue sin presionar mode++; mode = mode % 3; // incrementar modo digitalWrite(mode_Pin, !(mode == 1)); // LED ajuste hora digitalWrite(min_Pin, !(mode == 2)); // LED ajuste minutos updateFlag = true; } // ********************************************************************************** // // RUTINAS DE OPERACIÓN // ********************************************************************************** // // función newDelay void newDelay (int dly) { for (int z=1; z< dly; z++) { delay(1); if(rotating || updateFlag) break; } } // función initMatrix(): inicialización de registros MAX7219 void initMatrix() { maxSingle(max7219_reg_scanLimit, 0x07); // todas las 8 columnas en uso maxSingle(max7219_reg_decodeMode, 0x00); // modo matriz LED (no 7 seg.) maxSingle(max7219_reg_shutdown, 0x01); // no en modo apagado maxSingle(max7219_reg_displayTest, 0x00); // no en test de visualización for (int e=1; e<=8; e++) {maxSingle(e,0); } // limpiar registros LED (apagar todos) maxSingle(max7219_reg_intensity, 0x00); // intensidad. Rango: 0x00 a 0x0f } // función adjTime(): incrementa/decrementa (según dirección encoder) horas/minutos (según modo) void adjTime(DateTime now, boolean dir) { if(mode == 1) { // ajustar horas int adj_hrs = now.hour(); if(dir) { // incrementar if(++adj_hrs >= 25) adj_hrs = 1; } else { // decrementar 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) { // ajustar minutos 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() )); } } // función rotChar(char): para carácter char, transpone bits 90° (arriba-abajo => izquierda-derecha) // y guarda resultados en 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)); } } } // función show_time_and_date: imprime cadena de hora y 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) { // Modo normal 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) { // Ajustando horas new_mess += hr; } if(mode == 2) { // Ajustando minutos if(mn < 10) new_mess += '0'; new_mess += mn; } return new_mess; } // función initDisplayString(): crea array de mensaje con espacios entre caracteres y al final 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++; } // espacios finales return x; } // función maxSingle(): carga datos en registro void maxSingle(byte reg, byte col) { digitalWrite(load, LOW); // inicio putByte(reg); // especificar registro putByte(col); // ((data & 0x01) * 256) + data >> 1); // poner dato digitalWrite(load,HIGH); } // función putByte(): carga datos a matriz, MSB a LSB void putByte(byte data) { byte i = 8; byte mask; while(i > 0) { // MSB a LSB mask = 0x01 << (i - 1); // crear máscara bit digitalWrite(clock, LOW); // tick if (data & mask){ // elegir bit digitalWrite(dataIn, HIGH);// enviar 1 } else { digitalWrite(dataIn, LOW); // enviar 0 } digitalWrite(clock, HIGH); // tock --i; // mover a bit menor } }
Esquemáticos
Proceso de manufactura
- Reloj POV con Arduino: Construye una pantalla LED de persistencia de visión
- Construye un reloj Numitron retro con Arduino: Simple, fiable y eficiente energéticamente
- Construye un Reloj Maestro LED Personalizado con Alarma – Visible desde 12 Metros
- Construye un juego Tetris con Arduino Nano en matriz LED 16x8 DIY
- MotionSense: Detección inteligente de intrusos con Arduino y ESP8266
- Pantalla LED Matricial Interactiva en Puerta con Sensor PIR - Saludo Navideño Arduino
- Construye un Robot Cuadrúpedo Inteligente con Arduino: Guía Paso a Paso
- Control de brillo LED sin interrupciones con Bolt IoT y Arduino UNO
- Crear un medidor IoT web con Arduino, Yaler e IFTTT
- Levitación magnética repulsiva con Arduino: Circuito simple y guía DIY
- Registrador de Datos de Alto Rendimiento para Globo Meteorológico con Arduino