Caja Mágica

Autores: Óscar Fuente Fernández y Antonio Herman Boeriu

ÍNDICE

  1. INTRODUCCIÓN
  2. MATERIAL UTILIZADO Y COSTES
  3. IMPELMENTACIÓN DEL HARDWARE
  4. DISEÑO DEL SOFTWARE
    • Librerías y variables globales
    • La inicialización de las variables (setup)
    • El comportamiento continuo del programa (loop)
    • Funciones/Subprogramas adicionales
  5. PROBLEMAS OBSERVADOS
  6. DISEÑO FINAL
  7. VIDEO DEMOSTRATIVO
  8. CONCLUSIONES
  9. CÓDIGO COMPLETO

1. INTRODUCCIÓN

El nombre de Caja Mágica es un nombre idealista para que atrajera el mayor interés posible, porque si lo llamara “caja inútil” perdería toda su gracia. Es un proyecto que no sabías que existía pero que una vez que lo conoces, querrás tener uno personal siempre. El proyecto consiste en una caja con interruptores, los cuales, al ser pulsados, son inmediatamente recolocados en su estado original por el “individuo” que se encuentra dentro de la caja. ¿Parece aburrido el proyecto? Entonces sígueme en esta travesía llamada memoria, pues te contaré todo el proyecto con lujo de detalles.

2. MATERIAL UTILIZADO Y COSTES

A continuación, se les mostrará los precios de los componentes utilizados:

NombreCantidadUtilizadosPrecio
Kit ELEGOO Arduino1 unidadMúltiples componentes39,07 €
Pack Servomotores 10 unidades1 unidad4 servomotores17,89 €
Pack Interruptores 10 unidades1 unidad3 interruptores10,42 €
Mini MP3 DFPlayer Player Módulo con Altavoz Redonda 2W 8Ohm1 unidad1 módulo DFPlayer y 1 altavoz 2W 80hm9,99 €
Tarjeta microSD 4 GB1 unidad1 microSD8,31 €
Caja de madera y materiales de decoración15 unidades15 unidades14,95 €
    
    
Precio total100,63 €

3. IMPELMENTACIÓN DEL HARDWARE

Ha llegado el apartado para explicar físicamente cómo están conectados todos los pines. Al final de este apartado se encontrará una tabla donde se observará de forma más visual como están conectados los pines y a qué.

Como el diseño final queda un poco engorroso por el número de cables utilizados, preferimos dividir el diseño en la memoria para que la explicación sea más clara.

Empezando con el módulo DFPlayer y los altavoces. Para que la Caja Mágica estuviera más completa, hemos añadido un módulo DFPlayer el cual se encarga de reproducir sonidos cargados en una memoria microSD a través de los altavoces. Pero, a diferencia de los zumbadores pasivos y activos, este módulo puede reproducir audios y canciones en mp3. Al utilizar este módulo, nos permitió una gran variedad de sonidos para que cumpliera mejor su función, entretener.

Como no se ha conseguido encontrar el módulo DFPlayer para el diseño 3D, hemos decidido poner una imagen de las conexiones del módulo como ejemplo y desarrollar a partir de ahí.  El pin VCC del módulo une con el pin de 5V del Arduino. Los pines RX y TX están unidos a los pines 5 y 3, respectivamente y el pin GND del módulo está unido al GND del Arduino. Tras esto solo queda especificar que el SPK_1 y el SPK_2 están conectados a pin positivo y al pin negativo del altavoz, respectivamente.

Pines del módulo DFPlayer

Con esto hemos terminado la parte del módulo, pero no sin antes mencionar que, en la parte superior del módulo, la zona gris en el medio de la imagen es donde se sitúa la microSD.

Tanto en los servomotores como en los interruptores tienen dos cables: positivo y negativo. El negativo de todos va a parar al mismo punto: GND del Arduino. El positivo es donde se marca la diferencia. Los interruptores 1, 2 y 3 están unidos a los pines 13, 12 y 2 respectivamente. Los servomotores 1, 2, 3 y 4 están unidos a los pines 11, 10, 9 y 6 del Arduino.

Modelo 2D del diseño de la Caja Mágica sin implementar el módulo DFPlayer y el altavoz.

