miércoles, 26 de octubre de 2016

Spider-Man: The Video Game

Corría aproximadamente el año 1991 cuando la compañía Sega nos obsequió con un videojuego para los salones recreativos basado en los súper héroes de Marvel.

El objetivo de la partida es recuperar un extraño y misterioso artefacto del que se ha apoderado Kingpin. La misión no será nada sencilla pero para ello podremos manejar a cuatro valientes personajes: Spider-man, Namor, la Gata Negra y Ojo de Halcón.

Mamporros a diestro y siniestro

El juego es un beat'em (o brawler) en el que hasta cuatro jugadores pueden participar de forma simultánea repartiendo golpes.
Además posee la particularidad de que cuenta con fases de plataformas en 2D. La transición entre las fases en "3D" y las de plataformas se realiza con un zoom marca de la casa y de la placa System 32 en la que corre el programa (ya que los sprites en las zonas de lucha son mucho más grande que en las zonas de plataformeo).

En el apartamento de Kingpin
A lo largo de las fases nos encontraremos con una gran cantidad de secuaces que intentarán que no lleguemos a nuestro destino. Desgraciadamente existen pocos enemigos diferentes siendo la repetición excesiva.
Los súper villanos de final de fase aportan mayor variedad al producto. Deberemos enfrentarnos a Kingpin, Venom, Doctor Octopus, Electro, el Lagarto, Scorpion, el Hombre de Arena, el Duente Verde y hasta al mismísimo Doctor Muerte.
Peleando contra el Duende Verde

En todo momento se intenta representar una estética estilo cómic. Al recibir golpes los personajes a veces muestran sus expresiones mediante bocadillos. También aparecen las típicas onomatopeyas de los tebeos representando sonidos.

Ojo de Halcón junto a Spiderman

El juego no aporta nada nuevo al genero que no se haya visto ya. Cada jugador cuenta con una barra de energía representada por un valor numérico que irá mermando ante los ataques de nuestros rivales. Los enemigos normales no muestran esa información en pantalla así que no podremos hacernos una idea de cuántos golpes quedan para su derrota. En cambio nuestros rivales de final de fase si mostrarán su energía en la parte superior de la zona de juego.
El Doctor Octopus hace acto de presencia

A nivel técnico el juego no tiene mucho que destacar más allá del efecto zoom antes comentado. Los sprites son grandes y se muestra una cifra más que correcta de enemigos en pantalla (incluso jugando a cuatro jugadores) sin que se muestren ralentizaciones.
Sin embargo las animaciones tienen escasos cuadros de animación y los fondos de pantalla además de ser muy sencillos no tienen ningún efecto de scroll parallax.
Venom haciendo de las suyas.

La mayor virtud de este juego es sin duda la diversión que ofrece jugando cuatro personas a la vez junto con el carisma de los héroes.

En la entrada de un peligroso castillo
Enemigos de colores.
Enfrentándonos al Doctor Muerte.
La Gata Negra acompañando a Spidey.



miércoles, 13 de julio de 2016

Cuentavueltas para Slot con Arduino


El proyecto

Vamos a realizar un contador de vueltas para un circuito de slot de 4 carriles.
Se tratará de un contador de vueltas digital construido partiendo de la base de una placa Arduino Mega 2560

Slot Counter 1.0
Slot Counter 1.0

Al final del artículos podéis encontrar el archivo sketch de Arduino , las librerías modificadas y el fichero Fritzing.

Características

  • Pantalla de test en la que se comprobará el funcionamiento del sistema.
  • Posibilidad de elegir entre Modo Carrera y Modo Entrenamiento
  • El número de vueltas de carrerá será configurable. Por defecto 20.
  • Antes de la salida se mostrará la vuelta más rápida y la temperatura.
  • Se realizará una cuenta atrás antes de la salida señalizándola con led rojos y verdes junto con el display de siete segmentos.
  • Se tendrá en cuenta la salida adelantada (o nula) antes de tiempo.
  • La pantalla mostrará el número de vueltas restantes (o realizadas) y el tiempo de la última vuelta de cada jugador.
  • Cada vez que un jugador pase por línea de meta se indicará con un sonido y el encendido de un led azul. Además el display de 7 segmentos mostrará el número del último jugador que ha pasado por meta.
  • Cuando algún jugador se encuentre en su última vuelta se encenderá un led amarillo.
  • Si algún jugador hace un tiempo mejor que el récord de vuelta se almacenará en memoria
  • El sistema es controlable desde los botones y desde un mando a distancia.

Seleccione número de vueltas
Seleccione número de vueltas

Lista de Componentes


  • 1 Placa Arduino Mega 2560 (Rev3)
  • 2 Displays de 7 segmentos(para representar unidades, decenas y también letras)
  • 4 Pulsadores
  • 4 Pulsadores D3C Omrom
  • 1 Piezo (para reproducir sonidos)
  • 1 Pantalla LCD de 4x20 con interfaz de conexión
  • 3 Leds rojos
  • 3 Leds verdes
  • 1 Led azul
  • 1 Led amarillo
  • 5 Resistencias de 220Ω
  • 1 Sensor de Temperatura LM35
  • 1 Receptor de infrarrojos
  • 1 Mando a distancia
  • 1 Cable USB de alimentación
  • 1 Transformador de móvil
  • 1 Montón de cables de colores
  • 1 Placa de prueba


Esquema Cuentavueltas
Esquema Cuentavueltas

El diseño del programa


El programa implementa una máquina de estados en el que cada uno representa una información en el display.  Cada estado se corresponde con una determinada función:

  • init() --> Inicia los valores de los pines y los objetos que serán manejador por la aplicación
  • test() --> Realiza una prueba de los diferentes elementos del sistema.
  • menu() --> Menú principal de la aplicación
  • seleccionNumVueltas() --> Pantalla para elegir el número de vueltas
  • carrera() --> Muestra el número de vueltas y tiempo en el display.
  • ganador()--> Calcula el ganador de una carrera (o ganadores en caso de empate)
Es posible moverse entre los estados test, menu, seleccionNumVueltas y carrera pulsando los botones [IZQUIERDA] y [DERECHA]

El estado al arrancar la placa es init y al terminar se pasa al estado menu. Para acceder al estado test se pulsa izquierda y para ir al estado seleccionNumVueltas se pulsa derecha.

Menú principal
Menú principal

Notas sobre el código


El programa está pensado para funcionar en una placa Arduino Mega 2560 dado que hace uso de las siguientes peculiaridades de la placa:
  1. Tamaño de memoria RAM y de Memoria de Programa
  2. Utiliza 4 interrupciones externas
  3. Utiliza la interrupción interna 6 para el control del tono.
Si se desea utilizar en otras placas habrá que adaptar tanto el programa como las librerías ya que el tema del sonido es dependiente de los temporizadores.

