LOCOFÓN: Teléfono bop-it

Para este trabajo se ha decidido explotar al máximo la cantidad de componentes que se nos proporciona en el kit y, para ello, se ha tomado la decisión de hacer algo sencillo pero que bien cumple con esto último. El proyecto en cuestión consiste en un juguete que sigue la lógica del típico “Simón Dice”; es decir, el juguete pedirá diversas tareas que requerirán que el usuario interactúe con los componentes en un tiempo límite y este, en caso de hacerlo en correcta manera, sumará puntuación reduciendo el tiempo gradualmente y, en caso contrario, se perderá la partida.
Así pues, y a modo de resumen, el proyecto usa componentes para generar tareas sencillas de entrada y lectura, e incitan al jugador a conseguir la mayor puntuación posible.
Circuito
En primer lugar, se diseña la electrónica en la web “WokWi” con el fin de modificar todo con facilidad y probar código sin necesidad de montar el circuito en las sesiones de prueba.
Así pues, y teniendo como objetivo el poder tratar de manera separada cada componente por programación, se procura diseñar distintos circuitos cerrados por cada componente, en lugar de un único circuito en serie.
Como se puede apreciar en el diagrama mostrado más abajo, se han utilizado gran cantidad de componentes y, con ellos, la mayor parte de los pines disponibles en la placa. Los componentes utilizados son los siguientes:
- Sensor ultrasonido
- Zumbador
- LCD I2C
- LED RGB (Junto con tres resistencias para evitar excesos de potencia)
- x3 Botones
- Potenciómetro
- Fotorresistor
- Joystick

Una vez diseñado y testeado todo digitalmente, se procede a su construcción electrónica.
Como se puede observar, por motivos funcionales y con el fin de ajustar el circuito a la carcasa, se tienen diversos cambios con respecto al diagrama inicial. Estos son:
- Se decide separar los botones y el led en una placa pequeña separada con el fin de elevar estos para ser accesibles una vez cerrado el prototipo
- Se conecta el joystick directamente al arduino UNO para poderlo sacar de la placa protoboard y acercarlo a la pared de la carcasa para que sea accesible por el usuario.

