viernes, 23 de mayo de 2025

jueves, 22 de mayo de 2025

El Navegante Pro

¡¡¡ Tenemos un nuevo canal en YouTube !!!

Su nombre es El Navegante Pro.

Si te gustan los videojuegos, el hardware, la música, los libros, los juegos de mesa... no dejes de visitarlo y ... ¡suscríbete!😎

@elnavegantepro

https://www.youtube.com/@elnavegantepro



viernes, 15 de julio de 2022

Adobe Premiere Pro Light Mode

Adobe Premiere Pro utiliza una interfaz de modo oscuro que resulta muy difícil de ver.

Aunque las preferencias de apariencia permiten cambiarla de negro a gris todavía sigue siendo demasiado oscura. Debemos subir el brillo del monitor al máximo para conseguir ver algo.

Debido a esto reducimos el tiempo de uso de la batería de nuestros portátiles.

Vamos a hacer que Adobe Premiere Pro se vea de la siguiente manera:

Adobe Premiere Pro Light Mode
Adobe Premiere Pro Light Mode


Hay que seguir los siguientes pasos:

  1. Abrir Adobe Premiere Pro
  2. Crear o seleccionar un proyecto
  3. Pulsar las teclas CTRL + F12 para abrir la consola
  4. En la pestaña de la parte superior "Console" pulsar en las tres rayas horizontales
  5. Seleccionar la opción "Debug Database View"
  6. Hacer clic junto a la lupa y buscar "light"
  7. Debajo aparecerá la opción "SupportLightUI.PPro" con valor "false"
  8. Marcar el check de la derecha con lo que cambiará su valor a "true"
  9. Cerrar la consola
  10. Ir al menú superior--> Edit -> Preferences -> Appearance
  11. Desplazar la barra "Brightness" a la derecha
  12. Desplazar la barra "Interactive controls" a la izquierda
  13. Desplazar la barra "Focus indicators" a la izquierda
Con este cambio veremos que la barra de brillo ahora permite cambiar la interfaz a diferentes niveles de blanco mientras que antes se quedaba tan sólo en gris.

Esta misma modificación se puede aplicar de manera similar en Media Encoder, Photoshop y Audition 


lunes, 21 de diciembre de 2020

Corrupt 2.0

 Introducción

Corrupt fue una de las últimas aventuras conversacionales publicadas en castellano.
Intentó innovar en muchos aspectos como el uso de dos fuentes de letras diferentes, la manera de escribir las descripciones, el uso de pequeños sonidos al teclear, tener cinco fases diferentes...

Corrupt 2.0: más colorido en la interfaz


Sin embargo el juego salió sin terminar de pulir al mercado con faltas de ortografía y errores de programación que en muchos casos hacían imposible terminar el juego.

Recuerdo que en la versión en cinta de Amstrad CPC464 las descripciones se quedaban en negro sin poder ver nada. El error se producía  al hacer alguna acción que no recuerdo en la pantalla del guardia con el M-16.
De igual manera en la segunda fase se producía un "Improper argument" en ciertas ocasiones.

Es posible que esos "errores" sólo ocurriesen en la versión en cinta, bien por algún problema de memoria o bien porque el código BASIC del programa sea diferente entre versiones.

Es una lástima porque con un par de días de trabajo el juego hubiera salido perfecto al mercado.

Además debió de haber algún problema con la producción de cintas ya que tuve que cambiarla dos veces ya que faltaban "bloques" del juego. 
El programa se componía de muchísimos ficheros que fueron pasados tal cual de la versión disco a cinta.
Eso ocasionaba que la cinta de Amstrad fuera tan larga (tardaba dos millones de años en cargar) que en la cadena de grabación de cassettes las cortaran demasiado no esperando que el juego ocupase tanto espacio.

