I made a Key Tester using the STDIO header included with GBDK. It works... Until it doesn't. All the keys register and print to the screen, except the Left(5) and Right(4) Directional Keys, which cause the program to crash and the screen to go blank.

Pressing Left shows me this line in the Debugger (BGB):

HRAM: FFAD D3                             undefined code                                     ;0 26

Pressing Right shows me this:

HRAM: FF00 10 10                          <corrupted stop>                                ;1 2
HRAM: FF82 B7                              or      a                                               ;0 26

And this line in the bottom right:


I've gone over countless documents to no avail, with no real answers as to why 6 out of 8 Gameboy keypresses register as expected.

Here is my code.

#include "C:\GBDK\include\stdio.h"
#include "C:\GBDK\include\gb\gb.h"

#define JOYPAD_REGISTER (*(volatile unsigned char *)0xFF00)
#define BUTTON_DOWN_REGISTER (*(volatile unsigned char *)0xFF80)
#define BUTTON_HIT_REGISTER (*(volatile unsigned char *)0xFF81)

void ReadJoypad() {
    unsigned char currentButtonState = 0;
    unsigned char previousButtonState, buttonHitState;
     //Directional Key Detection
    JOYPAD_REGISTER = 1 << 5;
    for (int i = 0; i < 4; i++) {
        currentButtonState = JOYPAD_REGISTER;
    currentButtonState = (~currentButtonState & 0x0F) << 4;

    JOYPAD_REGISTER = 1 << 4;
    for (int i = 0; i < 10; i++) {
        currentButtonState |= ~JOYPAD_REGISTER & 0x0F;

    previousButtonState = BUTTON_DOWN_REGISTER;
    buttonHitState = (previousButtonState ^ currentButtonState) & currentButtonState;
    BUTTON_HIT_REGISTER = buttonHitState;

    BUTTON_DOWN_REGISTER = currentButtonState;

    JOYPAD_REGISTER = 1 << 5 | 1 << 4;

void main() {
    while (1) {
        unsigned char buttonHitState = BUTTON_HIT_REGISTER;

        if (buttonHitState != 0) {
            for (int i = 0; i < 8; i++) {
                if (buttonHitState & (1 << i)) {
                    printf("Button %d is being pressed\n", i);

The first loop in ReadJoypad() is supposed to shift the 1 bit of 0xFF00 left five times and then poll for presses on the D-Pad. That section of code seems to be where all my problems are coming from. And I've tried countless printf() tests to see what might be going wrong from the point of initialization onward. Nothing...

The only function sourced from GB.h is delay()

  • 2
    The macro definitions make me anxious: *(volatile unsigned char *)0xFF00. I would have written those like (*(volatile unsigned char *)0xFF00), to make sure there were no surprises with how that macros combine with surrounding code.
    – Jon Hess
    Jan 24 at 1:58
  • 2
    What is the logic in not using variables for BUTTON_DOWN_REGISTER and BUTTON_HIT_REGISTER?
    – UncleBod
    Jan 24 at 11:55
  • 1
    I'm not assuming anything. I'm learning by example. The example didn't work, so I came here to learn why. Jan 24 at 16:41
  • 2
    If you would use "normal" way of declaring hram for gameboy in sdcc you prolly would get warning from linker... "normal" way is volatile __sfr __at (0xFFxx) variable_name;
    – Selvin
    Jan 24 at 20:01
  • 2
    The solution should be posted as an answer, and not in the question, as your last few edits have done. Thanks. Jan 24 at 23:20

2 Answers 2


The modern compiler, or rather, the underlying libraries and build system that come with the compiling environment put some important code and/or data in the addresses FF80 and FF81 which you happily overwrite.

It seems that there is a function called refresh_OAM and it is called periodically in vblank interrupt, and the code of this function is copied to HRAM address FF80 by default in the platform specific C startup code.

So do not use hard coded memory addresses unless you know from the docs that the addresses are free.

  • Those two addresses are used for both BUTTON_DOWN and BUTTON_HIT in the GB assembly for Tetris. So, it seemed reasonable to use the same addresses when writing my Key Tester in C. Jan 24 at 12:50
  • 7
    @OPMeStudio Each compiled program (for such a system) has static variables allocated to certain addresses. But it is an error to use the very same address for other programs. Jan 24 at 16:27
  • Of course, but when you're starting your experimentation from null, all you have are the examples and documentation available to you. I'll get it eventually. Jan 24 at 16:40
  • Ah, I was just assuming those were some memory mapped IO addresses from the gameboy hardware or something.
    – Jon Hess
    Jan 24 at 19:59
  • 5
    Well for function which copy shadow_oam to oam... It have to be in hram - that's how DMA works on gameboy... You can disable it with DISABLE_OAM_DMA; (but than most sprites related function in gbdk are useless)
    – Selvin
    Jan 24 at 20:17

The issue was that the default address set for GBDK's OAM refresh function was 0xFF80 which conflicted with code.

Luckily, GBDK offers a solution via LCC during the linker phase:

lcc -Wl-g_refresh_OAM=0xFEFF -o  test.gb main.o

This allowed me to change the address used by refresh_OAM to 0xFEFF. Which is commonly used for OAM manipulation in commercial titles.

Now, everything is in working order.

Thank you for all the answers and comments leading me to this answer.

// 0x0000 - 0x3FFF ROM 00
// 0x4000 - 0x7FFF ROM 01+
// 0x8000 - 0x9FFF VRAM
// 0xA000 - 0xBFFF ExRAM
// 0xC000 - 0xDFFF WRAM
// 0xE000 - 0xFDFF Echo RAM
// 0xFE00 - 0xFEFF OAM
// 0xFF00 - 0xF7FF HARDWARE
// 0xFF80 - 0xFFFE HRAM
// 0xFFFF Interrupt Switch

Please Note: I'm only using GBDK as a bootstrap with the goal of abandoning it entirely. Apart from dealing with the library's defaults in with regards to this code, this post in general does not pertain to GBDK.

  • 5
    This cannot be a solution. Why tell the linker to copy the refresh_OAM code to FEFF? The first byte of code goes to area that is marked "Not Usable"/"Prohibited" as using that area is unstable and may trigger OAM corruption in some HW platforms. And the next bytes will overwrite the IO registers. The solution is to either move it to some area that works, or force your variables to use area that is free, or better and simplest solution what you should be doing in the first place, let the compiler decide where to put the variable like in any normal C program.
    – Justme
    Jan 25 at 20:50
  • Whom are you quoting? Jan 28 at 8:40
  • @user3840170 Not any specific source, but if you take any source describing the memory map of the device, those kind of words are used for FEA0-FEFF area.
    – Justme
    Jan 28 at 11:07
  • @user3840170, stop changing the name of my post. GBDK uses FF80 for OAM refresh. Not every one does. Not knowing this caused a conflict, because my code and their code were interfering with one another. The goal is to nix GBDK from my workflow, so your title doesn't fit the issue. Whether or not my current fix will be my permanent fix is to be seen, but the issue isn't hardware, or me using that hardware incorrectly. Its two developers fighting over the same resource. Jan 29 at 13:44

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .