28 mayo 2005

Interrupciones. Primera aproximación.

No, no se trata de un nuevo inciso en nuestro proceso de aprendizaje. Es más, el tema que vamos a tratar no es sencillo, pero es crucial a la hora de programar. Se trata del manejo de interrupciones.

Una interrupción, como su propio nombre indica, es un evento que provoca que la CPU deje de ejecutar el código que estaba ejecutando y salte a una zona de código predefinida, que hará una tarea determinada (normalmente rápida) y vuelva a donde estaba en el preciso instante en que se produjo la interrupción.

Hay dos tipos de interrupciones: las generadas por la circuitería interna de la consola (interrupciones hardware) y aquéllas generadas por los programas (interrupciones software). Comenzaremos hablando de las primeras.

Para poder manejar las interrupciones, tendremos que acceder a una serie de registros. Son los siguientes:

REG_IME (0x0400:0208) es el registro maestro de interrupciones. Si queremos habilitar las interrupciones, deberemos poner este registro a 1.

REG_IE (0x400:0200) nos permite habilitar o deshabilitar cada uno de los 14 tipos de interrupción, a saber:

REG_IE[0] - INT_VBLANK (Vertical Blank - generada en la zona de retrazado vertical)
REG_IE[1] - INT_HBLANK (Horizontal Blank - generada en la zona de retrazado horizontal)
REG_IE[2] - INT_VCOUNT (Vertical Count - generada al principio de cada scanline)
REG_IE[3] - INT_TM0 (Timer 0 - temporizador 0)
REG_IE[4] - INT_TM1 (Timer 1 - temporizador 1)
REG_IE[5] - INT_TM2 (Timer 2 - temporizador 2)
REG_IE[6] - INT_TM3 (Timer 3 - temporizador 3)
REG_IE[7] - INT_COM (Communications - generada al finalizar una transmisión por el puerto de comunicaciones)
REG_IE[8] - INT_DMA0 (DMA - Acceso directo a memoria, canal 0)
REG_IE[9] - INT_DMA1 (DMA - Acceso directo a memoria, canal 1)
REG_IE[10] - INT_DMA2 (DMA - Acceso directo a memoria, canal 2)
REG_IE[11] - INT_DMA3 (DMA - Acceso directo a memoria, canal 3)
REG_IE[12] - INT_KEYS ( generada cuando alguno de los botones indicados en el registro REG_P1CNT se ha pulsado)
REG_IE[13] - INT_CART (generada cuando se extrae el cartucho de la consola)

Para habilitar un tipo de interrupción deberemos poner a 1 el bit correspondiente. Adicionalmente, algunos tipos necesitan de registros adicionales, como en el caso de INT_KEYS, o INT_VBLANK, INT_HBLANK e INT_VCOUNT, que hacen uso del registro REG_DISPSTAT.

REG_IF (0x400:0202) tiene la misma disposición que REG_IE y su misión es indicarnos qué interrupción se ha generado, poniendo a uno el bit correspondiente. Una vez que hemos tratado la interrupción, debemos escribir un 1 en este mismo registro en el bit que corresponda.

¿Cómo funciona el mecanismo de interrupciones? Una vez habilitadas, se da el siguiente proceso:
  1. Ocurre una interrupción. El procesador pasa a modo ARM (importante) y se guarda información del estado actual en la pila.
  2. La BIOS lee la dirección de memoria contenida en 0x0300:7FFC y el procesador salta a esa dirección.
  3. El código al que apuntaba el valor contenido en 0x0300:7FFC comienza a ejecutarse. Como hemos indicado anteriormente, se trata de código en modo ARM.
  4. Una vez finalizado el tratamiento, debemos indicar que la interrupción ha sido tratada, escribiendo un 1 en el bit correspondiente del registro REG_IF, y volver de la interrupción con la instrucción: bx lr
  5. Se recupera el estado que se almacenó en la pila y continúa la ejecución donde se dejó en el momento de "levantarse" la interrupción.
En principio, los pasos 1, 2 y 5 son automáticos (gestionados por la BIOS). Sólo tenemos que preocuparnos de poner en la dirección 0x0300:7FFC la dirección en la que se almacenará nuestro código gestor de interrupciones. Por comodidad, podemos definir un registro al efecto:
typedef void (*fp)(void);  //un puntero a una función de tipo void que no recibe parámetros
#define REG_IRQ_HANDLER (*(fp*)0x3007FFC)
Y una primera aproximación sería:
void gestor_interrupciones()
{
... tratamos la interrupción ...
}