Curiosamente la diversión que no me pudo dar el juego por sus numerosos "bugs" me la ofreció en forma de cacharreo. La estructura principal del programa estaba escrita en BASIC con lo que pude cotillear e intentar arreglar alguna cosilla.

Ahora, muchos años después el juego ha sido modificado dando lugar a la versión 2.0.

Corrupt 2.0: nueva pantalla de carga


Los cambios generales

  1. Se da visibilidad a todos los archivos del disco. Para ello se ha utilizado la rom ParaDos incluida por defecto en el emulador WinApe. Con |DRIVE se han puesto todos los archivos como DIRECTORIO (ya que con SYSTEM no se ven desde BASIC).

  2. La pantalla de carga ha sido sustituida por una basada en la propia carátula en papel del juego. Se ha escaneado y utilizado el programa ConImgCPC para su conversión al formato del Amstrad. Además ahora se muestra el dibujado en lugar de permanecer la pantalla en negro hasta estar terminada. El juego continúa cargando sin necesidad de pulsar una tecla.

  3. Se ha reducido drásticamente la cantidad de ficheros en basic (.bas, .ba0, .ba1, .2as) que utilizaba el juego. Ahora el cargador principal “DISC.BAS” contiene la pantalla de carga, el menú principal, las instrucciones, el final del juego, la carga del tipo de letra, la carga de los gráficos de las fases, la pantalla decorativa de las siguientes fases y la llamada al cargador propio de cada fase (FASEx.BAS en lugar de FASEx.2AS).

  4. Se han eliminado la mayor parte de los comentarios de los programas basic así como reenumerado los números de línea. Se han realizado pequeñas optimizaciones como por ejemplo sacar instrucciones (TAG, TAGOFF) dentro de bucles que no tenía más efecto que ralentizar la ejecución. Se ha cambiado la gestión de teclas del menú. En el texto con scroll se ha añadido nota del autor de la versión 2.0. Se han eliminado los símbolos “!” en las instrucciones de carga de disco. Cuando se selecciona cargar una frase el pequeño cartel de “CORRUPT” se muestra desde el principio en lugar de una pantalla en negro.

  5. Se han eliminado los ficheros CORRx.bin. Estos ficheros contenían los textos de las descripciones. Se cargaban en ram y luego se recorrían sus posiciones de memoria para asignar su contenido en un arreglo de cadenas desc$(). Esto causaba un mayor tiempo de carga y espera (se mostraba el texto “Descodificando datos” en pantalla y tocaba ser muy paciente). Esos textos ahora se incluyen dentro de los ficheros FASEx.BAS siendo además así más fácil su modificación.

  6. Se ha coloreado el marco decorativo del juego.

  7. La consola de introducción de órdenes ahora muestra el texto en color.

  8. Se ha introducido una sangría de primera línea en la descripción de las localizaciones.

  9. Las descripciones de localidades ahora terminan siempre con punto.

  10. Se han utilizado guiones para partir las palabras de final de línea.

  11. Aunque el tipo de letra no permite apreciarlo ahora se usan letras mayúsculas al principio de las frases y en los nombres.

  12. Se han corregido faltas de ortografía y la estructura de algunas frases.

  13. Tras cada punto y seguido se añade un espacio.

  14. Se ha restablecido que al insultar cuatro veces a la máquina ésta se reinicie (el código se encontraba comentado en basic).

  15. Para hacer compatible el programa con el CPC464 no se ha utilizado la orden GRAPHICS PAPER. En su lugar hay una llamada a &BBE4 con dos parámetros (GRAPHICS PAPER 2) y sin parámetros (GRAPHICS PAPER 0). Se utiliza para el fondo de las flechas de dirección ahora que se ha coloreado ya que se posiciona en pantalla en el lugar que ocupa el cursor gráfico.

  16. Al introducir incorrectamente los códigos de acceso de las fases se mostraba el texto “estás haciendo trampas?” pero desaparecía tan rápido que no daba tiempo a verlo. Ahora se espera a la pulsación de una tecla para borrarlo.

  17. Se ha cambiado “descoonecto” por “desconecto.

