Creating homebrew for the Game Boy Advance Part 1: Getting Started

By Kosmas Raptis on January 10, 2022

Prologue

Good day and Happy New Year!

Today, we are moving away from the traditional programming on PCs and into something a little more… fun, in my opinion. We are going to start programming for consoles. And not just any console, but for the Game Boy Advance. I’m very excited about this, personally. The GBA has been one of the most fun consoles to program for me. I hope that through these tutorials I manage to transmit this to you! What are we waiting for? Let’s start!

Why the Game Boy Advance?

Now, you might be thinking, why did I pick the Game Boy Advance?

I have some reasons:

  • Very fun and easy to program. Probably my favorite console for homebrew
  • Wide support with lots of emulators and full system documentation
  • It’s the console I know best right now, so it’s the best I can teach

Combining all these, it’s very convenient to teach here. In my opinion, it should also be anyone’s entry point to programming for consoles. I hope that, through these tutorials, you will see the fun side of programming this little beast and enjoy it as much as I do!

Explaining the GBA internals

First, we’ll start by explaining what components the Game Boy Advance has in it.

The main processor

First, the main processor. This is an ARM7TDMI chip (ARM7 + 16-bit Thumb + JTAG Debug + fast Multiplier + enhanced ICE). This processor runs at a nice 16.8MHz (So it does 16.8 million cycles per second). While this might sound slow (and, to be honest, it is really slow for today’s standards), you won’t need much more, as most things you will be doing are handled by specialized hardware for those jobs instead of having to be done by the processor in your code. It’s good enough for the Game Boy Advance. This processor’s architecture is 32-bit. This means that it can access up to 32 bits at once, and has 32 bit registers, as well as an instruction set that supports working with 32-bit values. Don’t let the ARM7 part of the name fool you, this is NOT an ARMv7 processor, it’s not modern. Actually, this processor is based on the ARMv4T architecture (Remember, T stands for Thumb) which is at least 20 years old, and implements the ARMv4T instruction set. We don’t care too much about its architecture for now, as most of these tutorials will be using C as the programming language of choice, which usually abstracts away the tiny details. Much later, we’ll get into Assembly programming to speed up our code, and that is when we will talk about the processor at a lower level, but for now you shouldn’t care too much, as everything will be revised there.

The system’s memory

Now, for the memory. In the retro console world, we deal more with bits rather than bytes, so memory capacities will be listed as both KB (KiloBytes) and Kb (Kilobits), Remember that 1 byte = 8 bits, so the capacity in Kb is 8 times the capacity in KB. The Game Boy Advance has 3 different types of memory that we can use (excluding anything on a cartridge), and they are all byte-addressable, meaning each address holds 1 byte that consists of 8 bits.

IWRAM

First, there is IWRAM (Internal Working RAM). This RAM is embedded in the processor itself (hence the name “internal”), and is 32KB large, or 256Kb. Since it’s internal to the processor, it’s fast enough to return the data we ask it for within the same cycle as we requested it (and it’s 1 of only 2 types of memory in the GBA with this ability, the other one being VRAM or video RAM). This RAM is typically used for time-critical code and data access. However, it’s very small compared to other types of RAM. This is also the only type of RAM on the GBA that has a bus width of 32 bits, meaning it’s the only one that can give us 32 bits in one read. All the others have a bus width of 16 bits, meaning they will need 2 separate reads to give us 32 bits of data.

EWRAM

Then, there is EWRAM (External Working RAM). In this case, “external” does not mean it’s outside the GBA, but rather outside of the processor. There’s much more of it than IWRAM. Here, we got 256KB or 2048Kb of it. It’s quite slower than IWRAM, needing 3 cycles to access data. 2 where the processor is waiting for the requested data, and 1 where the data is received by the processor. Additionally, it sits on the 16 bit bus, meaning it can only access 16 bits in one go. This is where you can store big amounts of data and code downloaded to the GBA from a PC using a multiboot cable.

VRAM

Lastly, the GBA comes with built-in VRAM. VRAM stands for Video RAM, but is commonly referred to as video memory too. We’ve got 96KB or 768Kb of this to work with. As the name suggests, this is the part of memory that is used for creating what you see on the screen. We’ll get more into what it holds when explaining the PPU. This memory is as fast as IWRAM. It can return data in the same cycle as it is requested. However, the bus here can only be written 16 bits at a time, instead of 32 like IWRAM can.

Cartridge memory

Lastly, there are 2 types of memory external to the GBA and on the game cartridge itself: The game ROM and the game save memory.

Game ROM and speed modes

The game ROM is the cartridge’s main memory. As the name suggests, it can only be read by the GBA (ROM = Read Only Memory) and contains all the game data and code for the GBA to use. Game ROM generally can have 2 different speed modes: slow and fast.

Slow mode is also known as 4/2 mode. This means that generally, the processor will have to wait 4 cycles before it receives data from the cartridge. The other number is how many cycles the processor will wait when doing a sequential read (A sequential read is when you want to access a piece of data right next to the one you just accessed).

Fast mode is also known as 3/1 mode. For a non-sequential read, the processor will wait 3 cycles, but just 1 for a sequential read.

Any cartridge can work at any mode. The reason this choice exists is because of power consumption. Fast mode consumes more power, and the GBA is a handheld console, so it runs on batteries. Power consumption is a concern.

Game ROM uses a 16 bit bus.

Game save memory

This one is simple. As the name implies, it’s the memory that holds your save file(s). There are 3 major types of this kind of memory, each with a different way of access: Static RAM (SRAM, usually powered by a battery), Flash ROM, or serial EEPROM.

The audio subsystem

Not a lot to see here. Basically, there are all the channels that previous Game Boy consoles had, used mainly for compatibility with older games, and additionally there are now 2 stereo 16-bit digital channels with the ability to play sampled audio and music. There is no built-in hardware mixer to play multiple sounds at the same time, and so you need to write your own.

The video subsystem

This is a juicy one, let’s start from the basics and work up.

So, the GBA’s screen is a TFT (Thin Film Transistor) LCD (Liquid Crystal Display) and has a resolution of 240 pixels wide by 160 pixels tall, giving us a 3:2 aspect ratio.

The GBA has a chip responsible for the video output called a PPU (Picture Processing Unit). It supports 6 different video modes, 3 tile-based and 3-bitmap based, each with its own benefits and drawbacks.

Keep in mind that all the console’s hardware is memory-mapped. This means that, in order to use it, we just write values to special memory addresses. We will be using our dear GBATEK for the reference on the memory addresses and all technical info we need.

Tile-based video modes

Video modes 0, 1 and 2 are tile-based. Tiles are small 8×8 or 8×16 pixel areas that can contain graphics. A tile is produced by just having an index in a tilemap (which contains the actual graphics data for every tile). The tilemap gets the color value for each pixel of the tile by using an index in a color palette that we define ourselves.

Mode 0 has 4 available backgrounds (think of them as layers) that we can use, but they cannot be rotated or scaled.

Mode 1 has 3 available backgrounds, but only one can be rotated/scaled

Mode 2 has 2 available backgrounds, with both backgrounds giving you the ability to rotate and scale them.

The tile modes are usually more efficient than the bitmap video modes the GBA provides. The hardware is just more tailored towards tile graphics due to the console’s nature and the way tile graphics work. For example, if you are using tile modes then there is hardware scrolling, where you just tell the PPU to scroll a background to a point and it does all the work of changing the tiles on the screen for you. And, also, tiles are usually more performant than the bitmap modes. Say you change the entire screen’s contents in bitmap mode. Depending on how you do it, it’s too slow, the change will be noticeable as it’s happening. Whereas with tiles, it’s faster, not noticeable. Using palettes also provides convenience when you want to use a tile but with a different color.

Picking a tile-based mode is basically a tradeoff between how many backgrounds you can have and how many of them allow rotation and scaling effects.

In case this is not clear/thorough enough, don’t worry. It’s not meant to be thorough right now. Tile based video modes will get covered in more detail when we use them.

Bitmap-based video modes

