Esta pagina se ve mejor con JavaScript habilitado

GameBoy Dev10: Memory Bank Switching

 ·  🎃 kr0m

Es recomendable la lectura de los artículos anteriores para comprender mejor el actual:


En este artículo aprenderemos como la GameBoy gestiona las distintas posiciones de memoria, cual es el propósito de cada región y como nos afecta en cuanto a espacio de almacenamiento ROM/RAM según las capacidades del cartucho. Conociendo este funcionamiento seremos capaces de leer desde los bancos ROM y leer/escribir desde los bancos RAM del cartucho ampliando de este modo las posibilidades de nuestro juego.

Vamos a dividir el artículo en varias secciones para una mayor claridad:


Espacio de memoria

El espacio de direcciones de memoria de la GameBoy es de 16 bits lo que nos dá 65535 posiciones de 8 bits cada una que en kilobytes equivale a 64KB, el rango de direcciones empieza en 0x0000 y termina en 0xFFFF.

Este espacio de memoria está dividido en varias partes, cada parte se reserva para un uso específico.

El cartucho básico de GameBoy está limitado a 32KB(2 bancos de 16KB) de almacenamiento y la RAM a los 8KB de la propia consola, pero mediante una técnica llamada Bank Switching es posible seleccionar un banco de ROM/RAM distinto y de este modo acceder a su contenido, esto amplía mucho las posibilidades de nuestros juegos ya que el límite de almacenamiento ROM sube a 8MB y el de RAM a 128KB.

El bank switching tiene una limitación gestionando el acceso a los bancos ROM y es que el primer banco del cartucho queda permanentemente asignado al área de memoria: 0x0150-0x3FFF, pudiendo mapear los otros bancos ROM solo en el área: 0x4000-0x7FFF.

ROM cartucho VRAM
RAM cartucho Working RAM
Copia de la Working RAM-512B OAM/IO/HighRAM/Interrupts

La imágenes han sido extraídas de un video muy interesante el cual recomiendo su visualización.

También recomiendo este otro video donde se explica el propósito de las regiones de la RAM y como a través de un bug se puede explorar y modificar partes de la memoria de la consola, sabiendo esto y localizando la parte a modificar consigue modificar el flujo de ejecución del juego.

Y este otro con mucha información técnica acerca del funcionamiento de la GameBoy.

En la siguiente tabla podemos ver de forma rápida las direcciones de memoria de cada área:

Mem.Área Función Tamaño
0x0000 - 0x00FF Inicio de la GB y vectores de interrupción 255 bytes
0x0100 - 0x014F Cabecera del cartucho 79 bytes
0x0150 - 0x3FFF Entrypoint: ROM-Bank0 fijo - ROM cartucho 16.047 bytes
0x4000 - 0x7FFF ROM-Seleccionable - ROM cartucho 16.383 bytes
0x8000 - 0x9FFF VRAM 8.191 bytes
0xA000 - 0xBFFF RAM-Seleccionable - RAM cartucho 8.191 bytes
0xC000 - 0xDFFF WRAM 8.191 bytes
0xE000 - 0xFDFF EchoRAM 7.679 bytes
0xFE00 - 0xFE9F OAM 159 bytes
0xFEA0 - 0xFEFF No utilizado 95 bytes
0xFF00 - 0xFF7F Registros I/O 127 bytes
0xFF80 - 0xFFFE High RAM 126 bytes
0xFFFF - 0xFFFF Control de interrupciones 1 byte

Hay una peculiaridad en cuanto al primer banco de ROM y es que este tiene un tamaño reducido ya que sus 16KB están compartidos con el “inicio de la GB + vectores de interrupción” y “la cabecera del cartucho”:

16383-336 = 16.047 bytes     -> Bank0
32767-16384 = 16.383 bytes   -> Switchable Bank

Además el primer banco ROM es estático no permite bank switching, esta región de memoria siempre tendrá el bank0 del cartucho cargado sin posibilidad de sustituir su contenido, por este motivo este banco suele contener código utilizado con mayor frecuencia ya que su acceso estará siempre disponible sin necesidad de hacer bank switching.

Como ya hemos explicado en este mismo artículo mediante bank switching podemos acceder a los bancos de ROM/RAM del cartucho, tan solo debemos seleccionar el banco deseado y su contenido será cargado en la dirección de memoria 0x4000 - 0x7FFF en caso de tratarse de ROM y en 0xA000 - 0xA7FF en caso de tratarse de RAM de 2KB, si el banco de RAM fuese de 8KB sería el rango de memoria 0xA000 - 0xBFFF.