Cambios concretos en algunas fases

Fase 1

  •  Al entrar en las oficinas del FBI era necesario acercarse al policía y preguntar por Secnas para poder terminar la primera fase. La acción es completamente innecesaria (pues es posible llegar a su despacho directamente) pero sin ella no era posible obtener el código de acceso para la fase 2. Se ha eliminado su obligatoriedad para acabar la fase uno 

Fase 3

  • Vender la droga era una tarea prácticamente imposible ya que había que utilizar el símbolo del dólar $: “DECIR 6000$”. Ahora se permiten además las expresiones “DECIR 6000”, “DECIR 6.000”, “DECIR 6,000”, “DECIR SEIS MIL” y “DECIR SEIS MIL DOLARES”.

  • El texto de la última pantalla del almacén no se mostraba. Se ha rescatado desde la versión de PC.

Fase 4

  • En el momento de girar la rueda de la caja fuerte era necesario poner un 0: “GIRAR 05”. Se ha relajado la condición y ahora se admite “GIRAR 5”

Fase 5

  • Se ha cambiado en una descripción "llevas un cristal" por "un cristal podría ayudarte" ya que es posible que todavía no se haya recogido el cristal.

  • Se ha corregido el mapeo de la salida después de conseguir el contenido de la caja fuerte ya que llevaba a una pantalla del barco que no correspondía.


Descarga


Es posible descargar el fichero desde este enlace: corrupt 2.0






lunes, 8 de abril de 2019

Solución teléfono sin audio bluetooth en coche

El problema

Algunos teléfonos móviles (como el Xiaomi MI5) no transmiten el audio a los equipos de sonido de algunos coches. La conexión con el móvil se realiza bien pero en las propiedades de la conexión bluetooth aparece algo como "no media". El resultado es que el sonido se escucha por el móvil en lugar de por los altavoces del vehículo con lo que la función de manos libres no funciona.

La causa

El audio a través del bluetooth se puede transmitir utilizando diferentes códecs. Algunos de los más conocidos son aac o sbc. Tanto el móvil como el equipo de audio del coche deben utilizar el mismo códec para poderse escuchar.

El problema es que el equipo del coche se intenta comunicar utilizando aac y el móvil le dice que sí que es capaz de utilizar AAC pero resulta que luego el móvil no tiene esos códecs con lo cual no se transmite el audio. En resumen: xiaomi mi5 le está mintiendo al equipo de audio diciendo que puede hablar un idioma del que realmente no tiene ni idea.

En un equipo de audio de coche más antiguo el teléfono funciona sin problemas porque el coche le dice que si puede utilizar SBC y el teléfono le responde que si (porque para SBC sí que tiene los códecs)

Se trata de un doble error de xiaomi:

- No haber introducido los codecs AAC
- Si no pones los codecs AAC entonces no digas que si puedes utilizar AAC

La solución

Debemos introducir un par de líneas en el archivo build.prop y reiniciar el móvil. Una
de ellas le dirá que no utilice AAC (ya que no tiene los codecs) y la otra línea le dirá al móvil que primero intente conectarse utilizando SBC y después con AAC.

persist.bt.a2dp.aac_disable=true
persist.vendor.bt.a2dp_offload_cap=sbc-aac

Cómo hacerlo

Habrá que hacer una serie de pasos según el teléfono:

  1. Desbloquear el bootloader
  2. Ejecutar TWP
  3. Instalar Magisk
  4. Hacer root al móvil
  5. Instalar la app BuildProp Editor
  6. Añadir las líneas anteriormente comentadas
  7. Quitar el root
  8. Desinstalar magisk
  9. Volver a bloquear el bootloader
