Hans (Robot controlado por voz)

Proyecto

El proyecto Hans consiste en un robot capaz de realizar determinadas acciones las cuales son ordenadas mediante la voz.

El Objetivo de este proyecto es poder añadirle más funciones al robot en el futuro con el fin de imitar a un asistente personal.

Acciones que realiza Hans

A la hora de pedir a Hans que realice una acción, primero habrá que decir su nombre y a continuación lo que queremos que haga a través de una palabra clave. Hasta ahora, Hans es capaz de distinguir 8 comandos los cuales son:

  • Hans: Activa el resto de los comandos.
  • Hola: Emita un sonido como respuesta al saludo.
  • Adios: Emito un sonido a modo despedida.
  • Adelante: Se desplaza hacia delante.
  • Atras: Se desplaza hacia atrás.
  • Derecha: Gira a la derecha.
  • Izquierda: Gira a la Izquierda.
  • Tiempo: Establece un temporizador de 30 segundos.

Cada vez que Hans reconozca cada uno de los comandos, se encenderán dos diodos LEDs para confirmarlo.

Componentes Utilizados

Hans está formado por los siguientes componentes:

  • Placa ELEGOO UNO R3
  • Módulo de Reconocimiento de Voz (VR3)
  • Módulo Controlador de Motores (L298N)
  • Diodos LEDs x2
  • Batería Portátil
  • Zumbador
  • Motor DC x2
  • Ruedas x4
  • Piezas de LEGO
  • Cables

Algunos de los componentes fueron comprados como:

COMPONENTESPRECIO
Placa ELEGOO19,99€
Módulo de Reconocimiento de Voz (VR3)18,43€
Pack (Rueda x4, Motor DC x4, Módulo controlador de motor x2 y cables)20,12€
Cartón-Pluma x21,50€
TOTAL60,04€

Módulo de Reconocimiento de Voz (VR3)

El módulo de reconocimiento de voz es el componente encargado de almacenar los comandos, captarlos a través de la voz y de transmitirlos a la placa. A la hora de trabajar con este tipo de módulo debíamos tener en cuenta varios factores:

  • Capacidad de memoria del módulo: El máximo número de comandos que se pueden almacenar en el módulo son 80.
  • Tono de la voz: El módulo tiene en cuenta el tono de la voz de la persona que entrene los comandos, por lo tanto, era muy probable que, si los comandos habían sido entrenados por una voz aguda, al módulo le resultaría difícil, pero no imposible, reconocerlos a través de una voz grave.
  • Conexiones TX y RX: dependiendo de la marca de la placa electrónica que se emplease, TX y RX deben estar conectados a unos pines digitales de dicha placa o en otros. En nuestro caso debían conectarse a los pines 2 y 3 respectivamente.
  • El comando puede estar formado por una o dos palabras.
  • La velocidad de los baudios debe estar configurada a 115200

Una vez conectada el módulo a la placa ELEGOO nos disponemos a entrenar los comandos, pero antes ha sido necesario descargar e instalar la librería voicerecognitionv3.h. Dichas librerías traen consigo varios códigos de ejemplo del cual solamente nos interesaba vr_sample_train. Con dicho código se han entrenado los comandos de la siguiente manera:

Compilamos el código,lo subimos a la placa y seguidamente abrimos el monitor serie.

En el monitor serie se visualizan una serie de comandos que el módulo de reconocimiento de voz trae consigo y para qué sirve cada uno de ellos. Para poder registrar y entrenar los comandos que queríamos que el módulo reconociese ha sido necesario seguir los siguientes pasos:

  • 1º Sigtrain: Para indicar que queremos entrenar un comando, debemos escribir sigtrain 1 Adelante donde 1 es la posición que ocupará en el vector de comandos (como hemos dicho anteriormente, es un vector de 80 posiciones) y Adelante el nombre que le hemos puesto. Es importante saber que, si al comando le ponemos un nombre, no quiere decir que al pronunciar esa palabra el módulo vaya a reconocerla. Por ejemplo, puede haber un comando llamado rojo que se active cuando se diga la palabra azul.
  • 2º Load: Una vez entrenado el comando debemos cargarlo para que pueda ser utilizado. Para ello utilizaremos la sentencia load seguido de los comandos que queremos cargar. Por ejemplo, si queremos cargar el primer y segundo comando que hemos entrenado sería load 0 1.