Modelo 2D de la implementación del módulo DFPlayer en el Arduino.

A continuación, se mostrarán 3 imágenes sobre como está situado el Arduino y los componentes dentro de la caja.

Vista general de todas las conexiones

Vista enfocada en los pines del Arduino

Vista enfocada en las conexiones de la protoboard

NombrePines del ArduinoPines del módulo DFPlayerPines altavoz
Interruptor 113
Interruptor 212
Interruptor 32
Servomotor 111
Servomotor 210
Servomotor 39
Servomotor 46
LED 1 Verde8
LED 2 Azul7
LED 3 Rojo4
Módulo DFPlayer RX5RX
Módulo DFPlayer TX3TX
Altavoz +SPK_1Positivo
Altavoz –SPK_2Negativo
Ground (Tierra)GNDGND –
Voltaje5VVCC –

4. DISEÑO DEL SOFTWARE

El diseño del software se puede dividir en cuatro partes principales:

  • Librerías y variables globales
  • La inicialización de las variables (setup)
  • El comportamiento continuo del programa (loop)
  • Funciones/Subprogramas adicionales

A partir de aquí desarrollaré las tres partes mencionadas anteriormente:


a. Librerías y variables globales

Librerias y variables globales

En esta sección, se inicializan los componentes y se establecen los valores de las variables. Al principio de todo, se incluyen las librerías Servo.h, SoftwareSerial.h y DFRobotDFPlayerMini.h. Entre ellas Servo.h la cual proporciona las funciones necesarias para controlar servomotores. SoftwareSerial.h proporciona las funciones necesarias para utilizar comunicaciones serie con un módulo externo. Y, por último, DFRobotDFPlayerMini.h que maneja la funcionalidad del módulo DFPlayer y los altavoces para que reproduzcan audios, sonidos o música en formato mp3.

Tras las librerías:

  • Servo servo1;
  • Servo servo2;
  • Servo servo3;
  • Servo servo4;

Estos apartados declaran cuatro objetos Servo, que representan los cuatro servomotores que se van a utilizar en el circuito.

  • int interruptorPin1 = 13;
  • int interruptorPin2 = 12;
  • int interruptorPin3 = 2;

Estos apartados declaran tres variables enteras que almacenan los números de los pines del Arduino a los que están conectados los interruptores.

  • int servoPin1 = 11;
  • int servoPin2 = 10;
  • int servoPin3 = 9;
  • int servoPin4 = 6;

Estos apartados declaran cuatro variables enteras que almacenan los números de los pines del Arduino a los que están conectados los servomotores.

  • const int ledPin1=8;
  • const int ledPin2=7;
  • const int ledPin3=4;

Estos apartados declaran tres variables enteran que almacenan los números de los pines del Arduino a los que están conectados los LEDs. El 1 es led verde, 2 es led azul y 3 es led rojo.

  • unsigned long tiempoInicio = 0;

Esta variable almacena el tiempo en milisegundos en el que se inició el Arduino.

  • const unsigned long tiempoEspera = 3000;

Esta variable almacena el tiempo en milisegundos que se espera entre cada vez que se mueve un servomotor.

  • bool resetearContador = false;

Esta variable indica si se debe reiniciar el contador de tiempo.

  • int estadoLed;

Esta variable se encarga del estado del led, es decir, si se enciende o se apaga.

  • SoftwareSerial mySerial(5, 3);

Esta línea de código inicializa el objeto SoftwareSerial, que se utilizará para comunicarse con el módulo DFPlayer Mini. Configuramos los pines RX y TX del modulo a los pines 5 y 3.

b.   La inicialización de las variables (setup)

La inicialización de las variables (setup)

Como podemos observar, desde la línea 33 hasta la 37 se inicializan los servomotores, mientras que de la línea 39 a la 42 se establece como modo de entrada para los interruptores. También se configuran los pines de los LED en las líneas comprendidas entre 44 y 47. A continuación, desde la línea 49 a las 53 se establecen en posiciones determinadas los servomotores. Los tres primeros, que estarán encargados de los interruptores, a 178° mientras que el cuarto se posiciona en 120°, el cual se encargará de la tapa de la caja.

