Máquina de Reflejos

Introducción

SEyTR_Mo_G2_2022-23 hemos creado una máquina de reflejos cuyo objetivo principal era crear un juego de reflejos con luces, dónde se trabaje la velocidad de reacción. Este tipo de juegos son empleados por deportistas de élite tales como futbolistas o pilotos de Fórmula 1. Aunque también se puede usar como entretenimiento a la vez que mejoramos cualidades como la concentración, coordinación…

Esta máquina de reflejos cuenta con una serie de “módulos” que están integrados por un sensor ultrasonido y 4 diodos leds, además encontramos un zumbador que realiza diferentes tipos de pitidos en función del momento (inicio de la partida, final, acierto, fallo, cambiar parámetros de la partida como el tiempo…), una pantalla LCD que hace de display por la cual sale toda la información del juego y un sensor infrarrojos para controlar el juego con un mando IRremote sin necesidad de controlarlo desde el pc.

En los siguientes apartados describiremos cada una de las partes del proyecto más a fondo.

________________________________________

Idea inicial

En la idea original, el proyecto iba a ontar con 8 módulos, formados por 4 leds en serie y un ultrasonidos. Además de un Arduino uno, un decodificador 3 a 8 y un multiplexor con las mismas señales de selección, y una resistencia final para proteger a todos los módulos del circuito. A continuación veremos las dificultades encontradas y la evolución del proyecto con las soluciones a las cuales hemos llegado.

________________________________________

Software (Código)

#include <IRremote.h>
#include <TimerFreeTone.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>

#define TRIGGER 2   //Pin digital 2 para el Trigger del sensor
#define ECHO A0
#define ECHO2 A1
#define ECHO3 A2
#define ECHO4 A3
#define ECHO5 3
#define ECHO6 4

#define IR_PIN 7
#define PITIDO_INICIAL 0
#define PITIDO_CORRECTO 1
#define PITIDO_FALLO 2
#define PITIDO_SUBIR 3
#define PITIDO_BAJAR 4

int vcc1 = 10;
int vcc2 = 11;
int vcc3 = 12;
int vcc4 = 13;
int vcc5 = 9;
int vcc6 = 8;
int val;
int echo;
int buzzPin = 5;

IRrecv irrecv(IR_PIN);
decode_results results;
unsigned long keyValue = 0;
int n;
unsigned long gameTime = 20000;
unsigned long moduleTime = 3000;
int multiplicator = 1;

LiquidCrystal_I2C lcd(0x26,16,2);


void setup() {

  pinMode(TRIGGER, OUTPUT); //pin como salida
  pinMode(vcc2, OUTPUT);
  pinMode(vcc3, OUTPUT);
  pinMode(vcc1, OUTPUT);
  pinMode(vcc4, OUTPUT);
  pinMode(vcc5, OUTPUT);
  pinMode(vcc6, OUTPUT);
  pinMode(ECHO, INPUT);
  pinMode(ECHO2, INPUT);
  pinMode(ECHO3, INPUT);
  pinMode(ECHO4, INPUT);
  pinMode(ECHO5, INPUT);
  pinMode(ECHO6, INPUT);
  pinMode(buzzPin, OUTPUT);

  irrecv.enableIRIn();

 // Serial.begin(9600);//iniciailzamos la comunicación

  lcd.init();
  lcd.backlight();
  lcd.clear();
  lcd.setCursor(2,0);
  lcd.print("BIENVENIDO: ");
  lcd.setCursor(4,1);
  lcd.print("EL NANO ");

  randomSeed(analogRead(0));
}