Os dejo unos sencillos tutoriales de cómo hacer estos pasos. Os recomiendo tener el archivo boot.img dentro de la carpeta downloads del móvil por si el paso de desistalar el Magisk fallara y el móvil no arrancara.

El archivo boot.img se encuentra dentro del .zip con el firmware de vuestro móvil que se puede descargar de Xiaomi.

Si el móvil se queda en el logo inicial arrancáis TWP e instaláis el boot.img en la partición boot y solucionado.

Root:
http://en.miui.com/thread-1889098-1-1.html

Bloqueo:
https://c.mi.com/thread-480578-1-0.html


miércoles, 21 de febrero de 2018

Sistema Retro: OrangePi y Lakka

Introducción

Vamos a preparar una consola retro utilizando una placa Orange Pi Plus 2e. Hemos elegido ese modelo ya que incorpora una memoria interna eMMC en la que instalaremos la distribución Lakka y así no necesitaremos arrancar el sistema desde una tarjeta microSD.

Materiales necesarios
Materiales necesarios

Aunque existen otras distribuciones dedicadas a la emulacion como RetroOrangePi no dan la misma sensación de robustez que Lakka.

Orange Pi Plus 2e
Orange Pi Plus 2e

En RetrOrangePi se nota demasiado que es un sistema operativo con un montón de programas llamándose unos a otros. Se ven pantallas negras lanzando líneas de scripts, en cuanto entras en alguna opción de configuración de Retropie aparecen pantallas en modo "ascii", es un jaleo en cuanto a archivos de configuración, las unidades USB no se automontan para leer las roms directamente de ellas...

RetrOrangePi no fue elegido
RetrOrangePi no fue elegido

Sin embargo con Lakka la sensación es de un sistema casi comercial. Su interfaz se asemeja al de la PS3. No hay pantallas negras, todo es bonito y muchas cosas se configuran solas.

Material necesario

  1. Placa Orange Pi Plus 2e (OPI)
  2. Transformador
  3. Cable HDMI
  4. Caja para la placa.
  5. 2 mandos de juego.
  6. Pendrive o Disco Duro donde guardar las roms.
  7. Tarjeta microSD.
  8. Distribución lakka para la Orange Pi Plus 2e.
  9. Putty
  10. Win32DiskImager
  11. WinBinFex
  12. Cutter
  13. Pinzas
  14. Destornillador
  15. Gomas
  16. Cable de red
  17. Archivo de shader
  18. Archivo overlay

Montando la caja

La caja viene desmontada en piezas de plástico protegidas con papel pegado.

Preparando la caja
Preparando la caja


Con ayuda de un cutter levantamos una esquina del papel y después tiramos de él para despegarlo.

La caja lista para ser montada
La caja lista para ser montada

Sujetamos la placa OPI con los tornillos a la pieza base. Con la ayuda de unas pizas sujetaremos las tuercas mientras atornillamos los tornillos.

Base de la caja
Base de la caja

Después montamos el resto de la caja y nos fijamos bien cómo conectar el ventilador para que funcione correctamente ya que en muchas fotos de internet (incluso de los vendedores) la posición del conector viene al revés y el ventilador puede no funcionar.

Pequeño ventilador

Los disipadores vienen ya preparados con pegatinas por debajo con lo cual no es necesario aplicar ningún tipo de pasta térmica.

Conexión del ventilador

La caja se desmonta muy fácilmente así que es recomendable sujetarla con gomas o incluso pegarla.

Caja montada

Finalmente conectamos el cable HDMI, alimentación, hdmi, los mandos, el ratón (a través de un adaptador OTG a usb) y el pendrive.


Instalando Lakka

Utilizando el programa Win32DiskImager volcamos la imagen del Lakka desde nuestro PC a la tarjeta microSD. Insertamos la tarjeta en la OPI  y encendemos el sistema.

Una vez que el programa haya arrancado y veamos la interfaz del Lakka procederemos a instalar la distribución en la memoria interna.