REG_IRQ_HANDLER = gestor_interrupciones;

REG_IE = INT_CART;
REG_IME = 1;
De esta forma, cada vez que se genere una interrupción de las indicadas (en el ejemplo, la extracción del cartucho), se ejecutará la función 'gestor_interrupciones'.

Como vemos, en la Gameboy Advance no existe una tabla de vectores de interrupción. Por tanto, el módulo gestor de interrupciones debería discriminar qué interrupción se ha producido y llamar a la rutina de tratamiento correspondiente. También aprovecharemos para que, una vez ejecutada la rutina de control, escribiremos un 1 en el bit de REG_IF correspondiente, para dar por tratada la interrupción.

El módulo gestor de interrupciones debe ser extremadamente rápido, como todo código que se ejecute al levantarse una interrupción. Por tanto, lo escribiremos en ensamblador del ARM. Todo ello, y un ejemplo práctico de uso de los temporizadores, en la siguiente entrega.

27 mayo 2005

Inciso: Instalación del entorno de desarrollo

Manuel, un amable lector, me ha comentado por correo electrónico que no era capaz de instalar el compilador en su Linux. Quizás di algunas cosas por sentadas al comentar las herramientas que íbamos a utilizar, así que voy a detallar todo el proceso de descarga, instalación y compilación del gbasnake.

No voy a realizar una exposición muy detallada de momento del uso de herramientas auxiliares como Make, que ayudan en la compilación de proyectos complejos, ya que cada cual se monta el entorno de desarrollo un poco a su medida y según sus preferencias. Quizás más adelante aborde ese tema. Tras este inciso me gustaría seguir profundizando en el conocimiento del hardware de la consola y cómo manejarlo.

Entorno Windows

Entramos en la página devkit.tk y clicamos en la sección "Downloads". Aquí encontraremos un enlace al DevkitPro en Sourceforge. Descargamos el devkitARM en su versión para Windows. Una vez lo tengamos en nuestra máquina, procederemos a instalarlo. Para ello, basta con hacer doble clic en el ejecutable que acabamos de descargar. Nos solicitará un directorio donde descomprimirse (por ejemplo, c:\devkitarm\) y copiará todos los ficheros necesarios bajo ese directorio.

En la página devkit.tk, nos recomiendan un árbol de instalación, que sería algo parecido a:
  • c:\devkitPro
    • devkitARM
    • devkitPPC
    • msys
    • libgba
    • libmirko
    • libnds
    • proyectos
      • gba
He marcado en negrita lo que necesitamos mínimamente pare empezar. Msys es recomendable, pero no imprescindible. Msys es un sistema mínimo GNU para Windows, lo más parecido a trabajar con la shell de UNIX pero en el sistema de Microsoft.

Tan solo queda poner el directorio c:\devkitPro\devkitARM\bin\ en el PATH (para que el sistema pueda encontrar el compilador y ejecutarlo). Para compilar programas y generar los ficheros de ROMs (.gba) uso el siguiente script, que es el que usan en Drunken Coders en todos sus ejemplos. Pongo tanto la versión para consola de Windows como la "traducción" que he hecho a lenguaje de script.

CONSOLA WINDOWS

@echo off
REM just add .c file and .o file to the next two lines
set CFILES= gbasnake.c
set OFILES= gbasnake.o

set GAME= gbasnake
set SPECS=gba_mb.specs

set DEVDIR=C:\devkitPro\devkitARM
set DEVKITPRO=C:\devkitPro
set DEVKITARM=C:\devkitPro\devkitARM

PATH=%DEVDIR%\bin;%path%

REM set our paths so the linker knows were to look for the libs
set LIBDIR= %DEVDIR%\arm-elf\lib\interwork
set LIBDIR2= %DEVDIR%\libgba\lib

REM set our include directories so the compiler can find our include files
set INCDIR= %DEVDIR%\arm-elf\include
set INCDIR2= ..\..\..\include

REM the compiler and linker flags
set CFLAGS= -I. -I%INCDIR% -I%INCDIR2% -c -g -O2 -Wall -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer -ffast-math -mthumb -mthumb-interwork