Se ha comentado todo el programa para que sea lo más didáctico posible. No obstante vamos a ver algunos puntos a modo de resumen:
  • Se utiliza CONST siempre que sea posible. El compilador sustituye el texto de la variable por su valor númerico antes de compilar, ahorrando memoria RAM.
  • Se  utiliza la macro F() para el manejo de cadenas constantes. Esto hace que el programa busque las cadenas en la Memoria de Programa (Flash) con lo que se ahorra memoria RAM.
  • Se utilizan los tipos de memoria más pequeños (p.ej: byte) de nuevo con el fin de ahorrar memoria RAM.
  • NO SE UTILIZA la clase String para el manejo de cadenas dinámicas. La clase String de Arduino es una fuente de problemas de memoria (fragmentación y liberación). En su lugar se utilizan una matriz como cadena vacía, otra matriz representando una línea del display y las funciones de copia de C (strcpy... etc)
  • Existen dos objetos que chocan por usar los mismos temporizadores de Arduino: Tone y RMTControl. El problema es que utilizan el TIMER 2. Se ha utilizado una librería Tone que no es la que viene por defecto en el entorno y se ha modificado para que no use el TIMER 2 si hay otros displonibles.
  • Para evitar el rebote (bounce) en los pulsadores se ha introducido el control por tiempo en el momento de comprobar la pulsación de un botón. No se ha utilizado la librería debounce para no aumentar el tamaño del programa. La solución por hardware de usar contensadores y resistencias creando un filtro atenúa el problema pero no lo solucionan con lo cual no es una solución válida. La única solución hardware posible es incluir un biestable Flip-Flop RS. Es algo muy barato y que Arduino debería incorporar ya de seríe activándolo con algo así:

                                                      pinMode (14, flipflop)
  • Aunque se deshabiliten las funciones de interrupción las interrupciones se siguen quedando en cola y llamarán a las funciones de interrupción en cuanto se habiliten de nuevo. En vez de usar la solución de llamar a una función de interrupción vacía el programa accede a bajo nivel para borrar la cola de peticiones de interrupción.
  • El control del paso por meta de los coches se usa a través de las 4 líneas de interrupción externas. Ésto es así porque no podemos perder el paso por meta bajo ningún concepto. Cuando un coche pasa por la línea de salida se debe aumentar su número de vueltas, poner a cero su temporizador, comprobar si ha ganado la carrera, encender un led azul y apagarlo, emitir un sonido, escribir su número en el Display de 7 segmentos y encender un led amarillo si es la última vuelta. Todo esto lleva tiempo de cálculo y durante el proceso puede suceder que otro coche pase por la línea de meta. Como las interrupciones se encolan el paso del otro coche también será procesado después de acabar el proceso del primero.
En plena carrera
En plena carrera

Notas sobre el hardware


Ruido electromágnetico


El principal problema y más importante fue debido al ruido electromagnético que provocan los motores de los coches. Las interferencias eran tales que se marcaban falsos pasos por linea de meta constantemente.

Se probó a colocar condensadores entre las patillas de los motores de los coches sin obtener mejora alguna.

Se colocaron condensadores y resistencias creando un filtro en las entradas de los pines utilizados para las interrupciones externas con poca o nula mejora.

Finalmente se utilizó un cable paralelo de impresora blindado. Tanto el blindaje como los cables no utilizados se unieron todos a tierra.

Consumo eléctrico


Debido a la cantidad de componentes no es posible, por ejemplo, encender todos los leds a la vez
aunque en la ejecución normal del programa nunca se da ese caso.




Código Fuente



/*

    Slot Counter v 1.0

    Contador de vueltas de 4 carriles.

    Escrito para RetroPasado

*/

//Librerías:

//#include 

#include 

//Manejo de IR (infrarrojos - mando a distancia)
#include 

//Tone
#include 

//Para poder manejar dispositivos I2C (el Display)
#include 

//Librería para el Display
#include 

//Para poder manejar dispositivos OneWire (el Sensor)
#include 

//Librería para el Sensor
#include 

//Para manejar la EEPROM
#include 



/* HARDWARE - PINES
Se asignan con define para ahorrar memoria y porque su
asignación no es conflictiva. Para el resto de variables
estáticas se utilizará const
*/

#define LEDROJO1 10
#define LEDROJO2 9
#define LEDROJO3 8
#define LEDAZUL 13
#define LEDSVERDES 12
#define LEDAMARILLO 46
#define BTN_IZQUIERDA 7
#define BTN_DERECHA 4
#define BTN_ARRIBA 6
#define BTN_ABAJO 5

#define INT_CARRIL0 2
#define INT_CARRIL1 3
#define INT_CARRIL2 19
#define INT_CARRIL3 18


#define ZUMBADOR 11

#define SENSOR_TEMPERATURA 31

#define IRPIN 48   // pin al que conectamos el receptor de IR

#define IR_ARRIBA 3994140416
#define IR_ABAJO 3860446976
#define IR_IZQUIERDA 3944005376
#define IR_DERECHA 3910582016

#define IR_0 4060987136
#define IR_1 4278238976
#define IR_2 4261527296
#define IR_3 4244815616
#define IR_4 4211392256
#define IR_5 4194680576
#define IR_6 4177968896
#define IR_7 4144545536
#define IR_8 4127833856
#define IR_9 4111122176

#define IR_OK 3927293696
#define IR_ASTERISCO 4077698816
#define IR ALMOHADILLA 4044275456

#define DECENAS 1
#define UNIDADES 0
#define  J 10
#define  E 11
#define  OFF 12 
#define  C 13
#define  A 14
#define  E 15
#define  N 16
#define  P 17
#define  R 18
  


OneWire oneWire(SENSOR_TEMPERATURA);
DallasTemperature sensors(&oneWire);

LiquidCrystal_I2C lcd(0x27, 20, 4);

NECIRrcv ir(IRPIN) ;
//decode_results results;




/* SOFTWARE */
unsigned long codigo_ir = 0; //Código leido por el sensor IR

Tone tone1;

const unsigned long TIEMPO_ENTRE_PULSACIONES_LARGO = 1000; //0.50s
const unsigned long TIEMPO_ENTRE_PULSACIONES_CORTO = 50; //0.050s
const long unsigned TIEMPO_ENTRE_PULSACIONES = TIEMPO_ENTRE_PULSACIONES_LARGO;

//Dado que el procesador es de 8 bits se utilizan tipos
//de 8 bits para ahorrar memoria y mejorar rendimiento

//Definición de los estados (pantallas) posibles
const byte ESTADO_PRUEBA_SISTEMA  = 0;
const byte ESTADO_SELECCION_NUM_VUELTAS = 1;
const byte ESTADO_PREPARANDO_SALIDA     = 2;
const byte ESTADO_SALIDA_NULA           = 3;
const byte ESTADO_CARRERA               = 4;
const byte ESTADO_GANADOR               = 5;
const byte ESTADO_MENU                = 6;

//Estado actual en que se encuentra el sistema
byte estadoActual = ESTADO_MENU;

//Flag de salida nula
volatile boolean salidaNula = false;

//Flag indicando  modo Carrera o modo Entrenamiento
boolean modoCarrera = true;

//Opción seleccionada del menú
int posMenu = 0;

//Número de vueltas por defecto
const int NUM_VUELTAS_DEFECTO = 20;

//Número de vueltas seleccionadas desde el menú
int numVueltas = NUM_VUELTAS_DEFECTO;