Modes 3, 4 and 5 are bitmap-based. These modes are not as interesting, but they are far easier to work with and 2 out of the 3 of them are capable of using the full GBA color range of 32,768 colors at once, instead of the tile limitation of 256 colors. Why 32,768 (215) and not 65,536 (216), you may ask? The GBA VRAM has a 16-bit bus, so why not? The answer is that, usually, when it comes to computer graphics, one bit of the 16 bits is used for the Alpha (transparency) channel, leaving 15 bits for the actual colors (5 bits per color channel, and there are 3 channels). However, do note that on the GBA there is also no Alpha channel, so that isn’t even used, anyway. With this, using 16 bits for 3 colors would make you use 1 more bit for one of the color channels, which is weird (though used in certain places), so instead we just use 15 bits. A Nintendo DS in backwards compatibility mode also uses that last bit for transparency, but it’s basically just a value of 1 for fully opaque and 0 for fully transparent. We don’t care about the Nintendo DS in this series, instead we’re going to focus on the GBA only.

Mode 3 is the best-looking mode out of these, and gives you the full 240×160 resolution and all 15 bits of color without a palette. However, there is only one framebuffer, as the VRAM is too small to hold 2 of these, so you can’t do a framebuffer swap or anything of the sort, unless you create a buffer in EWRAM, which is not as fast.

Mode 4 also gives you 240×160, but uses 8-bit color and color palettes, exactly like tiles do. However, there are 2 framebuffers in VRAM for this, so you can do some nice tricks.

Mode 5 gives you 15-bit color without using a palette, but only 160×128 resolution. Also has 2 framebuffers.

Bitmap modes can be used by just directly plotting color values to the pixels in VRAM (except for mode 4, where instead of color values you feed it pallette indices), something not possible with tile modes. This is what makes them easier to use.

Picking a bitmap-based mode is a tradeoff between resolution and color depth (also no dual framebuffer for mode 3).

Still with me? Perfect! Let’s set up our toolchain so we can start coding now that we know some things about the insides of the GBA.

Setting up devKitARM

Our toolchain of choice is the FOSS (Free and Open Source Software) devKitARM toolchain. Specifically, we want to use its compiler that targets GBA. devKitARM also provides libraries to aid Game Boy Advance development, but we are not going to use them. Instead, we’ll be doing everything ourselves. After all, this is a Game Boy Advance programming tutorial, not a devKitARM programming tutorial. Follow these instructions to install devKitPro’s tools, and then install the Game Boy Advance tools. On Linux and macOS, just install the gba-dev package from devKitPro’s package manager.

Making a project

OK, so we are going to use Visual Studio Code for this job. open it. You should see a screen similar to this:

If you’re not, don’t worry. We want to press the Open Folder button, accessible from either this start menu here or the File menu on the top left and then pressing Open Folder. In the menu that pops up, create a new folder. I have already set up a folder called “Typical9GBA”, so I am going to go inside it and create a “HelloGBA” folder. I highly suggest that none of your folders contain spaces in their names, as devKitARM’s Makefile does not have good support for that (as in it just doesn’t work).

Once you’re done creating it and selecting it, you should see this:

Click on Open Folder again and create a directory called source (this is what devKitARM will look for your code files). Then, press “New File”, and if prompted just select “Text File”. Immediately save it inside source, and call it main.c

This file will hold our program’s source code.

Create another new file outside of source, and call it Makefile. This will be what tells devKitARM how to compile our project. This is long to write, so instead I am going to copy the template Makefile that devKitARM gives us in its examples folder (its location changes depending on the operating system).

#---------------------------------------------------------------------------------
.SUFFIXES:
#---------------------------------------------------------------------------------

ifeq ($(strip $(DEVKITARM)),)
$(error "Please set DEVKITARM in your environment. export DEVKITARM=<path to>devkitARM")
endif

include $(DEVKITARM)/gba_rules

#---------------------------------------------------------------------------------
# TARGET is the name of the output
# BUILD is the directory where object files & intermediate files will be placed
# SOURCES is a list of directories containing source code
# INCLUDES is a list of directories containing extra header files
# DATA is a list of directories containing binary data
# GRAPHICS is a list of directories containing files to be processed by grit
#
# All directories are specified relative to the project directory where
# the makefile is found
#
#---------------------------------------------------------------------------------
TARGET		:= $(notdir $(CURDIR))
BUILD		:= build
SOURCES		:= source
INCLUDES	:= include
DATA		:=
MUSIC		:=

