¡Bienvenido!
En esta entrada vamos a aprender a programar y controlar un PrintBot desde un terminal con la ayuda de Protocoder.
Protocoder es una herramienta de prototipado de aplicaciones móviles, permite crear interfaces y programas rápidamente. Protocoder está desarrollado por Victor Díaz, y puedes encontrar información y descargar Protocoder aquí.
Utilizaremos Protocoder como interfaz, que emplearemos para comunicarnos con la placa Zum BT-328 mediante Bluetooth, y podremos mover el PrintBot Evolution de manera libre o utilizar cada uno de los modos automáticos: sigue líneas, sigue luz y evita obstáculos.
Para controlar el PrintBot Evolution utilizaremos dos programas:
- Código Protocoder: almacenará la interfaz gráfica y las funciones encargadas de enviar datos a la placa Zum a través del Bluetooth del terminal.
- Código Zum BT-328: almacenará la programación del PrintBot Evolution, como por ejemplo, los modos de funcionamiento o la comunicación Bluetooth.
Podréis descargar ambos códigos al final de esta entrada. Recuerda que para ejecutar el código de Protocoder deberás tener instalado Protocoder en tu terminal (puedes descargarlo desde aquí).
Código Protocoder
Para ver y modificar el código de Protocoder, en primer lugar, accede a la aplicación Protocoder desde tu terminal e introduce la dirección IP que aparece en la parte inferior de la pantalla del navegador web de tu ordenador.
Descarga el código al final de esta entrada y pasa el archivo printbot_evolution_protocoder.proto a tu terminal. En tu terminal, con un explorador de archivos, dirígete a la carpeta donde hayas guardado el archivo, generalmente descargas, pulsa sobre el código de Protocoder y aparecerá un dialogo, escoge abrir con Protocoder. Se abrirá una ventana y escoge la opción install. Ya podrás utilizar y modificar el código.
Una vez que hayas importado el archivo .proto, verás el proyecto en la pestaña del navegador MY PROJECTS. Pulsa sobre el icono y verás el código. Dentro del código podemos distinguir varías partes:
- Crear botón de texto: crea un botón con un texto dentro de él, es útil para dar más información sobre el uso que se tiene del botón. Para crearlos utilizamos un código como este:
123var btnSiguelineas = ui.addButton("Sigue lineas",240, 350, w, h).onClick(function(){ // Creamos el botón. Parametros: ("texto a mostrar",posición X, posicion Y, ancho, alto)send("siguelineas"); // Pasamos como parámetro a la función send (enviar en inglés) la palabra siguelineas, que se recibirá completamente en Arduino.});
Dentro del código, con var btnSiguelinas estamos declarando el botón, tendremos que dar un nombre distinto a cada botón para que no haya conflictos. Después, con ui.addButton decimos que la variable btnSiguelineas será un botón. Entre paréntesis escribimos, por orden, el texto que aparecerá en el botón, la posición en X e Y, el ancho y el alto del botón. Después indicamos la función que se accionará al pulsar el botón, en este caso, envía por Bluetooth la palabra siguelinas. - Crear botón con imagen
123var btn180 = ui.addImageButton(x+2*w, y+3*w, w, w, "180.png",false).onClick(function(){ // Creamos un botón con imagen. Parámetros: (posiciónX,posiciónY,ancho,alto,"ruta de la imagen",true/false)send("180");});
Con var btn180 estamos declarando el botón, pero no es como el anterior, en este caso, utilizamos una imagen en vez de un texto. Después, con ui.addImageButton decimos que la variable btn180 será un botón de imagen. Entre paréntesis escribimos, por orden, la posición en X e Y, el ancho, el alto del botón y el nombre de la imagen que queremos que aparezca en el botón. Después indicamos la función que se accionará al pulsar el botón, en este caso, envía por Bluetooth la palabra 180.Para añadir una imagen o cualquier otro archivo, basta con arrastrar el archivo desde el explorador de archivos de tu ordenador al cuadro inferior derecha. Recuerda que cuanto más sencillo sea el nombre que tenga, más fácil será introducirlo en el código sin errores.
- Crear botones para el Dashboard: Para incluirlos lo hacemos del mismo modo que los botones para el dispositivo, solo que en vez de utilizar el prefijo ui, utilizamos el prefijo dashboard. Por defecto, el Dashboard no aparecerá, pero si quieres que así sea, modifica la línea dashboard.show(false); por dashboard.show(true);.
- Funciones para conexión y desconexión de Bluetooth: Se trata de las incluidas dentro de las variables btnConectar y btnDesconectar. Además, creamos una variable con el nombre btClient, que utilizaremos para “interactuar” a través del Bluetooth.
- Conectar: Para conectar, en primer lugar creamos un botón con el texto “Conectar Bluetooth” y dentro de la función que se activará al pulsar dicho botón, incluimos la conexión a Bluetooth.
12345678910111213var btnConectar = ui.addButton("Conectar bluetooth", 240, 150,300,100).onClick(function() { // Creamos el botón del bluetoothbtClient = network.bluetooth.connectSerial(function(status) { // Conectamos btClient -- Mostrará una lista de los dispositivos enlazados previamenteconsole.log("connected " + status); // Debugif (status === true){ // Si esta conectadoconsole.log(status); // guardamos el estadobluetoothOn = 1; // Cambiamos la variable de estado a conectado (1)ui.jump(btnConectar); // Hacemos que el boon "salte"ui.alpha(btnConectar, 0); // Hacemos completamente transparente el botónmedia.playSound("Drago"); // Reproducimos el sonido almacenado previamente.device.vibrate(750); // Hacemos que vibre el dispositivo}});});
Dentro de la función, además de conectarnos a Bluetooth, decimos que la variable bluetoothOn -que utilizamos como “señal” de conexión/desconexión al bluetooth- se le asigna el valor 1 y cambiamos la transparencia del botón a 0, es decir, completamente transparente. Además, incluimos algunas animaciones, como jump y hacemos que el dispositivo vibre cuando se ha conectado.
- Desconectar: Para desconectar, en primer lugar, creamos un botón con el texto “Desconectar” y dentro de la función que se activará al pulsar dicho botón, incluimos la desconexión a Bluetooth.
123456var btnDesconectar = ui.addButton("Desconectar", 540, 150,200,100).onClick(function() { // Creamos el botón de conectarbtClient.disconnect(); // Desconectamos una vez se ha pulsadobluetoothOn=0; // Cambiamos la variable de estado a desconectado (0)ui.alpha(btnConectar, 255); // Cambiamos la transparencia del botón a opacoui.jump(btnDesconectar); // Hacemos que "salte" el botón de desconectar});
Dentro de la función, además de desconectarnos a Bluetooth, decimos que la variable bluetoothOn -que utilizamos como “señal” de conexión/desconexión al Bluetooth- se le asigna el valor0 y cambiamos la transparencia del botón a 255, es decir, opaco.
- Conectar: Para conectar, en primer lugar creamos un botón con el texto “Conectar Bluetooth” y dentro de la función que se activará al pulsar dicho botón, incluimos la conexión a Bluetooth.
- Función para enviar y recibir datos por Bluetooth: La función para la transmisión de datos por Bluetooth es:
123456function send(str) { // Función que utilizamos para enviar un string (cadena de caracteres)if (bluetoothOn == 1) { // Si el bluetooth está conectado, se ejecuta el código de las siguientes lineasbtClient.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.}}
Esta función no tiene ningún botón, ya que se llamará a esta función cada vez que pulsemos sobre un botón, a excepción de los de conexión y desconexión por Bluetooth. Para enviar datos por Bluetooth utilizamos la orden btClient.send(“=”+str+”+”);. Esta función envía, por ejemplo, para mover el coche hacia delante: =avanzar+. En este caso, se ha realizado la codificación de esta manera, pero puedes probar con otro tipo de codificación. Se utilizan los caracteres = y + como delimitadores del inicio y final de la transmisión.
- Envío de datos por Bluetooth: Para enviar datos a través del Bluetooth utilizamos la función send() con un texto entre comillas dobles, por ejemplo, si queremos enviar la palabra retroceder utilizaríamos: send(“retroceder”);
Código Arduino
Para ver y modificar el código de Arduino, descarga el paquete al final de esta entrada y pulsa sobre el archivo printbot_evolution_arduino.ino. Si no tienes instalado el IDE de Arduino o otro IDE con el que puedas leer el archivo, puedes descargarlo visitando el curso de programación para makers con Arduino y Protocoder, ver diferentes IDEs y descargar tu favorito. Además, podrás aprender más sobre Arduino y Protocoder.
Una vez con el archivo abierto, dentro del código podemos distinguir varias partes:
- Declaración de variables e inclusión de librerías: Declaramos las distintas variables que utilizaremos durante el programa:
1234567891011121314151617181920212223242526272829303132333435//Para controlar los servos utilizamos la librería Servo.h#include "Servo.h"// Declaramos los servosServo servoR, servoL; // Servos de los motores derecho e izquierdoServo servoBat; // Servo que controla el movimiento del sensor Bat (ultrasonido)// Declaramos las variables relativas al sensor Bat// TP corresponde al "trigger pin" (pin que envía la señal de ultrasonido)// y EP corresponde al "Echo pin" (pin que recibe la señal de ultrasonido)// Midiendo el tiempo que pasa desde que se envía hasta que se recibe la señal de ultrasonido// podemos calcular la distancia.int TP = 4, EP = 5;//Declaramos los pines correspondientes a los sensores LDR (Resistencias Dependientes de la Luz)int ldrIzq = A2, ldrDrch = A3; //pines ldr//Declaramos los pines correspondientes a los sensores Infrarrojosint irIzq = 2, irDrch = 3; // pines infrarrojos//Tiempos por defecto para los Delays.// Un delay es un retardo, es decir, un periodo de tiempo dado en el que se para la ejecución// del programa hasta que se termina ese tiempo.// Por ejemplo, se utiliza aquí para conseguir que el PrintBot gire durante cierto tiempo.int defaultDelay = 200, lineasDelay = 0;//Retardos para conseguir que el PrintBot gire los diferentes ángulos.//Es posible que tengas que modificar estos tiempos para adecuarlo a tu PrintBot.int giro_180 = 1028, giro_90 = 514, giro_45 = 257, giro_30 = 200;//Declaramos los pines del zumbador (buzzer en Inglés)int pinBuz = 12;// Desde Protocoder recibiremos una cadena de caracteres (string)que utilizaremos para// recibir determinar cual es el movimiento o modo de funcionamiento del PrintBotString inString = "";
Dentro de las variables utilizaremos algunas para decir a Arduino en qué pines están conectados las vitaminas, y otras, como los delays, para modificar la ejecución del programa. Si los giros no se realizan correctamente o quieres modificarlos para que giren otros ángulos, puedes modificar las variables giro_180, giro_90, giro_45 y giro_30. - Setup: En la función setup, una de las que estamos obligados a utilizar en Arduino junto a loop inicializaremos el programa y los componentes que lo forman. El código de la función setupes:
1234567891011121314151617181920212223242526272829void setup() {//Iniciamos la comunicación serie. El Bluetooth de la placa ZUM BT 328// trabaja a 19200, por lo que si modificamos este valor no funcionara.Serial.begin(19200);Serial.flush(); // Vaciamos el puerto serieservoL.attach(6); // Decimos a Arduino que el servo Izquierda (Left) en el pin número 6servoR.attach(9); // Decimos a Arduino que el servo Derecha (Right) en el pin número 9servoBat.attach(11); // Decimos a Arduino que el servo que controla el sensor Bat (Ultrasonido) en el pin número 11servoR.write(90); // Escribimos el valor 90 grados en el servo derecha (90 equivale a parado)servoL.write(90); // Escribimos el valor 90 grados en el servo izquierda (90 equivale a parado)servoBat.write(90); // Colocamos el servo del sensor Bat en el centro// Decimos a Arduino que pines se utilizarán como entrada (INPUT) y como salida (OUTPUT)// Pines del sensor BatpinMode(EP, INPUT);pinMode(TP, OUTPUT);//pines ldrpinMode(ldrDrch, INPUT);pinMode(ldrIzq, INPUT);//pines infrarrojospinMode(irDrch, INPUT);pinMode(irIzq, INPUT);}
Dentro de esta función, iniciamos el puerto serie a una velocidad de 19200 bauds/s, que es la velocidad a la que funciona el módulo Bluetooth de la placa ZUM BT-328. A continuación decimos donde está conectado cada servo, paramos los servos de movimiento y llevamos el servo que lleva el sensor Bat, de ultrasonidos, a su posición central. Por último, con la orden pinMode(pin,mode) establecemos los diferentes pines como entradas, INPUT, o salida, OUTPUT, según corresponda. - Loop: Dentro de la función loop, la otra función de Arduino que estamos obligados a utilizar, tratamos la recepción de datos:
1234567void loop() {if (Serial.available() > 0) { // Si hay datos en el puerto serie (0 indica que hay)readFromAndroid(); // Llamamos a la función readFromAndroid para leer los datos recibidos por Bluetooth.}writeData(); // Una vez que hemos leído los datos del Bluetooth llamamos a la función// writeData que "decidirá" en función de los datos recibidos que modo hay que activar.}
Dentro de esta función, vemos la orden if (Serial.available() >0) que traducido a lenguaje “humano” sería: Si hay datos en el puerto serie, entonces haz las ordenes siguientes. En este caso, las ordenes son llamar a la función readFromAndroid();. Esta función se encarga de leer los datos enviados desde la aplicación de Protocoder.La otra orden que aparece en loop es writeData();. Esta función se encarga de entrar en los diferentes modos de control, tanto los manuales como los externos, una vez que se han leído los datos de la función anterior.
- readFromAndroid();:
12345678910111213void readFromAndroid() {char inChar; // Creamos una variable que sea un carácterwhile (Serial.available() > 0) {//Realiza las ordenes entre corchetes hasta que no haya datos en el puerto serie (bluetooth)// con la orden WhileinChar = (char) Serial.read(); // Almacena en la variable inChar los datos leídos desde el puerto serieSerial.flush(); // vacía el puerto serieif (inChar == '=') // = es el carácter de fin de envíoinString = ""; // Si se ha llegado al carácter de fin de envío vacía los datos almacenados en la variable inStringelse if (inChar != '+') // Si el carácter no es ni = ni + entonces, concatena los caracteres leídos en la variable inStringinString += inChar;}}
En vez de esta función, podríamos haber utilizado la función readString(); pero la lectura se realiza más despacio, por lo que las órdenes que enviamos desde el programa de Protocoder en nuestro terminal hasta el PrintBot Evolution llegarían con retraso.
- writeData();: Esta función es la encargada de controlar el flujo del programa, lee los datos que se han recibido en la función readFromAndroid() y en función de dichos datos, realiza una orden u otra.
- Funciones de movimiento: Estas funciones son las encargadas de mover el PrintBot Evolution. Estas funciones son: avanzar, retroceder, parar, izquierda, derecha, giro_180_grados, izquierdaLineas y derechaLineas. Podemos utilizar estas funciones en cualquier momento, solo tenemos que llamarlas. Por ejemplo, para utilizar la función giro_180_grados utilizamos la orden:
1giro_180_grados();
- Funciones de modos: Estas funciones se usan para que el PrintBot Evolution entre en los modos automáticos.
- Sigue lineas: En este modo automático se utilizan los sensores infrarrojos para detectar si el sensor está encima de una línea. En este modo se utilizan las funciones especiales de movimiento, izquierdaLineas y derechaLineas que mueven solo una rueda, en vez de las dos en direcciones contrarias, para realizar el movimiento.
- Esquiva obstáculos: En este modo automático se utiliza el sensor de ultrasonidos para detectar objetos y evitar chocarse contra ellos. El código de esta función es:
123456789101112131415161718192021222324252627282930313233343536373839void obstaculos() {// Los siguientes valores se utilizan para determinar las posiciones a las que// se pondrá el sensor bat (a través del servoBat).int izq = 140, drch = 50, cent = 90;// Comprueba si hay obstáculos en las tres posiciones. Si hay obstáculo se para.boolean obsIzq = buscaObstaculo(izq); //Llamamos a la función, que devuelve verdadero (true) si hay obstáculoif (obsIzq) {parar();}boolean obsCent = buscaObstaculo(cent);if (obsCent) {parar();}boolean obsDrch = buscaObstaculo(drch);if (obsDrch) {parar();}// Realiza una serie de comprobaciones y se desplaza hacia donde no hay obstáculos.if (!obsCent) { // No hay obstáculo en el centroavanzar();} else if (!obsDrch) { // No hay obstáculo en la derecha y si en el centroderecha();delay(giro_45);parar();} else if (!obsIzq) { // No hay obstáculo en la izquierda y si en el centro y derechaizquierda();delay(giro_45);parar();} else { // Hay obstáculo en el centro, derecha e izquierda. Por lo que se gira en 180ºretroceder(); // Hacemos que el printbot retroceda durante un cierto tiempodelay(giro_30); // Utilizamos el mismo tiempo que se utilizaría para girar 30ºgiro_180_grados(); // Por último, giramos el printbot 180 grados.}}
Dentro de esta función, en primer lugar, encontramos las variables izq,drch y cent, que determinan la posición del mini servo que mueve el sensor de ultrasonidos. Las posiciones tienen que apuntar a la izquierda, derecha y centro respectivamente, pero puedes ajustar las posiciones a otras distintas o crear nuevas posiciones.
Después comprobamos si hay obstáculos en la dirección a la que apunta el servo con la función buscaObstaculos(), que utilizamos para todas las posiciones pasando como dato el ángulo en el que deberá apuntar el servo. El código de la función es:
1234567891011121314151617181920212223boolean buscaObstaculo(int angulo) {// Se utiliza esta función para buscar obstáculos.// Para utilizarla hay que pasar como datosint distancia_max = 25; // Distancia máxima para determinar si la señal se convierte en un obstáculo o noint distancia; // Almacenaremos la distancia leída aquíservoBat.write(angulo); //Desplazamos el servo a la posición de lectura que hemos pasado como argumentodelay(200); // Paramos la ejecución para dar tiempo al servo a llegar.distancia = Distance(); // Guardamos la distancia que devuelve la función Distance() en la variable distanciaif (distancia != 0 && distancia < distancia_max) {// Si la distancia no es 0 y la distancia es menor de la distancia máxima// hacemos que suene un tono en el zumbador y devuelve true (verdadero)// indicando que hay un obstáculotone(pinBuz, 261, 100);return true;} else {// Si la distancia es 0 o la distancia es mayor de la distancia máxima// la función devuelve false (falso) indicando que no hay obstáculo.return false;}}Dentro de esta función podéis modificar la distancia máxima que utilizaremos para detectar obstáculos, es decir, cual es la distancia a partir de la cual si encontramos un objeto se considera obstáculo o no.
Para calcular la distancia, se utilizan las funciones auxiliares Distance y TP_init que se encargan de activar el sensor Bat, enviar una señal de ultrasonidos, calcular el tiempo que tarda en leerse la señal una vez que ha rebotado y, calcular en función de dicho tiempo la distancia a la que se encuentra.
Una vez que sabemos si tenemos o no un obstáculo delante en cada una de las tres posiciones, desplazamos el PrintBot hacia la zona que esté libre.
- Sigue luz: Este modo automático se utiliza para desplazar el PrintBot hacia la zona donde la intensidad de la luz sea mayor. Para ello, leemos el valor de cada uno de los pines donde están conectados los módulos LDR (del inglés, resistencia dependiente de la luz), comparamos los valores, giramos hacia el lado donde la intensidad de la luz sea mayor y movemos el PrintBot en linea recta durante 1 segundo.
Puedes ver esta entrada en ejecución en el siguiente vídeo:
- Modifica los iconos de la aplicación de Protocoder para incluir imágenes en vez de texto en los botones.
- Utiliza los sensores de infrarrojos para detectar precipicios. Puedes ver un ejemplo de como hacerlo aquí.
- Puedes crear un botón para que tu PrintBot mueva la cabeza y emita un sonido.
Si quieres aprender más sobre Arduino, Protocoder y robótica, puedes seguir el Curso de programación para makers con Arduino y Protocoder