A partir de la línea 61 hasta las 65 será la comprobación de errores del módulo DFPlayer, donde comprueba si se pudo inicializar el módulo. En la línea 67 se encarga del nivel del volumen del módulo y por lo tanto de la salida de los altavoces.

c.   El comportamiento continuo del programa (loop)

Loop – Condicional para comprobar si se ha activado algún interruptor antes de abrir la caja

Iniciando con la primera parte del void loop() observamos como las tres primeras líneas leen el estado de los interruptores. A continuación, el condicional comprueba el estado de todos los interruptores y en el caso de que hubiera alguno encendido, empieza el programa. Al iniciar el programa, se inicia el contador de tiempo, se reproduce un audio preestablecido para ese momento y tras un tiempo de espera de un segundo y medio, se abre la caja con el servomotor 4.

Loop – Bucle que comprueba si hay algún interruptor activo

En este bucle while se comprueba si hubiera algún interruptor activo y en ese caso se encenderían los LED de forma secuencial de cada color.

Loop – Condicionales para activar el servomotor correspondiente dependiendo de que interruptor se pulse

En esta zona, se activan los servomotores dependiendo de interruptor activado correspondiente a cada servomotor, es decir, si se activa el interruptor 1 entonces se mueve el servomotor 1, si se activa el interruptor 2 entonces se mueve el servomotor 2 y si se activa el interruptor 3 entonces se mueve el servomotor 3. Con esas tres últimas líneas, se lee el estado de los interruptores.

Loop – Continuación de la secuencia de los LED

 A continuación, en este condicional, continúa con el cambio de color/LED como ya se había comentado anteriormente en el primer while.

Loop – Comprobación de que los interruptores esten apagados

Para finalizar con la parte del void loop(), se trata de la comprobación de que los estados de los interruptores estén a 0, que el tiempo pasado sea mayor o igual al tiempo de estera y que se ha reiniciado el contador.

d.   Funciones/Subprogramas adicionales

En esta última parte solo quedaría explicar las cuatro funciones adicionales utilizadas para poder completar este proyecto.

Funciones/Subprogramas adicionales – accionServo(Servo &servo) y cerrarCaja(Servo &servo)

Empezaremos con estas dos funciones void: accionServo(Servo &servo) y cerrarCaja(Servo &servo). La función accionServo se encarga tanto del movimiento de los brazos de dentro de la caja como de los sonidos por cada movimiento. La función cerrarCaja se encarga de, como su nombre indica, de cerrar la caja dependiendo del número aleatorio que aparezca.

Funciones/Subprogramas adicionales – inactividad(Servo &servo) y lento(Servo &servo)

Para terminar con las dos últimas funciones. La función void inactividad(Servo &servo) se encarga del tiempo en el que no se haya pulsado ningún interruptor en algún tiempo y realiza una comprobación. La función void lento(Servo &servo) se encarga de la velocidad del cerrado de la tapa para que no de un gran choque contra la caja, como ya habíamos comentado en el apartado de problemas encontrados.

5. PROBLEMAS OBSERVADOS

En este apartado explicaremos los distintos problemas que nos han ido surgiendo y las diferentes soluciones que hemos ido encontrando para el correcto funcionamiento del proyecto

Problema con el servo para abrir la caja. Se tuvieron dos problemas principales con este caso. El primero ocurrió al darnos cuenta de que el servomotor no tenia suficiente fuerza para poder levantar la tapa de madera, por lo que se compró un servomotor más potente. Esto nos llevó al segundo problema, el cual consistía en el que, si bien ahora si tenía fuerza para abrir la caja, generaba una serie de espasmos que impedían el correcto funcionamiento del programa. Estos espasmos eran creados por pérdidas de potencia en los servomotores. Por lo que terminamos cambiando la tapa a una de cartón donde había una gran diferencia de peso frente a la de madera y esta estuviera sujetada con una cuerda para que no saliera disparada cuando se abría la caja.

Problema al cerrar la caja, ya que daba un gran choque entre la tapa y la caja. Esto fue solucionado gracias a un subprograma lento, es decir, un bucle que recorriese cada grado metiendo un tiempo de espera (delay) para que fuese más lento. Otro problema con la caja surgió ya que tras cierto tiempo no se cerraba la caja (que es una de las funciones que debía tener el programa), el cual fue solucionado al implementar una variable que detecte que está dentro del bucle de juego y a partir de que no se usen ningún interruptor y que al pasar el tiempo designado se parara.