set LDFLAGS= -mthumb -mthumb-interwork -specs=%SPECS% -L%LIBDIR% -L%LIBDIR2%

REM Compile the cfiles
arm-elf-gcc %CFILES% %CFLAGS%

REM link the o files
arm-elf-gcc -o %GAME%.elf %OFILES% %LDFLAGS%

REM gcc produces .elf exicutables...objcopy to .gba
arm-elf-objcopy -v -O binary %game%.elf %game%.gba

gbafix %game%.gba

REM remove all the ofiles
del %OFILES%
del %game%.elf

pause


SHELL SCRIPT

#!/bin/sh

GCC="arm-elf-gcc"

DEVDIR="/c/devkitPro/devkitARM"
DEVIKITPRO=/c/devkitPro/
DEVKITARM=$DEVDIR

PATH=$PATH:$DEVDIR/bin

CFILES=gbasnake
OFILES=gbasnake

GAME=gbasnake
SPECS="$DEVDIR/arm-elf/lib/gba_mb.specs"

LIBDIR=$DEVDIR/arm-elf/lib/interwork
LIBDIR2=$DEVDIR/lib

INCDIR=$DEVDIR/arm-elf/include
INCDIR2=$DEVDIR/include

CFLAGS="-I. -I$INCDIR -I$INCDIR2 -c -g -O2 -Wall -mcpu=arm7tdmi -mtune=arm7tdmi -fomit-frame-pointer -ffast-math -mthumb -mthumb-interwork "

LDFLAGS="-mthumb -mthumb-interwork -specs=$SPECS -L$LIBDIR -L$LIBDIR2"

# Compilar el fuente
$GCC $CFILES $CFLAGS

# Enlazar el objeto
$GCC -o $GAME.elf $OFILES $LDFLAGS

# Generar el .gba
arm-elf-objcopy -v -O binary $GAME.elf $GAME.gba

gbafix $GAME.gba

# Limpiar los archivos intermedios
rm $OFILES
rm $GAME.elf


Obviamente, el resultado de ambas soluciones es el mismo, ya que el compilador es el mismo, sólo cambiaría el entorno de desarrollo en el que estamos trabajando.

Ahora ya podemos descargar los fuentes completos de GBASnake (incluyendo algunos archivos de cabecera extra) y probar que nuestra instalación del compilador funciona.

Entorno Linux

El procedimiento es exacto al descrito anteriormente (salvo la parte de consola de Windows, que no es aplicable en este caso).

Entramos en la página devkit.tk y clicamos en la sección "Downloads". Aquí encontraremos un enlace al DevkitPro en Sourceforge. Descargamos el devkitARM en su versión para Linux. Una vez lo tengamos en nuestra máquina, procederemos a instalarlo. Para ello, basta descomprimir el fichero que nos hemos bajado. En mi caso ha sido algo como:

p2:/c/devkitPro# wget http://kent.dl.sourceforge.net/sourceforge/devkitpro/devkitARM_r12-linux.tar.bz2

p2:/c/devkitPro# tar xjvf devkitARM_r12-linux.tar.bz2


He respetado la misma estructura de directorios que se usa en Windows con Msys

Nos solicitará un directorio donde descomprimirse (por ejemplo, c:\devkitarm\) y copiará todos los ficheros necesarios bajo ese directorio.
  • /c/devkitPro
    • devkitARM
    • devkitPPC
    • msys
    • libgba
    • libmirko
    • libnds
    • proyectos
      • gba
Lo he hecho por comodidad, para no tener que escribir el script de compilación dos veces. En cualquier caso, las rutas de instalación son totalmente modificables. Probamos el script y vemos que todo funciona correctamente.

No olvidéis añadir la ruta de los binarios del compilador al PATH, en nuestro caso:

export PATH=PATH:/c/devkitPro/devkitARM/bin

NOTA IMPORTANTE:

La versión actual del compilador para Linux tiene un BUG (que explican en este enlace y que me ha hecho volverme loco durante un par de noches). Básicamente, hay que parchear el fichero arm-elf/lib/gba_crt0.s añadiendo la línea que falta en la función SkipEWRAMClear (la marco en negrita)