MBC: Memory Bank Switching

Para disponer de bank switching el cartucho debe venir equipado con un chip MBC o Memory Bank Controller, además de los correspondientes bancos de ROM/RAM, desde nuestro código seleccionaremos el banco a cargar escribiendo en el área ROM que por naturaleza es de solo lectura, esto resultaría inútil pero en caso de disponer de MBC esta escritura será interceptada analizando el valor a escribir e interpretándolo como el banco al que deseamos acceder.

La RAM externa suele venir acompañada de una batería de este modo es posible guardar datos de usuario que persisten entre inicios de la máquina, la RAM externa es igual de rápida que la interna por este motivo algunos desarrolladores han llegado a utilizar parte de la RAM con batería como RAM de uso normal y no para guardar datos. Algunos cartuchos sin MBC podían llevar RAM pero necesitaban un chip similar para funcionar.

El tamaño de los bancos varía según el tipo de memoria:

  • ROM: 16KB
  • RAM: 2KB u 8KB

Es recomendable deshabilitar la RAM en caunto se haya terminado de utilizar, de este modo minimizamos la posibilidad de corromper los datos por una subita pérdida de energía o por extracción del cartucho.

Cuando la GameBoy arranca cierta inforamción como el tipo de MBC, la cantidad de ROM y RAM instaladas entre otros datos es leída desde la cabecera del cartucho, según el tipo de cartucho que vayamos a utilizar debemos configurar ciertos bytes a los valores oportunos:

  • Byte $0147: Tipo de MBC.
  • Byte $0148: Tamaño de la ROM.
  • Byte $0149: Tamaño de la RAM.

Estos son los distintos tipos de cartuchos que existen en el mercado, en el enlace de las fotos podéis ver la circuitería de cada cartucho en detalle:

Byte $0147 ID Tipo Max ROM Max RAM Fotos
$00 ROM ONLY 32KB X https://gbhwdb.gekkio.fi/cartridges/no-mapper.html
$01 MBC1 2MB X https://gbhwdb.gekkio.fi/cartridges/mbc1.html
$02 MBC1+RAM 2MB 32KB https://gbhwdb.gekkio.fi/cartridges/mbc1.html
$03 MBC1+RAM+BATT 2MB 32KB https://gbhwdb.gekkio.fi/cartridges/mbc1.html
$05 MBC2 256KB X https://gbhwdb.gekkio.fi/cartridges/mbc2.html
$06 MBC2+RAM+BATTERY 256KB 512x4 bits https://gbhwdb.gekkio.fi/cartridges/mbc2.html
$08 ROM+RAM(Sin MBC) 16KB 8KB X
$09 ROM+RAM+BATTERY(Sin MBC) 16KB 8KB X
$0B MMM01 2MB X https://gbhwdb.gekkio.fi/cartridges/mmm01.html
$0C MMM01+RAM 2MB 32KB https://gbhwdb.gekkio.fi/cartridges/mmm01.html
$0D MMM01+RAM+BATT 2MB 32KB https://gbhwdb.gekkio.fi/cartridges/mmm01.html
$0F MBC3+TIMER+BATT 2MB X https://gbhwdb.gekkio.fi/cartridges/mbc3.html
$10 MBC3+TIMER+RAM+BATT 2MB 64KB https://gbhwdb.gekkio.fi/cartridges/mbc3.html
$11 MBC3 2MB X https://gbhwdb.gekkio.fi/cartridges/mbc3.html
$12 MBC3+RAM 2MB 64KB https://gbhwdb.gekkio.fi/cartridges/mbc3.html
$13 MBC3+RAM+BATT 2MB 64KB https://gbhwdb.gekkio.fi/cartridges/mbc3.html
$19 MBC5 8MB X https://gbhwdb.gekkio.fi/cartridges/mbc5.html
$1A MBC5+RAM 8MB 1MB https://gbhwdb.gekkio.fi/cartridges/mbc5.html
$1B MBC5+RAM+BATT 8MB 1MB https://gbhwdb.gekkio.fi/cartridges/mbc5.html
$1C MBC5+RUMBLE 8MB X https://gbhwdb.gekkio.fi/cartridges/mbc5.html
$1D MBC5+RUMBLE+RAM 8MB 1MB https://gbhwdb.gekkio.fi/cartridges/mbc5.html
$1E MBC5+RUMBLE+RAM+BATT 8MB 1MB https://gbhwdb.gekkio.fi/cartridges/mbc5.html
$20 MBC6 1MB 32KB https://gbhwdb.gekkio.fi/cartridges/mbc6.html
$22 MBC7+SENSOR+RUMBLE+RAM+BATT 8MB 1MB https://gbhwdb.gekkio.fi/cartridges/mbc7.html
$FC Pocket Camera 1MB 128KB https://upload.wikimedia.org/wikipedia/commons/e/ea/GameBoy-Hardware_1.JPG
$FD Bandai TAMA5(Tamagotchi) 512KB X https://gbhwdb.gekkio.fi/cartridges/tama5.html
$FE Hudson HuC-3(infrarojos+buzzer)+RAM+BATT 2MB 32KB https://gbhwdb.gekkio.fi/cartridges/huc3.html
$FF Hudson HuC-1(infrarojos)+RAM+BATT 2MB 32KB https://gbhwdb.gekkio.fi/cartridges/huc1.html