Anteriormente hemos dicho que el máximo número de comando que puede soportar el módulo es de 80, pero durante la ejecución del código solamente pueden estar cargados a la vez 7 de ellos. Esto es debido a que el módulo de reconocimiento de voz está escuchando todo el tiempo mientras el código está siendo ejecutado. Cada vez que escuche una palabra el módulo deberá buscar en el array de comandos cuál de todos es el que ha sido nombrado. Si se entrenasen 80 comandos el módulo tardaría demasiado cada vez que digamos algo. Para evitar este problema el módulo incluye un buffer donde se pueden cargar hasta 7 comandos (esto se puede modificar), de manera que si decimaos cualquier palabra el módulo sólo tendrá que buscar entre 7 elementos. Para este proyecto hemos entrenado 8 comandos, pero más adelante (Código) se explicará cómo hemos podido utilizar todos a la vez.

Módulo Controlador de Motores (L298N)

Gran parte de las acciones que Hans puede realizar están relacionadas con el movimiento, para ello ha sido necesario emplear dos motores DC junto a cuatro ruedas. Dado que el robot puede andar y retroceder, era necesario que los motores fueran capaces de poder cambiar de sentido. Para ello ha sido necesario emplear para cada uno de los motores un Puente H el cual permitiría cambiar el sentido del motor y controlar la potencia que queremos suministrarle gracias a los transistores. Para construir dicho circuito se requería cuatro transistores y resistencias, pero los componentes no fueron el problema. A la hora de conectar un motor para comprobar que el circuito cumplía con su función todo estaba correcto, pero cuando lo unimos con las ruedas y pusimos a prueba la fuerza del motor notamos que no era capaz de moverlo debido a que los dos circuitos se llevaban parte de la potencia que el motor necesitaba para que Hans pudiera moverse. Por lo tanto, se optó por emplear un módulo controlador de motores que aparte de incluir dos Puentes H en un chip minimizando el tamaño del circuito, permitía suministrar entre 3V-35V y 2A sin riesgo de quemar la placa. El voltaje que se aportaba a dicho módulo no superaba los 12V siendo así lo suficiente como para que los motores pudieran mover el robot.

Antes de utilizar el módulo controlador de motores era necesario conocer algunas de sus características para su buen uso y, muy importante, no romperlo. Entre sus características están:

  • En caso de que el voltaje suministrado al módulo no superase los 12V no era necesario desactivar el jumper y, por lo tanto, el regulador de voltaje estaría activo aportando parte de ese voltaje a la parte lógica del módulo. En el caso contrario haría falta desactivar los jumpers y, por lo tanto, haría falta aportar 5V para la parte lógica dado que el modulador de voltaje queda inactivo.

Hay que tener en cuenta que si tratamos de meter voltaje por el borne de +5V sin haber quitado el jumper podemos quemar la placa. Si no superamos lo 12V podemos utilizar el borne de +5V como salida para que funcione como fuente de energía para otro componente aportando esos voltios y 0,5A.

  • Los pines controladores (In1, In2, In3 e In4) deberán ir conectados a nuestra placa electrónica (Arduino, ELEGOO, etc) dado que el comportamiento de los motores depende de la combinación binaria de los pines. Los dos primeros empezando por la izquierda (In1 e I2) controlan el motor A y los de la derecha (I3 e I4) el motor B.

Diseño de Hardware

Una vez entrenados los comandos y entendido cómo funcionaba el módulo controlador de motores, hemos conectado todos los componentes para que a la hora de hacer el código sepamos a qué corresponde cada pin digital y cómo debemos trabajar con ellos.