@---------------------------------------------------------------------------------
SkipEWRAMClear: @ Clear Internal WRAM to 0x00
@---------------------------------------------------------------------------------
mov r0, #3
lsl r0, #24 @ r0 = 0x3000000
ldr r1, =__iwram_end
sub r1, r0
bl ClearMem


y recompilando esa parte con:

make CRT=gba

¡¡¡Felices compilaciones!!!

18 mayo 2005

Leyendo los botones

Cuando hicimos el port del juego Snake, ya tuvimos que implementar la lectura de los botones para poder manejar la serpiente y reiniciar la partida. Ahora vamos a entrar un poco más en profundidad en este tema.

La Gameboy Advance posee 10 botones, como ya se ha comentado. El manejo de estas entradas se hace mediante dos registros de Entrada/Salida, que son los siguientes:

REG_KEYS (0x0400:0130)

Cada bit del 0 al 9 hace referencia a cada uno de los botones:

REG_KEYS[0] -> A
REG_KEYS[1] -> B
REG_KEYS[2] -> Select
REG_KEYS[3] -> Start
REG_KEYS[4] -> Derecha
REG_KEYS[5] -> Izquierda
REG_KEYS[6] -> Arriba
REG_KEYS[7] -> Abajo
REG_KEYS[8] -> R
REG_KEYS[9] -> L

El valor de cada bit es 1 si el botón no está pulsado y 0 si lo está, de forma que REG_KEYS = 0x03FF en estado de "reposo".

REG_KEYCNT (0x0400:0132)

Este registro se usa para controlar las "interrupciones del teclado". Tiene la misma configuración que REG_KEYS pero añadiendo dos bits:

REG_KEYCNT[0] -> A
REG_KEYCNT[1] -> B
REG_KEYCNT[2] -> Select
REG_KEYCNT[3] -> Start
REG_KEYCNT[4] -> Derecha
REG_KEYCNT[5] -> Izquierda
REG_KEYCNT[6] -> Arriba
REG_KEYCNT[7] -> Abajo
REG_KEYCNT[8] -> R
REG_KEYCNT[9] -> L
REG_KEYCNT[E] -> I
REG_KEYCNT[F] -> Op

I activa o desactiva las interrupciones del teclado. Op determina la forma de calcular si lanzar la interrupción o no. Si vale 0, se aplicará una operación OR a los botones indicados, mientras que si vale 1, aplicará una operación AND.

Ejemplo: si cargamos en este registro el valor binario 1100000000001111 (0xC00F), conseguiremos que se genere una interrupción cuando los botones A, B, Select y Start se presionen simultáneamente.

Bien, ya tenemos todo para leer las pulsaciones de los botones, pero nos falta algo muy importante. Hasta ahora sabemos cuándo un botón está pulsado o no. Realmente esta no es la información que necesitamos, sino que lo lógico es saber si un botón ha cambiado de estado. ¿Por qué? Imaginemos el botón Start con su función típica de activar la pausa del juego. Leemos el estado de los botones y detectamos que Start fue pulsado. Entramos en el estado "pausa", del que saldremos cuando detectemos nuevamente que Start está pulsado. El problema es que el microprocesador de la Gameboy Advance ejecuta código mucho más rápido de lo que imaginamos. Por tanto, una pequeña pulsación de Start por nuestra parte puede implicar muchas pausas/continuaciones. De hecho, podemos considerar que el estado en el que quedará el juego tras soltar el botón es totalmente aleatorio.

Resumiendo, lo que nos interesan son los cambios de estado de los botones. Y podemos conseguir esa información mediante estas funciones:
void LeerBotones()
{ bot_ant= bot_act; bot_act= REG_KEYS; }

u16 BotonCambio(u16 boton)
{ return (bot_act^bot_ant) & boton; }

u16 BotonMantiene(u16 boton)
{ return ~(bot_act|bot_ant) & boton; }

u16 BotonPulsado(u16 boton)
{ return (~bot_act&bot_ant) & boton; }

u16 BotonSoltado(u16 key)
{ return (bot_act&~bot_ant) & boton; }

Lo que haremos será llamar a LeerBotones() cada ciclo de nuestro programa, y de esa forma podremos saber si un botón en concreto ha cambiado de estado.

Por ejemplo, BotonPulsado(KEY_START) devolverá 1 si se pulsó el botón Start, y 0 en caso contrario. Este valor lo devolverá una única vez. Hasta que no soltemos y volvamos a pulsar el botón, la función no volverá a devolver 1.