La mayoría de los cartuchos no utilizan MBC o si lo hacen es de tipo 1,2 o 5. Los MBCs tipo 3,6 y 7 están reservados para juegos especiales que precisan de un RTC, guardar datos del usuario, respuestas ápticas o funciones avanzadas relacionadas con algún tipo de sensor.

En la tabla anterior se indica la máxima cantidad de ROM/RAM instalable pero en la cabecera se debe indicar la cantidad exacta instalada en el cartucho mediante los siguientes valores:

Byte $0148 ID Tamaño Número de bancos
$00 32KB 2(No ROM banking)
$01 64KB 4
$02 128KB 8
$03 256KB 16
$04 512KB 32
$05 1MB 64
$06 2MB 128
$07 4MB 256
$08 8MB 512
$52 1.1MB 72 3
$53 1.2MB 80 3
$54 1.5MB 96 3

En cuanto a la RAM externa ocurre exactamente igual debemos indicar la cantidad exacta instalada en el cartucho mediante los siguientes valores:

Byte $0149 ID Tamaño Número de bancos
$00 0KB No ROM banking
$01 - No utilizado
$02 8KB 1
$03 32KB 4
$04 128KB 16
$05 64KB 8

NOTA: Si se utiliza un cotrolador de tipo MBC2, el ID debe ser $00 ya que la RAM no la proporciona un banco externo si no 4 bits del propio MBC.


Uso de la memoria del cartucho

Ahora que ya conocemos los tipos de cartuchos estamos en condiciones para ponernos a programar y hacer uso de todas sus características.

Como ejemplo vamos a utilizar un MBC5+RAM+BATT o tipo $19 ya que parece ser el modelo menos problemático y el que recomiendan utilizar en los chats de GBDK de Discord:

Never use MBC1, always use MBC5, and use SWITCH_ROM/SWITCH_RAM macros, not MBC-specific ones.
All modern flash carts are MBC5. MBC1 is slow and a bit odd (you can not use all pages). for the homebrew there is no sense at all to use it.

De este modo seremos capaces de almacenar información en el banco ROM del MBC y guardar los datos del usuario en la RAM que serán preservados gracias a la batería del cartucho.

Los bancos de ROM/RAM se generan guardando en ficheros externos los datos e indicando ciertos parámetros en la compilación, de este modo nos generará los ficheros objeto de cada banco de memoria.

Para mi ejemplo simplemente con un MBC con $00(2 - No ROM banking) bancos de ROM y $02(1) bancos de RAM sería suficiente ya que tendría a mis disposición 32KB de ROM y 8KB de RAM, pero para que el ejemplo sea mas completo optaremos por un MBC con $01(4) bancos de ROM y $03(4) bancos de RAM, lo que equivale a 4x16 = 64KB ROM y 4x8 = 32KB RAM.

El número de bancos de RAM no es escalonado es decir solo podemos elegir entre 1,4,8,16 bancos, como necesitamos 2 tendremos que instalar 4.

En la ROM vamos a guardar tanto datos estáticos como código y la RAM la utilizaremos a modo de almacenamiento de datos por parte del usuario ya que tiene batería y no se perderán.

Tanto las variables, constantes como el resto del código se definen en un único fichero, lcc detectará de forma automática si se trata de una variable la cual colocará en RAM y si se trata de cualquier otro tipo de estructura la colocará en ROM, por ejemplo:

const unsigned char const_text[] = "I am a constant text in ROM";
unsigned char var_text[32] = "I am initialized text in RAM";

