Es recomendable la lectura de los artículos anteriores para comprender mejor el actual:
- GameBoy Dev00
- GameBoy Dev01
- GameBoy Dev02
- GameBoy Dev03
- GameBoy Dev04
- GameBoy Dev05
- GameBoy Dev06
- GameBoy Dev07
- GameBoy Dev08
- GameBoy Dev09
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:
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:
#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:
#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.
#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:
#!/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
1b
Tamaño de la memoria ROM, en este caso 01 -> 64KB: 4 bancos de 16KB
01
Tamaño de la memoria RAM, en este caso 03 -> 32KB: 4 bancos de 8KB
03