GameBoy Advance Barebones

From OSDev Wiki
Jump to navigation Jump to search

WAIT! Have you read Getting Started, Beginner Mistakes, and some of the related OS theory?

This page or section refers to its readers or editors using I, my, we or us. It should be edited to be in an encyclopedic tone.

Difficulty level
Difficulty 2.png
Medium

Finally what you were expecting is going to be real. I'm going to show you the GameBoy Advance barebones.

Requirements

The Screen

The Nintendo GameBoy Advance screen, is an LCD with 240x160 resolution, that is entirely refreshed every 60th of a second. After a scanline has been drawn (the HDraw period, 240 pixels), there is a pause (HBlank, 68 pixels) before it starts drawing the next scanline. Likewise, after the 160 scanlines (VDraw) is a 68 scanline blank (VBlank) before it starts over again. To avoid tearing, positional data is usually updated at the VBlank. This is why most games run at 60 or 30 FPS.

The diagram of the GameBoy Advance Display
Subject Length Cycles
Pixel 1 4
HDraw 240px 960
HBlank 68px 272
Scanline HDraw+HBlank 1232
VDraw 160*Scanline 197120
VBlank 68*Scanline 83776
Refresh VDraw+VBlank 280896

Let's Code

Here I'm going to show you two ways to do it: One using Assembly(that it's better to begin with it, because you understand better how the machine works) and the second one using C. But it will be just a simple pixel-plotting at the time. Later I will add more tutorials about other things.

ARM Assembly

.arm
.text
.global main

.macro plot_pixel x, y, color
    mov r0, #0x6000000
    mov r1, \color
    ldr r2, =(\x+\y*240)*2
    strh r1, [r0, r2]
.endm

main:
    mov r0, #0x4000000
    mov r1, #0x400
    add r1, r1, #3
    str r1, [r0]

    plot_pixel 115, 80, #0x1F
    plot_pixel 120, 80, #0x03E0
    plot_pixel 125, 80, #0x7C00

infin:
    b infin


C

int main()
{
    *(unsigned int*)0x04000000 = 0x0403;

    ((unsigned short*)0x06000000)[115+80*240] = 0x001F;
    ((unsigned short*)0x06000000)[120+80*240] = 0x03E0;
    ((unsigned short*)0x06000000)[125+80*240] = 0x7C00;

    while(1);

    return 0;
}

Compiling

To compile the Assembly source code, you just need to do this:

arm-elf-gcc -mthumb-interwork -specs=gba.specs kernel.S
arm-elf-objcopy -O binary a.out a.gba

To compile the C source, just use this Makefile:

PATH := $(DEVKITARM)/bin:$(PATH)

# --- Project details -------------------------------------------------

PROJ    := first
TARGET  := $(PROJ)

OBJS    := $(PROJ).o

# --- Build defines ---------------------------------------------------

PREFIX  := arm-eabi-
CC      := $(PREFIX)gcc
LD      := $(PREFIX)gcc
OBJCOPY := $(PREFIX)objcopy

ARCH    := -mthumb-interwork -mthumb
SPECS   := -specs=gba.specs

CFLAGS  := $(ARCH) -O2 -Wall -fno-strict-aliasing
LDFLAGS := $(ARCH) $(SPECS)


.PHONY : build clean

# --- Build -----------------------------------------------------------
# Build process starts here!
build: $(TARGET).gba

# Strip and fix header (step 3,4)
$(TARGET).gba : $(TARGET).elf
	$(OBJCOPY) -v -O binary $< $@
	-@gbafix $@

# Link (step 2)
$(TARGET).elf : $(OBJS)
	$(LD) $^ $(LDFLAGS) -o $@

# Compile (step 1)
$(OBJS) : %.o : %.c
	$(CC) -c $< $(CFLAGS) -o $@
		
# --- Clean -----------------------------------------------------------

clean : 
	@rm -fv *.gba
	@rm -fv *.elf
	@rm -fv *.o

#EOF

Explanation

This is a quick and dirty explanation of the earlier code. Those previously mentioned warped minds for whom this section is intended will probably prefer it that way. A more detailed discussion will be given later.

As I said, GBA programming is low-level programming and sometimes goes right down to the bit. The 0x04000000 and 0x06000000 are parts of the accessible memory sections. These numbers themselves don't mean much, by the way; they just refer to different sections. There aren't really 0x02000000 between these two sections. As you can see in the memory map, these two sections are for the IO registers and VRAM, respectively.

To work with these sections in C, we have to make pointers out of them, which is what the unsigned int* and unsigned short* do. The types used here are almost arbitrary; almost, because some of them are more convenient than others. For example, the GBA has a number of different video modes, and in modes 3 and 5 VRAM is used to store 16-bit colors, so in that case casting it to halfword pointers is a good idea. Again, it is not required to do so, and in some cases different people will use different types of pointers. If you're using someone else's code, be sure to note the datatypes of the pointers used, not just the names.

The word at 0400:0000 contains the main bits for the display control. By writing 0x0403 into it, we tell the GBA to use video mode 3 and activate background 2. What this actually means will be explained in the video and bitmap mode chapters.

In mode 3, VRAM is a 16-bit bitmap; when we make a halfword pointer for it, each entry is a pixel. This bitmap itself is the same size as the screen(240x160) and because of the way bitmaps and C matrices work, by using something of the form array[y*width + x] are the contents of coordinates(x, y) on screen. That gives us our 3 pixel locations. We fill these with three 16-bit numbers that happen to be full red, green and blue in 5.5.5 BGR format. Or is that RGB, I always forget. In any case, that's what makes the pixels appear. After that there is one more important line, which is the infinite loop. Normally, infinite loops are things to be avoided, but in this case what happens after main() returns is rather undefined because there's little to return to, so it's best to avoid that possibility.

Links