void loop() {

  if (irrecv.decode(&results)) {
    if (results.value == 0XFFFFFFFF) {results.value = keyValue;}
    //Serial.println(results.value, HEX);
    if (results.value == 0x460523B) { // POWER
      int puntuacion = juego();
      lcd.clear();
      lcd.setCursor(0,0);
      lcd.print("Puntuacion: ");
      lcd.print(puntuacion);
    } else if (results.value == 0x6C6AA223) { // PLUSVOL
      if (gameTime < 60000) {
          buzz(PITIDO_SUBIR);
          gameTime = gameTime + 5000;
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print("Time: ");
          lcd.print(gameTime / 1000);
          lcd.print("s");
          delay(500);
        }
    } else if (results.value == 0xFDF07FE0) { // MINUSVOL
        if (gameTime > 10000) {
          buzz(PITIDO_BAJAR);
          gameTime = gameTime - 5000;
          lcd.clear();
          lcd.setCursor(0,0);
          lcd.print("Time: ");
          lcd.print(gameTime / 1000);
          lcd.print("s");
          delay(500);
        }
    } else if (results.value == 0x303188DF) { // PLUSCH
        if ((moduleTime < gameTime) && (moduleTime < 500)){
          buzz(PITIDO_SUBIR);
          moduleTime += 100;
        } else if ((moduleTime < gameTime) && (moduleTime >= 500)) {
          buzz(PITIDO_SUBIR);
          moduleTime += 500;
        }

        lcd.clear();
        lcd.setCursor(0,0);       
        lcd.print("Delay: ");
        lcd.print((float)moduleTime / 1000.0);
        lcd.print("s");
        delay(500);
    } else if (results.value == 0x8ED6B32A) { // MINUSCH
      if ((moduleTime > 100) && (moduleTime <= 500)){
          buzz(PITIDO_BAJAR);
          moduleTime -= 100;
        } else if ((moduleTime > 100) && (moduleTime > 500)) {
          buzz(PITIDO_BAJAR);
          moduleTime -= 500;
        }

        lcd.clear();
        lcd.setCursor(0,0);
        lcd.print("Delay: ");
        lcd.print(((float)moduleTime / 1000.0));
        lcd.print("s");
        delay(500);
    } else if (results.value == 0xDC82D446) { // ONE: Modo principiante
        gameTime = 40000;
        moduleTime = 10000;
        buzz(PITIDO_SUBIR);
        lcd.clear();
        lcd.setCursor(0,0);       
        lcd.print("Time: ");
        lcd.print(gameTime / 1000);
        lcd.print("s");
        lcd.setCursor(0,1);
        lcd.print("Delay: ");
        lcd.print(((float)moduleTime / 1000.0));
        lcd.print("s");      
    } else if (results.value == 0x6B7A82B7) { // TWO: Modo fácil
        gameTime = 40000;
        moduleTime = 5000;
        buzz(PITIDO_SUBIR);       

        lcd.clear();
        lcd.setCursor(0,0);       
        lcd.print("Time: ");
        lcd.print(gameTime / 1000);
        lcd.print("s");
        lcd.setCursor(0,1);
        lcd.print("Delay: ");
        lcd.print(((float)moduleTime / 1000.0));
        lcd.print("s");
    } else if (results.value == 0x496E52EA) { // THREE: Modo medio
        gameTime = 40000;
        moduleTime = 500;
        buzz(PITIDO_SUBIR);       

        lcd.clear();
        lcd.setCursor(0,0);       
        lcd.print("Time: ");
        lcd.print(gameTime / 1000);
        lcd.print("s");
        lcd.setCursor(0,1);
        lcd.print("Delay: ");
        lcd.print(((float)moduleTime / 1000.0));
        lcd.print("s");
    } else if (results.value == 0x24B1BD49) { // FOUR: Modo medio
        buzz(PITIDO_SUBIR);       
        lcd.clear();
        lcd.setCursor(0,0);       
        lcd.print("Time: ");
        lcd.print(gameTime / 1000);
        lcd.print("s");
        lcd.setCursor(0,1);
        lcd.print("Delay: ");
        lcd.print(((float)moduleTime / 1000.0));
        lcd.print("s");
    }
    //Serial.println("He salido del switch");

    results.value = keyValue;

    irrecv.resume();
  }

}



void  buzz(int value) {
  if(value == PITIDO_INICIAL){
    TimerFreeTone(buzzPin,500,500);
    delay(300);
    TimerFreeTone(buzzPin,500,500);
    delay(300);
    TimerFreeTone(buzzPin,2000,1000);
  }
  else if(value == PITIDO_CORRECTO){
    TimerFreeTone(buzzPin,3000,200);
    delay(100);
    TimerFreeTone(buzzPin,3000,200);
  }
  else if(value == PITIDO_FALLO){
    TimerFreeTone(buzzPin, 500, 500);
  }
  else if (value == PITIDO_SUBIR){
    TimerFreeTone(buzzPin, 1000, 100);
  }
  else if (value == PITIDO_BAJAR){
    TimerFreeTone(buzzPin, 500, 100);
  }
}



