El conector utilizado es un clásico DB9 utilizando solo cinco pines de los nueve; dos para alimentación y tres para señal.
Conexionado
Para el conexionado se pueden utilizar cualquiera de las E/S del Arduino (inclusive las analógicas).
Para el ejemplo utilicé las E/S 49 para Data, 51 para Latch y 53 para el Clock (los pines utilizados se pasan como parámetros de la rutina de inicialización de la librería).
Protocolo
El protocolo utilizado es el de los Joystick’s NES:
60 veces por segundo, el CPU del NES genera un pulso de 12uS sobre el pin Latch. Esta señal toma el estado de cada botón y el mismo se almacena a razón de 1 bit por botón en un registro de 8 bits.
6 uS después el CPU envía un tren de 8 pulsos de 12uS de período al 50% de duty (6uS de estado bajo + 6uS de estado alto) por el pin Clock.
Sincronizado con el flanco descendiente del pin clock se realiza un desplazamiento del registro de 8 bits por el pin Data. El CPU del NES captura el estado de cada botón leyendo el estado del pin Data en cada flanco ascendente del pin Clock.
Cada botón tiene su “bit” específico.
Lo anteriormente mencionado puede apreciarse en la siguiente figura:
Notas
Es posible presionar múltiples teclas al mismo tiempo sin ningún inconveniente (salida con tantos bits 1 como botones apretados).
Si bien el Joystick tiene 10 botones, 2 de los mismos son “virtuales”. A y B turbo equivalen a la pulsación repetida de los botones A y B respectivamente.
La rutina de “lectura” de teclas es de implementación extremadamente sencilla:
unsigned char CheckButtons(void){
unsigned char buttons = 0;
unsigned char i;
digitalWrite(_LatchPin, 1);
delayMicroseconds(12);
digitalWrite(_LatchPin, 0);
for (i = 0 ; i < BUTTON_COUNT ; i++){
delayMicroseconds(6);
buttons <<= 1;
buttons |= digitalRead(_DataPin) ? 0 : 1;
digitalWrite(_ClockPin, 1);
delayMicroseconds(6);
digitalWrite(_ClockPin, 0);
}
return buttons;
}
Librería
La librería de control es bien simple; Se trata de una única clase que verifica las teclas pulsadas / presionadas y genera los eventos asociados.
Puede descargarse del repositorio de blog desde aquí
Características generales:
No utiliza interrupciones (usa Polling).
Cuenta con Buffer circular de los últimos botones presionados (últimos 16).
Se pueden asociar Callback’s a los eventos de teclas presionadas y pulsadas.
Permite la utilización de múltiples instancias (más de 1 Joystick).
Clase pública
class FamilyGameJoystick {
public:
void Init(unsigned char DataPin, unsigned char LatchPin, unsigned char ClockPin);
void Release(void);
unsigned char Poll(void);
bool ButtonDown(unsigned char button);
bool ButtonPressed(void);
unsigned char GetButton(void);
void onButtonDown(void (*function)(unsigned char));
void onButtonPress(void (*function)(unsigned char));
};
Constantes utilizadas
BUTTON_RIGHT
BUTTON_LEFT
BUTTON_DOWN
BUTTON_UP
BUTTON_START
BUTTON_SELECT
BUTTON_B
BUTTON_A
Descripción de los métodos
void Init(unsigned char DataPin, unsigned char ClockPin, unsigned char LatchPin);
Rutina de inicialización de la librería.
Se pasan como parámetros los pines del Arduino conectados a los pines Data (pin 4 del DB9), Clock (pin 2 del DB9) y Latch (pin 3 del DB9) del Joystick.
void Release(void);
Libera los recursos asociados a la librería (puertos).
unsigned char Poll(void);
Rutina de Polling.
Interroga el Joystick en busca de teclas pulsadas y genera los eventos asociados.
Debe ser llamada periódicamente desde el bucle principal de la aplicación para el correcto funcionamiento del resto de las rutinas.
Puede ser llamada desde una interrupción.
Retorna máscara de 8 bits con botones actualmente pulsados utilizando las constantes [BUTTON_RIGHT .. BUTTON_A].
bool ButtonDown(unsigned char button);
Retorna [true] si el / los botones pasados en el parámetro [button] están pulsados.
Se entiende “pulsado” como el botón apretado que se mantiene en ese estado mientras se realiza el chequeo.
El parámetro [button] es una máscara de 8 bits utilizando las constantes [BUTTON_RIGHT .. BUTTON_A].
bool ButtonPressed(void);
Retorna [true] si hay al menos un botón en el buffer de botones presionados
Se entiende como “presionado” a la acción de pulsar y liberar cualquiera de los botones.
unsigned char GetButton(void);
Retorna el primer botón del buffer de botones presionados.
El valor retornado se encuentra en el rango [BUTTON_RIGHT .. BUTTON_A].
void onButtonDown(void (*function)(unsigned char));
Registra “callback” de tecla pulsada.
La rutina [function] pasada como parámetro se “ejecuta” mientras se mantenga pulsada una o varias teclas.
El parámetro pasado a la función se encuentra en el rango [BUTTON_RIGHT .. BUTTON_A].
void onButtonPress(void (*function)(unsigned char));
Registra “callback” de tecla presionada.
La rutina [function] pasada como parámetro se “ejecuta” cada vez que se presiona (pulsa y libera) una tecla.
Sketch de ejemplo
A continuación un ejemplo simple sin la utilización de eventos.
Este y otros ejemplos se adjuntan con la librería y pueden ser abiertos como Sketch’s de ejemplo de la librería (Archivo -> Ejemplos -> FamilyGameJoystick).
#include <FamilyGameJoystick.h><familygamejoystick .h="">
//variable global de acceso al Joystick
FamilyGameJoystick Joystick;
void setup() {
// inicialización del terminal serial
Serial.begin(9600);
// rutina de inicialización del Joystick
// E/S 49 de arduino a pin Data del Joystick
// E/S 51 de arduino a pin Latch del Joystick
// E/S 53 de arduino a pin Clock del Joystick
Joystick.Init(49, 51, 53);
}
//bucle principal de la aplicación
void loop() {
// se llama a la rutina de Polling
// la misma establece comunicaciones con el joystick, detecta pulsación de botones,
// genera los eventos asociados, etc.
Joystick.Poll();
//si se presionó un botón (buffer de hasta 16 botones)
if (Joystick.ButtonPressed()){
Serial.print("Botón presionado: ");
// escribimos máscara del botón presionado
Serial.println(Joystick.GetButton());
}
//si el botón "izquierda" está siendo pulsado
if (Joystick.ButtonDown(BUTTON_LEFT)){
//decrementar la posición X del personaje
Serial.println("Izquierda");
}
//si el botón "derecha" está siendo pulsado
if (Joystick.ButtonDown(BUTTON_RIGHT)){
//incrementar la posición X del personaje
Serial.println("Derecha");
}
}
</familygamejoystick>
En lo próximo
Generaré otros ejemplos de aplicación (tengo un PacMan a punto de salir del horno).
Implementaré sistema “anti-rebotes” (estimé estaría implementado en el controlador del Joystick / a nivel eléctrico pero me equivoqué).
Corregiré pequeños detalles, documentaré el código fuente, puliré la documentación, etc.
Espero el contenido haya sido de utilidad.
Consultas / comentarios son siempre bienvenidos.
Nos vemos en la próxima entrada, Saludos!