Con estos conocimientos podemos hacer prácticamente todo en cuanto a lectura de botones. Hemos aprovechado para añadir a nuestra serpiente el clásico botón de pausa (Start).

Código fuente.
ROM.

Un acercamiento superficial al hardware

Como todos sabréis, Gameboy Advance es un sistema portátil de videojuegos creado por Nintendo. Las características más importantes de su hardware son:
  • Procesador ARM de 32 bits, corriendo a 16,78 MHz.
  • Pantalla LCD de 240x160 pixels, capaz de visualizar 32768 colores (15 bits).
  • Memoria dividida en diferentes zonas. La Entrada/Salida está mapeada en memoria.
  • 10 botones (cruceta de 4 direcciones, A, B, L, R, Select y Start).
  • Sonido estéreo, con 6 canales disponibles, 4 de ellos ya presentes en la Gameboy original y dos conversores Digital-Analógico.
La CPU puede ejecutar dos juegos de instrucciones: el ARM, que es un juego de instrucciones de 32 bits, y el THUMB, que usa instrucciones de 16 bits. THUMB es un subconjunto de instrucciones del juego ARM. El procesador cuenta además con 16 registros de 32 bits, si bien los tres últimos no son de propósito general (r13 = SP, r14 = LR y r15 = PC). Además, en modo THUMB sólo están disponibles los 8 primeros.

Cuando trabajemos en zonas de memoria con tamaño de palabra de 32 bits, usaremos código ARM, mientras que preferiremos usar THUMB para las zonas con palabra de 16 bits.

La memoria está dividida en diferentes áreas:

ROM del sistema (0x0000:0000 - 0x0000:3FFF [16KB] palabras de 32 bits).
Es la BIOS del sistema. No se puede leer su contenido, sólo ejecutarse.

EWRAM (0x0200:0000 - 0x0203:FFFF [256KB] palabras de 16 bits).
RAM externa de trabajo (External Work RAM). Disponible para código y datos. En caso de usar un cable multiboot, aquí se almacenaría el código descargado y empezaría la ejecución.

IWRAM (0x0300:0000 - 0x0300:7FFF [32KB] palabras de 32 bits).
RAM interna de trabajo (Internal Work RAM). También disponible para código y datos. Es la memoria más rápida, de hecho se encuentra embebida dentro del procesador.

IORAM (0x0400:0000 - 0x0401:03FF [1KB] palabras de 16 bits).
Zona de Entrada/Salida mapeada en memoria. En esta zona se encuentran mapeados los registros de Entrada/Salida, que nos servirán para prácticamente controlar todo el hardware.

PALRAM (0x0500:0000 - 0x0500:03FF [1KB] palabras de 16 bits).
Zona de almacenamiento de paletas de vídeo. Cada una contiene 256 entradas de 15 bits. La primera de ellas para fondos, la segunda para sprites.

VRAM (0x0600:0000 - 0x0601:7FFF [96KB] palabras de 16 bits).
Memoria de vídeo. La estructura de los datos almacenados dependerá del modo de vídeo en que estemos trabajando.

OAM (0x0700:0000 - 0x0700:03FF [1KB] palabras de 32 bits).
Memoria de atributos de objetos (Object Attribute Memory). En esta zona se controlan los sprites.

PAKROM (0x0800:0000 - variable [variable] palabras de 16 bits).
En esta zona se mapea la ROM de los cartuchos de software. Normalmente será la zona donde comience la ejecución, salvo que hayamos arrancado mediante un cable multiboot.

CartRAM (0x0E00:0000 - variable [variable] palabras de 8 bits).
Esta es la RAM usada en los cartuchos para guardar partidas, datos de configuración, etc. Su longitud es variable. Puede ser de tipo SRAM, FlashROM o EEPROM. En cualquier caso, su comportamiento será el mismo.

La pantalla de la GBA, como hemos comentado antes, consta de 240x160 pixeles. Se refresca cada 280,896 ciclos de CPU, o sea, alrededor de los 59,73 Hz. Soporta 6 modos de funcionamiento, algunos basados en pixeles y otros en bloques (tiles).

La consola da soporte a 128 sprites simultáneos, de hasta 64x64 pixeles.

