Está en la página 1de 28

d

Sistemas Empotrados y de Tiempo Real Curso 2011/2012

PROYECTO FINAL: JUEGO CONTROLADO POR VOLANTE

Marta Aguado Díaz Yolanda García Sánchez Sara Martínez Herrera

ÍNDICE

1. DESCRIPCIÓN DEL PROYECTO...................................................................1 2. MATERIAL UTILIZADO Y PRESUPUESTO ...................................................2 3. IMPLEMENTACIÓN ..................................................................................3 4. DIAGRAMAS DEFINITIVOS .......................................................................7 5. HOJAS DE CARACTERÍSTICAS DE COMPONENTES ....................................9 6. CÓDIGO.................................................................................................12 7. CONCLUSIÓN ......................................................................................... 26 - Dificultades surgidas ...................................................................... 26 - Tiempo dedicado al proyecto ......................................................... 26 - Opinión personal ........................................................................... 26

que pierde en el caso de chocar contra un obstáculo. DESCRIPCIÓN DEL PROYECTO Este proyecto consiste en un mando. Cuando pierda todas las vidas. deberá reiniciar el juego. en forma de volante. pero en este caso con un sólo coche que debe avanzar por una pantalla. En realidad. el usuario puede girar hacia la derecha o la izquierda con el movimiento del mando. En principio el usuario dispone de un determinado número de vidas. es el entorno del vehículo el que se mueve hacia abajo en la pantalla.1. A continuación se presenta un esquema con las pantallas del juego y las posibilidades que tiene el usuario: 1 . con el que controlar un sencillo videojuego de conducción. Dicho juego será semejante a la prueba que hay que superar para el examen psicotécnico del carnet de conducir.

resistencias de 10K 50 € aprox. (proporcionado) Material para el volante: funda de volante de coche con soporte y caja 5.00 € Kit Arduino UNO: cables jumper. botones push. Materiales de unión: cinta..00 € aprox.2. protoboard. clavos. Se disponía de ellos TOTAL 66.95 € Sensor digital tilt x2 7. pegamento.. plastilina. MATERIAL UTILIZADO Y PRESUPUESTO Componente Imagen Precio Shaftless Vibration Motor 4.95€ 2 ..

De esta forma. Como se puede observar en el esquema del siguiente apartado. 3 . la implementación hardware está basada en el uso de sensores tilt. Figura 1: resultado final del volante Por otro lado. este proyecto consiste en un volante al que se ha adherido una caja con todas las partes utilizadas (placa Arduino. constan de tres conexiones: a un pin digital. cuando el volante se gira hacia la derecha. pasando de apagado a encendido y viceversa si el sensor alcanza determinado ángulo. el sensor colocado en ese lado emite una señal digital de activación. placa de conexión y componentes). IMPLEMENTACIÓN A nivel externo. Se consideran equivalentes a botones push pero con un mecanismo de activación diferentes: son una versión de los interruptores de mercurio. tal que contienen una bola mecánica en el interior que conecta dos pines. colocados verticalmente a ambos lados de la mencionada caja.3. componentes que pueden detectar la inclinación de un objeto. que se leerá mediante Arduino y después se tratará para mover en consecuencia el vehículo que se maneja. con la necesaria salida para el puerto USB. Para el volante objeto de nuestro proyecto se han utilizado dos de estos sensores digitales. a voltaje y a tierra.

cuando el jugador está situado en un menú. Subir y Bajar. conectando el pin a 5 voltios. Los pulsadores o switches conectan dos puntos de un circuito al ser pulsados. de forma que el pin está conectado a tierra (a través de la resistencia pulldown) y se lee LOW (bajo ó 0). una flecha se mueve como corresponda para mostrar cuál será la opción elegida si se pulsa el botón de Play/Pause. se establece la unión entre sus dos extremos. utilizados para manejar el juego. montados con su correspondiente resistencia. Estos botones corresponden a funciones de Play/Pause. de forma que. de 10K Ohmios. por lo que se lee HIGH (alto ó 1).Figura 2: sensores tilt El volante dispone de 3 botones push. 4 . Cuando el botón se cierra (pulsado). si pulsa los botones de subir o bajar. Cuando el pulsador está abierto (sin pulsar) no hay conexión entre las dos patas del pulsador.