#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH	:=	-mthumb -mthumb-interwork

CFLAGS	:=	-g -Wall -O2\
		-mcpu=arm7tdmi -mtune=arm7tdmi\
		$(ARCH)

CFLAGS	+=	$(INCLUDE)

CXXFLAGS	:=	$(CFLAGS) -fno-rtti -fno-exceptions

ASFLAGS	:=	-g $(ARCH)
LDFLAGS	=	-g $(ARCH) -Wl,-Map,$(notdir $*.map)

#---------------------------------------------------------------------------------
# any extra libraries we wish to link with the project
#---------------------------------------------------------------------------------
LIBS	:= -lmm -lgba


#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing
# include and lib
#---------------------------------------------------------------------------------
LIBDIRS	:=	$(LIBGBA)

#---------------------------------------------------------------------------------
# no real need to edit anything past this point unless you need to add additional
# rules for different file extensions
#---------------------------------------------------------------------------------


ifneq ($(BUILD),$(notdir $(CURDIR)))
#---------------------------------------------------------------------------------

export OUTPUT	:=	$(CURDIR)/$(TARGET)

export VPATH	:=	$(foreach dir,$(SOURCES),$(CURDIR)/$(dir)) \
			$(foreach dir,$(DATA),$(CURDIR)/$(dir)) \
			$(foreach dir,$(GRAPHICS),$(CURDIR)/$(dir))

export DEPSDIR	:=	$(CURDIR)/$(BUILD)