Código
El código consiste de de dos funciones principales, setup() y loop(), y cinco funciones secundarias que gestionan las distintas órdenes del juego Joystick(), Potenciometro(), Ultrasonic(long d), RGBLed() y LuzSensor().
En el Setup se inicializan los valores de todos los componentes y pines, de las variables necesarias para el juego (tiempoInicioOrden, numComponente, puntuación…), y se hace la secuencia de inicio del componente, en la cual el lcd muestra preparados..listos..ya!.
En cada paso del Loop se actualizan los valores de todos los pines, se actualiza el temporizador y se muestra por pantalla, se comprueba la condición de victoria, y se comprueba la condición de derrota.
En todas las secciones de los componente se utiliza el mismo sistema, que consiste en un “setup” mediante el uso de un booleano que solo se ejecuta una vez por componente, que muestra por pantalla la orden, y en cada componente gestiona la orden, por ejemplo, en los botones y el joystick asigna el número aleatorio que decide la orden, y en el potenciómetro, distingue en qué posición inicia el componente.
Tras esto, comprueba en cada paso del loop si se ha cumplido la condición que toque dependiendo de la orden y el componente

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
//Crear el objeto lcd dirección 0x3F y 16 columnas x 2 filas
LiquidCrystal_I2C lcd(0x27,16,2); //
// Joystick
const int pinJoyX = A0;
const int pinJoyY = A1;
int Xvalue = 0;
int Yvalue = 0;
// Ultrasonnic sensor
const int Trigger = 3;
const int Echo = 2;
long t; //timepo que demora en llegar el eco
long d; //distancia en centimetros
// Buzzer
const int buzzerPin = 10;
// Potentiometer
const int potenPin = A2;
// RGB buttons
const int gButPin = 6;
const int bButPin = 5;
const int rButPin = 4;
int bButtonState = 0;
int rButtonState = 0;
int gButtonState = 0;
const int gLedPin = 8;
const int bLedPin = 9;
const int rLedPin = 7;
//Sensor de luz
const int luzPin = A3;
int luzValue = 0;
// VARIABLES DEL PROGRAMA
// Tiempo
float intervaloMax = 7;
float contadorIntervalo = 0;
float loopTime;
unsigned long tiempoInicioOrden; // Momento en que comenzó la orden actual
unsigned long intervaloMaxMs = (unsigned long)(intervaloMax * 1000);
unsigned long currentMillis;
// Seleccion de componente
int numComponente;
// Gestion del juego
bool ordenCompletada = false;
int puntuacion = 0;
bool hasPerdido = false;
bool compSetup = true;
int joyRand = 0;
bool potVal = true;
int randRGB;
void setup() {
Serial.begin(9600);
//LDC
// Inicializar el LCD
lcd.init();
randomSeed(analogRead(pinJoyX) + millis());
//Encender la luz de fondo.
lcd.backlight();
//Ultrasonic sensor
pinMode(Trigger, OUTPUT); //pin como salida
pinMode(Echo, INPUT); //pin como entrada
// BUZZER
pinMode(buzzerPin, OUTPUT);
//Botones RGB
pinMode(rButPin, INPUT);
pinMode(gButPin, INPUT);
pinMode(bButPin, INPUT);
//Led RGB
pinMode(rLedPin, OUTPUT);
pinMode(gLedPin, OUTPUT);
pinMode(bLedPin, OUTPUT);
//Sensor de luz
pinMode(luzPin, INPUT);
Serial.begin(9600);
//hace un sonidito
lcd.clear();
lcd.print("Preparado...");
tone(buzzerPin, 8000);
delay(1000);
noTone(buzzerPin);
lcd.clear();
lcd.print("Listo...");
tone(buzzerPin, 8000);
delay(1000);
noTone(buzzerPin);
lcd.clear();
lcd.print("Ya!");
tone(buzzerPin, 10000);
delay(1000);
noTone(buzzerPin);
lcd.clear();
//comienza
tiempoInicioOrden = millis();
numComponente = random(1, 6); //Del 1 al 5. Sí, el 6 no entra
}
void loop() {
currentMillis = millis();
// INICIALIZACION DE VALORES DEL COMPONENTE
unsigned long startTime = millis();
// Joystick
Xvalue = analogRead(pinJoyX);
delay(100); //es necesaria una pequeña pausa entre lecturas analógicas
Yvalue = analogRead(pinJoyY);
// Ultrasonic sensor
digitalWrite(Trigger, LOW);
delayMicroseconds(2);
digitalWrite(Trigger, HIGH);
delayMicroseconds(10); //Enviamos un pulso de 10us
digitalWrite(Trigger, LOW);
t = pulseIn(Echo, HIGH); //obtenemos el ancho del pulso
d = t*.0343/2; //escalamos el tiempo a una distancia en cm
//BOTONES RGB
rButtonState = digitalRead(rButPin);
gButtonState = digitalRead(gButPin);
bButtonState = digitalRead(bButPin);
//SENSOR DE LUZ
luzValue = analogRead(luzPin);
//Serial.println(luzValue);
//MOSTRAR TEMPO
lcd.setCursor(14,1);
lcd.print(intervaloMaxMs-((currentMillis - tiempoInicioOrden)));
lcd.setCursor(1,0);
// INICIALIZACION DE VALORES DEL PROGRAMA
if(ordenCompletada){
// Sonido buzzer correcto
tone(buzzerPin, 10000);
delay(100);
noTone(buzzerPin);
// Subir puntos
puntuacion++;
lcd.clear();
lcd.print("puntuacion");
lcd.setCursor(12,0);
lcd.print(puntuacion);
lcd.setCursor(1,0);
delay(1000);
lcd.clear();
//Reducir timepo máximo
intervaloMax -= 0.5;
intervaloMax = max(1, intervaloMax);
intervaloMaxMs = (unsigned long)(intervaloMax * 1000);
// Generar nueva orden
numComponente = random(1, 6); //Del 1 al 5. Sí, el 6 no entra
// Pasar a siguiente orden
ordenCompletada = false;
contadorIntervalo = 0;
tiempoInicioOrden = millis();
currentMillis = millis();
compSetup = true;
lcd.clear();
}
if(currentMillis - tiempoInicioOrden > intervaloMaxMs || hasPerdido){
// PERDER JUEGO
digitalWrite(rLedPin, LOW);
digitalWrite(gLedPin, LOW);
digitalWrite(bLedPin, LOW);
lcd.clear();
lcd.print("has perdido");
lcd.setCursor(0, 1);
lcd.print("puntuacion");
lcd.setCursor(12,1);
lcd.print(puntuacion);
lcd.setCursor(1,0);
// Sonido malo
tone(buzzerPin, 2000);
delay(2000);
noTone(buzzerPin);
// Pantalla LCD perdido
puntuacion = 0;
// Mostrar puntuacion
lcd.clear();
lcd.print("Reiniciando en");
lcd.setCursor(0,1);
lcd.print("3");
delay(1000);
lcd.setCursor(2,1);
lcd.print("2");
delay(1000);
lcd.setCursor(4,1);
lcd.print("1");
delay(1000);
lcd.clear();
lcd.print("Preparado...");
tone(buzzerPin, 8000);
delay(1000);
noTone(buzzerPin);
lcd.clear();
lcd.print("Listo...");
tone(buzzerPin, 8000);
delay(1000);
noTone(buzzerPin);
lcd.clear();
lcd.print("Ya!");
tone(buzzerPin, 10000);
delay(1000);
noTone(buzzerPin);
lcd.clear();
delay(100);
//comienza
intervaloMax = 7;
intervaloMaxMs = (unsigned long)(intervaloMax * 1000);
tiempoInicioOrden = millis();
numComponente = random(1, 6); //Del 1 al 5. Sí, el 6 no entra
compSetup = true;
}
switch(numComponente){
case 1: //Joystick
JoyStick();
break;
case 2: //Potenciometro
Potenciometro();
break;
case 3: //Ultrasonidos
Ultrasonic(d);
break;
case 4: //Botones RGB
RGBLed();
break;
case 5: //Sensor de luz
LuzSensor();
break;
}
}
void JoyStick(){
if(compSetup == true){
// Mostrar texto
lcd.print("Mueve el joystick");
lcd.setCursor(0, 1);
joyRand = random()%4 + 1;
switch(joyRand){
case 1:
lcd.print("Arriba");
break;
case 2:
lcd.print("Abajo");
break;
case 3:
lcd.print("Derecha");
break;
case 4:
lcd.print("Izquierda");
break;
default:
break;
}
}
Serial.print(Xvalue);
Serial.println(Yvalue);
switch(joyRand){
case 1:
if(Xvalue < 100){
ordenCompletada = true;
}
break;
case 2:
if(Xvalue > 900){
ordenCompletada = true;
}
break;
case 3:
if(Yvalue < 100){
ordenCompletada = true;
}
break;
case 4:
if(Yvalue > 900){
ordenCompletada = true;
}
break;
}
compSetup = false;
}
void Potenciometro(){
if(compSetup == true){
lcd.print("Volumen");
if(analogRead(potenPin) > 500){
potVal = true;
} else {
potVal = false;
}
}
Serial.print(potVal);
if(potVal){
if(analogRead(potenPin) < 100){
ordenCompletada = true;
}
} else {
if(analogRead(potenPin) > 900){
ordenCompletada = true;
}
}
compSetup = false;
}
void Ultrasonic(long d){
if(compSetup == true){
lcd.print("Distancia");
}
Serial.println(d);
if(d<20 && d>15){
ordenCompletada = true;
}
compSetup=false;
}
void RGBLed(){
if(compSetup == true){
lcd.print("Botones");
randRGB = random()%3 + 1;
switch(randRGB){
case 1:
digitalWrite(rLedPin, HIGH);
break;
case 2:
digitalWrite(gLedPin, HIGH);
break;
case 3:
digitalWrite(bLedPin, HIGH);
break;
default:
break;
}
}
switch(randRGB){
case 1:
if(rButtonState == HIGH){
ordenCompletada = true;
digitalWrite(rLedPin, LOW);
}
break;
case 2:
if(gButtonState == HIGH){
ordenCompletada = true;
digitalWrite(gLedPin, LOW);
}
break;
case 3:
if(bButtonState == HIGH){
ordenCompletada = true;
digitalWrite(bLedPin, LOW);
}
break;
default:
break;
}
compSetup=false;
}
void LuzSensor(){
if(compSetup == true){
lcd.print("Sensor de luz");
}
Serial.println(luzValue);
if(luzValue > 200){
ordenCompletada = true;
}
compSetup=false;
}
Empotrado
A la hora de hacer el empotrado, se comenzó haciendo varios bocetos hasta encontrar la idea final para el diseño.