Problemas con los brazos (servomotores). Al tratarse de un brazo giratorio que tenia que golpear un interruptor, no tenía ni que pasarse de fuerza ni quedarse corto. Por ello, tras prueba y error, se consiguió obtener los ángulos exactos para los servomotores para que pulsaran correctamente los interruptores.

Problema con el módulo DFPlayer. Aquí surgió tanto un problema con el software y otro con el hardware. El problema con el software fue que, aunque los audios/sonidos se guardaran de la forma necesaria, al implementarla en la programación, los nombres estaban totalmente cambiados sin posibilidad de probarlos más que a través de prueba y error. El otro problema mencionado relacionado con el hardware fue que, tras tantos unos de sacar y meter la microSD en el módulo, para poder corregir el problema anteriormente comentado de los audios/sonidos, se terminó doblando y dejando de hacer contacto el módulo con la microSD, el cual fue arreglado mecánicamente al doblarlo físicamente hacia el otro lado con una palanca. De todos modos, nos obligó a comprar uno nuevo en el caso de que esa medida no durara lo suficiente.

6. DISEÑO FINAL

A continuación, se mostrarán las imágenes del diseño tras haber realizado todas las decoraciones para sea un producto final listo para la venta al público:

Vista de la caja abierta y los muñecos mostrándose

Vista de la caja abierta y los muñecos ocultos

Vista de la caja cerrada – Vista frontal

7. VIDEO DEMOSTRATIVO

Video demostrativo de todas las funciones de la Caja Mágica

8. CONCLUSIONES

Tras innumerables pruebas y errores de distintos proyectos para ver cual era el que más nos convencía, llegamos a la conclusión que esta “Caja mágica” era perfecta para generar interés y aceptación del profesor y los demás visitantes, tanto como para generarnos a nosotros mismos una gran motivación de seguir adelante. ¿Pero esto significa que fue muy fácil de realizar? Ojalá fuera todo tan bonito en la realidad. No, fue bastante complejo el conseguir terminar el código correctamente, ya que fue bastante sorprendente que tras casi haber terminado el código y que funcionara como debía, al día siguiente dejó de funcionar sin nosotros tener idea del porqué. ¿Pero esto nos detuvo? No, vimos de este problema una nueva esperanza para realizar el código de nuevo implementado nuevas y mejores ideas funcionales.

Desde nuestro punto de vista, es un proyecto bastante original que ha quedado bastante bien. Por razones obvias no va a ganar ningún premio de tecnología ya que, más que un avance revolucionario, es un entretenimiento (o juguete) que puedes tener en tu casa para entretenerte o distraerte de una forma amena. Ahora que nos estamos acercándonos a la Navidad, sería un regalo perfecto para un capricho.

Eso sí, esto anteriormente dicho es desde nuestro punto de vista, por lo que siempre es más cómodo el encontrar fallas y críticas en los trabajos de los demás antes que el de uno mismo. Por ello, las fallas y críticas observadas en este proyecto que no han sido nombradas por nosotros mismos se lo dejaremos al sentido del gusto de la persona que lo este leyendo. Ante todo, si has llegado a leer hasta esta parte del proyecto es que te ha gustado.

Muchas gracias por su atención a lo largo de este proyecto.

9. CÓDIGO COMPLETO

A continuación, situaré el código completo en el caso de que lo quisieras probar por vuestra cuenta:

#include <Servo.h>                        // Biblioteca para contralar servomotores
#include <SoftwareSerial.h>               // Biblioteca para comunicacion serial
#include <DFRobotDFPlayerMini.h>          // Biblioteca para contralar el DFPlater Mini

Servo servo1;
Servo servo2;
Servo servo3;
Servo servo4;

int interruptorPin1 = 13;                 // Pin del primer interruptor 
int interruptorPin2 = 12;                 // Pin del segundo interruptor
int interruptorPin3 = 2;                  // Pin del tercer interruptor

