Caja Mágica
Autores: Óscar Fuente Fernández y Antonio Herman Boeriu
ÍNDICE
- INTRODUCCIÓN
- MATERIAL UTILIZADO Y COSTES
- IMPELMENTACIÓN DEL HARDWARE
- 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
- PROBLEMAS OBSERVADOS
- DISEÑO FINAL
- VIDEO DEMOSTRATIVO
- CONCLUSIONES
- 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:
Nombre | Cantidad | Utilizados | Precio |
Kit ELEGOO Arduino | 1 unidad | Múltiples componentes | 39,07 € |
Pack Servomotores 10 unidades | 1 unidad | 4 servomotores | 17,89 € |
Pack Interruptores 10 unidades | 1 unidad | 3 interruptores | 10,42 € |
Mini MP3 DFPlayer Player Módulo con Altavoz Redonda 2W 8Ohm | 1 unidad | 1 módulo DFPlayer y 1 altavoz 2W 80hm | 9,99 € |
Tarjeta microSD 4 GB | 1 unidad | 1 microSD | 8,31 € |
Caja de madera y materiales de decoración | 15 unidades | 15 unidades | 14,95 € |
Precio total | – | – | 100,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.
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.
A continuación, se mostrarán 3 imágenes sobre como está situado el Arduino y los componentes dentro de la caja.
Nombre | Pines del Arduino | Pines del módulo DFPlayer | Pines altavoz |
Interruptor 1 | 13 | – | – |
Interruptor 2 | 12 | – | – |
Interruptor 3 | 2 | – | – |
Servomotor 1 | 11 | – | – |
Servomotor 2 | 10 | – | – |
Servomotor 3 | 9 | – | – |
Servomotor 4 | 6 | – | – |
LED 1 Verde | 8 | – | – |
LED 2 Azul | 7 | – | – |
LED 3 Rojo | 4 | – | – |
Módulo DFPlayer RX | 5 | RX | – |
Módulo DFPlayer TX | 3 | TX | – |
Altavoz + | – | SPK_1 | Positivo |
Altavoz – | – | SPK_2 | Negativo |
Ground (Tierra) | GND | GND | – |
Voltaje | 5V | VCC | – |
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
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)
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)
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.
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.
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.
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.
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.
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.
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:
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.
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);
}
}