TONC: Curso programacion GBA IV

  • En esta lección se verá en profundidad como controlar los botones de la GBA.


Contenido

Introducción

  • La GBA dispone de un pad de 4 direcciones (D-pad); dos botones de control (Select y Start); dos botones de disparo (A y B) y dos botones en la espalda (L y R), haciendo un total de 10 botones. Los principios de la manipulación de los botones es muy simple, tenemos un registro con el estado de los botones y podemos ver que botón o botones han sido presionados dependiendo de si sus bits están a 1 o 0.

Los registros del teclado

REG_KEYINPUT

  • La GBA tiene 10 botones. Sus estados se encuentran en los 10 primeros bits del registro REG_KEYINPUT que se encuentra en la localización de memoria 0400:0130h. Los nombres de las constantes definidas que se usan son KEY_x, donde x es el nombre del botón en mayúscula.
F E D C B A 9 8 7 6 5 4 3 2 1 0
- L R abajo arriba izquierda derecha start select B A
  • Comprobar si un botón es presionado o no debería ser obvio sino llega a ser por un pequeño detalle, los bits son activo en bajo. Por lo que un "0" indica que el botón esta presionado, mientras que un "1" indica que no. De tal manera que el estado por defecto del registro REG_KEYINPUT es 0x03FF y no 0.
  • Para comprobar si un botón esta presionado se hace tal que así:
#define KEY_DOWN_NOW(key)  (~(REG_KEYINPUT) & key)
  • Como se ve en el código primero se invierte REG_KEYINPUT y luego se enmascara con el botón(es) que se quiere(n) comprobar.

REG_KEYCNT

  • Este registro proporciona un control extra al registro de botones. Se encuentra en la dirección 0400:0132h de la memoria y se usa para las interrupciones del teclado, algo parecido al registro REG_DISPSTAT. Con el bit REG_KEYCNT{14} se puede activar la interrupción del teclado. Las condiciones para activar esta interrupción es determinada por REG_KEYCNT{0-9}, que dice cual es el botón que vigilar y REG_KEYCNT{15} que dice las condiciones exactas.
F E D C B A 9 8 7 6 5 4 3 2 1 0
Op I - L R abajo arriba izquierda derecha start select B A
bits nombre define descripción
0-9 keys KEY_x Botón(es) a comprobar para lanzar la interrupción.
E I KCNT_IRQ Activa la interrupción del teclado.
F Op KCNT_OR, KCNT_AND Operador booleano usado para determinar cuando disparar una interrupción de teclado o no. Si está a 0 se usa un OR (se activa si uno de los bits de los botones 0-9 esta presionado); si está a 1 se usa una AND (se activa si todos los bits 0-9 están presionados).

Profundizando en el estado de los botones

  • Comprobar el estado de los botones con KEY_DOWN_NOW() es muy facil, pero hay mejores y preferibles métodos de tratar el estado de los botones:
  • Estado sincrónico: se lee el estado de los botones en un determinado punto y se hace uso de esa variable, en vez de hacer lecturas repetidas a REG_KEYINPUT cuando se procesa una entrada.
  • Estados de transición: donde se rastrea no solo el estado actual de los botones, sino también el estado anterior.
  • Por último 'tribools': variables de tres estados (-1, 0, +1) que pueden ser usados para simplificar el procesamiento de la dirección.

Estado sincrónico

  • El uso de KEY_DOWN_NOW() es una forma asíncrona de tratar los botones, ya que compruebas el estado de los botones en el momento en que el código lo necesita. Aunque funciona, no es la mejor de hacerlo ya que:
  • Es menos eficiente en términos de código debido a que el registro es cargado y leído siempre que es necesario.
  • Cuando hay mas de un botón pulsado a la vez quizás no se registre ya que puede que el código que lee el estado de los botones se encuentre separado.
  • El principal problema es que no puedes hacer mucho con ello, solo obtener el estado actual. Por ejemplo al pausar/despausar un juego, esto normalmente se hace presionando el botón Start, y luego pulsar Start de nuevo para despausarlo. Pero el el juego corre mas rápido de lo que tu puedes reaccionar, así que el botón Start estará presionado durante múltiples 'frames' por lo que con KEY_DOWN_NOW() el juego se pausará y despausará durante este tiempo; el estado del juego cuando finalmente dejes de presionar el botón es aleatorio.
  • Y aquí es donde entra el estado sincrónico. Simplemente leemos el estado una vez al comienzo del 'frame' por ejemplo, y usamos ese estado durante todo el 'frame'. Para llevar la cuenta de los cambios de estado, se salva el estado en el 'frame' anterior.
void key_poll ()
Comprueba el estado de los botones y aquellos repetidos.
INLINE u32 key_curr_state (void)
Recoge el estado actual de los botones.
INLINE u32 key_prev_state (void)
Recoge el estado previo de los botones.
INLINE u32 key_is_down (u32 key)
Devuelve los botones que están actualmente pulsados.

key: botones que queremos comprobar que estén pulsados.

INLINE u32 key_is_up (u32 key)
Devuelve los botones que no están actualmente pulsados.

key: botones que queremos comprobar que no estén pulsados.

INLINE u32 key_was_down (u32 key)
Devuelve los botones que estaban previamente pulsados.

key: botones que queremos comprobar que estaban previamente pulsados.

INLINE u32 key_was_up (u32 key)
Devuelve los botones que no estaban previamente pulsados.

