TONC: Curso programacion GBA I

  • 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.


Contenido

¿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.
  • Cartucho de juegos:
Á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.


Primer programa TONC.png

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