int servoPin1 = 11;                       // Pin del primer servo
int servoPin2 = 10;                       // Pin del segundo servo
int servoPin3 = 9;                        // Pin del tercer servo
int servoPin4 = 6;                        // Pin del cuarto servo

const int ledPin1=8;                      //Pin del led verde
const int ledPin2=7;                      //Pin del led azul
const int ledPin3=4;                      //Pin del led rojo

unsigned long tiempoInicio = 0;           // Variable para almacenar el tiempo de inicio
const unsigned long tiempoEspera = 3000;  // Tiempo de espera en milisegundos (5 segundos)
bool resetearContador= false;             // Boolean para saber donde entrar

int estadoLed;                            //Variable del estado del led      

SoftwareSerial mySerial(5, 3);            // RX, TX para el módulo DFPlayer Mini
DFRobotDFPlayerMini myDFPlayer;

void setup() {
  // Concecsión de los servomotores con los pines correspondientes
  servo1.attach(servoPin1);
  servo2.attach(servoPin2);
  servo3.attach(servoPin3);
  servo4.attach(servoPin4);

  // Configuración de los pines de los interruptores
  pinMode(interruptorPin1, INPUT_PULLUP);
  pinMode(interruptorPin2, INPUT_PULLUP);
  pinMode(interruptorPin3, INPUT_PULLUP);

  // Configuración de los pines de los LEDS como salida
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);

  // Configuración de la posición inicial de los servomotores
  servo1.write(170);
  servo2.write(170);
  servo3.write(170);
  servo4.write(90);

  Serial.begin(9600);
  Serial.println("Iniciando el programa...");

  // Inicializa el módulo DFPlayer Mini
  mySerial.begin(9600);

  //  Verificación de iniciar el módulo DFPlayer Mini
  if (!myDFPlayer.begin(mySerial)) {
    Serial.println(("No se pudo iniciar el módulo DFPlayer Mini."));
    while (true); // Bucle infinito si no se inicia el módulo DFPlayer Mini
  }

  myDFPlayer.volume(30); // Ajusta el volumen (0-30)
}