de forma que cuando dicho pin se active. se obtiene una vibración nada despreciable. como elemento de salida se ha incluido un pequeño motor vibrador (motor de DC). Figura 4: motor vibrador Como se ha ido comentando anteriormente. en el que el volante mueva el vehículo que guía el juego (representado como un círculo). 5 . se necesita relacionar todos estos componentes para ofrecer la interfaz de un juego. Se ha utilizado para transmitir una vibración por el volante cuando se produce una colisión. conectado a un pin digital y a tierra. generando así mayor sensación de realismo al reproducir un choque. haya interacción con los menús mediante los botones y las colisiones produzcan la vibración del volante.Figura 3: interacción de los menús Además.

mediante un código numérico: se puede enviar del 1 al 6 (49 a 53 en ASCII. El caso del vibrador es muy similar. por ejemplo.También es posible manejar texto mediante el comando text(). En primer lugar se debe cargar la imagen con loadImage(nombre) y después simplemente mostrarla donde se desee. el código de Processing consta de una función setup(). Se ha intentado diseñar un juego agradable y sencillo de comprender. un entorno con lenguaje de programación propio que permite crear imágenes.Para ello se ha utilizado Processing. y un bucle draw() que se repite constantemente. tal que de forma sencilla se puede declarar. Esta estrategia se ha utilizado tanto con los dos sensores como con los botones. al leer en Processing) y se tratará de los sensores derecho e izquierdo y los botones de Bajar.Para dar mayor dinamismo y que cada partida sea diferente. configurando la posición (x.list()[0]. Además. la posición en el eje x de los obstáculos y sus colores son aleatorios. y). . con image(nombre.Processing permite mostrar y mover imágenes en formato jpg o png (los menús. de forma análoga a lo que ocurre con Arduino. proporcionando las coordenadas. por ejemplo. se leen repetidamente mediante Arduino las señales de los puertos a los que se han conectado los sensores tilt. 9600). Serial.read()) e incrementar o decrementar la posición del vehículo. imagen de Game Over. width. 6 . Como se puede observar en el apartado 6. Se debe cargar la fuente que se desee y también se puede decidir el color y la posición. Para mover el vehículo. detalles de las casas y árboles a los lados de la carretera. Dicha posición puede cambiar fácilmente a lo largo del tiempo modificando el valor de las variables x e y. Si uno de ellos se ha activado (siempre que el otro no lo esté. una elipse. que se ejecuta una vez. Permite además una fácil conexión con Arduino mediante el puerto serie (abierto con new Serial(this. se manda por el puerto serie una señal (con Serial. Algunos de los aspectos a destacar son: .println()). Subir y Play/Pause respectivamente. con la sentencia ellipse(x. pero en sentido contrario: es Processing quien detecta una colisión. animaciones e interacciones. se trabaja con coordenadas y píxeles. y. x.y) y sus dimensiones ancho y alto. símbolos de las vidas. activando la señal del pin correspondiente.…). Se ha utilizado en los créditos finales. La parte de Processing es capaz de leer esa señal del puerto (con puerto. escribirá un 6 en el puerto y Arduino lo leerá. . para evitar inconsistencias). weight).

y se han intercalado sonidos de colisiones. DIAGRAMAS DEFINITIVOS A continuación se presenta el diagrama definitivo del proyecto con todos los componentes. una explosión cuando se pierden todas las vidas o aplausos al ganar. Figura 5: diagrama de componentes 7 . equivalente a la foto. que luego se pueden manejar con las funciones play() o pause(). 4.Se ha utilizado la librería para sonido Minim. que permite crear variables de tipo AudioPlayer.. En este juego existe una canción principal que suena constantemente.

Figura 6: montaje final de todos los componentes 8 .

Motor vibrador Figura 7: hoja de características del motor vibrador 9 . HOJAS DE CARACTERÍSTICAS DE COMPONENTES .5.

.Sensor tilt 10 .

Figura 8: hoja de características del sensor tilt 11 .

// botón arriba const int buttonAbajo = 4. pinMode(buttonPin. } sensor2State = digitalRead(buttonPin2). // botón abajo const int ledPin = 13. Sara Martínez Herrera. } void loop(){ //Leer la señal de los sensores. URJC.6. Si están activados.println(0). int int int int int sensor1State=0. if (sensor1State == HIGH && sensor2State == LOW) { Serial. pinMode(buttonPin2. sensor2State=0. buttonStateAbajo=0. void setup() { Serial. // the number of the LED pin const int pinVibrador = 11. // Sensor derecho const int buttonPin2 = 3. buttonStateOk=0. if (sensor2State == HIGH && sensor1State == LOW) { 12 . buttonStateArriba=0.INPUT).begin(9600). //Sensor izquierdo const int buttonOK = 6. // botón de OK const int buttonArriba = 5. pinMode(pinVibrador. Yolanda García Sánchez.println(1). CÓDIGO Arduino: /* Proyecto Final Sistemas Empotrados: Crash Road. sensor1State = digitalRead(buttonPin). */ const int buttonPin = 2. Marta Aguado Díaz. escribir en el puerto //serie un 1 (sensor derecho) o un 2 (sensor izquierdo).OUTPUT). } else { Serial. Grado Ingeniería Informática. INPUT).

println(0). rec = Serial. } else { Serial.available()>0){ int rec = 0.println(4). buttonStateAbajo = digitalRead(buttonAbajo). 4 y 5. digitalWrite(pinVibrador. } } } 13 . } buttonStateOk = digitalRead(buttonOK). if (rec== 6){ digitalWrite(pinVibrador.Serial.println(3). //boton OK => 53 } //Si se ha recibido señal de que hay colisión (6 por el puerto). HIGH).println(5). delay(1000). escribir en el puerto serie 3. if (buttonStateAbajo == HIGH) { Serial.read(). LOW). if(Serial. if (buttonStateArriba == HIGH) { Serial. Abajo y Play/Pause. } buttonStateArriba = digitalRead(buttonArriba). if (buttonStateOk == HIGH) { Serial. vibrar un segundo.println(2). } //Para los botones de Arriba.

25. float[] xs = new float[MAX_CUAD]. int int int int int int vida1X vida1Y vida2X vida2Y vida3X vida3Y = = = = = = 25. Grado Ingeniería Informática. Marta Aguado Díaz. //Obstáculos. casita. Yolanda García Sánchez.Processing: /* Proyecto Final Sistemas Empotrados: Crash Road.*. int y3=55. Serial puerto. int[] lineas = new int[10]. //Posición del coche. import ddf. int vidas = 3. URJC.*. toPlay. */ import processing. //Imágenes utilizadas. float[] colors = new float[MAX_CUAD*3]. vida. meta. 10. Sara Martínez Herrera. 14 . int v = 300. int val3 = 0. 65. pause.serial.minim. gameOver. //Triángulo de menú principal. int x1=340. //Para leer datos del puerto serie int val = 0. int y1=70. int MAX_CUAD = 40. instrucciones. 25. arbol. PImage menu. //Puerto serie para comunicación arduino-processing. int ALTO = 600. 120. int ANCHO = 600. int val2 = 0. int y2=40. int x3=300. int x2= 340. int[] ys = new int[MAX_CUAD].

int xCreditos = 130. boolean over = false. int xCasa = 29. int xArbol = 20. int colisionado = -1. //Fuentes para los créditos. int yCasa2 = -200. int yMeta = -40. //Posiciones iniciales de créditos y meta. boolean jugar = false. int x22 = 420. PFont fuente. boolean inicio = true. boolean parar = false. int yCasa5 = -2300. String creditos = " Pulsa para volver a jugar\n\n\n\n\n\n\n\n\n\n\n\n" + " Marta Aguado Díaz\n\n" + " Yolanda García Sánchez\n\n" + " Sara Martínez Herrera\n\n". int yCasa7 = -3100. int y11 = 55. //Posiciones iniciales detalles. int x33 = 380. int yWinner = -200. boolean ganado = false. int yCasa = 200. int xCasa2 = 520. int y22 = 25. boolean repetido = false. int xWinner = 100. int yCasa6 = -2500. fuenteGrande. int y33 = 40.//Triángulo del menú Pause int x11 = 420. boolean instrucc = false. int yCreditos = -1550. 15 . boolean menu1 = true. int yCasa4 = -1650. boolean puertoAbierto = false. int yCasa3 = -900.

yArbol2 = -400.loadFile("colision. aplausos.loadFile("aplausos. colision2 = minim. -3100. explosion = minim. -2300. ALTO).mp3"). yArbol4 = -2900. yArbol3 = -1300. //Música y efectos. void setup() { //Restablecer valores iniciales. 25. yArbol3 = -1300. vida1X vida1Y vida2X vida2Y vida3X vida3Y = = = = = = 25. -200. colision2.loadFile ("crash0. 25. minim = new Minim(this). Minim minim. yArbol = 350.mp3"). xArbol = 20. //Música (librería Minim). colision1. cancion = minim. yArbol4 = -2900. 120. xCasa2 yCasa2 yCasa3 yCasa4 yCasa5 yCasa6 yCasa7 = = = = = = = 520. AudioPlayer cancion.int int int int int yArbol = -100. xArbol2 = 520. vidas = 3. explosion. colision1 = minim.mp3").loadFile("explo.mp3"). aplausos = minim. yCasa = 200. xArbol2 = 520. -900.mp3"). xCasa = 29. 65. 16 . yArbol2 = -400. 10. -2500.loadFile("colision. size(ANCHO. -1650.

pause = loadImage("FondoPAUSE. G y B para cada uno de los cuadrados colors[k] = random(255).play().. -150. 120.. for (int i=0.png"). //-50. instrucciones = loadImage("instr. puertoAbierto = true.png"). //random(370) devuelve valores entre 0 y 370 => entre 130 y 470 } for (int j=0.i++) { //Coordenadas x aleatorias xs[i] = random(340) + 130. nombrePuerto. arbol = loadImage("arbol.if (!puertoAbierto) { //Solo abrir una vez (por si se vuelve a empezar).j<MAX_CUAD. -250.. fuenteGrande = loadFont("FuenteGrande. vida = loadImage("Vida1. gameOver = loadImage("gameOver.j++) {//Coordenadas y fijas (salen poco a poco). fuente = loadFont("Fuente.png").. //-20.vlw"). } //Cargar fuentes e imágenes. } cancion.png"). menu = loadImage("FondoS2. puerto = new Serial(this.k++) { //R. 50. //Obstáculos..n++) { //Coordenada y de las líneas discontinuas. String nombrePuerto = Serial. k<MAX_CUAD*3.png").jpg"). } for (int n=0. toPlay= loadImage("FondoS. lineas[n] = 70*n +20. } //setup 17 .n<10. casita = loadImage ("casita.i<MAX_CUAD.vlw"). } for (int k = 0.jpg").jpg"). meta = loadImage("meta. ys[j] = -100*j-50.jpg").list()[0].. 9600).

x3=500.available () > 0) { val = puerto.void draw() { image(toPlay. y1=70. } if (val==53) { // pulsado OK menu1=true. image (menu. 0). 18 . y2=85. } if ( (y3==55) && (val==51)) { //De Jugar a Instrucciones x1=540. } else if ( (y3==100) && (val==51)) { //De Instrucciones a Salir x1=340. } if (!inicio) { if (!repetido) { delay(10). y1=115. } else if ((y3==145) && (val==52)) { //De Salir a Instrucciones x1=540. x2=540. y1=160. y3=100. //Primer menú while (puerto. inicio=false. y2=130. while (puerto.available () > 0) { val = puerto. } else if ((y3==100) && (val==52)) { //De Instrucciones a Jugar x1=340.read(). y3=145. x2=540. y1=115. x3=300. 0. x2=340. 0. y2=85. x3=500.read(). 0). y3=100.

700). 100. } else if ( (y3==55) && (val==53)) { //Pulsado Jugar menu1 = false. -10.p<10. rect(500. x3=1000.//Vuelve a aparecer por arriba } } 19 . background(78). y2=1000. strokeWeight(1). 700). } else if ((y3==145) && (val==53)) { //Pulsado Salir exit(). rectMode(CORNER). x2=1000. fill(0. 255. y3). } else if ((y3==100) && (val==53)) { instrucc = true. y1=1000. jugar = true. y3=55. if (lineas[p] == 620) { //Si toca el borde inferior lineas[p] = -10. fill(255. 180. } if (jugar) { //Apartar el triángulo x1=1000. rect(-10.x2=340. y2=40. 100).p++) { //líneas discontinuas. x2. y1. y3=1000. //Lados de la carretera. x3. x3=300. } strokeWeight(3). for (int p = 0. y2. 255). rectMode(CENTER). 110. triangle(x1. -10.

image(arbol. yArbol = yArbol+2. // Incremento de la posición del coche. //Mostrar casas. for (int m=0. yCasa = yCasa+2. vida1X. image(casita. image(casita. vida3X.m++) { //Mover líneas discontinuas. yCasa3). xArbol2. yCasa4 = yCasa4+2. } // Leer sensores if (val == 49) { //Detectado sensor derecho v2 = 10. yArbol3). m<10.n++) { //Pintar líneas discontinuas rect(300. image(casita. yCasa2). yCasa6 = yCasa6+2. lineas[n]. image(vida. yCasa2 = yCasa2+2. yCasa5). yCasa4). xCasa2. parar juego. yArbol4). vida3Y). image(arbol. image(vida. } //Mover casas y árboles. yArbol2). } else v2 = 0. n<10.read(). image(vida. lineas[m] = lineas[m] + 2. } else if (val ==50) { v2 = -10. xCasa2. //Mover coche //Detectado sensor izqdo 20 . image(arbol. yArbol). image(casita. xCasa. árboles y vidas. yArbol2 = yArbol2+2. xArbol2. int v2 = 0. yCasa3 = yCasa3+2. yArbol4 = yArbol4+2. 20. yArbol3 = yArbol3+2. yCasa5 = yCasa5+2. xCasa2.available () > 0) { val = puerto. image(casita. image(arbol. vida2Y). vida2X.if (!parar) { // Si está en pause. while (puerto. xArbol. v= v + v2. yCasa). } for (int n=0. vida1Y). xCasa2. 40). xArbol2.

ganado = true. colors[i+1]. //Solo una colisión por cuadrado. 21 . 100. fill(colors[i]. text (" WINNER". //Mover meta y créditos. } for (int j=0. 204). rect(xs[i]. for (int i =0. yCreditos = yCreditos + 2. yMeta). v=120. 40). fill(255. xWinner. textFont(fuenteGrande).j++) { //Colisiones if (( ((v+20>=xs[j]-25) && (v+20<=xs[j])) || ((v-20<=xs[j]+ 25) && (v-20>=xs[j])) || ((v+20<=xs[j]+25) && (v-20>=xs[j]-25)) || (((xs[j]+25)-(v+20)<25) && (v+20<xs[j]+25) ) || ( ((v-20)-(xs[j]-25)<25) && (v-20>xs[j]-25) ) ) && (ys[j]<=500)&& (ys[j]>=450) && colisionado != j) { colisionado = j. // Color coche if (v<=120) //No sobrepasar bordes de carretera. yWinner = yWinner + 2. ys[i]. 102. colision(). yMeta = yMeta + 2. image (meta. 475. i<MAX_CUAD. i<MAX_CUAD. yWinner). i++) { //Mover obstáculos ys[i]= ys[i]+2. if (ganado) { //Se ha acabado ganando. 255. i++) { //Asignar colores aleatorios y pintar obstáculos. 50). 40. ellipse(v.fill(0. colors[i+2]). } } //if (!parar). } } if (ys[MAX_CUAD-1] > 515 && vidas>0) { //Último obstáculo sobrepasa coche. //Pintar coche rectMode(CENTER). } for (int i =0.j<MAX_CUAD. if (v>=480) v=480. 50. 255).

text(creditos. 85. menu1 = false. 130.available () > 0) { val2 = puerto. yCreditos). 100. 0. } } // Leer sensores if (parar) { //Mostrando menú Pause inicio = false. if ((y33==40) && (val==51)) { //De Continuar a Volver a Empezar x11 y11 x22 y22 x33 y33 } else if ((y33==85) && (val==51)) { //De Volver a Empezar a Intrucciones x11 y11 x22 y22 x33 y33 } else if ((y33==130) && (val==51)) { //De Instrucciones a Salir = = = = = = 515. 515. image(pause.play().read(). } } while (puerto. 550. = = = = = = 590.available () > 0) { val3 = puerto. if (yMeta==ALTO) { //Sonido final. 590. println(val3).textFont(fuente). aplausos. if (val2==53) { //Pulsado ok parar = true. 475. 22 . 0). } */ //Mover triángulo o actuar según dato del puerto. 70. /* while (puerto. xCreditos. 115. 145.read().

320. } else if ((y33==40)&&(val==53)) { //Continuar image(pause. 40. 55. = = = = = = 590. else if ((y33==175) && (val==52)) { //De Salir a Instrucciones x11 y11 x22 y22 x33 y33 } else if ((y33==130) && (val==52)) { //De Instrucciones a Volver a Empezar x11 y11 x22 y22 x33 y33 } else if ((y33==85) && (val==52)) { //De Volver a Empezar a Continuar x11 y11 x22 y22 x33 y33 } else if ((y33==175) && (val==53)) { //Salir exit(). } else if ((y33==130) && (val==53)) { //Instrucciones instrucc=true. 100. 85. 550. = = = = = = 515. 1000. 1000). 190. 175.x11 y11 x22 y22 x33 y33 } = = = = = = 320. 130. } else if ((y33==85) && (val==53)) { //Volver a Empezar = = = = = = 420. 515. 25. 160. 280. 380. 420. 115. 590. 145. parar = false. 475. 70. 23 .

x22. y2=40. 255). setup(). 0. 800. image(gameOver. setup(). parar = false.cancion. } //if parar if (over && !parar) { //En el menú gameOver. if (val==53) { //Si se pulsa ok . y11. parar = false. 255.pause(). ganado = false. } //Pintar triángulo de los menús. y33). 255). draw(). x3=300. x33. cancion. x1=340. y22.pause(). 616). explosion.volver a menú inicial. strokeWeight(1). 255. repetido = true. inicio = false. -50. ganado = false. inicio = false. fill(255. y3=55. over = false. } } 24 . jugar = true.play(). menu1 = false. strokeWeight(3). y1=70. triangle(x11. x2=340. fill(255. menu1 = true.

1000). } } } } //draw void colision() { if (vidas==3) { //Se tenían todas las vidas. vidas = 2. 1000.write(6). over = true. vida3X=ANCHO*2. } else if (vidas==1) { vidas = 0.available ()>0) { val3 = puerto. } } 25 . vida3Y=ALTO*2.play(). 0). colision1. } else if (vidas==2) { vidas = 1.} //if (jugar) } //if !inicio if (instrucc) { image(instrucciones.write(6). vida1X=ANCHO*2.write(6). puerto. puerto. vida2Y=ALTO*2. vida1Y=ALTO*2. colision2. vida2X=ANCHO*2. while (puerto.play(). 0.read(). puerto. instrucc = false. if (val3==53) { //Pulsado ok image(instrucciones.

