Home New Tutorials Programs
1-R4DS Setup 2-Software Space Invaders
Old Tutorials DSEmu
1-Setup 4-Sound 7-FIFO
2-Framebuffer 5-SRAM 8-Interrupts
3-Keys 6-Filesystems 9-Microphone
10-Extended Rotation Background

Homebrew Nintendo DS Development Part 3

This tutorial was tested and works with libnds dated 2005-12-12 and DSEmu 0.4.9.

Part Two of this tutorial series went through how to use the framebuffer graphics mode and the vertical blank interrupt

This part, Part Three, will go through how to handle the user pressing the keys on the Nintendo DS.

REG_KEYINPUT Register

The Nintendo DS has a hardware register that contains bits that are turned on when the keys on the device are pressed. The register, called REG_KEYINPUT in libnds, is located at memory address 0x4000130. It is read-only and the following bits will be turned off (ie. set to '0') when the relevant key is pressed:

KEYS BitKey'ndslib' defineDown if...Up if ...
0AKEY_AClearedSet
1BKEY_BClearedSet
2SelectKEY_SELECTClearedSet
3StartKEY_STARTClearedSet
4Directional RightKEY_RIGHTClearedSet
5Directional LeftKEY_LEFTClearedSet
6Directional UpKEY_UPClearedSet
7Directional DownKEY_DOWNClearedSet
8Right Alternate ButtonKEY_RClearedSet
9Left Alternate ButtonKEY_LClearedSet

REG_KEYXY Register

You'll notice that there are two keys missing. These are the 'X' and 'Y' keys which are new to the Nintendo DS and don't exist on the Gameboy Advance.

These two keys are read from a different register, REG_KEYXY, located at memory address 0x04000136. Unfortunately this register can only be accessed by the ARM7, whereas the KEYS register can be accessed by both the ARM7 and the ARM9.

To allow reading this register from the ARM9, the default ARM7 template code from 'ndslib' reads it during the vertical blank interrupt and stores it in IPC->buttons. IPC is a structure containing useful data read by the ARM7 that can be accessed from the ARM9. Here is the relevant section from the ARM7 template code:

void InterruptHandler(void) {
   [...]

   but = REG_KEYXY;

   [...]

   IPC->heartbeat = heartbeat;
   IPC->buttons   = but;
   IPC->touchX    = x;

   [...]
}

The REG_KEYXY register has bits cleared when button X or Y is currently pressed. It also contains bits to indicate whether the touch screen has the pen pressed down on it and when the screen hinge on the DS is closed:

XKEYS BitKey'ndslib' defineDown if...Up if ...
0X(1 << 0)ClearedSet
1Y(1 << 1)ClearedSet
2Pen Down(1 << 6)ClearedSet
3Hinge(1 << 7)SetCleared

Note that 'Hinge' is different in that the relevant bit is set if the hinge is closed and cleared if it is open.

Reading the Keys

To make it a bit easier to read the keys from REG_KEYINPUT I take the complement of its value. This allows use of the '&' operator to see if the key is pressed. By taking the complement we can write code like:

    uint16 keysPressed = ~(REG_KEYINPUT);
    if(keys_pressed & KEY_UP)
      --shape_y;

Instead of the less intuitive:

    if(!(REG_KEYINPUT & KEY_UP))
      --shape_y;

The same can be done for the REG_KEYXY register on the ARM7::

    uint16 specialKeysPressed = ~IPC->buttons;

    // Y Key
    if(specialKeysPressed & (1 << 1))
      shape_color = RGB15(7, 7, 7);

    // X Key
    if(specialKeysPressed & (1 << 0))
      shape_color = RGB15(0, 15, 15);

    // Pen Down
    if(specialKeysPressed & (1 << 6))
      shape_color = RGB15(0, 31, 31);

    // Hinge closed
    if(!(specialKeysPressed & (1 << 7)))
      shape_color = RGB15(0, 0, 0);