Finalmente, teniendo en cuenta el tamaño de los componentes, que obligaba a que el prototipo fuese de un tamaño medianamente grande o alargado, y sabiendo que el público objetivo principal serían niños, se decidió tomar un diseño que fuese divertido y a la vez compacto.
Para ello, según la colocación de los componentes pensada, el conector de la pila tendría que sobresalir por arriba, lo que podía asimilarse con la antena de un teléfono antiguo. De este modo, la idea final acabó siendo que la temática del juego fuese la interacción con los componentes del arduino como si de los botones de un teléfono se tratase.
Así pues, se realizó un sketch final del prototipo del teléfono, con la colocación de los componentes y las medidas tomadas para posteriormente ser modelado.

Para el modelado en 3D del prototipo se utilizó la aplicación Blender. A pesar de que puede que no sea el mejor programa de modelado 3D para hacer piezas, es el que mejor controlamos y más fácil nos resulta.
Activando ciertas opciones dentro del programa, se pueden ver las medidas del modelo, por lo que es muy útil a la hora de ser algo más precisos, además de modificar el grosor de todas las paredes a la vez del modelo según se necesite, sin tener que ir cara a cara cambiando la medidas. Como se puede observar, el modelo contiene todos los espacios abiertos, de diferentes formas y tamaños, para los distintos componentes, y la forma da como resultado un teléfono antiguo, que cumple con el objetivo estético del empotrado.