//Número de vueltas que lleva cada jugador
volatile int numVueltasJugador[4] = { -1, -1, -1, -1};

//Tiempo que lleva en la vuelta actual de cada jugador
volatile unsigned long tiempoVueltaJugador[4] = {0, 0, 0, 0};

//Instante en el que se inició la carrera
unsigned long tiempoInicioCarrera = 0;


//Tiempo de la vuelta más rápida (modo Carrera)
const unsigned long HORA = 3600000;

//Es necesario declarar como volatile las variables
//que se modifican desde una función de interrupción
volatile unsigned long mejorTiempoVuelta = HORA;

//Tiempo de la vuelta más rápida (modo Entrenamiento)
volatile unsigned long mejorTiempoVueltaEntrenamientoJugador[4] = {HORA, HORA, HORA, HORA};


//Tiempo que hace que se pulsó cada botón
//para evitar dobles pulsaciones y falsas lecturas
volatile unsigned long tiempoBtnIzquierda = 0;
volatile unsigned long tiempoBtnDerecha = 0;
volatile unsigned long tiempoBtnArriba = 0;
volatile unsigned long tiempoBtnAbajo = 0;


//Redefinición de caracteres para
//números grandes en el LCD
//Cada caracter es una matriz de 8x5 pixels
byte arribaIzq[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B00011,
  B01111,
  B01111,
  B11111
};
byte arribaCentro[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B11111,
  B11111,
  B11111,
  B11111
};

byte arribaDer[8] = {
  B00000,
  B00000,
  B00000,
  B00000,
  B11000,
  B11110,
  B11110,
  B11111
};

byte abajoIzq[8] = {
  B11111,
  B01111,
  B01111,
  B00011,
  B00000,
  B00000,
  B00000,
  B00000
};
byte abajoCentro[8] = {
  B11111,
  B11111,
  B11111,
  B11111,
  B00000,
  B00000,
  B00000,
  B00000
};

byte abajoDer[8] = {
  B11111,
  B11110,
  B11110,
  B11000,
  B00000,
  B00000,
  B00000,
  B00000
};

byte uTilde[8] = {
  B00010,
  B00100,
  B01000,
  B00000,
  B10001,
  B10001,
  B10001,
  B01110
};

byte interrogacion[8] = {
  B00100,
  B00100,
  B00000,
  B00100,
  B01000,
  B10001,
  B10001,
  B01110
};

//Usando los caracteres anteriores
//se definen los número del 0 al 9
//Cada uno ocupa 4x3 caracteres.
byte conjuntoCaracteres[10][4][3] = {
  { // Número 0
    {0, 1, 2},
    {255, 16, 255},
    {255, 16, 255},
    {3, 4, 5}
  },
  { // Número 1
    {16, 16, 2},
    {16, 16, 255},
    {16, 16, 255},
    {16, 16, 5}
  },
  { // Número 2
    {0, 1, 2},
    {0, 1, 255},
    {255, 16, 16},
    {3, 4, 5}
  },
  { // Número 3
    {0, 1, 2},
    {0, 1, 255},
    {3, 4, 255},
    {3, 4, 5}
  },
  { // Número 4
    {0, 16, 2},
    {255, 16, 255},
    {3, 4, 255},
    {16, 16, 5}
  },
  { // Número 5
    {0, 1, 2},
    {255, 1, 2},
    {16, 16, 255},
    {3, 4, 5}
  },
  { // Número 6
    {0, 1, 2},
    {255, 1, 2},
    {255, 16, 255},
    {3, 4, 5}
  },
  { // Número 7
    {0, 1, 2},
    {16, 1, 255},
    {16, 4, 255},
    {16, 16, 5}
  },
  { // Número 8
    {0, 1, 2},
    {255, 1, 255},
    {255, 4, 255},
    {3, 4, 5}
  },
  { // Número 9
    {0, 1, 2},
    {255, 16, 255},
    {3, 4, 255},
    {16, 16, 5}
  }

};




//Para pintar en pantalla
char strLineaBuff[21]; //Representa una línea del display (20 caracteres)
// + el caracter null de fin de línea \n
const char strLimpia[1] = {""}; // 2 posiciones: cadena vacía 
//+ caracter null de fin de línea \n



// Se definen los segmentos que se encienden y se apagan
// para cada uno de los números además de las letras J y E y otro
// con todos los segmentos apagados
// Cada número se compone de los segmentos: A B C D E F G
// Arduino pin: 32,33,34,35,36,37,38
//              39,40,41,42,43,44,45
const byte conjuntoDigitos[19][7] = { 
  { 0, 0, 0, 0, 0, 0, 1 }, // 0
  { 1, 0, 0, 1, 1, 1, 1 }, // 1
  { 0, 0, 1, 0, 0, 1, 0 }, // 2
  { 0, 0, 0, 0, 1, 1, 0 }, // 3
  { 1, 0, 0, 1, 1, 0, 0 }, // 4
  { 0, 1, 0, 0, 1, 0, 0 }, // 5
  { 0, 1, 0, 0, 0, 0, 0 }, // 6
  { 0, 0, 0, 1, 1, 1, 1 }, // 7
  { 0, 0, 0, 0, 0, 0, 0 }, // 8
  { 0, 0, 0, 1, 1, 0, 0 }, // 9
  { 1, 0, 0, 0, 0, 1, 1 }, // J --> 10
  { 0, 1, 1, 0, 0, 0, 0 }, // E --> 11
  { 1, 1, 1, 1, 1, 1, 1 }, // OFF --> 12 
  { 0, 1, 1, 0, 0, 0, 1 }, // C --> 13
  { 0, 0, 0, 1, 0, 0, 0 }, // A --> 14
  { 0, 1, 1, 0, 0, 0, 0 }, // E --> 15
  { 1, 1, 0, 1, 0, 1, 0 }, // n --> 16
  { 0, 0, 1, 1, 0, 0, 0 }, // P --> 17
  { 1, 1, 1, 1, 0, 1, 0 }  // r --> 18
};



/* FIN DE SOFTWARE - FIN DE DEFINICIÓN DE VARIABLES*/

