Reloj binario con Nodejs y Arduino

Escrito por: Ulises Gascón

Oct 18, 20159 min read

Gif animado que muestra el funcionamiento de la pantalla LCD y la Matriz de Leds en su conjunto

La idea de este post es mostrar como hacer un reloj binario decimal usando JavaScript. Para el software utilizaremos Node.js y Johnny-five, para el hardware utilizaremos un display LCD con una interfaz I2C y una Matriz de Leds de 8x16.

Como podemos ver en la imagen, el objetivo es mostrar la hora y fecha de manera convencional en el LCD y la hora en formato binario en la Matriz de LEDs.

Esquema eléctrico

Bien, aunque en la foto parece muy aparatoso, la realidad es que en este caso tanto la matriz como el LCD comparten las mismas conexiones.

Conexiones electricas representadas en un protoboard

Esquema de conexiones:

  • Positivo (5v) a positivo (5v)
  • Negativo (GND) a negativo (GND)
  • SDA a A4
  • SCL a A5

Como podéis ver... es muy sencillo gracias a que utilizamos I2C. Francamente, esta es una de las cosas que me ha cambiado la vida a la hora de trabajar con Arduino.

El código

Como no puede ser de otra manera, todo el código fuente está desde hace algún tiempo en Github.

Antes de que os lancéis a por mi... quiero recordaros que este código esta pensado para enseñar. El código lo he mantenido todo lo simple, lo que conlleva que sea excesivamente largo, ello sin contar que los lectores más expertos ya se habrán dado cuenta que sería necesario incluir al menos un callback para optimizar la sincronización entre la matriz y el LCD... y otros pequeños detalles. Pero no olvidemos que el plan es hacer este script a un nivel lo más básico y asequible posible, para que pueda ser una buena toma de contacto con Johnny-Five y Nodejs para los lectores que están acostumbrados a trabajar con Arduino desde la perspectiva de C/C++ y Python.

Leyendo un reloj binario

Creo que sería buena idea antes de empezar a desglosar el código, que empecemos por entender como se lee un reloj binario. Aprovecho para mostraros una de las primeras fotos del prototipo cuando aún estaba enfocando cuales serian los LEDs afortunados de participar en el proyecto y cuales serian los que quedarían relegados debajo de una máscara de papel hecha con un post-it.

Se muestran varias imagenes que explican como evoluciona el prototipo desde dibujos hasta conexiones

Pasemos a la lectura de la hora en la última foto. El primer paso es dividir la hora en tres bloques clásicos (horas, minutos y segundos). Si a esta división básica de tres grupos le sumamos el hecho de que vamos a trabajar en el formato 24horas (HH:MM:SS), con lo cual tenemos ante nosotros seis columnas.

Ahora viene la parte matemáticamente más interesante: ¿Cómo representar del cero al nueve con el menor número de LEDs?. La opción más fácil sería usar nueve LEDs, pero eso seria la solución más obvia. En este caso, yo he seguido el patrón más común para los relojes binarios que es usar hasta cuatro LEDs y sumar los valores de los LEDs que están encendidos. El valor de los LEDs varia según su posición en la columna, de abajo a arriba, los valores son uno, dos, cuatro y ocho. Con la siguiente imagen se ve aun más fácilmente

Representacion visual por columnas de como se distribuye el tiempo en un reloj binario

De la primera columna solo el primer led esta encendido, y en la segunda ninguno... por lo que la hora es las 10. En cuanto a los minutos la tercera columna solo tiene dos LEDs encendido el primero y segundo empezando por abajo, lo que suma 3 (1+2), en la siguiente columna tres LEDs encendidos y suman 7 (1+2+4). Ya sabemos que son las 10:37. Nos faltan los segundos. La quinta columna solo tiene un led encendido y su valor es 4. La última columna solo tiene dos LEDs encendidos que suman 9 (8+1). Y así es como confirmamos que la hora de la imagen corresponde a las 10:37:49.

