Comunicación Bluetooth
En este tutorial vamos a aprender cómo crear una comunicación remota mediante Bluetooth para nuestros programas que requieran de una conexión inalámbrica para su funcionamiento.
Bluetooth
Un módulo Bluetooth es un dispositivo inalámbrico de corto alcance (WPAN – Wireless Personal Area Network) que puede llegar a los 10 metros de distancia para los modelos más baratos. Hoy en día existe una amplia variedad que nos permiten el doble de velocidad de transmisión y hasta cuatro veces más alcance.
Con este dispositivo podemos crear programas que respondan a través de una aplicación u otro dispositivo capaz de comunicarse por Bluetooth. Para ello requeriremos el Bluetooth de un móvil que se conectará a nuestro módulo Bluetooth HC-05.
El módulo Bluetooth HC-05 funciona a través de un puerto serie. El puerto serie consta de 2 pines (RX y TX), que se corresponden con los pines de recepción y transmisión respectivamente y estará conectada a nuestra placa Arduino.
Arduino dispone de un modo de comunicación por puerto serie por defecto que son los pines 0 y 1; la placa Arduino Mega dispone de varios puertos serie; pero podemos especificar otros puertos serie sobre otros dos pines cualesquiera con la librería SoftwareSerial.
La lógica es que la placa sea capaz de transmitir (TX) un dato al pin de recepción de la placa (RX) y viceversa. La placa debe recibir (RX) los datos que le llegan al módulo Bluetooth y se transmiten (TX) a la misma. Por ello deberemos conectar el circuito de la siguiente manera.
Cuando lo conectemos a través de nuestra aplicación veremos parpadear un LED del Bluetooth intermitentemente.
La tasa de Baudios es la velocidad de la información que se transmite al Bluetooth. Por defecto todos los dispositivos Bluetooth están configurados a la velocidad de 9600. No nos adentraremos mucho en este aspecto, pero solo hay que saber que si dos dispositivos no se comunican a la misma velocidad no entenderemos lo que nos dicen si hablan muy rápido.
IMPORTANTE: En caso de que el programa no funcione, es posible que la razón sea por la configuración de baudios. Los valores más usuales son 9600 bps, 38400 bps , 19200 bps o 115200 bps.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | void setup() { Serial.begin(9600); pinMode(0, INPUT); pinMode(1, INPUT); } void loop() { if (Serial.available()){ String BTData = Serial.readString(); Serial.println( BTData ); } } |
Curiosamente los pines 0 y 1 son los menos recomendables para conectar cualquier dispositivo a menos que no tengamos opción, o el resto de pines ya estén ocupados, ya que estos dos pines se usan para programar la placa y si se encuentran conectados a otro elemento no nos cargará ningún programa para modificar nuestros proyectos.
Librería SoftwareSerial
Para que no resulte tan complicado, vamos a utilizar una librería SoftwareSerial para poder especificar otros dos pines para sustituir la comunicación serie y así no tener que conectar y desconectar cables.
Modificamos los pines de transmisión y recepción a los pines 10 para RX y el Pin 11 para TX.
SoftwareSerial es una librería que hace que podamos elegir cuáles son nuestros pines de comunicación en lugar de utilizar los que vienen por defecto que son el 0 y el 1, que son los que se definen para la comunicación Serie por Hardware. Pero como esos pines son los que se utilizan para programar la placa y leer desde el monitor serie, vamos a dejarlos libres y utilizar una comunicación Serie por Software.
Programación con ArduBlockly
Dentro de la sección Serial, disponemos de un conjunto de métodos para incluir este tipo de comunicación y definir los pines a lo que queremos asociar nuestra transmisión de datos.
Para saber que nuestro dispositivo Bluetooth funciona, vamos a lanzar un mensaje por el puerto serie en el caso de que hayamos enviado un dato desde el móvil a nuestra placa.
Realizamos el siguiente código de bloques y lo cargamos.
En el momento que emparejemos los dispositivos y apretemos un botón nos deberá aparecer el mensaje que hemos definido desde el monitor serie.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | #include <SoftwareSerial.h> char BTData; SoftwareSerial comm(10,11); void setup() { Serial.begin(9600); comm.begin(9600); BTData = ( char )(( ' ' )); } void loop() { if (comm.available()) { BTData = (comm.read()); Serial.println(BTData); } } |
Aplicación Bluetooth
Podemos usar cualquier aplicación disponible en cualquier Store que pueda conectarse por Bluetooth, incluso crear una aplicación propia si necesitamos desarrollar algo muy concreto. En este caso utilizaremos RoboPad y el menú que nos aparece es el siguiente.
Establecer la comunicación es muy fácil. Solo hay que apretar el botón azul de arriba a la izquierda y buscar el nombre de nuestro módulo Bluetooth. Por defecto todos los módulos tiene el mismo nombre (HC-05), así que tendremos que fijarnos en la dirección que tiene nuestro modelo concreto, si tenemos muchos dispositivos conectados alrededor.
IMPORTANTE: Tenemos que habilitar la conexión Bluetooth desde la sección de ajustes de nuestro teléfono.
Cuando se establezca conexión; el LED pasará a parpadear un poco más lento y nos indicará que los Bluetooth del móvil y el HC-05 están emparejados.
La aplicación nos pedirá una contraseña de cuatro dígitos.
El código es………….. Contraseña: 1234
Para vuestras contraseñas de seguridad personales esperamos que pongáis alguna más complicada. 😉
Ahora nos toca interpretar los datos que nos envía la aplicación.
Cuando ya estemos sincronizados apretaremos a los botones. ¿Pero qué hacen estos botones?
Nada más y nada menos que transmitir letras al Bluetooth. Aquí hay una lista de eventos asociados a la aplicación Robopad:
-
Arriba → U (85)
-
Izquierda →L (76)
-
Derecha →R (82)
-
Abajo → D (68)
-
Stop →S (83)
-
Modo siguelineas → I (73)
-
Otra opción → M (77)
Estas letras se corresponden con un número que puede leer la placa y aparecen en el código ASCII.
Una vez hecho esto procederemos a programar la placa para enviar datos de un lado a otro.
Programar acciones por Bluetooth
Si os habéis dado cuenta, y como se documenta anteriormente, cada botón está asociado a una letra, pero puede ser algo confuso. El botón UP por ejemplo ejecuta las letras USS, estas 3 letras contienen una letra indicativa de UP y dos de Stop.
ATENCION: La letra S se envía cuando dejamos de apretar el botón. Es decir que si mantenemos presionado el botón solo enviamos la U, hasta que liberamos que envía la letra S
A partir de este momento, nuestra misión será leer el dato y asociar distintas instrucciones en función de los valores leídos. Pero deberemos tener cuidado a la hora de leer y gestionar la información leída por el Bluetooth.
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 | # include <SoftwareSerial.h> char BTData; SoftwareSerial comm(10,11); void setup() { Serial.begin(9600); comm.begin(9600); pinMode(13, OUTPUT); BTData = (char)(( ' ' )); } void loop() { if (comm.available()) { BTData = (comm.read()); Serial.println(BTData); if (BTData == ( 'U' )) { digitalWrite(13, HIGH); Serial.println( "LED ON" ); } else if (BTData == ( 'D' )) { digitalWrite(13, LOW); } } } |
Lectura char vs String
Podemos leer datos desde el módulo Bluetooth como si fuera un carácter o como si fuera una linea de texto completa. Hay que prestar atención, porque estos dos tipos de dato son distintos con sus ventajas e inconvenientes.
El tipo de dato carácter, solo lee una letra y se declara como char, mientras que el tipo de dato texto, lee una palabra completa y se declara como String.
1 | #include <SoftwareSerial.h> |
Como podremos observar, entre este programa y el programa anterior, usamos la función read o readString respectivamante si queremos leer un tipo de dato char o un tipo de dato String.
String proporciona una serie de métodos que nos permiten extender sus capacidades en el envío de información y manipular los datos para desarrollar programas más avanzados, por lo que nos facilita mucho las opciones de búsqueda y selección sobre la información transmitida.
¿En qué caso debo leer texto o caracteres por Bluetooth?
En función del tipo de robot, imaginemos que tenemos un servomotor que se puede abrir y cerrar en mayor y menor medida. Esta medida está definida por un número de 0 a 180, por lo que es facilmente deducible que para el valor máximo necesitamos leer 3 letras de golpe; una por cada dígito que es el que puede aplicar para valores mayores de la centena.
Si además de eso tenemos varios servomotores, podemos añadir una letra para especificar cual es el motor que se ha de mover junto con el número que aparecerá a continuación.
En la aplicación Robopad existe un modelo de robot en el que un botón aumenta o disminuye un valor que aporta la letra C y del que podemos leer las siguientes acciones con el programa anterior.
Esta instrucción está asociada a las pinzas del robot Beetle que podemos abrir o cerrar según un número. Abrir cuando el número es más bajo (En este caso el valor más pequeño es 10) y cerrarlo progresivamente en incrementos de 3 hasta su máximo que es el valor 55.
Esto es así, para no tener que crear un montón de botones para cada nivel de apertura de las pinzas. Pero ahora tenemos que saber interpretar estos valores y separar el texto del número y así mover el motor que controla esas pinzas.
Así pues, los botones de las pinzas siguen el siguiente patrón.
- Pinzas abiertas al máximo –> C10
- Abrir pinzas –> C## – 3 (Decrece el valor de tres en tres)
- Cerrar pinzas –> C## +3 (Aumenta el valor, hasta un máximo de 55)
En este caso debemos interpretar el dato leído del Bluetooth como un texto.
Pasar un valor de texto a número
Otro problema es la forma de extraer ese número de la variable String y almacenarlo en otra variable numérica para controlar otros actuadores mediante ese número.
Hay que tener en cuenta que los datos numéricos que aparecen en el monitor serie pueden no coincidir con los movimientos del servomotor, por lo que recomendamos ejecutar un función de mapeado para cambiar la escala, tal y como vimos en un tutorial anterior.
En este programa definimos unos valores mínimos y máximos para la pinza y así convertir fácilmente su rango de apertura y adecuarlos a nuestro gusto desde el propio programa.
Leemos el primer carácter, y si coincide con el que proporciona un número a continuación, leemos un número con el bloque to Integer leyendo el resto de la cadena.
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 | #include <SoftwareSerial.h> #include <Servo.h> char BTData; int servoMin; int servoMax; int servoValue; SoftwareSerial comm(10,11); Servo motor; void setup() { Serial.begin(9600); comm.begin(9600); BTData = ( char )(( ' ' )); servoMin = ( int )(40); servoMax = ( int )(115); servoValue = ( int )(0); motor.attach(10); } void loop() { if (comm.available()) { BTData = (comm.read()); Serial.println(BTData); if (BTData == ( 'C' )) { servoValue = ((comm.readString()).toInt()); motor.write((map(servoValue,10, 55, servoMin , servoMax))); } } } |
RECOMENDACIÓN: Una vez que tengamos un montaje completo de robot, deberemos de usar una batería o unas pilas para no depender de la alimentación del cable del USB del ordenador.
Extensión del protocolo de comunicación
Como hemos visto anteriormente; con la aplicación Robopad; a cada botón se le asocia un caracter o un par de números y según los valores leidos se ejecuta la acción. A esto se le llama un protocolo y en este caso hemos visto uno muy sencillo.
Estos protocolos están diseñados por el desarrollador según la aplicación descargable. Nosotros podríamos diseñar uno propio; pero si no es nuestra aplicación, a veces nos podemos encontrar con el problema de que NO conocemos de antemano el protocolo incorporado por la aplicación.
Protocolo de control
Para abordar esta cuestion con un protocolo más complicado vamos a realizar un modelo práctico con una aplicación que se llama ArduDroid y nos permite controlar las entradas y salidas de nuestra placa Arduino por Bluetooth.
Para conocer su funcionamiento primero leeremos el protocolo y después modificaremos el código para implementar las acciones a nuestro gusto.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | #include <SoftwareSerial.h> SoftwareSerial BT(10, 11); // RX, TX String Data; void setup() { Serial.begin(9600); BT.begin(9600); BT.println( "ArduDroid" ); pinMode(ledpin,OUTPUT); } void loop() { if (BT.available()){ Data = (BT.readString()); Serial.println(Data); } delay(100); } |
Una vez que sepamos cómo es el protocolo de la aplicación podemos programar nuestra placa para que responda en función de nuestras especificaciones.
Para más información se puede consultar aquí la página del desarrollador que es un experto bluethootero.
¿Serías capaz de crear el programa para responder convenientemente con este protocolo de la aplicación sin mirar la respuesta?
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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | #define START_CMD_CHAR '*' #define END_CMD_CHAR '#' #define DIV_CMD_CHAR '|' #define CMD_DIGITALWRITE 10 #define CMD_ANALOGWRITE 11 #define CMD_TEXT 12 #define CMD_READ_ARDUDROID 13 #define MAX_COMMAND 20 // max command number code. used for error checking. #define MIN_COMMAND 10 // minimum command number code. used for error checking. #define IN_STRING_LENGHT 40 #define MAX_ANALOGWRITE 255 #define PIN_HIGH 3 #define PIN_LOW 2 String inText; void setup() { Serial.begin(9600); Serial.println( "ArduDroid 0.12 Alpha by TechBitar (2013)" ); Serial.flush(); } void loop() { Serial.flush(); int ard_command = 0; int pin_num = 0; int pin_value = 0; char get_char = ' ' ; //read serial // wait for incoming data if (Serial.available() < 1) return ; // if serial empty, return to loop(). // parse incoming command start flag get_char = Serial.read(); if (get_char != START_CMD_CHAR) return ; // if no command start flag, return to loop(). // parse incoming command type ard_command = Serial.parseInt(); // read the command // parse incoming pin# and value pin_num = Serial.parseInt(); // read the pin pin_value = Serial.parseInt(); // read the value // 1) GET TEXT COMMAND FROM ARDUDROID if (ard_command == CMD_TEXT){ inText = "" ; //clears variable for new input while (Serial.available()) { char c = Serial.read(); //gets one byte from serial buffer delay(5); if (c == END_CMD_CHAR) { // if we the complete string has been read // add your code here break ; } else { if (c != DIV_CMD_CHAR) { inText += c; delay(5); } } } } // 2) GET digitalWrite DATA FROM ARDUDROID if (ard_command == CMD_DIGITALWRITE){ if (pin_value == PIN_LOW) pin_value = LOW; else if (pin_value == PIN_HIGH) pin_value = HIGH; else return ; // error in pin value. return. set_digitalwrite( pin_num, pin_value); // Uncomment this function if you wish to use return ; // return from start of loop() } // 3) GET analogWrite DATA FROM ARDUDROID if (ard_command == CMD_ANALOGWRITE) { analogWrite( pin_num, pin_value ); // add your code here return ; // Done. return to loop(); } // 4) SEND DATA TO ARDUDROID if (ard_command == CMD_READ_ARDUDROID) { // char send_to_android[] = "Place your text here." ; // Serial.println(send_to_android); // Example: Sending text Serial.print( " Analog 0 = " ); Serial.println(analogRead(A0)); // Example: Read and send Analog pin value to Arduino return ; // Done. return to loop(); } } // 2a) select the requested pin# for DigitalWrite action void set_digitalwrite( int pin_num, int pin_value) { switch (pin_num) { case 13: pinMode(13, OUTPUT); digitalWrite(13, pin_value); // add your code here break ; case 12: pinMode(12, OUTPUT); digitalWrite(12, pin_value); // add your code here break ; case 11: pinMode(11, OUTPUT); digitalWrite(11, pin_value); // add your code here break ; case 10: pinMode(10, OUTPUT); digitalWrite(10, pin_value); // add your code here break ; case 9: pinMode(9, OUTPUT); digitalWrite(9, pin_value); // add your code here break ; case 8: pinMode(8, OUTPUT); digitalWrite(8, pin_value); // add your code here break ; case 7: pinMode(7, OUTPUT); digitalWrite(7, pin_value); // add your code here break ; case 6: pinMode(6, OUTPUT); digitalWrite(6, pin_value); // add your code here break ; case 5: pinMode(5, OUTPUT); digitalWrite(5, pin_value); // add your code here break ; case 4: pinMode(4, OUTPUT); digitalWrite(4, pin_value); // add your code here break ; case 3: pinMode(3, OUTPUT); digitalWrite(3, pin_value); // add your code here break ; case 2: pinMode(2, OUTPUT); digitalWrite(2, pin_value); // add your code here break ; // default: // if nothing else matches, do the default // default is optional } } |
Los ejercicios de código, proyectos y recursos que desarrollaremos durante el curso se pueden consultar a través de nuestro Github.