En lakka vamos a configuración --> configuración de red --> IP y tomamos nota de esa dirección.

Para ello desde el PC instalamos el programa putty y nos conectamos a la IP obtenida con root/root.

Ejecutamos: sh /usr/dev/install_to_emmc

Una vez que el script termine de ejecutarse sacamos la tarjeta microSD de la OPI. En lakka vamos a opciones-->reiniciar y el sistema volverá a arrancar pero esta vez desde la memoria interna.


Cambiando la resolución de salida

Lakka viene configurado con una resolución de 1080p. Es posible cambiar la resolución a 720p de la modificando el archivo script.bin de la siguiente manera:

1.- Nos conectamos a la OPI utilizando putty.

2.- Copiar a un directorio que podamos ver desde windows el fichero script.bin. Para ello tecleamos en el putty:

cd ..cp /flash/script.bin /storage/roms/script.bin
3.- Abrimos el fichero script.bin con el programa WinBinFex y nos generará un archivo .txt

4.- Editamos el archivo .txt y establecemos la línea
 screen0_output_mode = 5
5.- Abrimos el fichero .txt con el programa WinBinFex y nos generará un nuevo archivo script.bin

6.- Lo volvemos a copiar en la misma carpeta.

7.- Ejecutamos desde el putty:

mount -o remount,rw /flash
cd flash
rm script.bin
cp /storage/roms/script.bin /flash/script.bin
reboot


Utilizando roms

Desde nuestro PC abrimos un explorador de archivos y tecleamos \\lakka con lo que tendremos acceso a algunas carpetas de Lakka. Vamos a devices\roms\nombre del pendrive

Allí podemos crearnos una estructura de carpetas como roms\amstrad roms\snes... e ir añadiendo las roms.

Luego vamos al menú de lakka con el símbolo más (+) y escaneamos la carpeta de roms de nuestro pendrive. Se crearan unos archivos conocidos como listas de reproducción que lakka mostrará como nuevas entradas del menú con el icono de cada sistema.


Mostrando carátulas y posters

Para que se muestre la carátula de los juegos debemos descargar los archivos que contienen las imágenes. Dede Lakka vamos a opciones-->thumnails... y los activamos.


Mejorando el aspecto: shaders

Lakka puede utilizar shaders para emular la apariencia de un monitor CRT. No obstante sólo se podrán utilizar shaders que no hagan cálculos muy intensivos.

Descargamos el archivo de shaders desde aquí:
Lo copiamos a la carpeta retroarch/shader de la OPI
En la OPI iniciamos un juego, salimos al menú. Vamos a opciones de shaders --> seleccionar shader --> aplicar shader y volvemos al juego.

El siguiente video muestra un overlay que sólo tiene el dibujo de los altavoces en los laderales y donde las scanlines son generadas mediante shaders.


Mejorando el aspecto: overlays

Dado que los juegos retro se muestran en una relación de aspecto 4:3 y nuestros televisores tienen una relación 16:9 en los laterales aparecen unas franjas negras.

Para rellenarlas con una imagen descargamos el arvhivo de overlays. Lo copiamos en la carpeta de la op retroarch/overlay. Desde lakka vamos a configuración, OSD, seleccionamos el archivo de overlay y establecemos una transparencia de 0.85. Éste archivo incluye también líneas que imitan las "scanlines" de los monitores CRT así que es una opción de peso para no utilizar shaders.

Este video tiene un overlay con el dibujo de los altavoces (como en el ejemplo anterior) pero además incorpora las scanlines dibujadas así que no hace uso de shaders.








viernes, 10 de febrero de 2017

Formula D

FormulaD es un juego de mesa en el que pueden jugar hasta diez jugadores.
El tablero muestra un circuito de carreras. El objetivo consiste en cruzar la línea de meta con
nuestro coche antes que nuestros rivales.