A modo de ejemplo podemos generar un fondo como ya vimos en GameBoy Dev3 , una variable de prueba y una función(código) todo esto en dos bancos distintos.

El contenido del banco0 es el siguiente:

vi bank0.c

#include <gb/gb.h>

// BackgroundMapBank0
const unsigned char BackgroundMapBank0[] =
{
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x66,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x66,
  0x68,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x66,0x68,0x66,0x68,
  0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x68,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x68,0x68,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,
  0x66,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x68,0x68,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,
  0x66,0x68,0x66,0x66,0x66,0x68,0x66,0x66,0x66,0x68,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x68,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x66,0x66,0x68,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x68,
  0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x66,0x66,0x66,
  0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x66,
  0x68,0x68,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x66,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x68,0x68,0x66,0x66,
  0x66,0x68,0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66
};

unsigned int var_bank0;

void function_bank0(){
    scroll_bkg(-16,0);
}

El contenido del banco1:

vi bank1.c

#include <gb/gb.h>

// BackgroundMapBank1.c
const unsigned char BackgroundMapBank1[] =
{
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x66,0x66,0x66,0x68,0x68,0x68,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x66,0x68,0x66,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x68,0x68,0x68,0x68,0x68,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x66,
  0x68,0x68,0x68,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x68,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x66,0x68,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x66,0x66,0x66,0x68,0x68,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x68,0x68,0x68,0x66,
  0x66,0x68,0x68,0x68,0x68,0x68,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x68,0x68,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,
  0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66,0x66
};

unsigned int var_bank1;

void function_bank1(){
      scroll_bkg(16,0);
}

El código principal carga el contenido BackgroundMapBank0, BackgroundMapBank1, var_bank0, var_bank1 y las funciones function_bank0, function_bank1 del cartucho.

Tan solo debemos tener en cuenta que debemos utilizar la palabra extern a menos que se trate de una función en cuyo caso no es necesario:

extern const unsigned char BackgroundMapBank0[];
extern const unsigned char BackgroundMapBank1[];

extern unsigned int var_bank0;
extern unsigned int var_bank1;

void function_bank0();
void function_bank1();

Por defecto la ROM viene habilitada ya que es necesaria para todos los juegos, pero para poder utilizar la RAM externa debemos habilitarla previamente mediante la macro:

ENABLE_RAM;

La selección del banco de ROM/RAM a cargar se hace mediante las macros:

SWITCH_RAM(N);
SWITCH_ROM(N);

Al principio del programa se comprueba que las variables no tengan valor, si es así se inicializan a 0, el valor a comprobar se ha obtenido desde el debuger de Emulicious, explicado mas abajo en el apartado de debugging.

if (var_bank0 == 0xFFFF){
      var_bank0 = 0;
}

A continuación el código completo, donde se realizan varias tareas:

  • Carga desde el banco0 un fondo.
  • Se hace scroll del fondo mediante una función albergada en el banco0.
  • Se lee e incrementa en uno el valor de una variable también albergada en el banco0.

El proceso se repite para el banco1 de ROM/RAM pero el fondo es distinto, el scroll es en setido contrario y la variable se incrementa de cinco en cinco.

vi 10.c
#include <gb/gb.h>
#include <stdio.h>
#include <gbdk/console.h>

#include "BackgroundTiles.c"

extern const unsigned char BackgroundMapBank0[];
extern const unsigned char BackgroundMapBank1[];

extern unsigned int var_bank0;
extern unsigned int var_bank1;

void function_bank0();
void function_bank1();

void main(){
    set_bkg_data(102, 3, BackgroundTiles);
    SHOW_BKG;
    DISPLAY_ON;

    // Check previous saved RAM values
    ENABLE_RAM;
    SWITCH_RAM(0);
    if (var_bank0 == 0xFFFF){
        var_bank0 = 0;
    }
    SWITCH_RAM(1);
    if (var_bank1 == 0xFFFF){
        var_bank1 = 0;
    }
    DISABLE_RAM;

    while(1){
        ENABLE_RAM;

        // BANK0
        SWITCH_RAM(0);
        SWITCH_ROM(0);
        set_bkg_tiles(0, 0, 20, 18, BackgroundMapBank0);
        delay(900);
        function_bank0();
        delay(900);
        scroll_bkg(0,0);
        cls();
        var_bank0 = var_bank0 + 1;
        gotoxy(0,0);
        printf("var_bank0: %d", var_bank0);
        delay(1000);

        // BANK1
        SWITCH_RAM(1);
        SWITCH_ROM(1);
        set_bkg_tiles(0, 00, 20, 18, BackgroundMapBank1);
        delay(900);
        function_bank1();
        delay(900);
        scroll_bkg(0,0);
        cls();
        var_bank1 = var_bank1 + 5;
        gotoxy(0,0);
        printf("var_bank1: %d", var_bank1);
        delay(1000);

        DISABLE_RAM;
    }
}

Mediante las opciones de compilación definiremos los ficheros con el contenido de los bancos ROM/RAM, como ya hemos comentado anteriormente tanto las variables, constantes como el resto del código se definen en un único fichero, lcc detectará de forma automática si se trata de una variable la cual colocará en RAM y si se trata de cualquier otro tipo de estructura la colocará en ROM:

-Wf-bo0 -Wf-ba0 -c -o bank0.o bank0.c -> Genera tanto el banco0 ROM como el banco0 RAM
-Wf-bo1 -Wf-ba1 -c -o bank1.o bank1.c -> Genera tanto el banco1 ROM como el banco1 RAM

NOTA: Por defecto los datos del juego se meten en los bancos de ROM0/1 que vienen en el cartucho sin MBC.

Otro paso importante es indicar el tipo de cartucho que vamos a utilizar y los bancos de ROM/RAM que va a disponer:

-Wl-yt0x1B -Wl-yo4 -Wl-ya4 -o 10.gb 10.o bank0.o bank1.o
-Wl-yt0x1B -> Cartucho MBC5: 0x1B
-Wl-yo4 -> Cuatro bancos de ROM, aunque solo vayamos a utilizar 2.
-Wl-ya4 -> Cuatro bancos de RAM, aunque solo vayamos a utilizar 2.

El script de compilación con todas las opciones de debug habilitadas sería este:

vi make.sh

#!/usr/local/bin/bash

~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -Wf-bo0 -Wf-ba0 -c -o bank0.o bank0.c
~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -Wf-bo1 -Wf-ba1 -c -o bank1.o bank1.c
~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -c -o 10.o 10.c
~/GBDEV/gbdk/bin/lcc -Wf--vc -Wf--debug -Wf--nooverlay -Wf--nogcse -Wf--nolabelopt -Wf--noinvariant -Wf--noinduction -Wf--noloopreverse -Wf--no-peep -Wf--no-reg-params -Wf--no-peep-return -Wf--nolospre -Wf--nostdlibcall -Wa-l -Wa-y -Wl-m -Wl-w -Wl-y -Wl-yt0x1B -Wl-yo4 -Wl-ya4 -o 10.gb 10.o bank0.o bank1.o

El resultado final es el siguiente:

Como se puede apreciar en el video al recargar la ROM esta lee los datos guardados en la RAM respaldada por batería del cartucho, pudiendo así leer datos de una ejecución previa.

Emulicios habrá generado un fichero llamado 10.sav es aquí donde se almacena la información de esa RAM.

Garrus $ ~/GBDEV/code> ls -la 10.sav 
-rw-r--r--  1 kr0m  kr0m  32768 11 jun.  16:46 10.sav

La asignación de los bancos se puede hacer de distintas maneras, aunque yo solo he probado la forma manual:

  • Parámetros de la compilación: Utilizada en este artículo.
  • Definiendo el banco dentro del propio código mediante la keyword pragma.
  • Utilizando el autobanking de GBDK.

Debugging

Para debugear el valor inicial de las variables en RAM del cartucho, debemos mirar la dirección de memoria 0xA000 en Emulicious, pero hay que tener en cuenta que a pesar de que los bancos de RAM externos siempre se mapean en la dirección 0xA000, según el banco seleccionado Emulicious hace una distinción añadiendo el número del banco delante:


Algo importante que debemos tener en cuenta es que no podemos debugear el valor de las variables de la RAM externa desde VisualStudio Code, siempre aparecen con el valor inicial:


Podemos obtener los datos de la cabecera del cartucho mediante xdd.

Tipo de cartucho, en este caso 1B -> MBC5+RAM+BATT

xxd -plain -seek 0x147 -len 1 10.gb

1b

Tamaño de la memoria ROM, en este caso 01 -> 64KB: 4 bancos de 16KB

xxd -plain -seek 0x148 -len 1 10.gb

01

Tamaño de la memoria RAM, en este caso 03 -> 32KB: 4 bancos de 8KB

xxd -plain -seek 0x149 -len 1 10.gb

03
Si te ha gustado el artículo puedes invitarme a un RedBull aquí