/*
    Se ejecuta al encender Arduino
    Labores de comprobación:
    Muestra mensajes en el LCD
    Lee desde la EEPROM
    Encidence los leds
    Hace sonar el zumbador
*/
void setup() {

  //Serial.begin(9600);
  
  lcd.init ();
  //lcd.begin(20,4);
  lcd.backlight();

  //La macro F() permite que una cadena se guarde en la memoria Flash (PROGMEM)
  //y no consuma RAM. Sólo determinados métodos pueden leer el tipo de la cadena devuelta.
  lcd.print(F(" Slot Counter v1.0"));

  lcd.setCursor ( 0, 2 );
  lcd.print (F("Circuito 4 carriles"));

  sensors.begin();

ir.begin();

tone1.begin(ZUMBADOR);

  //irrecv.enableIRIn(); // Start the receiver
 
  //lcd.setCursor ( 0, 3 );
  //lcd.print ("(C) 2014 David Diaz");

  //Los pins de entrada se configuran como PULL UP (resistencia con +5v) ya que
  //si se configuran como PULL DOWN (resistencia con tierra) al pasar los coches
  //cerca de los interruptores se producen falsan lecturas debido a las interferencias
  //eléctricas que provocan.

  //Iniciación de los pins que manejan las interrupciones
  //Iniciación de los pins de entrada
  pinMode(INT_CARRIL0, INPUT_PULLUP);//2
  pinMode(INT_CARRIL1, INPUT_PULLUP);//3
  pinMode(INT_CARRIL2, INPUT_PULLUP);//19
  pinMode(INT_CARRIL3, INPUT_PULLUP);//18

  //Iniciación de los pins de entrada
  pinMode(BTN_IZQUIERDA, c);
  pinMode(BTN_DERECHA, INPUT_PULLUP);
  pinMode(BTN_ARRIBA, INPUT_PULLUP);
  pinMode(BTN_ABAJO, INPUT_PULLUP);

  //Iniciación de los pins de salida
  pinMode(LEDROJO1, OUTPUT);
  pinMode(LEDROJO2, OUTPUT);
  pinMode(LEDROJO3, OUTPUT);
  pinMode(LEDAZUL , OUTPUT);
  pinMode(LEDSVERDES, OUTPUT);
  pinMode(LEDAMARILLO, OUTPUT);
  pinMode(ZUMBADOR, OUTPUT);


  //Display de 7 segmentos 2 Digitos

  pinMode(32, OUTPUT);
  pinMode(33, OUTPUT);
  pinMode(34, OUTPUT);
  pinMode(35, OUTPUT);
  pinMode(36, OUTPUT);
  pinMode(37, OUTPUT);
  pinMode(38, OUTPUT);

  pinMode(39, OUTPUT);
  pinMode(40, OUTPUT);
  pinMode(41, OUTPUT);
  pinMode(42, OUTPUT);
  pinMode(43, OUTPUT);
  pinMode(44, OUTPUT);
  pinMode(45, OUTPUT);


  //Se introducen en la memoria del display LCD
  //los nuevos caracteres para números grandes
  lcd.createChar(0, arribaIzq);
  lcd.createChar(1, arribaCentro);
  lcd.createChar(2, arribaDer);
  lcd.createChar(3, abajoIzq);
  lcd.createChar(4, abajoCentro);
  lcd.createChar(5, abajoDer);

  lcd.createChar(6, uTilde);
  lcd.createChar(7, interrogacion);

  delay(5000);
}


/*
    Bucle de ejecución de Arduino
    alterna entre los diferentes
    estados (pantallas) de la aplicación
*/
void loop() {

  switch (estadoActual) {

    case ESTADO_PRUEBA_SISTEMA:
      estadoPruebaSistema();
      break;

    case ESTADO_MENU:
      estadoMenu();
      break;

    case ESTADO_SELECCION_NUM_VUELTAS :
      estadoSeleccionNumVueltas();
      break;

    case ESTADO_PREPARANDO_SALIDA:
      estadoPreparandoSalida();
      break;

    case ESTADO_CARRERA:
      estadoCarrera();
      break;

    case ESTADO_GANADOR:
      estadoGanador();
      break;

  }

}


void estadoPruebaSistema() {

  lcd.clear();
  lcd.print(F("  PRUEBA DE SISTEMA"));
  lcd.setCursor ( 0, 3 );
  lcd.print(F("Temp: "));
  sensors.requestTemperatures();
  lcd.print((char)223);
  lcd.print(sensors.getTempCByIndex(0));
  delay (3000);

  lcd.setCursor ( 0, 3 );
  lcd.print(F("Caracteres: "));
  lcd.write((uint8_t)0);
  lcd.write(1);
  lcd.write(2);
  lcd.write(3);
  lcd.write(4);
  lcd.write(5);
  lcd.write(6);
  lcd.write(7);
  delay (3000);

  lcd.setCursor ( 0, 3 );
  lcd.print (F("Encendido Leds      "));
  digitalWrite(LEDSVERDES, HIGH);
  delay (2000);
  digitalWrite(LEDROJO1, HIGH);
  delay (2000);
  digitalWrite(LEDROJO2, HIGH);
  delay (2000);
  digitalWrite(LEDROJO3, HIGH);
  delay (2000);
  //digitalWrite(LEDSVERDES, HIGH);
  //delay (2000);
  digitalWrite(LEDAZUL, HIGH);
  delay (2000);
  digitalWrite(LEDAMARILLO, HIGH);
  delay (2000);

  lcd.setCursor ( 0, 3 );
  lcd.print (F("Sonido Zumbador    "));
  tone1.play(NOTE_B3, 100);
  tone1.play(NOTE_G4, 200);
  delay (3000);

  //Si se pulsa el botón izquierda se borra la EEPROM
  if (digitalRead(BTN_IZQUIERDA) == LOW) {
    EEPROM.write(0, 0);
    lcd.print (F("  Record Borrado   "));
    delay (3000);
    mejorTiempoVuelta = EEPROM.read(0);
    strFormatoTiempo(mejorTiempoVuelta);
    delay (3000);
  }

  lcd.print (F("Lectura EEPROM     "));
  lcd.print (EEPROM.read(0));

  lcd.setCursor ( 0, 3 );
  lcd.print (F("Display 7 seg      "));

  for (byte contador = 0; contador < 100; contador++) {

    delay(100);
    escribirSieteSegmentos(contador / 10, DECENAS);
    escribirSieteSegmentos(contador % 10, UNIDADES);
    
  }

  delay(4000);


  estadoActual = ESTADO_MENU;
}