void loop() {
  // Lee el estado de los interruptores
  int interruptorEstado1 = digitalRead(interruptorPin1);
  int interruptorEstado2 = digitalRead(interruptorPin2);
  int interruptorEstado3 = digitalRead(interruptorPin3);

  if ((interruptorEstado1 == 1 || interruptorEstado2 == 1 || interruptorEstado3 == 1) && !resetearContador) { //Comprobar que se a tocado algun interruptor para que se abra la caja

      tiempoInicio = millis(); // Inicia el contador de tiempo para saber cuando cerrar la caja
      Serial.println("Inicio contador");
      Serial.println(tiempoInicio);

      myDFPlayer.play(1); // Reproduce archivo de audio
      delay(300);
      servo4.write(180);  // Abre la caja
      resetearContador=true;

      estadoLed=0;    // Posición inicial del LED

      delay(1200);        // Tiempo de espera para que se abra la caja
  }

  while (interruptorEstado1 == 1 || interruptorEstado2 == 1 || interruptorEstado3 == 1) { //Bucle donde compurba si hay algun interruptor activo
    // Cambio de las luces LED dentro de la caja
    switch (estadoLed) { 
      case 0: // Verde
        digitalWrite(ledPin1, HIGH);
        digitalWrite(ledPin2, LOW);
        digitalWrite(ledPin3, LOW);
        estadoLed = 1;
        break;
      case 1: //Azul
        digitalWrite(ledPin1, LOW);
        digitalWrite(ledPin2, HIGH);
        digitalWrite(ledPin3, LOW);
        estadoLed = 2;
        break;
      case 2: // Rojo
        digitalWrite(ledPin1, LOW);
        digitalWrite(ledPin2, LOW);
        digitalWrite(ledPin3, HIGH);
        estadoLed = 0;
        break;
    }

    // Acciona el servo configurado con el interruptor
    if (interruptorEstado1 == 1) {
      Serial.println("Tocado interruptor 1");
      accionServo(servo1);
    }
    if (interruptorEstado2 == 1) {
      Serial.println("Tocado interruptor 2");                                                                                  
      accionServo(servo2);
    }
    if (interruptorEstado3 == 1) {
      Serial.println("Tocado interruptor 3");
      accionServo(servo3);
    }

    // Lee el estado de los interruptores
    interruptorEstado1 = digitalRead(interruptorPin1);
    interruptorEstado2 = digitalRead(interruptorPin2);
    interruptorEstado3 = digitalRead(interruptorPin3);
  }

  // Seguir con el cambio de color de los LEDS cuando se esta comprobando el tiempo de espera
  if(resetearContador){
    switch (estadoLed) {
      case 0:
        digitalWrite(ledPin1, HIGH);
        digitalWrite(ledPin2, LOW);
        digitalWrite(ledPin3, LOW);
        estadoLed = 1;
        delay(500);
        break;
      case 1:
        digitalWrite(ledPin1, LOW);
        digitalWrite(ledPin2, HIGH);
        digitalWrite(ledPin3, LOW);
        estadoLed = 2;
        delay(500);
        break;
      case 2:
        digitalWrite(ledPin1, LOW);
        digitalWrite(ledPin2, LOW);
        digitalWrite(ledPin3, HIGH);
        estadoLed = 0;
        delay(500);
        break;
    }
  }
  
  // Comprobación que los estados de los interruptores esten a 0, que el tiempo pasado sea mayor o igual al tiempo de estera y que se ha reseteado el contador
  if((interruptorEstado1 == 0 || interruptorEstado2 == 0 || interruptorEstado3 == 0) && (millis()-tiempoInicio>=tiempoEspera) && (resetearContador)){
    Serial.println("cerrar");

    // Apagar los LEDS
    digitalWrite(ledPin1, LOW);
    digitalWrite(ledPin2, LOW);
    digitalWrite(ledPin3, LOW);

    cerrarCaja(servo4);     // Cerrar caja
    resetearContador=false; //Volver al estado original para que se puede volver a iniciar 

    tiempoInicio=millis();  //Para reiniciar el tiempo para el tema de inactividad
  }
  
  if((interruptorEstado1 == 0 || interruptorEstado2 == 0 || interruptorEstado3 == 0) && (millis()-tiempoInicio>=10000) && (!resetearContador)){ //Funcion para inactividad
    inactividad(servo4);   //Acción de inactividad

    tiempoInicio=millis(); //Reiniciar el tiempo para el tema de inactividad
  }
}

void accionServo(Servo &servo) { //Funcion para el funcionamiento de los brazos
  myDFPlayer.play(4); // Reproducir sonido
  delay(300);
  servo.write(0); // Movimiento del servo
  delay(700);       // Tiempo para la acción del servo
  servo.write(170); // Volver al estado original

  tiempoInicio=millis(); //Reiniciar tiempo para el tema cerrar caja
}

void cerrarCaja(Servo &servo){ //Funcion para que después de cerrar la caja se abra de nuevo en forma de amenaza
  int numeroAleatorio = random(100); // Genera un número aleatorio entre 0 y 99 

  if (numeroAleatorio < 60) { // 60% de que se cierre normal la caja
    delay(300);
    myDFPlayer.play(1);
    lento(servo4); 
    
  }else{                      // 40% para que haga una acción distinta al cerrar la caja
    Serial.println("Amenazar");

    // Se cierra la caja después de 2 segundo se vuelve a abrir
    delay(300);
    lento(servo4);
    myDFPlayer.play(1);
    digitalWrite(ledPin3,HIGH);
    delay(2000);

    servo.write(180);
    delay(700);

    myDFPlayer.play(3);
    
    delay(2000);
    digitalWrite(ledPin3,LOW);
    lento(servo4);  
  }
}

void inactividad(Servo &servo){ //Función para que despues de 10 segundos de inactividad algunas veces se revise el exterior
  int numeroAleatorio = random(5);

  if (numeroAleatorio < 3) {  //Para que pase algunas veces
    Serial.println("Inactividad");

    servo.write(180);
    delay(500);
    myDFPlayer.play(2);
    

    delay(3000);
    lento(servo4);
  }
}

void lento(Servo &servo){ //Función para que al cerrar la caja sea de forma lenta
  Serial.println("lento");
  for(int i=180; i>=90;i--){
    servo.write(i);
    delay(10);
  }
}

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 *