No resulto fácil. pero los resultados no eran relevantes así que se ha eliminado para limpieza del código. la más importante ha sido el Bluetooth. la motivación del equipo ha sido alta.Tiempo dedicado al proyecto: El proyecto comenzó el día 5 de Marzo y ha finalizado el 23 de Abril. ya que se trata de dos entornos completamente nuevos y que suponen una forma de trabajar muy distinta. por otra parte. con el programa principal dentro siempre de un bucle. CONCLUSIÓN . el total de horas dedicadas se estima que ha sido más de 50. . Contando con las horas de clase y el tiempo empleado fuera horario lectivo. es el “ruido” y los rebotes al pulsar los botones. es fácilmente adaptable a otros juegos de manejo de vehículos. y a problemas surgidos con las pilas y con el ordenador. sin embargo. trabajar con la implementación del código. manejar código y realizar un montaje físico. el resultado de este proyecto ha sido satisfactorio. Además.Opinión personal: En definitiva. en carreras y/o juegos en 3D. por ejemplo. se retiró del proyecto.Dificultades surgidas: En cuanto a las dificultades halladas a lo largo del proyecto. 26 . Creemos que muestra la capacidad de Arduino para crear proyectos divertidos y funcionales con pocos componentes y un presupuesto asequible. También hubo dificultades a la hora de lograr que el motor de vibración se activara al chocar contra los obstáculos. especialmente de Processing. que ha permitido trabajar con elementos tanto de entrada como de salida. Otro de los grandes conflictos. que dificultan el perfecto funcionamiento de los menús. . Inicialmente el mando debía comunicarse de manera inalámbrica con el ordenador mediante un Shield Bluetooth. Se intentó implementar un algoritmo anti-rebotes (basado en la técnica debounce). a pesar del problema con la conexión Bluetooth. que aún no se ha resuelto por completo.7. Hay que tener en cuenta que las dificultades señaladas en el apartado anterior y que los componentes llegaron más tarde de lo previsto. Es un proyecto bastante completo. pero debido a la falta de código.