/*
    Muestra en pantalla el menú del sistema.
    Interfaz:
    Botón arriba -> Subir opción menú
    Botón abajo  -> Bajar opción menú
    Botón derecha-> Aceptar
*/
void estadoMenu() {

  //Reinicio de variables
  numVueltasJugador[0] = -1;
  numVueltasJugador[1] = -1;
  numVueltasJugador[2] = -1;
  numVueltasJugador[3] = -1;

  //Tiempo que lleva en la vuelta actual de cada jugador
  tiempoVueltaJugador[0] = 0;
  tiempoVueltaJugador[1] = 0;
  tiempoVueltaJugador[2] = 0;
  tiempoVueltaJugador[3] = 0;

  //Mejores tiempos de vuelta para test
  mejorTiempoVueltaEntrenamientoJugador[0] = HORA;
  mejorTiempoVueltaEntrenamientoJugador[1] = HORA;
  mejorTiempoVueltaEntrenamientoJugador[2] = HORA;
  mejorTiempoVueltaEntrenamientoJugador[3] = HORA;


  //Apagado de LEDS y 7 Segmentos
  escribirSieteSegmentos(OFF, DECENAS);
  escribirSieteSegmentos(OFF, UNIDADES);

  digitalWrite(LEDROJO1, LOW);
  digitalWrite(LEDROJO2, LOW);
  digitalWrite(LEDROJO3, LOW);
  digitalWrite(LEDAZUL, LOW);
  digitalWrite(LEDSVERDES, LOW);
  digitalWrite(LEDAMARILLO, LOW);

  lcd.clear();
  lcd.setCursor ( 3, 0 );
  lcd.print(F("   CARRERA"));
  lcd.setCursor ( 3, 1 );
  lcd.print(F("ENTRENAMIENTO"));
  lcd.setCursor ( 3, 2 );
  lcd.print(F("    PRUEBA"));

  lcd.setCursor ( 0, posMenu );
  lcd.print(F("*"));
  lcd.setCursor ( 19, posMenu );
  lcd.print(F("*"));

  escribirSieteSegmentosPosMenu();
  
  while (true) {
  
    leerIr();
   
    if ( (tiempo(tiempoBtnAbajo) && digitalRead(BTN_ABAJO) == LOW) 
|| codigo_ir == IR_ABAJO){

      //Serial.println(F("ABAJO"));
      
      
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_LARGO;

      tiempoBtnAbajo = millis();

      lcd.setCursor ( 0, posMenu );
      lcd.print(F(" "));
      lcd.setCursor ( 19, posMenu );
      lcd.print(F(" "));

      posMenu++;



      if (posMenu == 3) {
        posMenu = 0;
      }

      lcd.setCursor ( 0, posMenu );
      lcd.print(F("*"));
      lcd.setCursor ( 19, posMenu );
      lcd.print(F("*"));
      
      escribirSieteSegmentosPosMenu();
      
      tone1.play(NOTE_G3, 100);

    }


    if ( (tiempo(tiempoBtnArriba) && digitalRead(BTN_ARRIBA) == LOW ) 
|| codigo_ir == IR_ARRIBA ){

      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_LARGO;

     //Serial.println(F("ARRIBA"));
 
      tiempoBtnArriba = millis();

      lcd.setCursor ( 0, posMenu );
      lcd.print(F(" "));
      lcd.setCursor ( 19, posMenu );
      lcd.print(F(" "));


      escribirSieteSegmentosPosMenu();
      
      posMenu--;

      if (posMenu == -1) {
        posMenu = 2;
      }

      lcd.setCursor ( 0, posMenu );
      lcd.print(F("*"));
      lcd.setCursor ( 19, posMenu );
      lcd.print(F("*"));
      
      escribirSieteSegmentosPosMenu();
         
      tone1.play(NOTE_G3, 100);

    }

    if ( (tiempo(tiempoBtnDerecha) && digitalRead(BTN_DERECHA) == LOW) 
|| codigo_ir == IR_DERECHA || codigo_ir == IR_OK ) {
      
      // Serial.println(F("DERECHA"));
       
       
      tiempoBtnDerecha = millis();

      switch (posMenu) {

        case 0:
          modoCarrera = true;
          numVueltas = NUM_VUELTAS_DEFECTO;
          estadoActual = ESTADO_SELECCION_NUM_VUELTAS;
          break;

        case 1:
          modoCarrera = false;
          numVueltas = 99;
          estadoActual = ESTADO_PREPARANDO_SALIDA;
          break;

        case 2:
          estadoActual = ESTADO_PRUEBA_SISTEMA;
          break;

      }

      tone1.play(NOTE_G3, 100);

      //Scroll de la pantalla hacia la izquierda
      //lcd.autoscroll();
      // for (int positionCounter = 0; positionCounter < 20; positionCounter++) {

      //lcd.scrollDisplayLeft();
      // lcd.print(F(" "));
      //  delay(100);
      //}
      //lcd.noAutoscroll();


      break;

    }



//Sólo para controlar el rebote y pulsación rápida de los botones
    if (tiempoCorto(tiempoBtnAbajo) && digitalRead(BTN_ABAJO) == HIGH) {
      tiempoBtnAbajo = 0;
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_CORTO;
    }

    if (tiempoCorto(tiempoBtnArriba) && digitalRead(BTN_ARRIBA) == HIGH) {
      tiempoBtnArriba = 0;
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_CORTO;
    }

  } //Fin de bucle

}




/*
    Muestra en pantalla el número de vueltas
    de la carrera. Interfaz:
    Botón arriba -> Aumentar número de vueltas
    Botón abajo  -> Disminuir número de vueltas
    Botón derecha-> Aceptar
*/
void estadoSeleccionNumVueltas() {

  escribirSieteSegmentos(OFF, DECENAS);
  escribirSieteSegmentos(OFF, UNIDADES);

  digitalWrite(LEDROJO1, LOW);
  digitalWrite(LEDROJO2, LOW);
  digitalWrite(LEDROJO3, LOW);
  digitalWrite(LEDAZUL, LOW);
  digitalWrite(LEDSVERDES, LOW);


  lcd.clear();
  lcd.print(F("Seleccione"));
  lcd.setCursor ( 0, 1 );
  lcd.print(F("N"));
  lcd.write(6);
  lcd.print(F("mero"));
  lcd.setCursor ( 0, 2 );
  lcd.print(F("De"));
  lcd.setCursor ( 0, 3 );
  lcd.print(F("Vueltas"));

  pintarNumero(numVueltas);

 int numeroPulsado = -1;

  while (true) {

    leerIr();
    
    //Si se ha pulsado un número del mando a distancia:
     switch (codigo_ir){
    
      case IR_0:numeroPulsado=0;break;
      case IR_1:numeroPulsado=1;break;
      case IR_2:numeroPulsado=2;break;
      case IR_3:numeroPulsado=3;break;
      case IR_4:numeroPulsado=4;break;
      case IR_5:numeroPulsado=5;break;
      case IR_6:numeroPulsado=6;break;
      case IR_7:numeroPulsado=7;break;
      case IR_8:numeroPulsado=8;break;
      case IR_9:numeroPulsado=9;break;
      default:numeroPulsado=-1;break;
    
    }
      if (numeroPulsado!=-1){
         //Serial.println(F("PULSADO NUMERO"));
        //Desplazamos el número de la derecha una posición a la izquierda y el
        //número pulsado en el mando a distancia se queda en la derecha.
        numVueltas = (numVueltas % 10) * 10 + numeroPulsado; 
        
        pintarNumero(numVueltas);
        
        
      }


    if ( (tiempo(tiempoBtnAbajo) && digitalRead(BTN_ABAJO) == LOW) 
|| codigo_ir==IR_ABAJO ) {

       //Serial.println(F("ABAJOOOOOOOOOOOOO"));
       
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_LARGO;

      tiempoBtnAbajo = millis();
      numVueltas--;

      if (numVueltas == 0) {
        numVueltas = 99;
      }
      pintarNumero(numVueltas);
    }


    if ( (tiempo(tiempoBtnArriba) && digitalRead(BTN_ARRIBA) == LOW) 
|| codigo_ir==IR_ARRIBA ) {

       //Serial.println(F("ARRIBAAAAAAAAAAAAA"));
       
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_LARGO;

      tiempoBtnArriba = millis();
      numVueltas++;

      if (numVueltas == 100) {
        numVueltas = 1;
      }
      pintarNumero(numVueltas);

    }

    if ( (tiempo(tiempoBtnDerecha) && digitalRead(BTN_DERECHA) == LOW) 
|| codigo_ir==IR_DERECHA || codigo_ir == IR_OK) {
      
       //Serial.println(F("DERECHAAAAAAAAAAA"));
       
       
      tiempoBtnDerecha = millis();
      estadoActual = ESTADO_PREPARANDO_SALIDA;
      tone1.play(NOTE_G3, 100);

      //Scroll de la pantalla hacia la izquierda
      //for (int positionCounter = 0; positionCounter < 20; positionCounter++) {

      //  lcd.scrollDisplayLeft();
      //   delay(150);
      // }


      break;

    }

    //Si se pulsa el botón izquierda se sale
    if ( (tiempo(tiempoBtnIzquierda) && digitalRead(BTN_IZQUIERDA) == LOW) 
|| codigo_ir==IR_IZQUIERDA || codigo_ir == IR_ASTERISCO ) {
      
       //Serial.println(F("IZQUIERDAAAAAAAAAAAAAA"));
       
       
      tiempoBtnIzquierda = millis();
      estadoActual = ESTADO_MENU;
      tone1.play(NOTE_G3, 100);

      //Scroll de la pantalla hacia la derecha
      //for (int positionCounter = 0; positionCounter < 20; positionCounter++) {

      // lcd.scrollDisplayRight();
      // delay(150);
      //}


      break;
    }



//Sólo para botones
    if (tiempoCorto(tiempoBtnAbajo) && digitalRead(BTN_ABAJO) == HIGH) {
      tiempoBtnAbajo = 0;
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_CORTO;
    }

    if (tiempoCorto(tiempoBtnArriba) && digitalRead(BTN_ARRIBA) == HIGH) {
      tiempoBtnArriba = 0;
      //TIEMPO_ENTRE_PULSACIONES=TIEMPO_ENTRE_PULSACIONES_CORTO;
    }

  } //Fin de bucle

}