Notice the '!' is required for the hinge test since it is the opposite of the other bit flags.

Moving a shape with keys

To demonstrate the use of keys we'll make a slight modification to the shape example from Tuturial Two.

Instead of having the shape move across the screen automatically we'll control it with the arrow keys. The other buttons will change the color of the shape. Closing the hinge will make it disappear (by changing the color to black). Re-opening the hinge and pressing another color button will make it appear again.

To change the color we'll use a global variable to hold the current color:

static uint16 shape_color = RGB15(31, 0, 0);

And the drawing code will be changed to use this variable for the color:

void on_irq() 
{	
  if(REG_IF & IRQ_VBLANK) {
    draw_shape(old_x, old_y, VRAM_A, RGB15(0, 0, 0));
    draw_shape(shape_x, shape_y, VRAM_A, shape_color);

    // Tell the DS we handled the VBLANK interrupt
    VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
    REG_IF |= IRQ_VBLANK;
  }
  else {
    // Ignore all other interrupts
    REG_IF = REG_IF;
  }
}

The shape is moved around by the arrow keys. The key press tests are done as seperate 'if' statements to allow pressing combinations of the keys to move diagonally:

    uint16 keysPressed = ~(REG_KEYINPUT);

    // Based on the key pressed, move the shape.
    if(keysPressed & KEY_UP)
      --shape_y;
    if(keysPressed & KEY_DOWN)
      ++shape_y;
    if(keysPressed & KEY_LEFT)
      --shape_x;
    if(keysPressed & KEY_RIGHT)
      ++shape_x;

The color changing keys are tested using the IPC->buttons value, but first we get the complement of it so we can use more intuitive tests:

    uint16 specialKeysPressed = ~IPC->buttons;

    // Change the color of the shape if the relevant key was pressed.
    if(keysPressed & KEY_A)
      shape_color = RGB15(31, 0, 0);

    if(keysPressed & KEY_B)
      shape_color = RGB15(0, 31, 0);

    if(keysPressed & KEY_SELECT)
      shape_color = RGB15(0, 0, 31);

    if(keysPressed & KEY_START)
      shape_color = RGB15(31, 31, 31);

    if(keysPressed & KEY_R)
      shape_color = RGB15(15, 0, 15);

    if(keysPressed & KEY_L)
      shape_color = RGB15(7, 15, 7);

    // Y Key
    if(specialKeysPressed & (1 << 1))
      shape_color = RGB15(7, 7, 7);

    // X Key
    if(specialKeysPressed & (1 << 0))
      shape_color = RGB15(0, 15, 15);

    // Pen Down
    if(specialKeysPressed & (1 << 6))
      shape_color = RGB15(0, 31, 31);

    // Hinge closed
    if(!(specialKeysPressed & (1 << 7)))
      shape_color = RGB15(0, 0, 0);

Building

The steps for building the application are exactly as outlined in the second tutorial. I use a default arm7_main.cpp and the ARM9 code is in arm9_main.cpp. A simple Makefile file is supplied to run the compiler commands.

The complete source code is supplied in keys_demo1.zip and you can download the keys_demo1.nds and keys_demo1.nds.gba files for running on the emulators or hardware.

Conclusion

This tutorial showed how to detect the different keys presses on the DS. The approach taken is not the only way to do things of course.

The approach shown here uses 'polling' where we check the key registers to see what value they contain at a point in time. We do this between each wait for the vertical blank interrupt. It is possible that a user could press and release a key so fast that both events happen before or after our polling check. Our program would never get the notification of the button press.

While this is highly unlikely to happen at 60 frames per second, it can be avoided by using 'interrupts'. Just like there is an interrupt for the Vertical Blank period, there is an interrupt for key events. We can have an interrupt function called immediately when a key is pressed. The interrupts tutorial demonstrates how to do this and outlines some disadvantages with the interrupt method. Tutorial six shows another approach to key handling which is more usable in 'real applications'.

Any comments or suggestions are welcomed. See my contact details below.