No ha sido necesario utilizar una protoboard debido a que no ha habido ningún tipo de inconveniente para conectar directamente cada uno de los elementos a la placa ELEGOO. Para poder conectar el módulo de reconocimiento de voz a la placa ha sido necesario soldar 4 pines al módulo (GND, VCC, TXD y RXD) para poder conectarlo de forma sencilla mediante cables macho-hembra. También hemos tenido que hacer uso del soldador para el zumbador y para poder conectar entre sí los dos diodos LEDs.

Como se puede observar en el diagrama, hemos utilizado una batería portátil como fuente de energía para evitar así cualquier tipo de inconveniente respecto a la duración de las pilas. Es cierto que la batería también se puede quedar sin energía, pero a la hora de recargarla no nos hemos topado con ningún tipo de problema dado que sólo había que conectarle un cable sin necesidad de tener que sacarla de la carcasa del robot.

Diseño de carcasa

El recubrimiento del robot está hecho a base de cartón-pluma y de una placa de madera de la cual ya disponíamos, por lo tanto, no hizo falta comprarla. Antes de construir lo que sería el resto de la carcasa con el cartón-pluma, fue conveniente colocar los componentes para evitar cualquier tipo de problema a la hora de maniobrar.

Para la parte de los ojos se emplearon unas piezas de LEGO de las que ya disponíamos. Esta idea fue muy acertada por la facilidad a la hora de implementarle los diodos LEDs, sólo hizo falta usar un poco de pegamento para fijarlos.

Una de las razones por las que se optó a utilizar el cartón-pluma fue por su buena consistencia, facilidad para manipularla a la hora de tener que realizar cortes y a que la pintura acrílica se adhería bastante bien al material y se secaba rápidamente. Para cortar de forma precisa se utilizó un bisturí dando un buen resultado.

Una vez construida toda la carcasa se decidió pintar a Hans con una combinación de colores atractiva e inspirándonos en otros diseños de robots conocidos. El resultado fue el siguiente:

En cuanto a la disposición del cableado interior, se procuró que pareciese lo menos caótica pasible:

Se puede observar que la base de la madera tiene un recubrimiento de cartón-pluma para que los módulos o placas pudieran estar fijas con la ayuda de chinchetas. Se comprobó su consistencia agitando el robot y poniéndolo de costado. Todo fue favorable.

Problemas Encontrados

El único problema encontrado a la hora de realizar el proyecto ha sido la falta de potencia que recibían los motores para poder mover el robot, el cual ha sido solucionado implementado un módulo de control de motores que hemos mencionado anteriormente.

Código

Para poder utilizar 8 comandos teniendo un tamaño de 7 para el buffer de comandos se ha empleado la siguiente técnica. En la función void loop() sólo se ha cargado el comando «Hans» a través del método myVR.load(), des esta forma en el buffer sólo hay cargado un comando. Una vez que el módulo detecta la palabra «Hans», entrará a su case correspondiente descargando el buffer a través del método myVR.clear() y a continuación cargará los 7 comandos que corresponden con las acciones que el robot puede realizar. Una vez que Hans haya realizado dicha acción tras habérsela pedido, el buffer volverá a descargarse y solamente se cargará el comando «Hans». Se ha resaltado en la parte del código dónde se cargan y descargan los comandos para poder verlo de forma más visual.


#include <SoftwareSerial.h>
#include "VoiceRecognitionV3.h"

VR myVR(2, 3);   //Pines a los que están conectados RXD y TXD

uint8_t records[7]; // se cargan a la vez 7 comandos
uint8_t buf[64];

//Para los diodos LEDs
int led = 13;
//Para el zumbador
int buzzer = 12;
//Pînes del In del módulo controlador de motores
int PinIN1 = 7;
int PinIN2 = 6;
int PinIN3 = 5;
int PinIN4 = 4;
boolean activado = false;

//Nombre que le ponemos a cada comando en el código junto a la posición que ocupan
#define Hans    (0)
#define adelante   (1)
#define atras (2)
#define derecha (3)
#define izquierda (4)
#define hola (5)
#define adios (6)
#define tiempo (7)


