GameBoy Tutorial for the Adventurous
Contents
Intro
In this tutorial, we will be setting up your environment for a quick program that will simple beep over and over. It's by far, the easiest and somewhat meaningful little task to understand and write.
What You Need
For this tutorial, we will be using the WLA-DX assembler, your choice of text editor, aaaand that's about it. I guess you will need something to make art and convert it to a usable format when we get to that. You will also need a GameBoy emulator like BGB. I recommend this one since it's debug features can't be beat, as far as I know.
Documentation
I'm just gonna link some docs here that I love very dearly and I will refer to them as we move along. You can open them up and look around if you're interested. Keeping them open in some tabs in your web browser is what I usually do.
- PanDoc - Everything you need to know about the hardware.
- Opcodes - All the opcodes available to us in an concise format.
- Memory Map - Map of our new home.
- WLA Docs - Documentation for our compiler. Useful for syntax and other things.
First Program
Open a text file and let's get our first source file going. It's going to be called main.s
. We will start with our header. Every game is going to need a header for the GameBoy to read or else it won't work quite so well. You can look up the details of the header in the PanDoc and follow along.
Alright, so our compiler WLA-DX will do the headers pretty easily. All you really need to do is know what to specify and what value you want it to be. I've tried to order these entries in the order they appear in the header for your sake. First is the logo data. Each cart has that chunk of memory tested for the official logo as the game is booted up. WLA-DX automatically puts it in so we don't have to. Next is the program name which is 16 uppercase ASCII letters filled with 0
afterwards. The new licensee code is just two ASCII characters which mean very little to us so I left them as just XX
. Then we have the cartridge type, $00
being hexadecimal for 0
and this means the cart is ROM only. Country code is just telling you where the intended release country is ($01
for non-Japanese). The old licensee code is just an older place for the licensee code which later was used for signifying SuperGameBoy capabilities.
.GBHEADER
NINTENDOLOGO
NAME "FIRSTPRG"
ROMDMG
LICENSEECODENEW "XX"
CARTRIDGETYPE $00
RAMSIZE $00
COUNTRYCODE $01
LICENSEECODEOLD $00
.ENDGB
Next part is to do some memory planning and telling the compiler some other information it needs.
.MEMORYMAP
DEFAULTSLOT 0
SLOTSIZE $4000
SLOT 0 $0000
SLOT 1 $4000
.ENDME
Here we are just telling the compiler that we want two slots of ROM at a length of $4000
each. You will notice, this lines up perfectly with the cartridge ROM in the memory map. On a side note, to get more space, you will swap out the second of these slots for another. This is called memory bank switching. It is what the memory bank controllers (MBCs) do.
Next up is the actual code. But the system actually plops you out of the boot up sequence at address $100
. Problem is that we don't have much room as this is before the header we just wrote (addresses $104
-$14F
). So we jump to after it at $150
. Then from there, we can do whatever we plan to do! In our case, we are going to make it beep.
.BANK 0
.ORG $100
nop
jp Start ; we do not have enough room here
.ORG $150
Start:
di ; disable interrupts
ld sp, $FFFE ; set stack to $FFFE
ld a, %00000001
ldh ($FF), a ; enable vblank interrupt
ld a, %10000000 ; turn on sound
ldh ($26), a ; NR52 - Sound On
ld a, %01110111
ldh ($24), a ; NR50 - Channel Control
ld a, %11111111
ldh ($25), a ; NR51 - Sound Panning
This is some setup code. First we have at address $100
, that jump to $150
code which I explained above. At $150
, we have disabled interrupts as we don't need them at the moment. Next, we load the stack pointer (sp
) with address $FFFE
which will be the bottom of the stack and fill downwards, all happening in High RAM (HRAM). Then we set the Vertical Blank interrupt flag in the Interrupt Enable register (IE). Then we set up our sound with the Noise Registers (NR) 50 through 52. NR52
turns on and off all sound (this will erase other sound registers as you turn it off). NR50
handles whether the individual channels are on or off and NR51
handles panning for each of the channels. Check the PanDocs for more information if you are interested.
Loop:
ld a, 40
pause:
halt ; pause until interrupted
nop ; needed after halt instruction
dec a
jr nz, pause ; loop back if not zero
ld a, %10001000 ; make some noise!
ldh ($11), a
ld a, %11110001
ldh ($12), a
ld a, %00000000
ldh ($13), a
ld a, %11000100
ldh ($14), a
jr Loop ; repeat the process
This is our main code. First is a loop label to jump back to. Next is the pause code. This loads 40
into the accumulator then halts, waiting for an interrupt. Only vertical blanking interrupts were enabled so it waits until the v-blank (when the screen is not being drawn). The nop
is necessary after the halt
instruction due to some wacky bug that makes the byte after a halt
run twice. Next, the accumulator (a
) is decremented once. This will modify the zero flag according to the result left in a
.
The noise is made by just loading some values into the registers for the first square channel. You may use the PanDocs to look up each of these registers but they include information like the frequency, pulse-width, volume envelope and some other data.
The next file we need to make, is the linkfile. Save a new text file as linkfile
. This file tells WLA-DX stuff like libraries and other neat stuff. As your project gets bigger, you will need to add files here so that the linker knows to add them to the project. Right now, your game is only the main.s
file so we only need to make the main.o
object file.
[libraries]
main.o
The next file is a bit of a time saver. It's a batch file to compile the rom and link it all up for us. We use the wla-gb
compiler and the provided wlalink
linker. The ending pause
command is put there to allow us to see the errors that have been returned if there are any.
wla-gb main.s
wlalink -S linkfile first.gb
pause