Como vemos, el hardware de la Gameboy Advance es lo suficientemente complejo como para no intentar abarcarlo todo en una primera aproximación. Iremos profundizando juntos en su conocimiento a medida que vayamos programando y vayamos necesitando afinar más para conseguir aquellos efectos que nos propongamos.



Enlaces:

15 mayo 2005

Primer proyecto rápido

Cuando uno empieza a programar, está ávido de ver resultados. En este caso, la sensación es la misma, así que tras un vistazo rápido a la documentación para aprender a generar gráficos en la pantalla de la consola y a detectar la pulsación de sus botones nos lanzamos a la tarea.

Lo que se me ha ocurrido ha sido hacer una conversión directa del juego de la serpiente (snake) que realicé para la competición de juegos en BASIC para Spectrum organizada por Bytemaniacos. Es una versión muy sencilla, sin incrementos de velocidad, ni gráficos. Tan solo una serpiente recogiendo frutas por la pantalla. Así que procedemos a descargar el código fuente en BASIC para proceder a la conversión.

Si miráis el código es muy sencillo. Se trata de un bucle en el que se mueve la serpiente, se comprueba si ha chocado con algo o si ha comido una fruta y se lee el teclado para ver si hay que cambiar de dirección. Este esquema ha sido adaptado de forma cutre salchichera a lenguaje C en 10 minutos, para poder ver resultados rápidamente.

La consola tiene registros mapeados en memoria. Pero para acceder a la mayoría de ellos se han definido constantes y macros en los archivos de cabecera, de forma que no tengamos que recordar las direcciones de memoria.

Los principales problemas que he encontrado han sido:

¿Cómo dibujar objetos en pantalla?
La consola tiene varios modos de vídeo. Más adelante estudiaremos todos. De momento basta con saber que el Modo 3 nos permite un acceso lineal a la memoria de vídeo, donde el color de cada pixel vendrá determinado por un valor de 16 bits. Para acceder a este modo de vídeo, lo haremos de la siguiente manera:
SetMode(MODE_3 | BG2_ENABLE);
Tanto la función como las constantes están definidas en los archivos de cabecera.

Para colorear un punto de la pantalla accederemos a la siguiente variable (definida en los archivos de cabecera):
VideoBuffer[x + y * SCREEN_WIDTH] = color;
donde SCREEN_WIDTH está definida en los archivos de cabecera (la pantalla en Modo 3 es de 240x160) y color, como hemos comentado anteriormente, es un valor entero de 16 bits, que podemos generar de la siguiente forma:
color = RGB16(componente_roja, componente_verde, componente_azul);
donde los valores de cada componente son enteros de 5 bits (o sea, entre 0 y 31).

¿Cómo leer la pulsación de los botones de la consola?

Podemos consultar el valor del registro REG_KEYS de la siguiente forma:
!(REG_KEYS & botón)
de forma que obtendremos 1 (true) si el botón indicado ha sido pulsado. Las constantes para los botones son las siguientes:
  • KEY_LEFT (pad izquierda)
  • KEY_RIGHT (pad derecha)
  • KEY_UP (pad arriba)
  • KEY_DOWN (pad abajo)
  • KEY_A (botón A)
  • KEY_B (botón B)
  • KEY_L (gatillo L)
  • KEY_R (gatillo R)
  • KEY_SELECT (botón Select)
  • KEY_START (botón Start)
¿Cómo "imprimir caracteres" en la pantalla?

Aquí no vale usar printf e imprimir una cadena en pantalla. Así que necesitaremos definirnos un juego de caracteres y construirnos las rutinas de escritura en pantalla. De momento sólo necesitamos números para el marcador, así que lo hemos hecho "a las bravas", definiendo un array de 8x8 para cada carácter numérico, poniendo un 1 en los pixels que deben ir iluminados. Por ejemplo, para el número cero (0):
    {
{0,0,0,0,0,0,0,0},
{0,0,1,1,1,0,0,0},
{0,1,0,0,0,1,0,0},
{1,0,0,0,0,0,1,0},
{1,0,1,1,1,0,1,0},
{1,0,0,0,0,0,1,0},
{0,1,0,0,0,1,0,0},
{0,0,1,1,1,0,0,0}
},