/*
    Muestra en pantalla la temperatura y el menor tiempo
    de vuelta.
    Se ejcuta una cuenta atrás de semáforo encendiento
    los leds rojos uno por uno y finalmente los tres verdes
    para indicar el comienzo de la carrera.
    Si un jugador sale antes de estar los leds verdes encendidos
    entonces se aborta la carrera (Salida Nula)
*/
void estadoPreparandoSalida() {
  //Serial.println("ANTES a");
  lcd.clear();
  //Serial.println("ANTES b");
  salidaNula = false;
  //Serial.println("ANTES c");
  //interrupts();

  //Se borra el buffer de interrupciones (ya que las interrupciones
  //siempre se encolan aunque no haya función que las escuche)
  //Así se evita que si existen interrupciones pendientes (por ejemplo cuando
  //un coche gana una carrera y los otros coches pasan se generan interrupciones
  //que aunque ya no se tienen en cuenta para contar el número de vueltas 
//se quedan encoladas).
  //Entonces al llegar a este punto las interrupciones encoladas saltaban 
//y llamaban a fnSalidaNula
  //cuando no se había producido ninguna interrupción en este momento.
  //Es necesario por tanto borrar la bandera que indica que se 
//ha producido una interrupción

  EIFR = 0xFF;

  attachInterrupt(0, fnSalidaNula, FALLING );
  attachInterrupt(1, fnSalidaNula, FALLING );
  attachInterrupt(4, fnSalidaNula, FALLING );
  attachInterrupt(5, fnSalidaNula, FALLING );
  //Serial.println("ANTES d");
  sensors.requestTemperatures();
  //  Serial.println("ANTES f");
  lcd.print(F("TEMPERATURA:"));
  lcd.setCursor(13, 0);
  lcd.print(sensors.getTempCByIndex(0));
  lcd.setCursor(23, 0);
  lcd.print((char)223);

  mejorTiempoVuelta = EEPROM.read(0);
  lcd.setCursor ( 0, 1 );
  lcd.print(F("MEJOR TIEMPO: "));
  limpiarBufferLinea();
  strFormatoTiempo(mejorTiempoVuelta);
  lcd.setCursor ( 0, 2 );
  lcd.print(strLineaBuff);

  if (modoCarrera) {
    escribirSieteSegmentos(C, DECENAS);
  } else {
    escribirSieteSegmentos(E, DECENAS);
  }
  while (!salidaNula) {

    delay(3000);

    if (!salidaNula) {

      escribirSieteSegmentos(3, UNIDADES);

      lcd.setCursor ( 0, 3 );
      lcd.print(F(" ! EN SUS PUESTOS ! "));
      digitalWrite(LEDROJO1, HIGH);
      tone1.play(NOTE_B3, 100);
      delay(1000);
    }

    if (!salidaNula) {

      escribirSieteSegmentos(2, UNIDADES);

      lcd.setCursor ( 0, 3 );
      lcd.print(F("  !! PREPARADOS !! "));
      digitalWrite(LEDROJO2, HIGH);
      tone1.play(NOTE_B3, 100);
      delay(1000);
    }


    if (!salidaNula) {

      escribirSieteSegmentos(1, UNIDADES);

      lcd.setCursor ( 0, 3);
      lcd.print(F("   !! ATENTOS !!   "));
      digitalWrite(LEDROJO3, HIGH);
      tone1.play(NOTE_B3, 100);
      delay(1000);
    }


    if (!salidaNula) {

      escribirSieteSegmentos(0, UNIDADES);

      lcd.setCursor ( 0 , 3);
      lcd.print(F("COMIENZO DE CARRERA "));

      digitalWrite(LEDROJO1, LOW);
      digitalWrite(LEDROJO2, LOW);
      digitalWrite(LEDROJO3, LOW);

      digitalWrite(LEDSVERDES, HIGH);
      tone1.play(NOTE_B3, 1000);


      attachInterrupt(0, aumentarVueltaJ0, FALLING );
      attachInterrupt(1, aumentarVueltaJ1, FALLING );
      attachInterrupt(4, aumentarVueltaJ2, FALLING );
      attachInterrupt(5, aumentarVueltaJ3, FALLING );

      tiempoInicioCarrera = millis();

      tiempoVueltaJugador[0] = tiempoInicioCarrera;
      tiempoVueltaJugador[1] = tiempoInicioCarrera;
      tiempoVueltaJugador[2] = tiempoInicioCarrera;
      tiempoVueltaJugador[3] = tiempoInicioCarrera;

      estadoActual = ESTADO_CARRERA;
      break;
    }

  } //Fin de while

  if (salidaNula) {

    lcd.clear();
    lcd.print(F("  !! SALIDA NULA !!"));
    digitalWrite(LEDAZUL, HIGH);
    tone1.play(NOTE_B3, 1000);
    delay(3000);
    digitalWrite(LEDAZUL, LOW);
    digitalWrite(LEDROJO1, LOW);
    digitalWrite(LEDROJO2, LOW);
    digitalWrite(LEDROJO3, LOW);

  }

}

/*
Muestra el texto salida nula y hace sonar
el zumbador
*/
void fnSalidaNula() {

  salidaNula = true;

}