void printSignature(uint8_t *buf, int len)
{
  int i;
  for (i = 0; i < len; i++) {
    if (buf[i] > 0x19 && buf[i] < 0x7F) {
      Serial.write(buf[i]);
    }
    else {
      Serial.print("[");
      Serial.print(buf[i], HEX);
      Serial.print("]");
    }
  }
}


void printVR(uint8_t *buf)
{
  Serial.println("VR Index\tGroup\tRecordNum\tSignature");

  Serial.print(buf[2], DEC);
  Serial.print("\t\t");

  if (buf[0] == 0xFF) {
    Serial.print("NONE");
  }
  else if (buf[0] & 0x80) {
    Serial.print("UG ");
    Serial.print(buf[0] & (~0x80), DEC);
  }
  else {
    Serial.print("SG ");
    Serial.print(buf[0], DEC);
  }
  Serial.print("\t");

  Serial.print(buf[1], DEC);
  Serial.print("\t\t");
  if (buf[3] > 0) {
    printSignature(buf + 4, buf[3]);
  }
  else {
    Serial.print("NONE");
  }
  Serial.println("\r\n");
}

void setup()
{

  myVR.begin(9600);
  Serial.begin(115200);
  Serial.println("Elechouse Voice Recognition V3 Module\r\nControl LED sample");
  pinMode(led, OUTPUT);
  if (myVR.clear() == 0) {
    Serial.println("Recognizer cleared.");
  } else {
    Serial.println("Not find VoiceRecognitionModule.");
    Serial.println("Please check connection and restart Arduino.");
    while (1);
  }

  //Cargamos sólo el comando Hans y comprobamos que lo ha hecho correctamente
  if (myVR.load((uint8_t)Hans) >= 0) {
    Serial.println("Hans loaded");
  }

}