Desmontemos el código

Nuestro primer paso lógico es definir el hardware y las dependencias de nuestro script.

var five = require("johnny-five");
var board = new five.Board();

//...

board.on("ready", function() {

// Definimos la Matriz de Leds (HT16K33 de 8X16) usando I2C 
var matrix = new five.Led.Matrix({
    addresses: [0x70],
    controller: "HT16K33",
    dims: "8x16",
    rotation: 2
});


// Definimos el LCD (LCM1602 de 4X20) usando I2C 
var lcd = new five.LCD({
    controller: "LCM1602",
    pins: ["A5", "A4"],
    rows: 4,
    cols: 20
});

// Limpiamos los dispositivos al arrancar
matrix.clear();
lcd.clear();

//...
}

Una vez Johnny-five ha cargado, definimos el LCD y la matriz de LEDs, para que luego los podamos usar a lo largo del script.

El siguiente paso lógico es crear las funciones para saber la hora y la fecha.


// Función para imprimir en el LCD la fecha en este formato (DD/MM/YYYY)
function getDateLCD () {
    var date = new Date();

    var year = date.getFullYear();

    var month = date.getMonth() + 1;
    month = (month < 10 ? "0" : "") + month;

    var day  = date.getDate();
    day = (day < 10 ? "0" : "") + day;

    return ""+day+"/"+month+"/"+year; // DD/MM/YYYY
}

// Función para imprimir en el LCD la hora en este formato (HH:MM:SS)
function getTimeLCD () {
    var date = new Date();

    var hour = date.getHours();
    hour = (hour < 10 ? "0" : "") + hour;

    var min  = date.getMinutes();
    min = (min < 10 ? "0" : "") + min;

    var sec  = date.getSeconds();
    sec = (sec < 10 ? "0" : "") + sec;

    return ""+ hour + ":" + min + ":" + sec; // HH:MM:SS
  };

// Función para devolver la hora en este formato (HHMMSS)
function getSpecialTime() {

    var date = new Date();

    var hour = date.getHours();
    hour = (hour < 10 ? "0" : "") + hour;

    var min  = date.getMinutes();
    min = (min < 10 ? "0" : "") + min;
    
    var sec  = date.getSeconds();
    sec = (sec < 10 ? "0" : "") + sec;
    
    return ""+hour+""+min+""+sec; //HHMMSS
}

Estas tres funciones nos devuelven la fecha en tres formatos distintos. Para el LCD necesitaremos la fecha (DD/MM/YY) y la hora (HH:MM:SS). Evidentemente para la matriz de LEDs necesitaremos un formato diferente (HHMMSS). Nuevamente los lectores más experimentados en JavaScript ya habrán pensado en refactorizar esto en una sola función, cierto... pero recordemos que el objetivo es hacer este script lo más básico y visual posible para los principiantes.

Bueno, veamos... ya que tenemos las funciones de tiempo definidas, va siendo hora de invocarlas.

//Funcion que regula el LCD

function runLCD () {
      lcd.cursor(0, 0).print("===== BCD CLOCK ====");
      lcd.cursor(1, 0).print("Date: "+getDateLCD()); // (Date: DD/MM/YYYY)
      lcd.cursor(2, 0).print("Time: "+getTimeLCD() ); // (Time: HH:MM:SS)
      lcd.cursor(3, 0).print("===================="); 
};

Con la función runLCD, nos aseguramos que el LCD imprima tanto la hora como la fecha. El resto del texto es estático. Así cuando se hace el refresco del display, el ojo humano no percibe el cambio.

Y ya va siendo hora de dividir el tiempo (HHMMSS) en trozos que podamos manejar individualmente, y con ello actualicemos la matriz.

// Función que regula el comportamiento de los leds
function runMatrix () {

    // Crear un array con cada elemento de la función que da la hora en formato (HHMMSS) -> [H,H,M,M,S,S]
    var res = getSpecialTime().split("");

// ...

    // Minutos Primera Columna
    if (res[2] == 0) {
      m11 = 0;
      m12 = 0;
      m14 = 0;

    } else if(res[2] == 1){
      m11 = 1;
      m12 = 0;
      m14 = 0;
        
    } else if(res[2] == 2){
      m11 = 0;
      m12 = 1;
      m14 = 0;

    } else if(res[2] == 3){
      m11 = 1;
      m12 = 1;
      m14 = 0;

    } else if(res[2] == 4){
      m11 = 0;
      m12 = 0;
      m14 = 1;            

    } else if(res[2] == 5){
      m11 = 1;
      m12 = 0;
      m14 = 1;

    } else {
      m11 = 0;
      m12 = 0;
      m14 = 0;
      console.log("Error in minutes column 1")
    };


// ...

/*
  === Formato ===
  xyz = h12, h22, s24, m13 ...
  x es h (horas), m (minutos) o s (segundos)
  y es columna de izquierda a derecha
  z es valor 1, 2, 4 o 8.
  Ejemplo: h11 -> en horas, columna 1, led que equivale al valor 1. 
  Más info -> https://upload.wikimedia.org/wikipedia/commons/thumb/2/27/Binary_clock.svg/640px-Binary_clock.svg.png?1435837556051
*/
// Representación visual de la matriz (8x16). Siendo 0 apagado y 1 encendido.
var bcdMatrix = [
    "0000000000000000",
    "0000000000000000",
    "0000"+h28+"000"+m28+"000"+s28+"000",
    "0000"+h24+"00"+m14+""+m24+"00"+s14+""+s24+"000",
    "000"+h12+""+h22+"00"+m12+""+m22+"00"+s12+""+s22+"000",
    "000"+h11+""+h21+"00"+m11+""+m21+"00"+s11+""+s21+"000",
    "0000000000000000",
    "0000000000000000" 
  ];

// Se Pinta el dispositivo con los datos
matrix.draw(bcdMatrix);
}

Dividimos el tiempo en un array de 6 elementos. A continuación pasamos cada elemento por una estructura if...else if...else, aunque nuevamente los lectores más experimentados exigirían una estructura Swith...case, y yo estoy totalmente de acuerdo, pero mantengamos el perfil del script todavía adecuado a todos los lectores...

Cuando se realiza la comparación, el script asigna el valor 0 ó 1 a cada una de las variables que controla uno de los LEDs de la columna correspondiente. Este planteamiento parece un poco complejo, pero si miramos bien la estructura del array bcdMatrix, observamos que casi todo son ceros y el resto son variables. Como dije al principio, hay muchos LEDs que no necesitaremos y siempre estarán apagados, estos corresponden a los ceros. El resto estarán en cambio constante y son controlados gracias a todas las variables que encontramos dentro del array.

Solo queda un último matiz ya para cerrar este desglose del código... y es el control del intervalo.

// Arranque y control de la repetición

// Intervalo que arranca la funcion RunIt, y la repite cada x milisegundos en funcion del valor de "intervalMS".
setInterval(runIt, intervalMS);

// Función que controla la ejecucción de todo lo demás
function runIt () {

  // Arranca el reloj en la Matriz de leds
  runMatrix();

  // Si el LCD esta activo... muestra los datos. Sino... se limpia
  if (LCDisEnable) {
  runLCD();
  }else {
    lcd.clear();
  };

};

Cada segundo (1000ms) se vuelve a ejecutar la función runIt que vuelve a ejecutar las funciones relativas al LCD y a la Matriz de LEDs.

El sistema en funcionamiento

Y con esto.. llegamos al final del post de esta semana, espero que hayáis disfrutado de la lectura tanto como yo de escribirlo. Y sobre todo, animaros a cacharrear un poco con Node.js... y compartirlo con la comunidad ;-)

Imágenes | Wikipedia y propias