LDA $00

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.

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