void loop()
{
  int ret;
  ret = myVR.recognize(buf, 50);

  if (ret > 0) {
    switch (buf[1]) {
      case Hans:
        //variable booleana a true para indicar que el nombre del robot se ha dicho
        activado = true;
        //encendemos los leds para indicar que el comando ha sido reconocido
        digitalWrite(led, HIGH);
        delay(1000);
        digitalWrite(led, LOW);
        //descargamos el buffer de comandos
        myVR.clear();
        //cargamos en el buffer los comandos de las acciones que Hans puede realizar
        myVR.load(uint8_t (1));
        myVR.load(uint8_t (2));
        myVR.load(uint8_t (3));
        myVR.load(uint8_t (4));
        myVR.load(uint8_t(5));
        myVR.load(uint8_t(6));
        myVR.load(uint8_t(7));
        break;
        
      case adelante:
      //Comprobamos si primero se ha dicho "Hans"
        if (activado) {
          digitalWrite(led, HIGH);
          delay(1000);
          digitalWrite(led, LOW);
          //activamos motores pasando una combinación binaria
          //En este caso es 01 para el motor B, haciendo que la rueda gire hacia delante
          digitalWrite (PinIN1, LOW);
          digitalWrite (PinIN2, HIGH);
          //Combinación 01 para que la rueda gire hacia delante
          digitalWrite(PinIN3, LOW);
          digitalWrite(PinIN4, HIGH);
          //Andará durante 2 segundos
          delay(2000);
          //Apagamos motores
          digitalWrite (PinIN1, LOW);
          digitalWrite (PinIN2, LOW);
          digitalWrite(PinIN3, LOW);
          digitalWrite(PinIN4, LOW);
          //Descargamos el buffer
          myVR.clear();
          //Cargamos únicamente "Hans"
          myVR.load((uint8_t)Hans);
          //Cambiamos el valor del booleano
          activado = false;
        }
        break;
        
      case atras:
        if (activado == true) {
          Serial.println("Atras");
          digitalWrite(led, HIGH);
          delay(1000);
          digitalWrite(led, LOW);
          digitalWrite (PinIN1, HIGH);
          digitalWrite (PinIN2, LOW);
          digitalWrite(PinIN3, HIGH);
          digitalWrite(PinIN4, LOW);
          delay(2000);
          digitalWrite (PinIN1, LOW);
          digitalWrite (PinIN2, LOW);
          digitalWrite(PinIN3, LOW);
          digitalWrite(PinIN4, LOW);
          myVR.clear();
          myVR.load((uint8_t)Hans);

          activado = false;
        }
        break;
        
      case izquierda:
        if (activado) {
          Serial.println("Derecha");
          digitalWrite(led, HIGH);
          delay(1000);
          //Para girar un motor deberá ir hacia delante y el otro hacia atrás
          digitalWrite(led, LOW);
          digitalWrite (PinIN1, LOW);
          digitalWrite (PinIN2, HIGH);
          digitalWrite(PinIN3, HIGH);
          digitalWrite(PinIN4, LOW);
          delay(2000);
          digitalWrite (PinIN1, LOW);
          digitalWrite (PinIN2, LOW);
          digitalWrite(PinIN3, LOW);
          digitalWrite(PinIN4, LOW);
          myVR.clear();
          myVR.load((uint8_t)Hans);
          activado = false;
        }
        break;
        
      case derecha:
        if (activado) {
          Serial.println("Izquierda");
          digitalWrite(led, HIGH);
          delay(1000);
          digitalWrite(led, LOW);
          digitalWrite (PinIN1, HIGH);
          digitalWrite (PinIN2, LOW);
          digitalWrite(PinIN3, LOW);
          digitalWrite(PinIN4, HIGH);
          delay(2000);
          digitalWrite (PinIN1, LOW);
          digitalWrite (PinIN2, LOW);
          digitalWrite(PinIN3, LOW);
          digitalWrite(PinIN4, LOW);
          myVR.clear();
          myVR.load((uint8_t)Hans);
          activado = false;
        }
        break;
        
      case hola:
        if (activado) {
          Serial.println("Hola");
          digitalWrite(led, HIGH);
          delay(1000);
          digitalWrite(led, LOW);
          //Emitimos sonido como saludo
          tone(buzzer, 123.33, 300);
          delay(200);
          tone(buzzer, 333.33, 300);
          delay(200);
          tone(buzzer, 523.33, 300);
          delay(200);
          tone(buzzer, 733.33, 300);
          delay(500);
          myVR.clear();
          myVR.load((uint8_t)Hans);
          activado = false;
        }
        break;
        
        case adios:
         if (activado) {
          Serial.println("Adios");
          digitalWrite(led, HIGH);
          delay(1000);
          digitalWrite(led, LOW);
          //Emitimos saludo como despedida
          tone(buzzer, 723.33, 300);
          delay(200);
          tone(buzzer, 533.33, 300);
          delay(200);
          tone(buzzer, 323.33, 300);
          delay(200);
          tone(buzzer, 133.33, 300);
          delay(500);
          myVR.clear();
          myVR.load((uint8_t)Hans);
          activado = false;
        }
        break;
        
        case tiempo:
        if(activado){
          Serial.println("Temporizador de 30 segundos activado");
          digitalWrite(led, HIGH);
          delay(1000);
          digitalWrite(led, LOW);
          delay(30000);
          tone(buzzer, 1, 500);
          delay(1000);
          tone(buzzer, 1, 500);
          delay(1000);
          tone(buzzer, 1, 500);
          delay(1000);
          tone(buzzer, 1, 500);
          delay(1000);
          tone(buzzer, 1, 500);
          delay(1000);
          myVR.clear();
          myVR.load((uint8_t)Hans);
          activado = false;
          }
          break;
          
      default:
        Serial.println("Record function undefined");
        break;
    }

    printVR(buf);
  }
}

Es cierto que el tamaño del buffer puede ser modificado, pero para este proyecto se ha preferido establecerlo en 7 en lugar de 8 para habituarnos a la hora de añadir más comandos en un futuro. Por ejemplo, si decidimos añadir 16 nuevos comandos, no sería conveniente ampliar el buffer a 16 posiciones dado que el módulo tardaría en verificar qué comando ha sido pronunciado como hemos dicho ya anteriormente.

Vídeo

También te podría gustar...

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *