Artigos
GBA em Assembly — 3. Sua primeira ROM
Prepare as ferramentas e construa uma ROM completa que pinta a tela e acende um pixel. Entenda cada etapa entre o texto e o hardware.
Vamos transformar um arquivo de texto em um programa que o Game Boy Advance consegue executar. O resultado será uma tela vermelha com um pixel branco no centro. Parece pouco, mas exige percorrer a cadeia inteira: instruções ARM, organização da ROM, configuração do vídeo e escrita na VRAM.
Se termos como registrador, palavra e endereço ainda estiverem confusos, retome bits, registradores e memória. O índice da série apresenta o percurso completo.
Roteiro deste capítulo
- Escolha as instruções para seu computador e confira as versões das quatro ferramentas.
- Extraia o pacote e identifique os três arquivos usados aqui:
primeira-rom.s,gba.lderom.py. - Construa o exemplo original com os quatro comandos, uma linha de cada vez, e abra
primeira.gbano mGBA. - Depois que a tela vermelha aparecer, leia como a inicialização, as escritas de vídeo e o linker produziram esse resultado. Então mude uma cor e reconstrua.
Ponto de conferência: você consegue produzir a tela vermelha original, mudá-la para verde e explicar qual valor editou no código. Não é necessário decorar cada diretiva de inicialização para continuar.
As ferramentas e o trabalho de cada uma
Consulte os comandos e parâmetros da construção para distinguir as ferramentas do terminal das instruções da CPU.
Vamos usar GNU Binutils para o alvo arm-none-eabi, Python 3 e uma instalação pronta do mGBA. O sufixo do Binutils indica que as ferramentas geram código para ARM, sem pressupor um sistema operacional no dispositivo. As ferramentas rodam no seu computador; o resultado roda na máquina emulada.
| Ferramenta | Entrada | Saída ou função |
|---|---|---|
arm-none-eabi-as | Texto Assembly .s | Objeto .o, com instruções e referências |
arm-none-eabi-ld | Objeto e regras de memória | Executável .elf, com endereços resolvidos |
arm-none-eabi-objcopy | Executável ELF | Sequência de bytes da ROM .gba |
rom.py | ROM recém-criada | Preenche e verifica o cabeçalho do cartucho |
| mGBA | ROM .gba | Executa e mostra o resultado |
Não vamos usar um compilador C. Python apenas prepara o cabeçalho no computador; nenhuma regra do programa será executada em Python no GBA.
O terminal é onde você digita os comandos de construção. O editor de texto é onde salva o código Assembly. O mGBA executa o jogo resultante. Não cole comandos de shell no arquivo .s, nem digite instruções ARM no terminal.
Escolha um caminho de preparação:
- macOS: abra o Terminal. Se o Homebrew não estiver instalado, conclua a instalação oficial do Homebrew e a configuração do shell antes de usar
brewabaixo. - Ubuntu: abra o Terminal e use os comandos
aptabaixo. - Windows: siga o guia de instalação do WSL da Microsoft para instalar e inicializar o Ubuntu. Abra Ubuntu pelo menu Iniciar para os comandos Linux abaixo; use o mGBA para Windows para jogar. Os comandos Linux não foram preparados para um prompt comum do PowerShell.
Execute uma linha de cada vez, pressione Enter e aguarde terminar. Os comandos de instalação são alternativas: você não precisa de brew e apt juntos. Uma solicitação de senha do sudo pode não mostrar caracteres enquanto você digita.
No macOS, com Homebrew já instalado, as ferramentas de construção podem ser instaladas assim:
brew install arm-none-eabi-binutils python
Em Ubuntu, incluindo Ubuntu dentro do WSL no Windows:
sudo apt update
sudo apt install binutils-arm-none-eabi python3
As fontes dos pacotes são a fórmula oficial do Homebrew e o catálogo do Ubuntu. Use o instalador do mGBA apropriado ao seu sistema. No Windows, você pode construir dentro do WSL e abrir o .gba no mGBA para Windows, acessando a pasta do WSL pelo explorador de arquivos.
Confira o ambiente antes de escrever código:
arm-none-eabi-as --version
arm-none-eabi-ld --version
arm-none-eabi-objcopy --version
python3 --version
Se algum comando não for encontrado, a instalação ou o PATH precisa ser corrigido. O assembler do seu próprio computador, chamado apenas as, pode ter outro alvo. Não o substitua sem verificar isso.
Preparar a pasta
Baixe o pacote de exemplos, extraia e abra um terminal na pasta extraída que contém gba.ld e rom.py. Os seis arquivos .s são programas separados. Neste capítulo, use somente primeira-rom.s.
Para entrar na pasta extraída, digite cd, um espaço e o caminho da pasta, colocando o caminho entre aspas se tiver espaços, e pressione Enter. No macOS, você pode arrastar a pasta para o Terminal depois de digitar cd para inserir seu caminho. No Ubuntu, pode usar “Abrir no Terminal” no gerenciador de arquivos. No WSL, use uma pasta dentro do Ubuntu; explorer.exe . abre a pasta atual do Ubuntu no Explorador de Arquivos do Windows para colocar os arquivos extraídos ali e, depois, selecionar a ROM pelo mGBA.
Confira onde você está e o conteúdo da pasta:
pwd
ls
pwd mostra a pasta atual; ls lista seus arquivos. Confirme que os três nomes exigidos aparecem. Se houver apenas outra pasta, entre nela antes de construir. Use um editor de texto simples ou de código, em vez de um processador de texto, para os arquivos .s, .ld e .py.
Se preferir digitar, crie primeira-rom.s com o bloco completo abaixo. Salve também gba.ld e rom.py na mesma pasta. Confira que o editor não acrescentou .txt ao nome.
.syntax unified
.cpu arm7tdmi
.arm
.section .header, "ax"
.global _start
_start:
b boot
.space 188
.section .text
boot:
msr cpsr_c, #0xdf
ldr sp, =0x03007f00
ldr r0, =0x04000000
ldr r1, =0x0483 @ Mode 3 + BG2 + forced blank.
strh r1, [r0]
ldr r0, =0x06000000
ldr r1, =0x001f @ Vermelho: intensidade 31 no canal R.
ldr r2, =38400 @ 240 * 160 pixels.
paint:
strh r1, [r0], #2
subs r2, r2, #1
bne paint
ldr r0, =(0x06000000 + 2 * (80 * 240 + 120))
ldr r1, =0x7fff @ Branco no pixel (120,80).
strh r1, [r0]
ldr r0, =0x04000000
ldr r1, =0x0403 @ Exibir o framebuffer pronto.
strh r1, [r0]
stop:
b stop
.ltorg
Primeiro, construa o programa sem alterações seguindo a próxima seção. Depois de ver o resultado, as explicações seguintes detalham sua inicialização e o desenho.
Montar, ligar e abrir
Execute estes quatro comandos, em ordem, na pasta dos arquivos. Se algum falhar, resolva o erro antes de passar ao seguinte.
arm-none-eabi-as -mcpu=arm7tdmi -g -o primeira.o primeira-rom.s
arm-none-eabi-ld -T gba.ld -o primeira.elf primeira.o
arm-none-eabi-objcopy -O binary primeira.elf primeira.gba
python3 rom.py primeira.gba
O último comando mostra o tamanho e o SHA-256 da ROM, além de verificar seu cabeçalho. Guarde o .elf: ele contém nomes e informações úteis para depurar, que não ficam disponíveis da mesma forma no arquivo binário puro.
Confira a saída depois de cada comando:
| Etapa | O que o comando faz | Saída esperada |
|---|---|---|
1: as | Traduz o texto ARM para um objeto; -mcpu escolhe a CPU, -g inclui depuração e -o dá nome à saída | primeira.o |
2: ld | Resolve endereços pelas regras de -T gba.ld; -o dá nome ao executável | primeira.elf |
3: objcopy | Grava os bytes puros com -O binary | primeira.gba, ainda sem a correção do cabeçalho |
4: python3 | Executa a ferramenta fornecida de cabeçalho nessa ROM | primeira.gba validado; tamanho e hash aparecem no terminal |
Os três primeiros comandos podem terminar com sucesso sem imprimir mensagens. Execute ls para conferir os arquivos de saída. Abra apenas o .gba final no mGBA. Uma ROM antiga pode continuar existindo depois de uma falha, então sua presença sozinha não comprova que a última tentativa deu certo.
Abra primeira.gba pelo comando de abrir arquivo do mGBA. Você deve ver uma tela vermelha com um ponto branco em (120,80). Aumente a escala da janela para enxergar esse pixel. Se sua alteração não aparecer, confirme que abriu a ROM da pasta atual e repetiu os quatro comandos.

Se a primeira execução falhar, escolha uma conferência pelo sintoma:
| Sintoma | Próxima conferência |
|---|---|
command not found | Volte à instalação. PATH lista as pastas onde o terminal procura ferramentas; reabra o terminal depois da configuração do shell indicada pelo instalador. |
| Não consegue abrir um arquivo de código exigido | Execute pwd e ls; confira pasta, nomes e extensões .txt ocultas. |
| Erro de sintaxe com número de linha | Confira a linha no editor, salve e repita o comando que falhou. Continue só depois que funcionar. |
| Símbolo não definido na etapa de link | Use o .s original completo, em vez de um fragmento explicativo. |
| Imagem antiga ou tela branca | Repita os quatro comandos com sucesso, incluindo rom.py, e reabra o .gba da pasta mostrada por pwd. |
Em cada edição seguinte, salve o código e repita os quatro comandos em ordem. Fechar o mGBA não reconstrói a ROM.
Do cartucho até a primeira instrução
A configuração inicial usa diretivas do assembler, desvios e labels e MRS e MSR.
.syntax unified, .cpu arm7tdmi e .arm escolhem a sintaxe, o processador e o conjunto de instruções. Essas diretivas orientam o assembler.
A primeira instrução, b boot, desvia para nosso código. Os 188 bytes seguintes reservam o restante dos primeiros 192 bytes, onde fica o cabeçalho do cartucho. rom.py vai preencher os campos de identificação e a soma de verificação. Não é uma BIOS, não contém o jogo de outra pessoa e não executa a lógica do exemplo.
Na entrada boot, msr cpsr_c, #0xdf escolhe o modo System da CPU com IRQ e FIQ desabilitadas. Vamos consultar o hardware diretamente, sem instalar tratadores de interrupção. sp recebe um endereço na RAM interna para a pilha. A primeira ROM ainda não chama rotinas que usem essa pilha, mas os exemplos seguintes vão usá-la.
Esses valores pertencem à configuração inicial deste exemplo. Não são uma fórmula geral para qualquer programa ARM, nem substituem a inicialização completa de uma biblioteca maior.
Configurar o vídeo e preencher a imagem
Para ler cada linha do laço, consulte LDR e STR, endereçamento com atualização da base e SUB e suas flags.
0x04000000 é o endereço de DISPCNT. Primeiro escrevemos 0x0483: Mode 3, camada BG2 habilitada e forced blank ligado. Forced blank mantém a exibição em branco enquanto preparamos a imagem.
A partir de 0x06000000, o programa escreve 38.400 pixels, pois 240 × 160 = 38.400. Cada pixel ocupa dois bytes. O valor 0x001f representa vermelho com intensidade máxima no formato que estudaremos no próximo capítulo.
Em strh r1, [r0], #2, a escrita acontece no endereço atual; depois, r0 avança dois bytes. subs diminui o contador e atualiza as flags. bne paint repete enquanto o contador ainda não chegou a zero.
Em seguida escrevemos branco em um único endereço:
endereço = início da VRAM + 2 × (y × largura + x)
= 0x06000000 + 2 × (80 × 240 + 120)
= 0x060096F0
Finalmente escrevemos 0x0403 em DISPCNT, desligando forced blank e mantendo Mode 3 e BG2. A imagem pronta passa a ser exibida. O laço stop mantém a CPU executando um salto para si mesma; a imagem permanece na VRAM sem precisarmos pintá-la outra vez.
Dizer onde o programa fica
Os campos deste arquivo são detalhados em linker e cabeçalho.
O arquivo gba.ld contém as regras do linker:
OUTPUT_FORMAT("elf32-littlearm")
OUTPUT_ARCH(arm)
ENTRY(_start)
MEMORY {
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 32M
RAM (rw) : ORIGIN = 0x02000000, LENGTH = 256K
}
SECTIONS {
.text : { KEEP(*(.header)) *(.text*) *(.rodata*) . = ALIGN(4); } > ROM
.bss (NOLOAD) : {
. = ALIGN(4); __bss_start = .; *(.bss*) *(COMMON)
. = ALIGN(4); __bss_end = .;
} > RAM
/DISCARD/ : { *(.comment) *(.note*) }
}
ASSERT(SIZEOF(.bss) < 0x3F000, "Game state exceeds EWRAM")
O ponto principal é que instruções e constantes ficam na ROM, começando em 0x08000000. A BSS, que usaremos para variáveis nos próximos exemplos, fica na EWRAM em 0x02000000.
KEEP(*(.header)) mantém o cabeçalho no início da seção de código. __bss_start e __bss_end dão nomes aos limites da área de variáveis. NOLOAD evita gastar bytes da ROM armazenando esse espaço vazio. Isso não zera a RAM: nos programas com variáveis, faremos a limpeza durante o boot.
Este script atende aos exemplos da série. Não há seção .data com valores iniciais para copiar, nem runtime C. Ao ampliar o programa com novos tipos de seção, o startup e o linker precisarão acompanhá-los. A documentação do GNU ld explica essa organização.
Três experiências pequenas
Primeiro, troque apenas a cor de preenchimento de 0x001f para 0x03e0. Reconstrua: a tela deve ficar verde e o pixel branco deve continuar no mesmo lugar.
Depois, volte à cor original e mude a coordenada horizontal de 120 para 121. O ponto deve andar um pixel para a direita. Por fim, altere o Y de 80 para 81: ele deve descer uma linha. No sistema de coordenadas da tela, Y cresce para baixo.
Você acabou de observar três coisas diferentes: o valor de uma cor, o endereço de um pixel e o efeito de uma escrita de 16 bits na VRAM. Esse vínculo entre código e resultado visual será nossa base.
Anterior: bits, registradores e memória. Próximo: como pixels formam um retângulo.