GLaDOS
Autores:
String nombre1 = ‘ ‘ Andrés Gonzalez Antohi ‘ ‘;
String nombre2 = ‘ ‘ Rodrigo Hervada Llahosa ‘ ‘;
String nombre3 = ‘ ‘ Marco Sánchez Nishimura ‘ ‘;
String nombre4 = ‘ ‘ Enrique Tamajón Castilla ‘ ‘;
1. Objetivo
El objeto de esta práctica ha sido crear una GLaDOS con el funcionamiento de un reloj despertador y reproductor de música. Además, debe ser capaz de realizar movimientos de brazo mecánico, incluyendo un menú de opciones que permita desplazarse por las distintas funcionalidades a través de una pantalla LCD y una aplicación Bluetooth.
2. Reparto de tareas y pasos realizados.
Al ser un proyecto de 4 personas, se decidió repartir las tareas de tal forma que permitiera especializarse en apartados concretos del mismo. Andrés fue el encargado principal de realizar el brazo mecánico, Enrique de realizar el código, y, por último, Marco y Rodrigo se encargaron del funcionamiento Bluetooth y el reproductor de audio y sus interconexiones con el altavoz y la pantalla LCD.
A pesar de esto, debido a que las sesiones de trabajo eran conjuntas, todos los integrantes participaron en los dinstintos apartados del proyecto, ayudándose unos a otros y realizando otro tipo de tareas de bricolaje.
El orden de desarrollo fue:
Primero, se trabajó por separado y en paralelo el brazo mecánico por una parte, y el resto de funcionalidades por otra. Cuando se acabaron por separado, se unieron ambos proyectos en un solo Arduino.
- Parte Bluetooth / Reproductor de audio / Pantalla LCD:
Tras la documentación necesaria, lo primero fue decidir qué componentes iban a ser necesarios y cómo obtenerlos. Una vez en posesión de ellos, el siguiente paso fue conectarlos a Arduino y comprobar que se había realizado de manera correcta. A continuación, se implementó un pequeño código de pruebas para entender el funcionamiento de los dispositivos.
A continuación, se desarrolló un primer código para el menú del sistema, al que se fueron añadiendo las distintas funcionalidades. Tras comprobar el correcto funcionamiento de este sistema se concluyó esta parte.
- Parte brazo mecánico:
Para la realización del brazo mecánico, en primer lugar se buscaron e imprimieron los modelos 3D correspondientes. Una vez impresas dichas piezas, se fueron probando individualmente con su servo correspondiente para comprobar sus límites de movimiento y velocidad. Tras asegurarnos del correcto funcionamiento de todas las partes, procedimos a realizar una base capaz de soportar el peso de la estructura y de organizar los cables de los servomotores y el led para evitar cualquier problema por culpa de que estos se enredasen.
- Unión de ambos sistemas:
A la hora de unir ambos sistemas, se tuvo que enfrentar a varios problemas, como podía ser una reorganización del circuito así como problemas debido a conflictos de puertos. Tras resolver todo, ya se pudo realizar una prueba global de GLaDOS.
3. Diseño
3.1. Boceto de la idea.
El diseño se centró en la necesidad de que GLaDOS colgara, por lo que se decidió aprovechar esto y colgarla de una tabla sujeta por patas, para que pudiera servir de mesilla con alarma incorporada.
3.2. Diseño para impresión del brazo mecánico.
Para el diseño del brazo mecánico, se buscaron modelos 3D del personaje GLaDOS. Tras encontrarlos, se pasarón al programa Ultimaker Cura, donde se decidió optar por una baja calidad de impresión para, no solo agilizar el proceso, sino también reducir el peso de las piezas. Por diversos problemas, mencionados en su correspondiente apartado, no se pudo imprimir la totalidad de los modelos 3D que componen a GLaDOS, por esta razón se tuvo que improvisar utilizando dos escuadras e improvisando la base.
Las impresiones finales tomaron un total de 24 horas 6 minutos y 261 gr de PLA.
El brazo mecánico cuenta con un total de 4 servomotores: uno para la cabeza (muñeca), otro para el cuello (codo), uno para el movimiento vertical (hombro) y otro para el giro de la base. Además, la cabeza tiene un led para simular al personaje del videojuego
3.3. Diseño del circuito en Tinkercad.
1: Amplificador y altavoz 4: Módulo Bluetooth
2: Servos 5: Lector de tarjetas SD
3: Pantalla LCD
3.4. Diseño real del circuito.
1: Amplificador y altavoz 4: Módulo Bluetooth
2: Servos 5: Lector de tarjetas SD
3: Pantalla LCD
3.5. Diseño final de la estructura.
Para los retoques finales se empapeló la caja contenedora del circuito con cartulinas blancas y cinta negra para continuar con la estética del videojuego ‘Portal’. Además, añadimos el logo de los creadores de GLaDOS y un ‘easter egg’ del propio videojuego.
4. Materiales
Componentes para Arduino | ||
Unidades | Componente | Precio total (€) |
1 | Tarjeta SD. | 0 |
1 | Lector tarjetas SD. | 0 |
1 | Módulo Bluetooth. | 6,17 |
1 | Altavoz 29mm 8Ω. | 0,82 |
1 | Amplificador TDA2030A. | 4,77 |
3 | Cable trenzado (1 metro). | 1,66 |
2 | Servomotor SG90 9G. | 0 |
2 | Servomotor SG90 9G. | 6,07 |
1 | Altavoces 2.0. | 0 |
1 | Arduino UNO kit ELEGOO. | 0 |
PRECIO FINAL: | 19,49 |
Materiales para la estructura | ||
Unidades | Componente | Precio total (€) |
1 | Pack cartulinas blancas. | 1,20 |
2 | Cinta bricolaje (pack de 2). | 2,40 |
1 | Cajas de cartón. | 0 |
1 | Tablero MDF (60cm x 30cm x 0,03cm). | 2,40 |
1 | Taburete. | 0 |
2 | Soporte escuadra. | 0,88 |
1 | Cintas (carrocero, americana, embalaje) | 0 |
1 | PLA (261 gramos) | 0 |
1 | Pistola de silicona + silicona | 0 |
1 | Soldador + estaño | 0 |
PRECIO FINAL: | 6,88 |
5. Problemas y soluciones
Siguiendo el esquema de los pasos realizados para el desarrollo del proyecto se mencionan los problemas que surgieron en cada etapa.
5.1. Brazo mecánico
Dada la baja calidad de las piezas, los tornillos rompían las capas, haciendo que las piezas resultasen inutilizables pues generan demasiados grados de libertad, o, en los casos extremos, se rompía completamente la pieza. Además, por causas desconocidas, la cama de la impresora 3D se desconfiguró en medio de una impresión, perdiendo 13 horas 25 minutos y 152 gr de PLA.
Como, a medida que se imprimían las piezas, se probaban para comprobar su correcto funcionamiento, se tuvo que reimprimir otra pieza, la cabeza, pues era muy pesada con la primera configuración a causa de un error en los ajustes del relleno, haciendo que fuese una pieza demasiado maciza que generaba demasiado estrés en el resto de la estructura, perdiendo 7 horas y 70 gr de material.
Las piezas finales tuvieron que ser limadas cuidadosamente para evitar colisiones que bloqueaban el correcto movimiento del brazo.
Puesto que se disponía de material de impresión limitado, a causa de los fallos, no se pudieron imprimir todos los modelos 3D que completaban a GLaDOS, teniendo que simular el funcionamiento de la mitad superior y la base mediante dos escuadras y una placa de madera contrachapada.
A la hora de mover los distintos servos del brazo, se tuvieron que probar uno a uno pues tenían límites de movimiento distintos (a causa de la naturaleza de las piezas) y de velocidad, pues la unión de el cuello con el resto del brazo y de la base con este era únicamente el propio servo con su brazo(este último unido a la pieza con tornillos), lo que hacía en determinadas velocidades los servos se desprendiesen de sus brazos rompiendo la estructura del brazo mecánico.
Puesto que la base fue hecha mediante una placa de contrachapado, en la que se hizo un agujero por la que colgaba el brazo y que se utilizaría para hacer que este pudiera girar, uniendo el servo a un techo de cartón. A causa del cartón y el peso de GLaDOS, el techo cedía, hundiéndose y provocando demasiado rozamiento para la fuerza limitada del servo de la base. Mediante el uso de cinta de carrocero y celo, se redujo el rozamiento entre la base de GLaDOS y la placa de contrachapado. No obstante, el techo seguía hundiéndose, por lo que se tuvieron que poner 3 columnas de cartón que solucionan este problema. Se sujetó el servo superior con cinta americana y dos placas de madera para evitar que este rompiera el cartón al girar.
5.2. Funcionamiento inadecuado de algunos componentes.
Se comprobó que, en ciertas pruebas, algunas veces ciertos componentes funcionaban de la manera esperada, y otras veces, esos mismos componentes, sin cambiar nada, dejaban de funcionar. Después de medir con un polímetro la corriente circulante dentro de un mismo cable, se llegó a la conclusión de que había alguno de ellos en los que la corriente no circulaba. Por ello, se optó por sustituir todos los cables por nuevo cable trenzado, ahorrándonos de esa forma posibles y futuros problemas. Sin embargo, este comportamiento errático prosiguió. Por lo tanto, se decidió sustituir también la placa base de conexiones. De esa forma, se arregló dicho problema.
Por otro lado, a lo largo de la realización de las pautas finales del proyecto, la pantalla LCD comenzó a sufrir continuos fallos, mostrando caracteres sin sentido alguno. Se piensa que ocurrió debido a que la pantalla tenía bajadas de tensión, ocasionando ruido, por lo que mostraba caracteres de manera errática. Se soluciona cambiando de nuevo todos los cables, organizando, y agrupándolos adecuadamente, a la vez que se fijó la pantalla para evitar que tuviese que moverse, posibilitando que algún cable se soltase.
5.3. Fallos de componentes de última hora.
Por otra parte, el día antes de la presentación, el módulo Bluetooth que se estaba utilizando dejó de funcionar repentinamente, se desconoce la causa aunque podría haber sido por un golpe. Por este motivo se tuvo que comprar otro módulo.
5.4. Incompatibilidad de librerías “servo.h” y “TMRpcm.h”
Debido a un problema encontrado debido a la compatibilidad entre del audio y el uso de los servos. Por un lado, la librería normal de los servos (“servo.h”), y la librería de audio (“TMRpcm.h”), usaban ambos el mismo Timer. Por lo tanto, al intentar usar ambos, el código colapsaba, y ninguno de los dos componentes llegaba a funcionar. Para solucionar este problema, se re-implementaron los servos usando la librería “ServoTimer2.h”.
6. Resultado final / casos de uso
Se ha logrado desarrollar de forma satisfactoria una ‘GLaDOS’ con las funciones deseadas. Estas funcionalidades son:
- Mostrar y configurar la hora.
- Configurar una alarma.
- Reproducir audio y música.
- Realizar movimientos de un brazo mecánico con cuatro ejes.
7.Código
#include <SoftwareSerial.h>
//Pantalla lcd
#include <LiquidCrystal.h>
LiquidCrystal lcd(A5, A4, A3, A2, A1, A0); //Pines pantalla LCD
//SD y audio
#include <SD.h>
#include <TMRpcm.h> //Reproducir asincronamente .wav
#include <SPI.h> //Librería SPI
#include "ServoTimer2.h"
#define SD_ChipSelectPin 4 //Pin SD
//Gestor del audio
TMRpcm audio;
//Servos
ServoTimer2 myservo;
ServoTimer2 myservo2;
ServoTimer2 myservo3;
ServoTimer2 myservo4;
int pos = 0; //Variables para control de servos
int pos2 = 0;
int pos3 = 0;
int pos4 = 0;
const int ledPIN = 9;
//Bluetooth
SoftwareSerial BTSerial(2, 3); //Pines bluetooth
bool mHour, mMusic, mGlados, mAlarm, mMenu; //Modos
int hour, minute, second; //Contador de hora
int aHour, aMinute; //Contador alarma
int newHour, newMinute; //Variables auxiliares para actualizar las horas y los minutos
bool settedAlarm, alarmIsOn; //Banderas para controlar la alarma
bool movementFinished; //Bandera para asegurar el funcionamiento del modo de movimiento
bool musicModed; //Bandera para asegurar que no suene dos veces el audio de entrada a un modo
int currentSong; //Indice de can
void setup() {
Serial.begin(9600);
BTSerial.begin(9600);
myservo.attach(5);
myservo2.attach(6);
myservo3.attach(7);
myservo4.attach(8);
pinMode(ledPIN, OUTPUT); //definir pin como salida
lcd.begin(16, 2);
mHour = false;
mMusic = false;
mAlarm = false;
mGlados = false;
mMenu = true;
settedAlarm = false;
movementFinished = true;
musicModed = false;
newHour = 0;
hour = 0;
aHour = 0;
minute = 0;
aMinute = 0;
second = 0;
currentSong = 0;
//Sd card/audio player
audio.speakerPin = 10;
if (!SD.begin(SD_ChipSelectPin)) //Returns 1 if the card is present
{
Serial.println("SD fail");
return;
} else {
Serial.println("SD complete");
}
Serial.println("Avances");
audio.setVolume(5);
menu();
saludo();
}
void vertical() {
digitalWrite(ledPIN, LOW);
for (pos3 = 2250; pos3 >= 1500; pos3 -= 8) {
myservo3.write(pos3);
delay(10);
}
for (pos3 = 1500; pos3 <= 2250; pos3 += 8) {
// in steps of 1 degree
myservo3.write(pos3);
delay(10);
}
}
void bailar() {
for (pos4 = 1500; pos4 <= 2083; pos4 += 8) {
myservo4.write(pos4);
delay(10);
}
vertical();
for (pos2 = 917; pos2 <= 1208; pos2 += 8) {
// in steps of 1 degree
myservo2.write(pos2);
delay(10);
}
for (pos = 2450; pos >= 1666; pos -= 8) {
myservo.write(pos);
delay(14);
}
for (pos = 1666; pos <= 2450; pos += 8) {
// in steps of 1 degree
myservo.write(pos);
delay(14);
}
for (pos4 = 2083; pos4 >= 750; pos4 -= 8) {
myservo4.write(pos4);
delay(10);
}
vertical();
digitalWrite(ledPIN, LOW);
for (pos2 = 1208; pos2 >= 750; pos2 -= 8) {
myservo2.write(pos2);
delay(10);
}
for (pos4 = 750; pos4 <= 1500; pos4 += 8) {
myservo4.write(pos4);
delay(10);
}
digitalWrite(ledPIN, HIGH);
for (pos = 2450; pos >= 1666; pos -= 8) {
myservo.write(pos);
delay(10);
}
for (pos = 1666; pos <= 2450; pos += 8) {
myservo.write(pos);
delay(10);
}
digitalWrite(ledPIN, HIGH);
}
void triste() {
for (pos3 = 2250; pos3 >= 1416; pos3 -= 8) {
myservo3.write(pos3);
delay(20);
}
for (pos = 2450; pos >= 1666; pos -= 8) {
myservo.write(pos);
delay(20);
}
delay(1000);
for (pos3 = 1416; pos3 <= 2250; pos3 += 8) {
myservo3.write(pos3);
delay(10);
}
for (pos = 1666; pos <= 2450; pos += 8) {
myservo.write(pos);
delay(10);
}
}
void negar() {
for (pos = 2450; pos >= 1916; pos -= 8) {
myservo.write(pos);
delay(10);
}
for (pos2 = 917; pos2 <= 1208; pos2 += 8) {
myservo2.write(pos2);
delay(10);
}
for (pos2 = 1208; pos2 >= 750; pos2 -= 8) {
myservo2.write(pos2);
delay(10);
}
for (pos2 = 750; pos2 <= 917; pos2 += 8) {
myservo2.write(pos2);
delay(10);
}
delay(100);
for (pos = 1666; pos <= 2450; pos += 8) {
myservo.write(pos);
delay(10);
}
}
void asentir() {
for (pos = 2450; pos >= 1666; pos -= 8) {
myservo.write(pos);
delay(10);
}
for (pos = 1666; pos <= 2450; pos += 8) {
myservo.write(pos);
delay(10);
}
}
void saludo() {
audio.play("Saludo.wav");
for (pos4 = 1500; pos4 <= 2250; pos4 += 8) {
myservo4.write(pos4);
delay(10);
}
for (pos4 = 2250; pos4 >= 750; pos4 -= 8) {
myservo4.write(pos4);
delay(10);
}
for (pos4 = 750; pos4 <= 1500; pos4 += 8) {
myservo4.write(pos4);
delay(10);
}
for (pos3 = 1500; pos3 <= 2250; pos3 += 8) {
myservo3.write(pos3);
delay(10);
}
for (pos2 = 750; pos2 <= 1208; pos2 += 8) {
myservo2.write(pos2);
delay(10);
}
for (pos2 = 1208; pos2 >= 750; pos2 -= 8) {
myservo2.write(pos2);
delay(10);
}
for (pos2 = 750; pos2 <= 917; pos2 += 8) {
myservo2.write(pos2);
delay(10);
}
delay(100);
digitalWrite(ledPIN, HIGH);
for (pos = 1666; pos <= 2450; pos += 8) {
myservo.write(pos);
delay(10);
}
}
/////////////////////////////////////////////////////////
////////////////******MODOS DE GLADOS****////////////////
/////////////////////////////////////////////////////////
//------------------------------------------------------
//----------------MOVIMIENTOS DE GLADOS-----------------
//------------------------------------------------------
void gladosMode() {
char leer = BTSerial.read();
lcd.clear();
lcd.setCursor(2, 0);
lcd.print("1.Yes 2.No");
lcd.setCursor(1, 1);
lcd.print("3.Dance 4.Sad");
switch (leer) {
case '1':
movementFinished = false;
audio.play("Yes.wav");
asentir();
movementFinished = true;
break;
case '2':
movementFinished = false;
audio.play("No.wav");
negar();
movementFinished = true;
break;
case '3':
movementFinished = false;
audio.play("Dance.wav");
bailar();
movementFinished = true;
break;
case '4':
movementFinished = false;
audio.play("Sad.wav");
triste();
movementFinished = true;
break;
case 'e':
if (movementFinished) {
mGlados = false;
mMenu = true;
musicModed = false;
}
break;
}
}
//------------------------------------------------------
//--------------------MODO DE MUSICA--------------------
//------------------------------------------------------
void musicMode() {
lcd.clear();
lcd.setCursor(1, 0);
lcd.print("1.Play 2.Stop");
lcd.setCursor(1, 1);
lcd.print("3.Next 4.Prev");
char leer = BTSerial.read();
switch (leer) {
case '1':
selectSong(currentSong);
break;
case '2':
audio.stopPlayback();
break;
case '3':
currentSong++;
if (currentSong > 5) {
currentSong = 0;
}
selectSong(currentSong);
break;
case '4':
currentSong--;
if (currentSong < 0) {
currentSong = 4;
}
selectSong(currentSong);
break;
case 'e':
mMusic = false;
mMenu = true;
musicModed = false;
break;
}
}
//------------------------------------------------------
//-------------------MODO DE ALARMA---------------------
//------------------------------------------------------
void alarmMode() {
char leer = BTSerial.read();
if (leer == 'h') { //+h
newHour++;
if (newHour > 23) {
newHour = 0;
}
} else if (leer == 'g') { //-h
newHour--;
if (newHour < 0) {
newHour = 23;
}
} else if (leer == 'm') { //+m
newMinute++;
if (newMinute > 59) {
newMinute = 0;
}
} else if (leer == 'n') { //-m
newMinute--;
if (newMinute < 0) {
newMinute = 59;
}
} else if (leer == 'e') {
aHour = newHour;
aMinute = newMinute;
newHour = 0;
newMinute = 0;
mAlarm = false;
settedAlarm = true;
mMenu = true;
musicModed = false;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("h/g=+/-hour->");
lcd.setCursor(14, 0);
lcd.print(newHour);
lcd.setCursor(0, 1);
lcd.print("m/n=+/-min->");
lcd.setCursor(13, 1);
lcd.print(newMinute);
Serial.println(newHour);
}
//------------------------------------------------------
//--------------------MODO DE HORA----------------------
//------------------------------------------------------
void hourMode() {
char leer = BTSerial.read();
if (leer == 'h') { //+h
newHour++;
if (newHour > 23) {
newHour = 0;
}
} else if (leer == 'g') { //-h
newHour--;
if (newHour < 0) {
newHour = 23;
}
} else if (leer == 'm') { //+m
newMinute++;
if (newMinute > 59) {
newMinute = 0;
}
} else if (leer == 'n') { //-m
newMinute--;
if (newMinute < 0) {
newMinute = 59;
}
} else if (leer == 'e') {
hour = newHour;
minute = newMinute;
newHour = 0;
newMinute = 0;
mHour = false;
mMenu = true;
musicModed = false;
}
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("h/g=+/-hour->");
lcd.setCursor(14, 0);
lcd.print(newHour);
lcd.setCursor(0, 1);
lcd.print("m/n=+/-min->");
lcd.setCursor(13, 1);
lcd.print(newMinute);
Serial.println(newHour);
}
/////////////////////////////////////////////////////////
////////////////******MOSTRAR MENU******/////////////////
/////////////////////////////////////////////////////////
void menu() {
lcd.setCursor(0, 0);
lcd.print("1.Hour 2.Glados");
lcd.setCursor(0, 1);
lcd.print("3.Alarm 4.Music");
}
/////////////////////////////////////////////////////////
///////////////******ACTUALIZAR HORA*****////////////////
/////////////////////////////////////////////////////////
void updateTime() {
if (second >= 60) {
minute += 1;
second -= 60;
}
if (minute >= 60) {
hour += 1;
minute -= 60;
}
if (hour >= 24) {
hour -= 24;
minute = 0;
second = 0;
}
}
/////////////////////////////////////////////////////////
/////////////////******MOSTRAR HORA*****/////////////////
/////////////////////////////////////////////////////////
void showHour() {
updateTime();
lcd.setCursor(0, 0);
lcd.print("1. Menu:");
lcd.setCursor(3, 1);
if (hour <= 9) {
lcd.println(hour);
lcd.setCursor(4, 1);
lcd.println(" ");
} else {
lcd.println(hour);
}
lcd.setCursor(5, 1);
lcd.print(":");
lcd.print(minute);
lcd.setCursor(8, 1);
lcd.print(":");
lcd.print(second);
second += 1;
delay(1000);
lcd.clear();
}
/////////////////////////////////////////////////////////
/////////////******SELECCIONAR CANCION*****//////////////
/////////////////////////////////////////////////////////
void selectSong(int thisSong) {
switch (thisSong) {
case 0:
audio.play("Portal.wav");
break;
case 1:
audio.play("Rosas.wav");
break;
case 2:
audio.play("Aurora.wav");
break;
case 3:
audio.play("AllStars.wav");
break;
case 4:
audio.play("IRWTSAYH.wav");
break;
}
}
void loop() {
if (settedAlarm == true && aHour == hour && aMinute == minute) {
alarmIsOn = true;
audio.play("Alarm.wav");
}
if (alarmIsOn == true) {
settedAlarm = false;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("ALARM! ALARM!");
lcd.setCursor(0, 1);
lcd.print("Push E to stop.");
char leer = BTSerial.read();
if (leer == 'e') {
alarmIsOn = false;
mMenu = true;
audio.stopPlayback();
}
}
if (mMenu == true && alarmIsOn == false) {
showHour();
if (BTSerial.available()) {
char confirmMenu = BTSerial.read();
if (confirmMenu == '1') {
mMenu = false;
menu();
confirmMenu == '0';
}
}
} else {
if (BTSerial.available()) {
if (mHour == false && mGlados == false && mAlarm == false && mMusic == false && alarmIsOn == false) { //LEER DATO, CASO BASE
char Dato = BTSerial.read();
switch (Dato) {
case '1':
mHour = true;
break;
case '2':
mGlados = true;
break;
case '3':
mAlarm = true;
break;
case '4':
mMusic = true;
break;
case 'm':
audio.play("Mondongo.wav");
break;
}
}
if (mHour == true) {
if (musicModed == false) {
musicModed = true;
audio.play("Hour.wav");
}
hourMode();
}
if (mAlarm == true) {
if (musicModed == false) {
musicModed = true;
audio.play("mAlarm.wav");
}
alarmMode();
}
if (mMusic == true) {
if (musicModed == false) {
musicModed = true;
audio.play("Music.wav");
}
musicMode();
}
if (mGlados == true) {
if (musicModed == false) {
musicModed = true;
audio.play("Glados.wav");
}
gladosMode();
}
}
if (Serial.available()) {
BTSerial.write(Serial.read());
}
}
}