/*
    Muestra en pantalla el número de vueltas de cada jugador
    y el tiempo transcurrido de la vuelta actual.
    Interfaz:
    Botón Izquierda --> Salir (regresa a la pantalla de selección de vueltas)
*/
void estadoCarrera() {

  lcd.clear();

  while (true) {

    leerIr();
    
    //Si se pulsa el botón izquierda se sale
    if ( (tiempo(tiempoBtnIzquierda) && digitalRead(BTN_IZQUIERDA) == LOW) 
|| codigo_ir==IR_IZQUIERDA || codigo_ir == IR_ASTERISCO ) {
      tiempoBtnIzquierda = millis();
      estadoActual = ESTADO_MENU;
      tone1.play(NOTE_B3, 100);
      break;
    }


    //Si algún coche ya tiene el número de vueltas se sale

    if (numVueltasJugador[0] >= numVueltas
        || numVueltasJugador[1] >= numVueltas
        || numVueltasJugador[2] >= numVueltas
        || numVueltasJugador[3] >= numVueltas
       ) {
      estadoActual = ESTADO_GANADOR;
      break;
    }




    //Pintar el tiempo que lleva en esta vuelta cada jugador
    lcd.setCursor ( 0, 0);
    limpiarBufferLinea();
    strObtenerNumVueltas(0);
    
    lcd.print(strLineaBuff);
    lcd.setCursor ( 0, 1 );
    limpiarBufferLinea();
    strObtenerNumVueltas(1);
    
    lcd.print(strLineaBuff);
    lcd.setCursor ( 0, 2 );
    limpiarBufferLinea();
    strObtenerNumVueltas(2);
    
    lcd.print(strLineaBuff);
    lcd.setCursor ( 0, 3 );
    limpiarBufferLinea();
    strObtenerNumVueltas(3);
    
    lcd.print(strLineaBuff);

  }
}

/*
    Muestra en el lcd al ganador/es de la carrera
    Interfaz:
    Botón Derecha --> Salir (regresa a la pantalla de selección de vueltas)

*/
void estadoGanador() {

  digitalWrite(LEDSVERDES, LOW);
  digitalWrite(LEDAZUL, HIGH);
  digitalWrite(LEDAMARILLO, LOW);
  tone1.play(NOTE_B3, 100);
  tone1.play(NOTE_DS4, 100);
  tone1.play(NOTE_G3, 100);

  //Si se deshabilitan todas las interrupciones
  //el display deja de funcionar
  //noInterrupts();

  //Se deshabilitan las interrupciones que se utilizan
  detachInterrupt(0);
  detachInterrupt(1);
  detachInterrupt(4);
  detachInterrupt(5);


  //Tiempo en completar la carrera
  tiempoInicioCarrera = millis();

  //Calcular orden de fin de carrera

  byte idJugadores[] = {0, 1, 2, 3};

  //Pruebas
  // numVueltasJugador[0] = 4;
  //numVueltasJugador[3] = 4;
  //Fin de Pruebas

  //Serial.println("ANTES");
  //Serial.println(numVueltasJugador[0]);
  // Serial.println(numVueltasJugador[1]);
  //Serial.println(numVueltasJugador[2]);
  // Serial.println(numVueltasJugador[3]);

  // Serial.println(idJugadores[0]);
  // Serial.println(idJugadores[1]);
  // Serial.println(idJugadores[2]);
  // Serial.println(idJugadores[3]);
  // Serial.println("FIN DE ANTES");

  bubbleSort(numVueltasJugador, idJugadores);

  //Serial.println("ORDENADO");
  //Serial.println(numVueltasJugador[0]);
  //Serial.println(numVueltasJugador[1]);
  //Serial.println(numVueltasJugador[2]);
  //Serial.println(numVueltasJugador[3]);

  //Serial.println(idJugadores[0]);
  //Serial.println(idJugadores[1]);
  //Serial.println(idJugadores[2]);
  //Serial.println(idJugadores[3]);
  //Serial.println("FIN DE ORDENADO");




  //Presentar los resultados
  lcd.clear();

  limpiarBufferLinea();
  strObtenerGanador(0, idJugadores);
  lcd.print(strLineaBuff);

  lcd.setCursor ( 0, 1 );
  limpiarBufferLinea();
  strObtenerGanador(1, idJugadores);
  lcd.print(strLineaBuff);

  lcd.setCursor ( 0, 2 );
  limpiarBufferLinea();
  strObtenerGanador(2, idJugadores);
  lcd.print(strLineaBuff);

  lcd.setCursor ( 0, 3 );
  limpiarBufferLinea();
  strObtenerGanador(3, idJugadores);
  lcd.print(strLineaBuff);


  while (true) {
    
    leerIr();

    if ( (tiempo(tiempoBtnDerecha) && digitalRead(BTN_DERECHA) == LOW) 
|| codigo_ir==IR_DERECHA || codigo_ir == IR_OK ) {
      tiempoBtnDerecha = millis();
      estadoActual = ESTADO_MENU;
      tone1.play(NOTE_B3, 100);
      break;

    }
  }

}


/*
    Devuelve una cadena con el formato ganador
    de un jugador
*/

void strObtenerGanador(byte numJugador, byte idJugadores[]) {

  char strAux[1]; //2 posiciones. El id del jugador + caracter null de fin de línea \n

  //Ya hemos ordenado los resultados, en la posición 0
  //se encuentra el ganador
  if (numVueltasJugador[0] == numVueltasJugador[numJugador]) {

    strcat(strLineaBuff, "GANADOR: JUGADOR: ");
    strcat(strLineaBuff, itoa(idJugadores[numJugador] + 1, strAux, 10));
    strcat(strLineaBuff, "!");

  }

}
/*
    Devuelve una cadena con el formato del número de vueltas
    de un jugador
*/
void strObtenerNumVueltas(byte numJugador) {

  char strAux[2]; //3 caracteres- 2 números + caracter null de fin de cadena \0

  if (numVueltasJugador[numJugador] == -1) {

    //strcat(strLineaBuff,"J");
    strcat(strLineaBuff, itoa(numJugador + 1, strAux, 10));
    strcat(strLineaBuff, ": NO HA SALIDO");

  } else {

    unsigned long tiempoActual = millis();

    strcat(strLineaBuff, itoa(numJugador + 1, strAux, 10));
    strcat(strLineaBuff, ": ");

    //MODO CARRERA: Se imprime el número de vueltas que quedan para terminar la carrera
    //MODO ENTRENAMIENTO: Se imprime el número de vueltas que se llevan recorridas

    if (modoCarrera) {

      int numVueltasRestantes = numVueltas - numVueltasJugador[numJugador];

      if (numVueltasRestantes < 10) {
        strcat(strLineaBuff, "0");
      }

      strcat(strLineaBuff, itoa(numVueltasRestantes, strAux, 10));

      strcat(strLineaBuff, " - ");
      strFormatoTiempo(tiempoActual - tiempoVueltaJugador[numJugador]);


    } else {

      if (numVueltasJugador[numJugador] < 10) {
        strcat(strLineaBuff, "0");
      }

      strcat(strLineaBuff, itoa(numVueltasJugador[numJugador], strAux, 10));

      strcat(strLineaBuff, " = ");
      strFormatoTiempo(mejorTiempoVueltaEntrenamientoJugador[numJugador]);

    }






    //str = str + numJugador + ": " + numVueltasJugador[numJugador] 
+ " - " + strFormatoTiempo(tiempoActual - tiempoVueltaJugador[numJugador]);

  }


}
/*
    Muestra en el LCD un número de dos cifras
    en tamaño grande (ocupando cuatro filas del display)
*/
void pintarNumero(byte numero) {

  tone1.play(NOTE_B3, 100);

  for (byte contadorCifras = 0; contadorCifras < 2; contadorCifras++) {

    //Ej: numero = 16
    byte cifra;

    if (contadorCifras == 0) {

      cifra = (numero / 10); // 16/10 = 1

    } else {

      cifra = numero % 10; // 16/10 = 1 y sobran 6. Cojo el resto 6.

    }

    //Los números se pintan en la columna 15 del display
    byte columnaInicial = 11 + (contadorCifras * 4);

    for (byte contadorFilas = 0; contadorFilas < 4; contadorFilas++) {

      for (byte contadorColumnas = 0; contadorColumnas < 3; contadorColumnas++) {

        lcd.setCursor(columnaInicial + contadorColumnas, contadorFilas);
        lcd.write((uint8_t)conjuntoCaracteres[cifra][contadorFilas][contadorColumnas]);


      } //Fin de for columnas
    } //Fin de for filas

  }//Fin de for cifras

}


