Mostrando entradas con la etiqueta slot. Mostrar todas las entradas
Mostrando entradas con la etiqueta slot. Mostrar todas las entradas

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



lunes, 24 de noviembre de 2014

Sts 4x4: piñones de metal

Introducción



En otros modelos de slot el motor de los coches tiene una corona que mueve el piñón del eje de las ruedas. Esto hace que el movimiento pueda ir del motor hacia las ruedas o, si movemos las ruedas, se moverá el eje del motor.

Sin embargo en el STS 4x4 el motor tiene acoplado un tornillo sin fin que mueve el piñón del eje. Recordemos que un tornillo sin fin transmite el movimiento hacia otra pieza pero no a la inversa. Así, el movimiento pasa del motor a las ruedas pero si se intenta girar las ruedas éstas no se moveran al no pasar el movimiento al piñón.

Ésto hace que el coche carezca casi de inercia al soltar de golpe el gatillo del mando y el vehículo se quede clavado. Todo esta pensado para tener un control muy delicado a la hora de pasar obstáculos difíciles. Además un coche se puede detener en una subida y no se irá hacia atrás ya que las ruedas no girarán.

El problema de este sistema es que los piñones sufren un gran estrés que ocasiona que sus dientes se desgasten muy rapidamente lo que provoca que el eje y las ruedas dejen de girar. Si un coche va a toda velocidad y se deja de acelerar de golpe la inercia hará que el coche siga en movimiento pero como las ruedas no girarán es el piñon el que recibe todo el esfuerzo de detener el coche.

La solución pasa por sustituir los piñones de plástico por unos de metal, más concretamente de latón. Con ellos el desgaste será muchísimo menor pero por contra harán  un poco más de ruido

El envase de los piñones

Colocando los piñones de metal

Los materiales necesarios son:

  • Piñones de latón
  • Pegamento Cianocrilato
  • Martillo
  • Tablita de madera con un agujero

Los dos últimos elementos se pueden sustituir por una herramienta dedicada al slot  para montar y quitar piñones.

Los piñones se distribuyen en una bolsita con un sencillo esquema para su colocación.

Comenzaremos quitando los piñones anteriores de los ejes. Suelen salir haciendo un poco de fuerza con la mano. También podemos ayudarnos de unos alicates.

Plástico contra metal

A continuación colocamos el nuevo piñón encima del agujero de la tablita. Ponemos el eje encima y golpeamos despacito el eje con cuidado de no doblarlo hasta que atraviese el piñón.

Colocando el piñón con cuidado

Comprobar la distancia a la que está colocado desde el borde del eje y pegarlo con una gotita de pegamento cianocrilato.

Ajustando el piñón

Si por algún motivo el piñón queda ligeramente desplazado de su posición original el único percance es que una de las ruedas del coche sobresaldrá algo por un lateral.

Comprobando medidas

Luego ponemos un poquito de pegamento en las llantas de las ruedas y las pegamos al eje.

Repetimos la operación tantas veces como sea necesario y... ¡¡a jugar!!

Resultado final

Y para terminar que no falten unos vídeos del resultado en acción












viernes, 14 de noviembre de 2014

Sts 4x4: líneas de tensión

Introducción

Cuando construímos circuitos de slot con un recorrido un poco largo es frecuente encontrarse con que en ciertos puntos de la pista los coches se desplazan más lentamente que en otros.

Lo que ocurre es que se debido al paso del tiempo los carriles de metal se deterioran y oxidan con lo que ofrecen más resistencia al paso de la corriente provocando caídas de tensión.

Otro motivo es la pérdida de contacto entre las pistas. Cuando esto ocurre la corriente que le llega al coche es la que queda tras recorrerse el circuito en sentido contrario con lo que será escasa.

Colocando las líneas de tensión

La solución es colocar unos cables desde la unión del mando en pista hasta diferentes puntos estratégicos del circuito. Ésto hará que la corriente llegue hasta allí por el lugar que oponga menos resistencia y ya desde ese sitio acuda hasta el coche.

En el caso particular del sts 4x4 tenemos la ventaja de que los tramos recta pequeños tienen unos huecos en lo que podemos introducir los cables haciendo la colocación muy sencilla.

Poniéndonos en marcha

Los materiales que necesitaremos son:

  • Un rollo de par de cobre del utilizado para altavoces de música o similar
  • Pistola de encolar en caliente

Comenzando cortando unos trozos de cable lo suficientemente largos para que lleguen a las partes del circuito donde aparecen caídas de tensión. Es recomendable colocar estos cables justo antes de una subida o desnivel. Pelamos y trenzamos el cable.

Cable de cobre

Por debajo de los tramos recta pequeños introducimos los extremos de los cables. Tened cuidado de respetar la polarización ya que sino provocaremos un cortocircuito (normalmente el mecanismos de seguridad del transformador sts 4x4 impedirá que pase algo pero más valer ser precavidos).

Para asegurar la sujección se recomienda utilizar la pistola de cola caliente o bien algún tipo de pegamento. Repetir este caso para todos los puntos del circuito que creamos necesarios.

Punto de anclaje de una línea de tensión

Ahora cogeremos los otros extremos de todos los cables y los uniremos en dos grupos.
Los introduciremos entre los agujeros del conector que ajusta el mando a la pista.

Varias líneas de tensión unidas.
Las líneas en el conector
Detalle de las líneas

Para terminar aseguraremos la conexión utilizando de nuevo la pistola de silicona caliente.

Las líneas aseguradas con silicona caliente

Ya sólo queda probar el circuito comprobando que los coches no sufren ralentizaciones en ciertas partes del circuito. También es posible utilizar un voltímetro o tester y ver que la tensión se mantiene más o menos constante en todas las pistas y en todo el recorrido.