CFILES		:=	$(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.c)))
CPPFILES	:=	$(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.cpp)))
SFILES		:=	$(foreach dir,$(SOURCES),$(notdir $(wildcard $(dir)/*.s)))
BINFILES	:=	$(foreach dir,$(DATA),$(notdir $(wildcard $(dir)/*.*)))

ifneq ($(strip $(MUSIC)),)
	export AUDIOFILES	:=	$(foreach dir,$(notdir $(wildcard $(MUSIC)/*.*)),$(CURDIR)/$(MUSIC)/$(dir))
	BINFILES += soundbank.bin
endif

#---------------------------------------------------------------------------------
# use CXX for linking C++ projects, CC for standard C
#---------------------------------------------------------------------------------
ifeq ($(strip $(CPPFILES)),)
#---------------------------------------------------------------------------------
	export LD	:=	$(CC)
#---------------------------------------------------------------------------------
else
#---------------------------------------------------------------------------------
	export LD	:=	$(CXX)
#---------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------

export OFILES_BIN := $(addsuffix .o,$(BINFILES))

export OFILES_SOURCES := $(CPPFILES:.cpp=.o) $(CFILES:.c=.o) $(SFILES:.s=.o)

export OFILES := $(OFILES_BIN) $(OFILES_SOURCES)

export HFILES := $(addsuffix .h,$(subst .,_,$(BINFILES)))

export INCLUDE	:=	$(foreach dir,$(INCLUDES),-iquote $(CURDIR)/$(dir)) \
					$(foreach dir,$(LIBDIRS),-I$(dir)/include) \
					-I$(CURDIR)/$(BUILD)

export LIBPATHS	:=	$(foreach dir,$(LIBDIRS),-L$(dir)/lib)

.PHONY: $(BUILD) clean

#---------------------------------------------------------------------------------
$(BUILD):
	@[ -d $@ ] || mkdir -p $@
	@$(MAKE) --no-print-directory -C $(BUILD) -f $(CURDIR)/Makefile

#---------------------------------------------------------------------------------
clean:
	@echo clean ...
	@rm -fr $(BUILD) $(TARGET).elf $(TARGET).gba


#---------------------------------------------------------------------------------
else

#---------------------------------------------------------------------------------
# main targets
#---------------------------------------------------------------------------------

$(OUTPUT).gba	:	$(OUTPUT).elf

$(OUTPUT).elf	:	$(OFILES)

$(OFILES_SOURCES) : $(HFILES)

#---------------------------------------------------------------------------------
# The bin2o rule should be copied and modified
# for each extension used in the data directories
#---------------------------------------------------------------------------------

#---------------------------------------------------------------------------------
# rule to build soundbank from music files
#---------------------------------------------------------------------------------
soundbank.bin soundbank.h : $(AUDIOFILES)
#---------------------------------------------------------------------------------
	@mmutil $^ -osoundbank.bin -hsoundbank.h

#---------------------------------------------------------------------------------
# This rule links in binary data with the .bin extension
#---------------------------------------------------------------------------------
%.bin.o	%_bin.h :	%.bin
#---------------------------------------------------------------------------------
	@echo $(notdir $<)
	@$(bin2o)


-include $(DEPSDIR)/*.d
#---------------------------------------------------------------------------------------
endif
#---------------------------------------------------------------------------------------

Sorry for the long file! This is a complete Makefile for a devKitARM project. Make sure you save it!

The wait’s over, it’s time to code!

Now, it’s time to write our code. First, we are going to make a program that fills the screen with a nice spectrum of red, green and blue using video mode 3.

First, we need code to set the video mode. This is very easy. According to GBATEK’s GBA LCD Video Controller section, there is a certain memory address that acts as a 16-bit register that you can write your options to and the hardware will just do the work for you. This is called DISPCNT and can be found in memory address 0x4000000 (0x meaning hexadecimal, same as GBATEK putting an h after numbers). From this, we can figure out the following simple function to write the register:

#include <stdint.h>

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x40000000 = flags;
}

This code here is simple. First, we include stdint.h to get access to the fixed-width integer types (to ensure we always know how large our numbers are). Then, we create a function that takes any flags we want to give it and writes them to the address the register is located in.

According to GBATEK, the actual video mode is set in bits 0 through 2, so we need to write a number to set those bits to represent the number 3 for video mode 3. This is simple, writing the number 3 is enough for this.

#include <stdint.h>

#define MODE3 0x3

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x40000000 = flags;
}

Then, GBATEK mentions that all bitmaps mode use background 2. Thus, we need to enable background to. According to it, we need to set bit 10 of the register to 1. A simple binary to hexadecimal conversion will show us that for this we just need to write the number 0x400 to the register.

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

We’ve got all we need now. Let’s create our main function.

In it, we are going to use our setVideoMode function to write the value we want to the register so we can initialize our video mode, then we can do the actual drawing.

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

int main() {
    setVideoMode(MODE3|ENABLEBG2);

    return 0;
}

And that’s it, this code will properly set the video mode. We won’t run it yet as we will not notice anything.

Notice the use of that | operator there. This is the Bitwise OR operator, and I used it to combine together the bits for MODE3 and ENABLEBG2 so that all the correct bits of the register are set. We can’t give the numbers to the register one at a time, as all the bits must be set at the same time. We need to combine numbers and give them to the register together to properly set our video mode.

Accessing the bitmap framebuffer

Now, all we need to start drawing is to write to the framebuffer. The Game Boy will take care of updating the screen for us.

In GBATEK’s “LCD VRAM Bitmap BG Modes” section, you can see that the framebuffer starts in address 0x6000000. Let’s define it in our code:

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

int main() {
    setVideoMode(MODE3|ENABLEBG2);

    uint16_t *frameBuffer = (uint16_t *) 0x6000000;

    return 0;
}

Note how the framebuffer is accessed like an array of uint16_t due to our 15-bit color depth.

Drawing some of them pixels

Now, it’s time to finally get to drawing pixels.

To do that, we will access the framebuffer as just a big array of pixels. The first pixel (pixel 0), is in position 0 and is the top left pixel (Very important to remember that the ordering goes top to bottom, left to right. A higher y value will actually be towards the lower end of the screen, which is weird, but an x value will be towards the right, which is something you might be used to).

“But, how do we calculate which position we want to write to for a certain screen coordinate?”

Glad you asked.

You see, in computer graphics, we use a certain formula to calculate a memory position for a screen coordinate in these specific circumstances. Here’s the secret sauce:

Position in array = pixel Y position * screen width + pixel X position

So, say you want to color the pixel in the center of the screen in mode 3. It has a resolution of 240×160. Simple math gives us this:

Position in array = 80 * 240 + 120

Actually, this is what we’re going to use in our code right now.

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

int main() {
    setVideoMode(MODE3|ENABLEBG2);

    uint16_t *frameBuffer = (uint16_t *) 0x6000000;

    frameBuffer[80 * 240 + 120] = 0x001F;

    return 0;
}

0x001F represents a completely red color, so this will color the pixel on the very center of the screen to red.

To make this easier, let’s use a macro (found on line 10) to make the 15-bit values for us from given RGB values (ranging from 0 to 255). This macro will basically clamp our values to the range the GBA can work with (5-bit RGB values instead of 8-bit values that this macro can take) and then pack it into a 15 bit number:

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

#define RGB(r,g,b) (uint16_t) ((b >> 3) << 10) + ((g >> 3) << 5) + (r >> 3)

int main() {
    setVideoMode(MODE3|ENABLEBG2);

    uint16_t *frameBuffer = (uint16_t *) 0x6000000;

    frameBuffer[80 * 240 + 120] = RGB(255, 0, 0);

    return 0;
}

Now we will build and run this code.

In VSCode, go to the Terminal menu and open a new terminal. Type make and press Enter.

If all went well, you should see something like this:

Now, you may run this code. There are 2 options for this:

  • Via an emulator on your computer
  • Running from a flash cartridge that you can program

There normally is a 3rd option, and that is downloading from a computer via a multiboot cable. However, the code itself needs to support multiboot, but we are not doing that now, as it requires quite more work. So there are only 2 options.

My choice here is an emulator. Specifically, I’m using Visual Boy Advance. If I run vba HelloGBA.gba, it should work. If you ran this code, you should see this on your screen:

As you can see, the center pixel is colored red.

Congratulations! You drew your first pixel!

Let’s now draw a line in that entire row of pixels. For this, we are going to use a loop that will iterate over every X position in that row and colors them red. Let’s try it:

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

#define RGB(r,g,b) (uint16_t) ((b >> 3) << 10) + ((g >> 3) << 5) + (r >> 3)

int main() {
    setVideoMode(MODE3|ENABLEBG2);

    uint16_t *frameBuffer = (uint16_t *) 0x6000000;

    // Remember our horizontal resolution is 240 pixels, pixels 0 through to 239
    for (int x = 0; x < 239; x++) {
        frameBuffer[80 * 240 + x] = RGB(255, 0, 0);
    }
    
    return 0;
}

Let’s build and run that the same way.

A red line! Awesome!

Extending our pixels: A static color spectrum effect

With clever (but simple) use of pixel coordinates in place of RGB values, we can make the GBA change a pixel’s color depending on its position. We want to achieve this:

Let’s take a look at it here.

You can notice three things, after careful inspection:

  • The red goes down the more we go to the right (x goes up, red goes down)
  • The green goes up the higher we go (y goes down, green goes up)
  • The blue goes up the more we go to the right (x goes up, blue goes up)

Thus, we can conclude that here we need to use a loop along with RGB values that meet these criteria.

After some thinking, this is the final result:

#include <stdint.h>

#define MODE3 0x3
#define ENABLEBG2 0x400

void setVideoMode(uint16_t flags) {
    *(uint16_t*) 0x4000000 = flags;
}

#define RGB(r,g,b) (uint16_t) ((b >> 3) << 10) + ((g >> 3) << 5) + (r >> 3)

int main() {
    setVideoMode(MODE3|ENABLEBG2);

    uint16_t *frameBuffer = (uint16_t *) 0x6000000;

    // Remember our horizontal resolution is 240 pixels, pixels 0 through to 239
    for (int x = 0; x < 239; x++) {
        // 160 pixels, 0 through 159
        for (int y = 0; y < 159; y++) {
            frameBuffer[y * 240 + x] = RGB(255-x, 255-y, x);
        }
    }
    
    return 0;
}

If you run that, you’ll get the above image.

Closing

That’s all for today! See if you can play around with the colors and make some cool effects! Remember that all the source code for this tutorial can be found on GitHub. I look forward to seeing you in the next tutorials! Also, please, if you liked this tutorial, forward the website, as it doesn’t pop up on Google. Have a great day!

Leave a comment

Design a site like this with WordPress.com
Get started