void selectOutput(int value) {
  n = random(0, 6);
  buzz(value);
  if(n == 0){
      val = vcc1;
      echo = ECHO;
  }else if(n == 1){
    val = vcc2;
    echo = ECHO2;   
  }else if(n == 2){
      val = vcc3;
      echo = ECHO3;
  }else if(n==3){
    val = vcc4;
    echo = ECHO4;
  }else if(n==4){
      val = vcc5;
      echo = ECHO5;
  }else if(n==5){
      val = vcc6;
      echo = ECHO6;
  }
}



int juego() {
  int score = 0;
  selectOutput(PITIDO_INICIAL);
  unsigned long start = millis();
  unsigned long current;
  unsigned long startTimeModule = millis();


  for(;;) {
    current = millis();
    if (current - start >= gameTime) break;
    digitalWrite(val, HIGH);
    digitalWrite(TRIGGER, LOW);//Inicializamos el pin con 0
    delayMicroseconds(5);
    digitalWrite(TRIGGER, HIGH);
    delayMicroseconds(10);          //Enviamos un pulso de 10us
    digitalWrite(TRIGGER, LOW);


    long t; //tiempo que demora en llegar el echo
    long d; //distancia en centimetros

    t = pulseIn(echo, HIGH); //obtenemos el ancho del pulso
    d = t/59;             //escalamos el tiempo a una distancia en cm

    lcd.clear();
    lcd.setCursor(0, 0);
    lcd.print("Tiempo: ");
    lcd.setCursor(8, 0);
    lcd.print((gameTime -(current - start)) / 1000);
    lcd.setCursor(0, 1);
    lcd.print("Puntos: ");
    lcd.setCursor(8, 1);
    lcd.print(score);

    if (d < 10 && d > 1) {
      score++;
      digitalWrite(val,LOW);
      selectOutput(PITIDO_CORRECTO);
      delayMicroseconds(500);   
      startTimeModule = millis();
    }
    else if (current  - startTimeModule >= moduleTime) {
      digitalWrite(val,LOW);
      selectOutput(PITIDO_FALLO);
      delayMicroseconds(500);    
      startTimeModule = millis();
    }
  }
    digitalWrite(val, LOW);

    lcd.clear();
    lcd.setCursor(6,0);
    lcd.print("FIN");
    buzz(PITIDO_INICIAL);

    return score;

}

________________________________________

Material para su construcción

________________________________________

Casos de uso

A continuación, expondremos los casos de uso de la máquina de reflejos:

  • Empezar partida: al pulsar el botón power del mando, se iniciará la partida con los datos de tiempo y delay que tenga establecidos.
  • Seleccionar modo de juego: con los botones 1-4 del mando, tenemos distintos modos de juego con tuplas tiempo-delay prestablecidas en orden creciente de dificultad, siendo el 1 el más sencillo y el 4 el más complejo.
  • Subir tiempo de juego: con el botón de subir volumen del mando, aumenta el tiempo de juego en 5 en 5 segundos, hasta un máximo de 1 minuto.
  • Bajar tiempo de juego: del mismo modo, con el botón de bajar volumen del mando, bajará el tiempo de 5 en 5 segundos hasta un mínimo de 10 segundos.
  • Subir delay de los sensores: con el botón de subir de canal aumenta el delay de 0,5 en 0,5 segundos hasta un máximo de 5 segundos. Tiene la particularidad que, si el delay es menor de 0,5 segundos, aumenta de 0,1 en 0,1 hasta que alcanza los 0,5 segundos que es cuando empieza a subir de nuevo de 0,5 en 0,5 segundos.
  • Bajar delay de los sensores: con el botón de bajar de canal disminuimos el delay de 0,5 en 0,5 segundos hasta los 0,5 segundos, a partir de entonces baja de 0,1 en 0,1 hasta un mínimo de 0,1 segundos de delay.

Anotaciones: todo el juego se controla por el mando IRremote. Con cada caso de uso el buzzer emite un pitido para indicar que se ha producido el cambio, del mismo modo que se muestra por la pantalla LCD.

________________________________________