key: botones que queremos comprobar que no estaban previamente pulsados.

  • El estado de los botones se almacena en __key_curr y __key_prev. La función que actualiza ambas variables es key_poll(). Por ejemplo, para comprobar cuando A está actualmente pulsado, simplemente se enmascara __key_curr con KEY_A, el bit para A. Esto es lo que key_is_down() hace. Aunque KEY_DOWN_NOW() devuelve (prácticamente) lo mismo, se recomienda el uso de key_is_down().

Estados de transición

  • Volviendo al problema de pausar/despausar. El comportamiento erroneo que causa KEY_DOWN_NOW() se conoce como rebote de un botón. Esto es ocurre porque la macro solo comprueba el estado actual. Lo que necesitamos para pausar/despausar correctamente es comprobar cuando un botón va hacia abajo (la transición) en vez de que esté simplemente presionado. Cuando un botón es pulsado (el momento en el que va hacia abajo), estará presionado en el estado actual, pero no en el anterior (__key_curr&~__key_prev). Esto se realiza mediante key_hit().
INLINE u32 key_transit (u32 key)
Devuelve los botones que son diferentes del estado anterior.

key: botones que queremos comprobar que son diferentes.

INLINE u32 key_held (u32 key)
Devuelve los botones que siguen siendo presionados con respecto al estado anterior (presionados en el estado actual y en el anterior).

key: botones que queremos comprobar que siguen siendo presionados.

INLINE u32 key_hit (u32 key)
Devuelve los botones que están presionados (presionados en el estado actual pero no en el anterior).

key: botones que queremos comprobar que están presionados.

INLINE u32 key_released (u32 key)
Devuelve los botones que se han soltado (presionados en el estado previo pero no en el actual).

key: botones que queremos comprobar que se han soltado.

Tribools

  • Imagina que tienes un juego/demo en el cual puedes mover cosas. Para hacer que un personajes se mueve a izquierda y derecha por ejemplo, tendrías que hacer algo tal que así:
// variable x, velocidad dx
if(key_is_down(KEY_RIGHT))
     x += dx;
else if(key_is_down(KEY_LEFT))
     x -= dx;
  • Para mover a la derecha la x se incrementa; para mover a la izquierda se decrementa. Funciona correctamente pero no es un código bonito. Lo que hace el código es que dependiendo de dos elecciones, la variable incrementa (+), decrementa (-) o no cambia (0). Esto es precisamente los que es un 'tribool', una variable con tres posibles estados, en este caso +1, 0 y -1.
// tribool: 1 if {plus} on, -1 if {minus} on, 0 if {plus}=={minus}
INLINE int bit_tribool(u32 flags, uint plus, uint minus) {
     return ((flags>>plus)&1) - ((flags>>minus)&1);
}
INLINE int key_tri_horz (void)
"Tribool" horizontal (derecha, izquierda) = (+, -).
INLINE int key_tri_vert (void)
"Tribool" vertical (abajo, arriba) = (+, -).
INLINE int key_tri_shoulder (void)
"Tribool" de los botones de la espalda (R, L) = (+, -).
INLINE int key_tri_fire (void)
"Tribool" de los botones de disparo (A, B) = (+, -).
  • Aunque las funciones descritas arriba solo usan __key_curr, es facil escribir el código para que usen otro tipo de estado. Por ejemplo, para hacer uno izquierda/derecha con key_hit se haría de la siguiente manera:
x += DX*bit_tribool(key_hit(-1), KI_RIGHT, KI_LEFT);

Ejemplo

  • Puedes encontrarlo en el directorio /code/basic/key_demo. Este ejemplo ilustra como se usan las funciones para manejar botones. Se muestra una imagen en modo 4 de la GBA (240x160 8bit); los colores de los botones cambian dependiendo de que botón se presiona. Cuando se presiona un botón se colorea de rojo; cuando se suelta de amarillo; y cuando se mantiene presionado de verde.
  • El resultado:


Ejemplo key demo TONC.png Ejemplo key demo2 TONC.png


  • El código:

key_demo.c

#include <string.h>

#include <tonc.h>
#include "gba_pic.h"

#define BTN_PAL  5
#define CLR_UP   RGB15(27,27,29)

int main()
{
	int ii;
	u32 btn;
	COLOR clr;
	int frame=0;

	memcpy(vid_mem, gba_picBitmap, gba_picBitmapLen);
	memcpy(pal_bg_mem, gba_picPal, gba_picPalLen);

	REG_DISPCNT= DCNT_MODE4 | DCNT_BG2;

	while(1)
	{
		vid_vsync();
		// ralentiza la comprobación de los botones para que los cambios sean visibles.
		if((frame & 7) == 0)
			key_poll();

		// analiza el estado de cada botón.
		for(ii=0; ii<KI_MAX; ii++)
		{
			clr=0;
			btn= 1<<ii;

			if(key_hit(btn))
				clr= CLR_RED;
			else if(key_released(btn))
				clr= CLR_YELLOW;
			else if(key_held(btn))
				clr= CLR_LIME;
			else
				clr= CLR_UP;
			pal_bg_mem[BTN_PAL+ii]= clr;
		}
		frame++;
	}

	return 0;
}

Explicación del código:

  • BTN_PAL_ID es el índice donde comienza la parte de la paleta que se usará para los botones y CLR_UP es el color gris.
  • Mediante if((frame & 7) == 0) key_poll(); nos aseguramos que se pueden ver los cambios de colores en los botones. Solo que comprueba el estado de los botones una vez cada 8 'frames'.
  • Por último señalar que no se están cambiando los colores de los botones, simplemente el color de la paleta que usa los píxeles que usa el botón.

Enlaces relacionados