Por tanto, definimos una variable global llamada caracteres en la que almacenamos nuestro rudimentario juego de caracteres numéricos, del 0 al 9. La rutina de impresión es tan sencilla como:

void PintaCaracter(caracter,x,y,color,color_fondo)
{
unsigned char tx, ty;
for(tx = 0; tx < 8; tx ++)
{
for(ty = 0; ty < 8; ty ++)
{
if(caracteres[caracter][ty][tx] == 1)
{
VideoBuffer[(x+tx) + (y+ty) * SCREEN_WIDTH] = color;
}
else
{
VideoBuffer[(x+tx) + (y+ty) * SCREEN_WIDTH] = color_fondo;
}
}
}
}
El juego va muy rápido, ¿cómo lo ralentizamos?

La solución buena y elegante es efectuar la sincronización del movimiento mediante interrupciones. En este primer ejemplo no lo vamos a hacer así, así que emplearemos métodos de espera activa, tanto para ralentizar cada iteración (mediante un bucle for vacío), como para esperar a la pulsación de una tecla (mediante un while del que no se sale hasta que se pulsa dicha tecla). Tampoco esperamos al redibujado de la pantalla para escribir en la memoria de vídeo y así evitar molestos parpadeos. Todas estas técnicas las iremos viendo más adelante, conforme vayamos profundizando en nuestro conocimiento del hardware de la Gameboy Advance.

Y con estas salvedades y un código muy chapucero, tenemos en pantalla la primera versión de nuestro juego de la serpiente. Cuando choquemos, hay que pulsar Start para volver a empezar la partida. Aquí tenéis la ROM con el juego listo para ser ejecutado.


Ver mi primer código corriendo en la GBA no tiene precio

Preparando las herramientas

Para comenzar a trabajar con la consola necesitamos urgentemente un compilador y algo de documentación. Aclarar que yo voy a trabajar bajo Windows, pero no creo que haya ningún inconveniente en usar las herramientas para Linux.

El compilador se puede descargar de este enlace, así como algo de información en la página devkit.tk.

También se puede descargar un kit de desarrollo como DevKit Advance.

Por supuesto, probar el código generado en la consola se hace muy pesado, así que no está de más instalarnos un emulador de Gameboy Advance, como Visualboy Advance.

Para conseguir documentación, de momento he echado mano de estos enlaces:
Una vez instalado el compilador, ya podemos empezar a juguetear. Lo primero que he hecho ha sido descargar los tutoriales de Drunken Coders y probar a ver que se compilan perfectamente y se genera el archivo .gba correspondiente.

Pues bien, ya tenemos nuestro entorno de trabajo listo. Se da por hecho el uso de un editor de texto para generar el código fuente en C o ensamblador (de momento comenzaremos por usar C). Ya podemos comenzar a crear nuestro software para la Gameboy Advance.

14 mayo 2005

Presentación y propósito

Hace casi 20 años, mi abuelo, Andrés Valero, me hizo un regalo que posiblemente cambió el resto de mi vida: un ordenador ZX Spectrum +.

Desde aquella época, siempre me llamó mucho la atención llegar a ser un programador de juegos, trabajar en los estudios españoles de aquella época (Dinamic, Topo, Opera...). No obstante, quizás porque me pilló demasiado joven, no lo conseguí y, aunque estudié la carrera, trabajo y vivo de la informática, siempre quedó aquella espinita clavada de no haber programado ningún juego para aquel entrañable ordenador.

Estas navidades recibí un nuevo regalo. Esta vez, mi novia me obsequió con una Gameboy Advance SP. He adquirido un cartucho Flash y este fin de semana, cinco meses después, comienzo a quitarme la espinita. Quizás programar juegos para ZX Spectrum hoy en día sea un poco anacrónico (aunque hay gente que lo sigue haciendo y con excelentes resultados). Pero el principal problema es que no tiene un público "mayoritario". Así que la idea es programar algunas cosillas para la Gameboy Advance. Nuevamente llego un poco tarde, con su hermana mayor (Nintendo DS) ya en el mercado. Pero puedo asegurar una cosa: ver ejecutándose un trocito de código propio en la consola es una experiencia incomparable.

Como decía, este fin de semana me he puesto manos a la obra. He instalado el compilador y he empezado a trastear. El resultado de todas esas investigaciones, presentes y futuras, serán la fuente de alimentación de este blog.