- Una vez configurado nuestro entorno de desarrollo y construido nuestro cable XBoo para probar nuestros programas en un hardware real, es hora de empezar a desarrollar.
- En este primer tutorial se hará un acercamiento a como trabajar con las diferentes secciones de memoria de la GBA. En el primer ejemplo se verá lo importante que es trabajar con una librería como es tonclib.
¿Qué es TONC?
- Su autor es Jasper Vijn (conocido como 'cearn'), puedes encontrar más información sobre él en su página web
- Según su autor, TONC es una guía para la programación de la GameBoy Advance (GBA). En este tutorial encontrarás información sobre varios aspectos relacionados con la programación de la GBA, como por ejemplo los sistemas de vídeo, tratamiento de los botones y la DMA (Acceso Directo a Memoria), pero también temas relacionados con las interrupciones, llamadas a la BIOS y trucos gráficos como el 'blending', 'windowing' y el modo 7. Todos los capítulos incluyen una demo no trivial sobre ellos.
- Tonclib es la librería que acompaña al conjunto de tutoriales conocidos como TONC.
CPU de la GBA
- CPU: 32-bit ARM7tdmi (16.78MHz). Dos conjunto de instrucciones:
- Código ARM (instrucciones de 32-bit).
- THUMB (instrucciones de 16-bit).
- Las instrucciones THUMB son un subconjunto del conjunto de instrucciones ARM; ya que las instrucciones son más cortas, el código puede ser más pequeño, pero su poder también es más reducido. Es recomendable que el código normal sea código THUMB (en la ROM/EWRAM), y el código crítico sea código ARM (en el IWRAM).
Secciones de la memoria
Área
|
Comienzo
|
Fin
|
Longitud
|
Tamaño del puerto
|
Descripción
|
SYSTEM ROM
|
0000:0000h
|
0000:3FFFh
|
16 KB
|
32-bit
|
Memoria BIOS. Puedes ejecutarla, pero no leerla.
|
EWRAM
|
0200:0000h
|
0203:FFFFh
|
256 KB
|
16-bit
|
External Work RAM. Disponible para tu código y datos.
|
IWRAM
|
0300:0000h
|
0300:7FFFh
|
32 KB
|
32-bit
|
Disponible para código y datos. Debido a que el bus es de 32 bit y el hecho de que está embebida en la CPU, la hacen la sección de memoria más rápida.
|
IORAM
|
0400:0000h
|
0401:03FFh
|
1 KB
|
32-bit
|
En esta sección es donde controlas los gráficos, el sonido, los botones y otras características.
|
PAL RAM
|
0500:0000h
|
0500:03FFh
|
1 KB
|
16-bit
|
Memoria para las dos paletas de colores, contiene 256 entradas de 15 bit cada una. La primera para los fondos, la segunda para los sprites.
|
VRAM
|
0600:0000h
|
0601:7FFFh
|
96 KB
|
16-bit
|
RAM de video. Aquí es donde se almacenan los datos usados para fondos y sprites (objetos).
|
OAM
|
0700:0000h
|
0700:03FFh
|
1 KB
|
32-bit
|
Memoria para los Atributos de los Objetos (Object Attribute Memory). Aquí es donde controlas los sprites.
|
Área
|
Comienzo
|
Fin
|
Longitud
|
Tamaño del puerto
|
Descripción
|
ROM
|
0800:0000h
|
variable
|
variable
|
16-bit
|
Aquí es donde se encuentra el juego y donde la ejecución empieza, excepto cuando estas corriendo el juego usando un cable multiboot. El tamaño varía, pero el límite es de 32 MB.
|
RAM
|
0E00:0000h
|
variable
|
variable
|
8-bit
|
Aquí es donde los se almacenan los datos guardados. La RAM del cartucho puede ser de la forma SRAM, Flash ROM o EEPROM. El tamaño total es variable, pero 64 kb en una buena medida.
|
- Las áreas VRAM, IWRAM, EWRAM, IORAM y PAL RAM son puestas a cero al arrancar por la BIOS.
- Para juegos simples y demos sera suficiente con:
- Cargar los datos de tus gráficos en PAL y VRAM.
- Ocuparse de las interacciones con IORAM y OAM.
Cartucho vs multiboot
- Hay dos tipos diferentes de construcciones gba: 'cartucho' y 'multiboot'. Una construccion (build) cartucho coloca el código principal y los datos en la ROM de 32 MB (
0800:0000h
) de un cartucho. Una construcción multiboot lo pone en la EWRAM de 256 kb (0200:0000
). Los juegos comerciales son obviamente construcciones cartucho, pero hacen uso de la construcción multiboot para hacer posible un multijugador con solo un cartucho.
- A parte del tamaño máximo, hay poca diferencia entre ambos con respecto a la jugabilidad. Para aplicaciones caseras (homebrew), multiboot tiene una ventaja, ya que puedes cargar el juego en el hardware sin necesidad de usar un caro flashcart (cartucho flash); puedes construirte tu propio cable PC-GBA facilmente.
- La selección del tipo de construcción se hace en la variable SPECS del makefile. Para construcciones cartucho hay que usar
-specs=gba.specs
y para construcciones multiboot -specs=gba_mb.specs
. Si la variable TARGET termina con _mb, la plantilla del makefile lo enlazará como un juego multiboot.
Primer programa
- Estos ejemplos han sido desarrollados por cearn como un apoyo a su tutorial TONC. La intención es dibujar tres puntos en la pantalla.
El registro de pantalla REG_DISPCNT
- Para este primer ejemplo se hará uso del registro de pantalla REG_DISPCNT (
0400:0000h
). Este registro contiene los bits principales para el control de la pantalla.
F
|
E
|
D
|
C
|
B
|
A
|
9
|
8
|
7
|
6
|
5
|
4
|
3
|
2 1 0
|
OW
|
W1
|
W0
|
Obj
|
BG3
|
BG2
|
BG1
|
BG0
|
FB
|
OM
|
HB
|
PS
|
GB
|
Mode
|
bits
|
nombre
|
define
|
descripción
|
0-2
|
Mode
|
DCNT_MODEx. DCNT_MODE#
|
Configura el modo de video. 0, 1, 2 son los modos 'tiled' y 3, 4, 5 son los modos 'bitmap'.
|
3
|
GB
|
DCNT_GB
|
Activado si el cartucho es un juego de GBC. Bit de solo lectura. 0=GBA, 1=GBC.
|
4
|
PS
|
DCNT_PAGE
|
Selección de página. Solo para los modos 4 y 5 donde se puede usar el cambio de página para una animación más suave. 0-1=Frame 0-1.
|
5
|
HB
|
DCNT_OAM_HBL
|
1=Permite el acceso a OAM durante el HBlank. OAM esta normalmente bloqueada durante el VDraw. Previene el parpadeo de la pantalla cuando hay demasiados sprites en una scanline.
|
6
|
OM
|
DCNT_OBJ_1D
|
Configura si los sprites almacenados en la VRAM usan una dimensión o dos. 1=1d, los tiles están almacenados secuencialmente. 2=2d, cada fila de tiles es almacenada 32x64 bytes desde el comienzo de la fila previa.
|
7
|
FB
|
DCNT_BLANK
|
1=Fuerza un 'blank'.
|
8-C
|
BG0-BG3, Obj
|
DCNT_BGx, DCNT_OBJ. DCNT_LAYER#
|
Activa el renderizado del fondo correspondiente y de los sprites. 0=Off, 1=On.
|
D-F
|
W0, W1, OW
|
DCNT_WINx, DCNT_WINOBJ
|
Permite el uso de la ventana 0, 1 y la ventana de Objeto, respectivamente. Las ventanas pueden usarse para enmascarar/ocultar determinadas áreas (como hacia la lampara en el Zelda:LTTP). 0=Off, 1=On.
|
Versión 1
- Puedes encontrarlo en el directorio
/code/basic/first
. Este primer ejemplo no hace uso de tonclib.
int main()
{
*(unsigned int*)0x04000000 = 0x0403;
((unsigned short*)0x06000000)[120+80*240] = 0x001F;
((unsigned short*)0x06000000)[136+80*240] = 0x03E0;
((unsigned short*)0x06000000)[120+96*240] = 0x7C00;
while(1);
return 0;
}
Explicación del código:
- La programación de la GBA es a bajo nivel. Interactúas directamente con la memoria, y no a traves de multiples capas de abstracción interpuestas por las APIs.
- Para trabajar con partes accesibles de la memoria como por ejemplo
0x04000000
y 0x06000000
necesitamos crear punteros a ellos.
- La palabra en la posición de memoria
0x04000000
(el registro REG_DISPCNT) es el que contiene los principales bits para controlar la pantalla.
- Desreferenciando el puntero
*(unsigned int*)0x04000000
accedemos al contenido del puntero. Es decir se convierte en una variable a la cual podemos asignar valores, leer su contenido o realizar operaciones bit, etc. En este caso le asignamos el valor 0x0403
:
F
|
E
|
D
|
C
|
B
|
A
|
9
|
8
|
7
|
6
|
5
|
4
|
3
|
2 1 0
|
OW
|
W1
|
W0
|
Obj
|
BG3
|
BG2
|
BG1
|
BG0
|
FB
|
OM
|
HB
|
PS
|
GB
|
Mode
|
0
|
0
|
0
|
0
|
0
|
1
|
0
|
0
|
0
|
0
|
0
|
0
|
0
|
0 1 1
|
Por lo que le decimos a la GBA que use el modo de video 3 y active el fondo 2. En próximas lecciones se explicará que significa esto.
- Con la VRAM trabajamos en su forma de puntero y no como una variable
((unsigned short*)0x06000000)
. Debido a esto VRAM funciona como un array y no como una variable. Ya que en el modo 3 la VRAM se comporta como un 'bitmap' de 16-bit (240x160 y 32760 colores), cuando creamos un puntero de media palabra a el, cada entrada es un pixel. Y debido a como funcionan las matrices en C, algo de la forma array[y*width + x]
es el contenido de la coordenada (x, y) y lo rellenamos con un color BGR 5.5.5:
- 0x001F = 00000 00000 11111 = rojo
- 0x03E0 = 00000 11111 00000 = verde
- 0x7C00 = 11111 00000 00000 = azul
- La linea
while(1);
es el bucle infinito del programa.
Versión 2
- Puedes encontrarlo en el directorio
/code/basic/second
. En este ejemplo se hace uso de tonclib. Si bien el código produce los mismo resultados que la versión 1, hay diferencias destacables en el código que facilitan su legibilidad:
- Se usan #defines para los nombres de las constantes y para las diferentes secciones de memoria.
- Creación de typedefs.
- En vez de dibujar los pixeles mediante un acceso a un array, se usa una subrutina.
#include <tonc.h>
int main()
{
REG_DISPCNT = DCNT_MODE3 | DCNT_BG2;
m3_plot( 120, 80, RGB15(31, 0, 0) ); // o CLR_RED
m3_plot( 136, 80, RGB15( 0,31, 0) ); // o CLR_LIME
m3_plot( 120, 96, RGB15( 0, 0,31) ); // o CLR_BLUE
while(1);
return 0;
}
Explicación del código:
- Del archivo tonc_types.h utilizamos:
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef u16 COLOR;
#define INLINE static inline
- Del archivo memmap.h utilizamos:
#define MEM_IO 0x04000000
#define MEM_VRAM 0x06000000
#define REG_DISPCNT *((volatile u32*)(MEM_IO+0x0000))
- Del archivo tonc_memdef.h utilizamos:
#define DCNT_MODE3 0x0003
#define DCNT_BG2 0x0400
- Del archivo tonc_video.h utilizamos:
#define SCREEN_WIDTH 240
#define SCREEN_HEIGHT 160
#define vid_mem ((u16*)MEM_VRAM)
INLINE void m3_plot(int x, int y, COLOR clr)
{ vid_mem[y*SCREEN_WIDTH+x]= clr; }
#define CLR_RED 0x001F
#define CLR_LIME 0x03E0
#define CLR_BLUE 0x7C00
INLINE COLOR RGB15(u32 red, u32 green, u32 blue)
{ return red | (green<<5) | (blue<<10); }
- Las funciones
inline
tienen todos los beneficios de las macros pero son como las funciones en su sintaxis y son resueltas en tiempo de compilación. Solo se usan en las definiciones de funciones cortas.
Versión 3
- Hay muchos caminos para llegar a Roma. El siguiente código muestra otra forma de dibujar 3 pixeles en la pantalla.
#include <tonc.h>
int main()
{
REG_DISPCNT= DCNT_MODE3 | DCNT_BG2;
m3_mem[80][120]= CLR_RED;
m3_mem[80][136]= CLR_LIME;
m3_mem[96][120]= CLR_BLUE;
while(1);
return 0;
}
Explicación del código:
- Para el ejemplo se utilizan ademas de lo visto en la versión 2, los siguientes defines y typedef de tonclib:
// typedef para toda una linea en modo 3
typedef COLOR M3LINE[M3_WIDTH];
// m3_mem es una matriz; m3_mem[y][x] es un pixel (x,y)
#define m3_mem ((M3LINE*)MEM_VRAM)
- En este caso se utiliza un array typedef llamado
M3LINE
lo que facilita que se pueda mapear la VRAM como una matriz donde cada pixel es representado por un elemento de la matriz.
Notas sobre la programación en la GBA
- Programar para una consola es substancialmente diferente a programar para un PC, especialmente si hablamos de la GBA. No hay sistema operativo, ni complejas API para aprender, eres solo tu frente a la memoria. Y solo tienes una CPU a 16 MHz con 96 kB de memoria de video.
- Toda CPU tiene un tipo de dato nativo, también conocido como palabra (word), o hablando en C, el 'int'. Generalmente hablando, la CPU esta mejor equipada para manejar ese tipo de datos que otro. La CPU de la GBA tiene como tipo de dato nativo 32-bit.
- Las variables pueden dividirse en dos grupos:
- Worker variables (piensa en los registros): variables locales y los parámetros de las funciones. Deben ser de 32-bit.
- Memory variables: elementos que están en memoria (arrays, variables globales, structs). Se pueden beneficiar de ser lo mas pequeñas posibles.
- Usa variables de 32-bit cuando puedas y otras cuando debas.
Enlaces Realacionados