En esta nueva entrada del curso de programación con Protocoder vamos a aprender a utilizar matrices LED para crear animaciones para nuestros robots. En este caso, utilizaremos la matriz para crear un ojo que podremos controlar remotamente gracias a Protocoder.
Lista de materiales
- Placa ZUM BT-328
- Matriz de LED: En este caso, utilizamos una placa basada en el chip MAX72xx
- Comodoro: Podéis descargar al final de la entrada los archivos de nuestro robot
Código Arduino
El código Arduino se encargará de recibir datos desde el programa de Protocoder y, en función de los datos recibidos, se moverá el ojo a una determinada posición. Además, el ojo parpadeará de manera “aleatoria” cada cierto tiempo.
Para controlar la matriz de LED utilizamos la librería LedControl, que podéis descargar aquí o junto al resto de código al final de la entrada. Si queréis conocer todas las posibilidades que brinda la librería, podéis encontrar más información en esta página de Arduino Playground.
Para comenzar a programar la parte de Arduino, tenemos que incluir la librería, definir los pines y crear un objeto LedControl, que en nuestro caso hemos llamado LM:
1 2 3 4 5 6 7 8 9 10 11 |
//Led Display #include "LedControl.h" #define MAX_DEVICES 1 #define CLK_PIN 13 #define DATA_PIN 11 #define CS_PIN 12 LedControl LM = LedControl(DATA_PIN, CLK_PIN, CS_PIN, MAX_DEVICES); |
Con la ultima línea del código anterior, creamos el objeto LM con el que controlaremos la matriz de LED.
También creamos la variable inString que se encargará de almacenar los datos que se reciban por Bluetooth. Las funciones readFromAndroid y writeData, se encargarán de almacenar los datos en inString y de “escoger” hacia donde “mira” el ojo, respectivamente. Si quieres conocer más a fondo estas funciones, puedes visitar esta página.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
String inString = ""; // String para lectura desde Protocoder void readFromAndroid() { char inChar; while (Serial.available() > 0) { inChar = (char) Serial.read(); Serial.flush(); if (inChar == '='){ inString = ""; LM.clearDisplay(0); }else if (inChar != '+'){ inString += inChar; } } } void writeData(){ if (inString == "up") { lookUp(); } if (inString == "down") { lookDown(); } if (inString == "center") { lookCenter(); } if (inString == "left") { lookLeft(); } if (inString == "right") { lookRight(); } } |
En este caso, una vez que se ha leído completamente el mensaje (después de recibir el carácter ‘=’) borramos la pantalla con la orden LM.clearDisplay(0);, para asegurar que la posición se represente correctamente.
En el “setup”, iniciamos el puerto serie, activamos la matriz LED (ya que por defecto está en modo de ahorro de energía), establecemos el brillo al máximo, “borramos” la matriz de LED y, finalmente, establecemos que el ojo mire hacia el centro.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
void setup() { Serial.begin(19200); /* The MAX72XX is in power-saving mode on startup, we have to do a wakeup call // Function to go to power-save mode: LM.shutdown(0,true); */ LM.shutdown(0, false); /* Set the brightness to max values value={0,15} */ LM.setIntensity(0, 15); /* and clear the display */ LM.clearDisplay(0); lookCenter(); // test rutines //testPos(250); //test(); } |
Se han incluido dos rutinas de prueba, testPos(), que cambia entre las posiciones del ojo y test(), que enciende progresivamente cada uno de los LED.
Para generar las diferentes posiciones de parpadeo podemos utilizar una matriz del archivo que podéis encontrar en las descargas, donde marcaremos qué LED queremos que estén encendidos y los “traduciremos” a código. La función que utilizamos para encender un LED es:
1 |
LM.setLed(0,fila, columna, estado) |
Estado puede ser false o true, para apagar o encender el LED, respectivamente. Para el caso de la función lookCenter(), que coloca el ojo mirando hacia delante, la matriz dibujada es:
El código de Arduino correspondiente a la imagen es:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void lookCenter() { LM.setLed(0, 3, 3, false); LM.setLed(0, 3, 4, false); LM.setLed(0, 4, 3, false); LM.setLed(0, 4, 4, false); LM.setLed(0, 1, 3, true); LM.setLed(0, 1, 4, true); LM.setLed(0, 2, 2, true); LM.setLed(0, 2, 3, true); LM.setLed(0, 2, 4, true); LM.setLed(0, 2, 5, true); LM.setLed(0, 3, 1, true); LM.setLed(0, 3, 2, true); LM.setLed(0, 3, 5, true); LM.setLed(0, 3, 6, true); LM.setLed(0, 4, 1, true); LM.setLed(0, 4, 2, true); LM.setLed(0, 4, 5, true); LM.setLed(0, 4, 6, true); LM.setLed(0, 5, 2, true); LM.setLed(0, 5, 3, true); LM.setLed(0, 5, 4, true); LM.setLed(0, 5, 5, true); LM.setLed(0, 6, 3, true); LM.setLed(0, 6, 4, true); } |
De esta manera podemos generar diferentes patrones con los LED. En nuestro caso se han generado funciones que modifican la posición de la pupila (lookLeft, lookRight, lookUp, lookDown y lookCenter) y funciones que simulan un parpadeo (blinkClose, blinkOpen y fullBlink).
La variable delayBlink cambia la velocidad de transición entre cierre y apertura de ojo. La utilizaremos para aumentar o reducir la velocidad del parpadeo. La función fullblink toma como parámetro el tiempo que pasa entre el ojo abierto y el ojo cerrado. La utilizaremos para aumentar o reducir la frecuencia del parpadeo.
1 2 3 4 5 6 7 |
int delayBlink = 10; void fullBlink(int timeClosed){ blinkClose(); delay(timeClosed); blinkOpen(); } |
Las funciones blinkClose y blinkOpen cierran y abren el ojo, respectivamente, y utilizan la variable delayBlink para determinar la velocidad del parpadeo.
Por ultimo, la función loop se encarga de controlar la rutina del parpadeo y la recepción de datos desde la conexión Bluetooth, llamando a las funciones readFromAndroid y writeData.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
void loop() { if (millis()>nextTimeDouble){ fullBlink(60); delay(100); fullBlink(60); nextTimeDouble = millis() + random(30000, 60000); writeData(); }else if (millis() > nextTime){ fullBlink(60); nextTime = millis() + random(1500, 4000); writeData(); } if (Serial.available() > 0) { readFromAndroid(); } writeData(); } |
Para gestionar el parpadeo del ojo, utilizamos la primera parte del código:
1 2 3 4 5 6 7 8 9 10 11 |
if (millis()>nextTimeDouble){ fullBlink(60); delay(100); fullBlink(60); nextTimeDouble = millis() + random(30000, 60000); writeData(); }else if (millis() > nextTime){ fullBlink(60); nextTime = millis() + random(1500, 4000); writeData(); } |
Para que el parpadeo sea diferente cada vez que se ejecuta la rutina, se han incluido dos tipos, simple y doble, cada uno controlado por una rutina similar. En primer lugar se comprueba si el tiempo actual de ejecución del programa es mayor que el tiempo establecido para el siguiente parpadeo. En caso de que la condición sea true, es decir, sea cierto la condición, se ejecutará la rutina del parpadeo y se establecerá el siguiente tiempo, sumando una cantidad variable, con la función random, a la variable que determina el siguiente tiempo.
Para leer el texto que se ha enviado a través de la conexión Bluetooth desde Protocoder, se utiliza el siguiente código:
1 2 3 4 |
if (Serial.available() > 0) { readFromAndroid(); } writeData(); |
Código Protocoder
El programa que vamos a generar en Protocoder se encargará de enviar, a través de la conexión Bluetooth, datos a Arduino.
Para comenzar la programación, utilizaremos la cabecera habitual de nuestros programas, además de la gestión de la conexión y desconexión Bluetooth. Si quieres saber más a fondo cómo funciona este código, puedes visitar esta entrada/a>.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
ui.toolbar.title("Robot-Eyes"); ui.toolbar.bgColor(55, 155, 155, 255); ui.toolbar.show(true); //ui.screenMode("fullscreen"); ui.screenOrientation("portrait"); ui.allowScroll(false); device.screenAlwaysOn(true); var margin = 10; var w = ui.screenWidth - 2*margin; var h = 150 + margin; var y = h + margin; var btStatus=0; // Variable que almacena el estado (conectado/no conectado o 1,0) del bluetooth var btClient; // "objeto" que usaremos para conectar,desconectar y trabajar con el bluetooth var btnConectar = ui.addButton("Connect bluetooth", margin, margin,w,h).onClick(function() { // Creamos el botón del bluetooth btClient = network.bluetooth.connectSerial(function(status) { console.log("connected " + status); if (status){ ui.toast("Connected"); btStatus = status; console.log("btStatus = " +btStatus); } }); }); var btnDesconectar = ui.addButton("Disconnect", margin, y,w,h).onClick(function() { // Creamos el botón de conectar btClient.disconnect(); // Desconectamos una vez se ha pulsado btStatus= false; // Cambiamos la variable de estado a desconectado (0) }); |
Para enviar datos a la placa ZUM, utilizamos la función send, que toma como parámetros la cadena de caracteres a enviar. Por ejemplo, para que el ojo mire hacia la derecha hay que enviar la palabra “right”, por lo que tenemos que escribir send(“right”);. El código de la función send es:
1 2 3 4 5 6 |
function send(str) { // Función que utilizamos para enviar un string (cadena de caracteres) if (btStatus) { // Si el bluetooth está conectado, se ejecuta el código de las siguientes lineas btClient.send("="+str+"+"); // Enviamos la cadena de caracteres entre los simbolos = y + console.log("="+str+"+"); // Mostramos en la consola la cadena de caracteres que se esta enviando. } } |
A continuación, vamos a crear los botones que determinarán la posición del ojo. Para ello, subimos los archivos que queramos utilizar a Protocoder y después creamos los botones con la orden:
1 2 3 |
ui.addImageButton(posiciónX,posiciónY,ancho,alto,"nombre del archivo",false).onClick(function(){ }); |
Para ajustar el tamaño y posición de los botones, utilizamos dos variables:
- WidthImg, utilizada para asignar el alto y ancho de la imagen.
- posX, utilizada para determinar la posición X de los botones.
En nuestro caso, incluimos la función send(); dentro de la función a realizar una vez que se ha pulsado el botón, enviando la orden correspondiente a la posición:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
ui.addImageButton(posX,3*y,widthImg,widthImg,"up.png",false).onClick(function(){ send("up"); }); ui.addImageButton(10*margin,3*y + widthImg,widthImg,widthImg,"left.png",false).onClick(function(){ send("left"); }); ui.addImageButton(posX,3*y + widthImg,widthImg,widthImg,"center.png",false).onClick(function(){ send("center"); }); ui.addImageButton(posX + widthImg,3*y + widthImg,widthImg,widthImg,"right.png",false).onClick(function(){ send("right"); }); ui.addImageButton(posX,3*y + 2*widthImg,widthImg,widthImg,"down.png",false).onClick(function(){ send("down"); }); |
Diseño 3D: Comodoro
Para realizar el diseño 3D de Comodoro se ha utilizado el programa Blender.
Para imprimir el diseño, utilizaremos Cura con los siguientes ajustes para generar el soporte:
Este soporte es muy fácil de eliminar y aporta estabilidad a la impresión.
Dentro de Cura, se ha modificado la escala a 40.79 para ajustar el diseño al tamaño de nuestra matriz de LED, pero el escalado a aplicar puede variar dependiendo del tamaño de la matriz que se utilice.