El resultado final del empotrado es el siguiente:
Como se puede observar, a pesar de las complicaciones técnicas, presupuestarias y de diseño, se consigue un acabado funcional y bastante compacto, al que posteriormente se añaden decoraciones con pintura con el fin de acercar el prototipo en mayor medida a la estética deseada como es la de un teléfono antiguo.



Costes de los materiales
Haciendo un cálculo estimado de los costes del empotrado, se comprueba que el precio estimado ha de estar entre 40 y 50 euros, dependiendo del lugar donde se obtengan los componentes.
No obstante, los gastos personales han sido mínimos, pues además de todos los componentes ofrecidos por el centro, los que se han empleado de manera externa han sido cedidos por conocidos que trabajan con este tipo de tecnologías. Incluso en cuanto a la impresión, se ha contado con la ayuda de la asociación universitaria “La Caverna del Dragón”, que han permitido el uso de su impresora 3D.
El único gasto real ha sido para adquirir la pila que alimenta el prototipo.
| COMPONENTE | PRECIO |
| Microcontrolador Elegoo Uno R3 x1 | 6,00€ |
| 830-Tie Points Breadboard x1 | 3,50€ |
| Mini Breadboard x1 | 1,09€ |
| Joystick x1 | 4,89€ |
| Ultrasonic Sensor x1 | 1,40€ |
| Buzzer x1 | 0,90€ |
| LCD1602 x1 | 5,99€ |
| I2C x1 | 5,00€ |
| RGB Led x1 | 0,10€ |
| Fotoresistor x1 | 1,10€ |
| Botones x3 | 0,69€ |
| Potenciómetro x1 | 0,70€ |
| Jumpers Macho-Macho x20 | 0,90€ |
| Jumpers Macho-Hembra x5 | 0,23€ |
| Resistencias x3 | 0,06€ |
| Pila 9V x1 | 2,30€ |
| Conector de pila x1 | 1,50€ |
| Filamento de impresión x1 | 10,00€ |
| TOTAL | 46,35€ |
Casos de uso
El sistema desarrollado presenta un juego, parecido al clásico “Bop-it”, donde el juguete manda una instrucción que el jugador debe realizar antes de que se acabe un límite de tiempo. Estas instrucciones se presentan en la pantalla LCD. A continuación, se describen los principales casos de uso del sistema:
Caso de uso 1: Inicio de partida
El usuario enciende el dispositivo conectando la fuente eléctrica. Tras ello, el LCD se encenderá y se iniciará una cuenta atrás para que el jugador se prepare para comenzar el juego.
Caso de uso 2: Generación de orden aleatoria
Una vez iniciada la partida, el sistema selecciona de manera aleatoria uno de los componentes disponibles (botones, joystick, potenciómetro, sensor de luz, sensor ultrasónico o LED RGB). Esta selección decide cual es la tarea que el usuario deberá realizar a continuación para seguir jugando.
Caso de uso 3: Interacción mediante botones
El sistema solicita al usuario pulsar el botón del color concreto entre los disponibles (rojo, azul y verde). En el LCD mostrará la instrucción y el color será indicado por el color del LED debajo de los botones. El jugador debe identificar y presionar el botón correcto.
Caso de uso 4: Interacción mediante joystick
El juego pide al usuario mover el joystick en una dirección específica (arriba, abajo, izquierda o derecha). Se comprueba continuamente la posición del joystick hasta que el jugador lo mueve en la dirección adecuada.
Caso de uso 5: Interacción mediante potenciómetro
El sistema solicita al usuario modificar la posición del potenciómetro, el cual simboliza una rueda de volumen con la estética y temática del teléfono. El movimiento deberá ser de un extremo a otro, dependiendo de donde se haya quedado en la acción anterior.
Caso de uso 6: Interacción mediante sensor ultrasónico
El sistema requiere que el usuario acerque o aleje la mano del sensor ultrasónico hasta alcanzar una distancia determinada. El sistema mide la distancia detectada y lo dará como válido cuando sea la distancia justa.
Caso de uso 7: Interacción mediante sensor de luz
El sistema pide al usuario tapar el agujero del modelo 3D donde se ubica el fotorresistor, provocando que entre menos luz. El sistema evalúa el cambio de luminosidad para determinar si la orden ha sido cumplida correctamente.
Caso de uso 8: Acierto de la orden
Si el usuario cumple correctamente la orden que se le pide dentro del tiempo límite, el sistema incrementa la puntuación. Además, se irá reduciendo ligeramente el tiempo disponible para la siguiente orden para aumentar progresivamente la dificultad del juego, hasta llegar a un tiempo límite de 2 segundos para realizar cada tarea.
Caso de uso 9: Error o agotamiento del tiempo
Si el usuario no realiza la acción correcta en el tiempo límite, el sistema considera la partida como perdida. Se muestra el resultado final en el LCD y el juego se reinicia, dando unos segundos al jugador para que asimile la derrota y vuelva a prepararse para la siguiente partida.
Demostración de uso
La demostración del uso se encuentra en el siguiente enlace de drive:
https://drive.google.com/file/d/1BbAeavCJIsj8CynCcXDfWkleBtfgeoXS/view?usp=sharing