Problemas y soluciones de la parte física

  • Tuvimos problemas en primer lugar con las pérdidas generadas por el multiplexor y decodificador, que daba lugar a que el echo no se recibiera de manera correcta desde los ultrasonidos.  Como solución decidimos retirar tanto el multiplexor como el decodificador, no solo por las pérdidas que generaban, sino porque también las especificaciones de estos eran particulares tales como enable negado, salidas del deco todas a high menos una low (cuando nos interesaba que fuera al revés). Para solucionar esto último empleamos puertas NAND como si fuesen NOT, lo cual generaba mayores pérdidas en el circuito.
  • En segundo lugar, tuvimos problemas al probar el prototipo montado en las protoboards, porque muchos cables no hacían bien contacto y daba lugar a pequeños fallos, o incluso llegamos a tener pérdidas de tiempo con los triggers, conectando dos puntos eléctricos con un cable de 3 cm. La solución de esto fue conectar todos los triggers en el mismo punto eléctrico de la protoboard (el positivo del lado en el cuál entraban todos los triggers sin problema).
  • A la hora de soldar los cables de los diodos, intentamos añadir un recubrimiento aplicando calor, para evitar contactos indeseados entre positivos y negativos, lo cual generó que uno de los módulos se deformara ligeramente uno de los módulos. La solución a ello fue aplicar en su lugar cinta aislante.
  • Al implementar la pantalla LCD con el módulo I2C, teníamos problemas, porque teníamos en los pines analógicos 4 y 5 ocupados, y el protocolo I2C busca en estos pines también las entradas SDA y SDL, porque no todos los arduinos disponen de estos pines. Hasta que encontramos toda la documentación y cambiamos ambos pines a digitales, dejando estos dos libres. Este problema tenía también una solución software cambiando la configuración del protocolo, pero decidimos que era más sencillo cambiar la disposición de 2 pines y más teniendo pines digitales libres en el Arduino.
  • La idea inicial era poner los 4 diodos en serie, pero la caída de voltaje era muy grande y no llegaba suficiente alimentación al ultrasonido para su correcto funcionamiento, por lo que tuvimos que modificar nuestra idea original y ponerlos en paralelo.

________________________________________

Problemas y soluciones de la parte software

  • Cuando implementamos el sensor de infrarrojos en el circuito para controlarlo con el mando, después de realizar 1 juego, el sensor IRremote cogía valores aleatorios y muy distintos a cada frecuencia recibida. Después de probar quitando las ultimas piezas recibidas detectamos que era el buzzer aunque sin ningún tipo de explicación aparente. Finalmente, era por el uso de la biblioteca predefinida para el uso del buzzer, ya que esta modificaba las frecuencias de los relojes del arduino que compartía con el IRremote, descuadrando el protocolo NEC que este seguía y hacía imposible que recibiera las señales completamente. Para solucionar este problema, decidimos cambiar la librería que usábamos de buzzer por otra librería BuzzerFreeTone, importándola de fuera de la librería de dependencias proporcionada por el IDE de Arduino.

A nivel software tuvimos pocos problemas en general, salvo la excepción del uso de la librería BuzzerFreeTone, no tuvimos mayores complicaciones, debido principalmente, a que la mayor parte de la complejidad de este proyecto era hardware y no software.

________________________________________

Posibles mejoras

  • Proteger la parte trasera del tablón dónde se encuentra todo el cableado, de manera que quede más protegido y sea más seguro a la hora de transportarlo.
  • Crear una scoreboard, dónde se muestren los récords históricos en cada modo de juego.
  • Implementar una segunda pantalla, para mostrar mayor cantidad de detalles, así como la posible scoreboard implementada.
  • Implementar otros modos de juego, dónde por ejemplo haya una secuencia de luces y a continuación se enciendan todas las luces y tengas que ir apagándolas en el mismo orden que la secuencia. Del mismo modo aquí se pueden sacar varios niveles de dificultad, tanto por la rapidez del delay en la secuencia como por el número de luces en cada secuencia.
  • Implementar algún tipo de pitido especial o mini canción con el buzzer en caso de realizar el récord en algún modo de juego.

________________________________________

Vídeo del funcionamiento del proyecto

________________________________________

Fotografías del proyecto ensamblado

Dejamos un par de módulos al aire libre para que se viera todos los componentes dentro del módulo, así como el cableado de este.

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 *