En muchas ocasiones; una vez que hemos desarrollado el programa completo de un robot, podremos fijarnos que su memoria queda en gran parte desaprovechada.

Por ejemplo, en el ejercicio del robot Beetle en el que incluíamos varíos modos de juego con comunicación Bluetooth para control remoto nos aparece que la memoría consumida es del 24%. En este tutorial extenderemos lo qué podemos hacer con esta memoria sobrante para incluir más modelos de juego.

Aún tenemos memoria suficiente para incluir más extensiones a nuestro robot. Así que vamos a ello.

Utilizaremos el robot Beetle para realizar estas pruebas y habremos de conectarlo en los siguientes pines para desarrollar este tutorial de forma funcional.

  • Sensor LDR Izquierdo –> A0
  • Sensor LDR Derecho –> A1
  • Bluetooth RX –> 2
  • Bluetooth TX –> 3
  • Sensor IR Izquierdo –> 4
  • Sensor IR Derecho –> 5
  • MiniServo Pinza –> 6
  • Servomotor Izquierdo –> 9
  • Servomotor Derecho –> 10

¿Qué hacemos con la memoria restante?

Esta bien, si no se nos ocurre ningún modelo de juego adicional para rellenar este espacio, desde aquí proponemos una solución que nos permitirá extender las opciones de comunicación Bluetooth con App Inventor, donde desarrollaremos una aplicación propia y aprenderemos a manejar la información que se intercambia entre el móvil y nuestro robot.

Antes utilizábamos la aplicación Robopad++ que podemos descargarnos desde Google Play.

El modo de programa que vamos a desarrollar en este tutorial nos servirá para poder jugar con la aplicación Robopad++, pero además nos va a permitir añadir más aplicaciones creadas por nosotros mismos. Por lo que tendremos un robot que podrá conectarse con varias aplicaciones diferentes con acciones extendidas y personalizadas.

¿Cómo se hace?

Para empezar necesitaremos instalar una librería nueva llamada ArduParser.

Esta librería utiliza un protocolo básico para comunicar cadenas de texto y poder leer distinta información basándose en unas pautas muy sencillas.

Su funcionamiento se basa en que cada linea de texto contiene un carácter de inicio específico, un carácter al final y un delimitador que nos servirá para dividir cada sección de la orden que queremos ejecutar.

Por ejemplo, yo quiero decirle a mi robot que se mueva hacia delante con una velocidad del 80% de su capacidad máxima.

Los delimitadores que voy a elegir son:

  • Caracter inicial –  ” <
  • Delimitador –  ” |
  • Caracter final –  ” >

Así que si quiero introducir la información en una sola linea quedará de la siguiente forma.

< MOVE | FORWARD | 80 >

La utilidad de este concepto es que podemos subdividir secciones dentro de una misma linea entendible por el robot que la procesa y ejecutar instrucciones especificando en detalle los parámetros que se requieren llevar a cabo. Además nos permitirá obtener los números con formato numérico ya sea entero o decimal.

Con RoboPad++ solo podíamos manejar letra por letra, y eso nos limitaba mucho nuestras posibilidades.

¿Cómo se programa?

Una vez que hemos instalado la librería ArduParser, lo que tenemos que hacer es desarrollar el programa a instalar desde la plataforma ArduBlockly para hacer todo el proceso un poco más sencillo.

Para que la programación con ArduParser se complemente con la que ya teníamos del post anterior, vamos a utilizar la función peek para leer datos desde el monitor serie y determinar si vamos a leer una sola letra o una cadena entera.

Para ello, hay que determinar muy bien las condiciones del protocolo. Como nosotros hemos determinado los símbolos ” < ” , ” | ” , ” > “. Es por ello, que si la letra leida empieza por el símbolo de inicio, procederemos a leer una linea. En caso contrario, realizaremos el control anterior letra por letra.

 

Como habréis visto, la primera lectura en la variables BTData, se realiza con peek, que lo que hace es no eliminar ese dato del buffer de la comunicación, por lo que cuando hagamos la acción read o readString, no habra más datos disponibles. Esto aunque parezca que no tiene sentido es muy importante si queremos que la comunicación funcione correctamente.