La caja pesa lo suyo. En su interior encontraremos el enorme tablero de juego (viene dividido en dos partes que habrá que unir). Debido a que los coches se mueven por todo el circuito es un juego que se juega muy bien estando de pie y retirando las sillas de la zona de juego.

El contenido de FormulaD

Existen dos modalidades de juego:  para principiantes y estándar. Cada una de ellas cuenta con un libro de reglas diferentes. Éstos manuales son a color y está toda la información bastante bien explicada.

Las reglas para principiantes son las más divertidas y las que dan mayor dinamismo al juego. Son las ideales para jugar con niños y con gente que no sea muy aficionada a los juegos de tablero.

Las reglas avanzadas pueden llegar a ralentizar demasiado el transcurso de la partida (sobre todo si juegan más de cuatro jugadores).

El desarrollo del juego a grandes rasgos es sencillo: tenemos una caja de cambios con 6 velocidades. Cuando llegue nuestro turno podemos subir o bajar velocidades y tiraremos el dado del color de la velocidad que hayamos seleccionado (cada dado tiene un conjunto de números diferentes).
El juego nos obliga a frenar en las curvas pues es obligatorio parar n veces en ellas para no salirnos.

Como curiosidad en cada ronda de juego los turnos se establecen por el orden de los coches en carrera. Así puede darse el caso de que en una partida de tres jugadores un jugador tira en tercera posición y adelanta a dos vehículos. Entonces en el siguiente turno como ahora va primero volverá a tirar con lo que habrá tirado dos veces seguidas. Hay que estar atento a estos detalles para que nadie se quede sin tirar en un turno de juego.

Los coches y los dados

Existen  muchos más detalles: en la salida se nos puede calar el coche. Si dos coches están uno al lado de otro es posible que se den un toque. Si hemos tenido que reducir varias velocidades en el mismo turno sufriremos daños mecánicos... Si además jugamos con las reglas estándar tendremos la posibilidad de elegir entre pilotos diferentes. Cada uno tiene unas cualidades y los coches sufrirán distintos tipos de daños. A esto hay que añadir la posibilidad del cambio de las condiciones climáticas.

Caja de velocidades y de daños.

En el reverso del tablero encontraremos un circuito urbano. No sólo difiere en su trazado sino que se aplican algunas normas diferentes. Jugaremos con el juego de fichas de coches de tuning. Hay una zona del circuito donde si caemos unos pandilleros nos tirarán piedras y podrán dañar nuestros vehículos. Además recibiremos puntos por pasar deprisa delante de la comisaría.

En la línea de salida.


Consideraciones


Los contras

  • El tablero del juego se encuentra partido. Esto ocasiona que sobre la mesa las partes se separen. Hubiera sido mucho mejor un tablero de una sola pieza.
  • Aunque existen 10 vehículos diferentes de cada tipo (fórmula y turismos) los colores de cinco de ellos son iguales y sólo se diferencian en una pequeña línea de color. Es muy muy fácil confundirse de vehículo. Resulta totalmente inexplicable porqué no han puesto coches de colores diferentes.
  • El acabado de los coches no es muy bueno. Las líneas de color están muy mal pintadas.
  • El cartón donde se aloja la palanca del cambio de velocidades se estropea muy rápidamente. En lugar de un cartón debería haber sido de plástico.

El circuito de Mónaco

Los pros

  • El juego es muy divertido y las reglas de principiantes son fáciles de aprender.
  • Se incluyen dos circuitos.
  • Aunque en la caja especifica que es para niños mayores de 10 años a partir de 6 pueden jugar perfectamente. Los chavales enseguida cogen el truco de reducir velocidades y contar las casillas que les quedan para la siguiente tirada y no salirse en las curvas.
  • La duración del juego es la justa. En el circuito de Mónaco una partida de 3 jugadores a una vuelta se tarda en completar 1 hora.
  • Posibilidad de adquirir ampliaciones del juego con nuevos circuitos.

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