void aumentarVuelta(byte numJugador) {

  digitalWrite(LEDAZUL, HIGH);

  escribirSieteSegmentos(numJugador + 1, UNIDADES);

  if (tiempo(tiempoVueltaJugador[numJugador])) {

    unsigned long time = millis();
    unsigned long tiempoVuelta = time - tiempoVueltaJugador[numJugador];


    if (modoCarrera) {

      almacenarSiEsRecorVuelta(tiempoVuelta);

    } else {

      if (tiempoVuelta < mejorTiempoVueltaEntrenamientoJugador[numJugador]) {
        mejorTiempoVueltaEntrenamientoJugador[numJugador] = tiempoVuelta;
      }
    }

    tiempoVueltaJugador[numJugador] = time;

    numVueltasJugador[numJugador]++;

    tone1.play(NOTE_G3 + (numJugador * 50), 100);


  }

  //Indicador de última vuelta (led Amarillo)
  if ((numVueltasJugador[numJugador] + 1) >= numVueltas) {
    digitalWrite(LEDAMARILLO, HIGH);
  }
  digitalWrite(LEDAZUL, LOW);


}



//Funciones de Interrupción

/*
    Aumenta la cantidad de vueltas del jugador 1
    y graba en la EEPROM si es un record de vuelta
*/
void aumentarVueltaJ0() {
  aumentarVuelta(0);
}

/*
    Aumenta la cantidad de vueltas del jugador 2
    y graba en la EEPROM si es un record de vuelta
*/
void aumentarVueltaJ1() {

  aumentarVuelta(1);
}

/*
    Aumenta la cantidad de vueltas del jugador 3
    y graba en la EEPROM si es un record de vuelta
*/
void aumentarVueltaJ2() {

  aumentarVuelta(2);
}

/*
    Aumenta la cantidad de vueltas del jugador 4
    y graba en la EEPROM si es un record de vuelta
*/
void aumentarVueltaJ3() {

  aumentarVuelta(3);

}

//Fin funciones de interrupción

//Graba en la EEPROM el record de vuelta
void almacenarSiEsRecorVuelta(unsigned long tiempoVuelta) {

  if (tiempoVuelta < mejorTiempoVuelta) {

    tone1.play(NOTE_B2, 100);
    mejorTiempoVuelta = tiempoVuelta;

    EEPROM.write(0, tiempoVuelta);

  }

}

//Ordenación por burbuja inverso (de mayor a menor)
void bubbleSort(volatile int numVueltasJugador[4], byte idJugadores[4]) {

  int temp;
  byte tempidjugador;

  for (int i = 0; i < 4; i++) {

    for (int y = 1; y < 4 - i; y++) {

      if (numVueltasJugador[y - 1] < numVueltasJugador[y]) {

        temp = numVueltasJugador[y - 1];
        numVueltasJugador[y - 1] = numVueltasJugador[y];
        numVueltasJugador[y] = temp;

        tempidjugador = idJugadores[y - 1];
        idJugadores[y - 1] = idJugadores[y];
        idJugadores[y] = tempidjugador;
      }
    }
  }

}

//Comprueba si ha pasado ya el tiempo mínimo
//desde que se pulso un botón para evitar
//dobles pulsaciones y falsas lecturas
bool tiempo(unsigned long tiempo) {

  return (millis() - tiempo) > TIEMPO_ENTRE_PULSACIONES;
}

bool tiempoCorto(unsigned long tiempo) {

  return (millis() - tiempo) > TIEMPO_ENTRE_PULSACIONES_CORTO;
}

//Formatea los milisegundos en 00:00.0
void strFormatoTiempo(unsigned long milisegundos) {

  char strAux[2]; //3 caracteres. El número como máximo tendrá 2 caracteres 
//+ el caracter nulo \0
  long restohoras = milisegundos % 3600000;

  long minutos = restohoras / 60000;
  long restominutos = restohoras % 60000;

  long segundos = restominutos / 1000;
  long restosegundos = restominutos % 10;

  if (minutos < 10) {
    strcat(strLineaBuff, "0");
  }
  strcat(strLineaBuff, itoa(minutos, strAux, 10));
  strcat(strLineaBuff, ":");
  if (segundos < 10) {
    strcat(strLineaBuff, "0");
  }
  strcat(strLineaBuff, itoa(segundos, strAux, 10));
  strcat(strLineaBuff, ".");
 
  strcat(strLineaBuff, itoa(restosegundos, strAux, 10));

}

/*
 Limpia el buffer de una linea del display
 */
void limpiarBufferLinea() {

  strcpy(strLineaBuff, strLimpia);

}


//Escribe un número 0-9 en el Display 7 segmentos
//posición es 0 ó 1
void escribirSieteSegmentos(byte digito, byte posicion) {

  byte pin = 32 + (7 * posicion);

  for (byte contador = 0; contador < 7; contador++) { //Recorremos cada segmento

    digitalWrite(pin + contador, conjuntoDigitos[digito][contador]);


  }
}

/*
Lee el código IR
*/
void leerIr(){
   if (ir.available()) {
      codigo_ir = ir.read();  
      //irrecv.resume(); 
     //Serial.println(codigo_ir); 
     //Serial.println(getFreeMemory());
     
  }else{
   codigo_ir = 0;
 }

}

/*
Escribe en el display de 7 segmentos
una abreviación de la opción de menú seleccionada
*/
void escribirSieteSegmentosPosMenu(){
  
    switch (posMenu){
    
    case 0:
    escribirSieteSegmentos(C, DECENAS);
    escribirSieteSegmentos(A, UNIDADES);
      
      
      
    break;
    
    case 1:
       escribirSieteSegmentos(E, DECENAS);
       escribirSieteSegmentos(N, UNIDADES);
       
    break;
    
    case 2:
      escribirSieteSegmentos(P, DECENAS);
      escribirSieteSegmentos(R, UNIDADES);
      
    break;
    
    }
}

Receptor de infrarrojos
Receptor de infrarrojos


Los archivos