jueves, 21 de agosto de 2014

STS 4x4: repación del brazo basculante

Introducción

El brazo basculante es la pieza que permite que los coches del Sts 4x4 estén
en contacto con la pista aunque las ruedas se encuentren alejadas del suelo.

A veces se puede partir el eje que une esta pieza con el chásis (sobre todo al montarlo
y desmontarlo).

Vamos a ver lo sencillo que resulta repararlo.

Material necesario


  • Un trocito de hierro (alambre fuerte)
  • Alicates de corte
  • Un mechero


¡A reparar toca!

Cogemos el trocito de hierro y con los alicates de corte partimos un trocito (no importa que quede un poco largo).
Un trocito de metal para reparar el daño
Lo sujetamos con los alicates y lo calentamos con el mechero.
Mientras todavía está caliente lo incrustamos (con cuidado) en el punto donde se rompió
el eje del brazo basculante.

La reparación
El plástico se derretirá y al enfriarse quedará perfectamente soldado.

Finalizando

Ahora sólo debemos colocar el muelle en el nuevo eje, darle una vuelta y acoplar
el brazo en el chásis.
Resultado final

lunes, 23 de junio de 2014

Sts 4x4: reparación cuando el motor no alcanza al piñón

Introducción

A veces los coches pierden tracción en alguno de los ejes aunque los piñones
se encuentren en perfecto estado.

El motivo es que el eje sin fin del motor no llega a engranarse bien con los piñones por muy poquito.

Material Necesario

  • Un trozo de cartón o una pajita de plástico

Reparando que es gerundio

El truco de la reparación consiste en introducir algo encima del motor de tal manera que al
colocar la carrocería de vehículo ésta presione al motor hacia abajo haciendo que encaje con el piñón.

Preparando el motor

Podemos doblar un trocito de cartón o una pajita. Lo encajamos en un lateral del motor y lo doblamos de tal forma que no caiga encima del eje con la bobina.

Colocamos la carrocería y... ¡a correr!


viernes, 13 de junio de 2014

Sts 4x4: Reparando pistas

Introducción

En el momento de salir al mercado las pistas de slot Sts 4x4 eran lo mejor que se podían encontrar.
Fabricadas con un material muy flexible permitían ser dobladas y forzadas para hacerlas encajar en su entorno.
Era muy sencillo construir un circuito de carrera apoyando las pistas encima de libros, haciendo puentes con sillas... montarlas y desmontarlas mil veces... eran perfectas.

Pero el paso de los años causa que las propiedades elásticas no sólo desaparezcan sino que las pistas se vuelvan muy quebradizas. Podría decirse que algunas se rompen, literalmente, con mirarlas.

Tramo cóncavo partido
Tramo cóncavo partido

El porqué de ese proceso no se conoce exactamente. Algo que lo delata es que las pistas huelen muchísimo a plástico.
Se desconoce si es un deterioro natural por el paso del tiempo o si se produce por estar dentro de cajas sin oxígeno durante muchos años.

Tramo cóncavo roto por detrás
Tramo cóncavo roto por detrás

Tratar de montar un circuito hoy en día con las pistas impolutas es totalmente imposible.
Los enganches entre ellas se romperán seguro y las propias pistas se pueden rajar de lado a lado con mucha facilidad.

No todo es malo

Afortunadamente las pistas se sujetan bastante bien aún careciendo completamente de enganches. Si además vamos a montar un circuito fijo (en el que los tramos irán pegados o clavados a una mesa de madera) el problema es como si no existiera.

Pero... ¿y si es el propio tramo el que se ha rajado? Veremos que es muy fácil repararlos y aunque no quedan como nuevos al ser circuitos "salvajes" las carreteras rotas no desentonan.

Arreglando

Necesitaremos:
  • Pistola térmica para silicona
  • Barritas de silicona térmica
Tan sólo debemos aplicar un poco de silicona en la pista y unir los tramos.
Esperar 1 minuto a que se enfríe y... ¡¡ya está!! perfectamente pegada y reparada.

Pegamos por detrás con silicona térmica
Pegamos por detrás con silicona térmica

En las fotos he utilizado silicona transparente pero recordad que también las venden de colores.

El tramo reparado
El tramo reparado

lunes, 5 de mayo de 2014

Sts 4x4: cambio de trencillas

Introducción

Tras vueltas y vueltas en los circuitos las trencillas que transmiten la corriente desde la pista al coche terminan hechas un desastre.
Esto ocasiona que los coches puedan llegar a pararse en algún punto del recorrido por un mal contacto.
Vamos a cambiarlas en menos de 5 minutos.

Manos a la obra

Tras comprobar que las trencillas viejas no pueden ser arregladas (limpiándolas o estirándolas con los dedos) adquirimos un par de trencillas de cobre nuevas.

Trencillas desgastadas de Sts 4x4
Trencillas desgastadas de Sts 4x4

Sacamos los enganches que sujetan las trencillas con las uñas y desmontamos la guía haciendo presión sobre ella para trabajar más cómodo:

Sacando la guía de su lugar...
Sacando la guía de su lugar...

Aplastamos un poquito el extremo de las trencillas para formar una cuña y las introducimos en los agujeros de la guía.
Colocando las nuevas trencillas
Colocando las nuevas trencillas

Colocamos la guía de nuevo en su sitio y ponemos los enganches.
No olvidar doblar un poco el final de las trencilla hacía arriba para mejorar el contacto en las salidas de las cuestas y en los obstáculos.

¡Trencillas nuevas preparadas!
¡Trencillas nuevas preparadas!