IMPORTANTE: Si elegimos otro carácter de inicio, distinto al que hemos elegido “<“, habrá que modificarlo por igual en el código.

CURIOSIDAD: Si elegimos varios patrones distintos en este protocolo; ya sea (” < ”  ” | ”  ” > “)   (” # ”  ” , ”   ” * “)  (” ¿ ”  ” ^ ”   ” ? “)  , podríamos asignarlo a tantas aplicaciones como patrones se nos ocurran. De ahí tener un multicontrol.

Para introducir este modo de aplicación es necesario definir nuestro objeto ArduParser, asignarle los carácteres del protocolo definido e integrarlo dentro de la lectura de la cadena. Recordemos que la lectura de un carácter es para reutilizar el código que disponíamos en el ejercicio anterior.

Para entender mejor cómo funciona. Disponemos de un primer condicional que lee la cadena de texto y la procesa. Por ello, en función del control que aporta, utilizaremos la variable BTData para caracteres y BTString para cadenas de texto.

Una vez que se ha procesado y tenemos una cadena de texto con el protocolo definido en partes podemos acceder a cada una de ellas con un índice, empezando por el 0, a través de la función Array String.

En este caso, quiere decirnos que si el primer parámetro es MOVE, despues buscaremos la dirección con el segundo parámetro obtenido UP, STOP o cualquiera de las opciones que queramos integrar.

 

Para desarrollar todo este apartado de forma ordenada vamos a ir integrando los campos que se requieren, pero como nos quedará muy largo, vamos a modular estos apartados dentro de funciones.

En la siguiente función integraremos todas las funciones que procesan la cadena de texto con el protocolo definido.

Y finalmente el programa principal quedaría de la siguiente manera para hacer lo mismo que haciamos en el tutorial anterior, extendiendo la nueva aplicación.

Para integrar el resto de bloques, podéis consultar el tutorial anterior e incluir las funciones de movimiento y siguelineas. El código completo será el siguiente.

#include <Servo.h>
#include <SoftwareSerial.h>
#include <ArduParser.h>

String status;
char BTData;
String BTString;
String ParserData1;
String ParserData2;
int LDR_diff;
int threshold;
int diff_threshold;
int ClawMin;
int ClawMax;
int letter;
int ClawValue;
int clawdegrees;
int LDR_Left;
int LDR_Right;

Servo LeftServo;
Servo RightServo;
SoftwareSerial comm(2,3);
arduParser BTParser("<","|",">");
Servo ClawServo;

void forward() {
  Servo_attach();
  LeftServo.write(0);
  RightServo.write(180);
}

void backwards() {
  Servo_attach();
  LeftServo.write(180);
  RightServo.write(0);
}

void turn_left() {
  Servo_attach();
  LeftServo.write(180);
  RightServo.write(180);
}

void turn_right() {
  Servo_attach();
  LeftServo.write(0);
  RightServo.write(0);
}

void readBTChar(char letter) {
  if (letter == ('I')) {
    Serial.println("BEETLE MODE AUTOMATIC");
    status = "BEETLE";
  } else if (letter == ('G')) {
    Serial.println("FOLLOWLINES MODE AUTOMATIC");
    status = "FOLLOW";
  } else if (letter == ('M')) {
    Serial.println("MANUAL CONTROL MODE ON");
    status = "MANUAL";
  } else if (letter == ('U')) {
    forward();
  } else if (letter == ('D')) {
    backwards();
  } else if (letter == ('L')) {
    turn_left();
  } else if (letter == ('R')) {
    turn_right();
  } else if (letter == ('S')) {
    stop();
  } else if (letter == ('C')) {
    ClawValue = (int)(((comm.readString()).toInt()));
    Serial.println(ClawValue);
    ClawServo.write((map(ClawValue,10, 55, ClawMin , ClawMax)));
  }
}

// Describe this function...
void stop() {
  LeftServo.write(90);
  RightServo.write(90);
  Servo_detach();
}

// Describe this function...
void Servo_attach() {
  LeftServo.attach(9);
  RightServo.attach(10);
}

// Describe this function...
void Servo_detach() {
  LeftServo.write(90);
  RightServo.write(90);
  LeftServo.detach();
  RightServo.detach();
}

// Describe this function...
void moveClaw(int clawdegrees) {
  ClawServo.write((map(clawdegrees,10, 55, ClawMin , ClawMax)));
}

// Describe this function...
void FollowLineMode() {
  if (digitalRead(4)) {
    RightServo.write(90);
    RightServo.detach();
  } else {
    LeftServo.attach(10);
    RightServo.write(180);
  }
  if (digitalRead(5)) {
    LeftServo.write(90);
    LeftServo.detach();
  } else {
    LeftServo.attach(9);
    LeftServo.write(0);
  }
}

// Describe this function...
void readBTParser() {
  if (BTParser.entry) {
    ParserData1 = (BTParser.data[0]);
    ParserData2 = (BTParser.data[1]);
    if (ParserData1 == "MOVE") {
      status = "MANUAL";
      if (ParserData2 == "FORWARD") {
        forward();
      } else if (ParserData2 == "BACKWARDS") {
        stop();
      } else if (ParserData2 == "LEFT") {
        turn_left();
      } else if (ParserData2 == "RIGHT") {
        turn_right();
      } else if (ParserData2 == "STOP") {
        stop();
      }
    } else if (ParserData1 == "STATUS") {
      status = ParserData2;
    } else if (ParserData1 == "CLAW") {
      moveClaw(BTParser.getInt(1));
    }
  }
}

void LightMode() {
  LDR_Left = (int)(analogRead(A0));
  LDR_Right = (int)(analogRead(A1));
  LDR_diff = LDR_Left - LDR_Right;
  Serial.print(LDR_Left);
  Serial.print(",");
  Serial.println(LDR_Right);
  if (abs(LDR_diff) > threshold) {
    if (LDR_diff > diff_threshold) {
      turn_left();
    } else if (LDR_diff < diff_threshold * -1) { turn_right(); } } else if (LDR_Left > threshold && LDR_Right > threshold) {
    forward();
  } else if (LDR_Left < threshold && LDR_Right < threshold) {
    stop();
  }
}


void setup() {
  Serial.begin(38400);
  comm.begin(38400);
  pinMode(4, INPUT);
  pinMode(5, INPUT);
  pinMode(A0, INPUT);
  pinMode(A1, INPUT);

  status = (String)("MANUAL");
  ClawServo.attach(6);
  LeftServo.attach(9);
  RightServo.attach(10);
  BTData = (char)(('0'));
  BTString = (String)("");
  ParserData1 = (String)("");
  ParserData2 = (String)("");
  LDR_diff = (int)(0);
  threshold = (int)(300);
  diff_threshold = (int)(100);
  ClawMin = (int)(40);
  ClawMax = (int)(115);

}

void loop() {
  BTParser.entry = LOW;
  if (comm.available()) {
    BTData = (comm.peek());
    if (BTData == ('<')) {
      BTString = (comm.readString());
      Serial.println(BTString);
      BTParser.parser (BTString);
      BTParser.entry = HIGH;
    } else {
      BTData = (comm.read());
      Serial.println(BTData);
      readBTChar(BTData);
    }
  }
  readBTParser();
  if (status == "FOLLOW") {
    FollowLineMode();
  } else if (status == "BEETLE") {
    LightMode();
  }

}


Eso sí, esta parte es del lado del programa del robot. Ahora tendremos que integrar estas cadenas de texto en nuestra aplicación con App Inventor.

Para introducir la programación de aplicaciones con comunicación Bluetooth podemos dirigirnos al siguiente tutorial.

En esencia, solamente tenemos que cambiar los textos para realizar una u otra acción en función de lo que nos interese. Los botones de movimiento quedarían de la siguiente forma.

Pero más interesante es la creación de un deslizador (Slider) en la interfaz que determine la posición de nuestra pinza con la siguiente programación en App Inventor. No nos olvidemos del rango de valores que tenemos definidos para pinza abierta y pinza cerrada.

 

Finalmente la memoria resultante después de todo este cambio aumenta hasta un 34%. Es decir que aún podemos seguir añadiendo algunas funcionalidades adicionales para mejorar nuestro robot.

En el siguiente tutorial podremos avanzar con este modelo, modificando los datos de calibración desde la aplicación creada con App Inventor y estableciendo una comunicación bidireccional.