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 2

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

Part One of this tutorial series went through how to compile and run a simple 'Hello World' style program displaying text on the screen.

This part, Part Two, will go through how to draw on one of the Nintendo DS screens using a framebuffer mode. Each screen on the DS can be put into a variety of different modes. Each mode has advantages and disadvantages but I'm going to use the framebuffer mode here as it is the easiest to directly draw to.

Framebuffer

A 'framebuffer' is a screen mode where the screen is mapped to a portion of memory. Writing data to this memory area will result in that data appearing on the screen. In the mode I'm going to use, each pixel on the screen is represented by two bytes of data. This corresponds to the C language type of a 16 bit unsigned integer. The data we write to this memory location is the color of the pixel that will appear in 555 format.

So we don't have to manually convert colors into 555 format there is a handy macro called 'RGB15' that lets us define the amount of red, green and blue colors in the pixel. Each red, green and blue component is a number from 0 through to 31. 0 Means no color of that component and 31 means the maximum color. Here are some examples:

RGB15Color
RGB15(31,0,0)Red
RGB15(0,31,0)Green
RGB15(0,0,31)Blue
RGB15(0,0,0)Black
RGB15(31,31,31)White

The following code snippet demonstrates how to fill the screen with the color blue given a pointer to the start of the framebuffer:

  uint16* framebuffer = ...;
  for(int i = 0; i < SCREEN_WIDTH * SCREEN_HEIGHT; ++i) 
    *framebuffer++ = RGB15(0,0,31);

Writing to the framebuffer will immediately draw the pixel. The nice thing about a framebuffer mode is you can draw anything you want, anywhere on the screen, and it directly uses the hardware acceleration of the 2D mode available on the Nintendo DS.

The downside of the framebuffer mode is you have to do everything yourself. There are no 'sprites', tiled maps, scrolling, etc unless you write the code to do it yourself. Other modes of the DS may be better suited for this type of thing and they'll be covered in a later tutorial. In the meantime, the framebuffer allows lots of flexibility and gets us familiar with the hardware.

Screens

From a hardware perspective there are two screens on the DS. One on the top and one on the bottom. The bottom screen is the only screen that is touch sensitive.

From a programming perspective there are two screens also. A 'main' screen and a 'sub' screen. Either of these two programming screens can be mapped to any of the two hardware screens. This example will use only one screen, the main screen, and it will be mapped to the top screen on the hardware.

To set the mode of the main screen we use a function called 'videoSetMode'. There is more than one framebuffer that can be mapped to a screen. This is to allow doing things like double buffering and page flipping. We're only going to use one framebuffer at the moment so the mode we will use is MODE_FB0.

  videoSetMode(MODE_FB0);

The memory area for the framebuffer is set to a number of areas called 'VRAM' with a letter from 'A' onwards, for each area. We need to tell the video system what VRAM area we are going to use for the framebuffer. We might as well use the first one, VRAM_A:

  vramSetBankA(VRAM_A_LCD);

Drawing a shape

The shape we are going to draw on the screen is a simple square in one color. To do this we write a function that takes the x and y coordinates where the shape will be drawn on the screen, the color to draw it in, and a pointer to the start of the framebuffer where the shape will be drawn:

void draw_shape(int x, int y, uint16* buffer, uint16 color)
{
  buffer += y * SCREEN_WIDTH + x;
  for(int i = 0; i < shape_height; ++i) {
    uint16* line = buffer + (SCREEN_WIDTH * i);
    for(int j = 0; j < shape_width; ++j) {
      *line++ = color;
    }
  }
}

The framebuffer is layed out in memory in rows. So if the screen were 200 pixels wide the first 200 uint16's in the frame buffer are the pixels for the first screen row. The second 200 uint16's would be the second row, etc.

The function, draw_shape, first computes where in the buffer the first pixel to draw to is by calculating 'y' rows down and 'x' pixels across. Note that SCREEN_WIDTH and SCREEN_HEIGHT are macros provided by 'ndslib' to return the screen width and height.

The function then goes through and draws each row of the shape by putting the pixel color data in the correct place in the framebuffer.

shape_height and shape_width are convenient static variables to allow changing these values for test purposes:

static int shape_width = 10;
static int shape_height = 10;

Moving the shape

To give the shape the appearance of moving across the screen we need to erase the shape at its current position and redraw it in a new position. This is done by keeping track of its 'x' and 'y' position before and after the move.

static int old_x = 0;
static int old_y = 0;
static int shape_x = 0;
static int shape_y = 0;

Drawing then becomes a simple matter of calling 'draw_shape' with the background colour (in this case, black) to erase it, using the old_x and old_y positions, and calling it again with the new 'x' and 'y' positions with the shapes color (red in this case):

  draw_shape(old_x, old_y, VRAM_A, RGB15(0, 0, 0));
  draw_shape(shape_x, shape_y, VRAM_A, RGB15(31, 0, 0));

Notice the framebuffer passed to the 'draw_shape' function is 'VRAM_A', the same framebuffer we mapped to the main screen earlier.

Not quite right...

A simple 'main' function to do the drawing and computing of the positions might now be something like:

int main(void)
{
  powerON(POWER_ALL);
  videoSetMode(MODE_FB0);
  vramSetBankA(VRAM_A_LCD);

  while(1) {
    old_x = shape_x;
    old_y = shape_y;
    shape_x++;
    if(shape_x + shape_width >= SCREEN_WIDTH) {
      shape_x = 0;
      shape_y += shape_height;
      if(shape_y + shape_height >= SCREEN_HEIGHT) {
	shape_y = 0;
      }
    }      
    draw_shape(old_x, old_y, VRAM_A, RGB15(0, 0, 0));
    draw_shape(shape_x, shape_y, VRAM_A, RGB15(31, 0, 0));
  }

If you run this you'll see some very fast 'slanted' shapes racing across the screen like in this Dualis screenshot.

Vertical Blank Interrupt

The reason for the slanted shapes in the previous attempt is because of the way screen display works. The hardware device redraws the screen every 1/60th of a second. It does this by visiting each pixel, row by row, and copying the contents of the framebuffer for that pixel to the hardware screen pixel.

While this is happening, inside our main, we are changing the contents of the frame buffer which gets written to the screen. So if the hardware draws our shape just before we erase it, it won't be erased immediately. If we then draw our new shape, just before the hardware draws it, it'll have parts of our old shape and parts of our new shape.

Thankfully the hardware has a way of telling us when it has finished drawing the screen. This is called the 'Vertical Blank Interrupt'. We can register a function that gets called when this happens.

An 'interrupt' is a hardware mechanism that 'interrupts' what we are doing at the current time (running inside a while loop in 'main' for example) to call a function to quickly do something else. When that interrupt function returns, the previous activity continues as if it was never interrupted.

To prevent the drawing problem we saw before, we want to draw to the framebuffer at a time when the hardware is not putting the contents of the framebuffer on the screen. The best time to do this is during the vertical blank interrupt.

Setting up the Interrupt

A tutorial on interrupts is available which goes into detail about how interrupts work. In the meantime, I'll outline briefly what is happening in the interrupt code.

First we need to tell the Nintendo DS what function we want called when an interrupt occurs:

void InitInterruptHandler()
{
  REG_IME = 0;
  IRQ_HANDLER = on_irq;
  REG_IE = IRQ_VBLANK;
  REG_IF = ~0;
  DISP_SR = DISP_VBLANK_IRQ;
  REG_IME = 1;
}

In this code snippet we want only VBlank interrupts to occur and the 'on_irq' function will be called when that happens.

The function on_irq will do the drawing to the framebuffer that we previously did in 'main':

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, RGB15(31, 0, 0));

    // 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 main part of this function does the drawing we did previously in the 'while' loop in 'main'. The rest of the function does 'interrupt' handling stuff.

We also need to tell the DS hardware that we handled the VBLANK interrupt. This is required for the 'swiWaitForVBlank' call we use later and I'll explain why then. The code to do that was:

    // Tell the DS we handled the VBLANK interrupt
    VBLANK_INTR_WAIT_FLAGS |= IRQ_VBLANK;
    REG_IF |= IRQ_VBLANK;

Still not quite right...

We can remove our drawing code from 'main' now and it becomes:

int main(void)
{
  powerON(POWER_ALL);
  videoSetMode(MODE_FB0);
  vramSetBankA(VRAM_A_LCD);
  InitInterruptHandler();
  while(1) {
    old_x = shape_x;
    old_y = shape_y;
    shape_x++;
    if(shape_x + shape_width >= SCREEN_WIDTH) {
      shape_x = 0;
      shape_y += shape_height;
      if(shape_y + shape_height >= SCREEN_HEIGHT) {
	shape_y = 0;
      }
    }      
  }

Unfortunately running this shows a problem. This screen shot from Dualis shows the shape appearing multiple times on the screen eventually forming a checkerboard appearance.

Hopefully the reason is clear. The hardware calls the on_irq function every 1/60th of a second when the vertical blank interrupt occurs. This is when The shape is erased and redrawn.

Unfortunately the 'while' loop in 'main' is not synchronized to this frame rate. It runs as fast as possible incrementing the coordinates where the shape gets drawn. It may do this 50 times before the 'on_irq' routine gets called. As a result the drawing routines erase the shape from 'old_x' and 'old_y' which has been updated 50 times since the shape was last drawn. So the wrong area gets erased.

Getting it right

We can fix this by telling the 'while' loop to 'go to sleep' until the interrupt occurs. This has the side effect of making the while loop less 'busy'. The ARM9 processor can effectively slow down until the interrupt occurs. It also enables our framerate to be tied to 60 frames per second without any effort. The function that does this is 'swiWaitForVBlank'.

You may remember previously that inside 'on_irq' we set some registers to say that we'd processed the VBLANK interrupt. This is required for 'swiWaitForVBlank' to work. If we don't set those registers then 'swiWaitForVBlank' will hang on the hardware waiting for notification that the interrupt is handled...which never happens.

Adding this single line makes our example program work on the hardware and the Dualis emulator properly:

int main(void)
{
  powerON(POWER_ALL);
  videoSetMode(MODE_FB0);
  vramSetBankA(VRAM_A_LCD);
  InitInterruptHandler();
  while(1) {
    old_x = shape_x;
    old_y = shape_y;
    shape_x++;
    if(shape_x + shape_width >= SCREEN_WIDTH) {
      shape_x = 0;
      shape_y += shape_height;
      if(shape_y + shape_height >= SCREEN_HEIGHT) {
	shape_y = 0;
      }
    }      
    swiWaitForVBlank();
  }

  return 0;
}

A Dualis screenshot of the running application is here.

Swapping Screens

In some versions of Dualis the top and bottom screens will appear swapped when compared to the application running on hardware. Dualis in this case appears to default the main screen to be the bottom screen.

I mentioned before about how the main screen can be mapped to run in the bottom or top hardware screen. The current demo runs with the main screen mapped to the top screen. This can be changed by using the function 'lcdSwap'. Calling this will map the main screen to the bottom touch screen. It can be called at any time to swap the screens. The modified main below will display the application in the touch screen on the hardware:

int main(void)
{
  powerON(POWER_ALL);
  videoSetMode(MODE_FB0);
  vramSetBankA(VRAM_A_LCD);
  InitInterruptHandler();
  lcdSwap();
  while(1) {
    old_x = shape_x;
    old_y = shape_y;
    shape_x++;
    if(shape_x + shape_width >= SCREEN_WIDTH) {
      shape_x = 0;
      shape_y += shape_height;
      if(shape_y + shape_height >= SCREEN_HEIGHT) {
	shape_y = 0;
      }
    }      
    swiWaitForVBlank();
  }

  return 0;
}

Building

The steps for building the application are exactly as outlined in the first 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 framebuffer_demo1.zip and you can download the framebuffer_demo1.nds and framebuffer_demo1.nds.gba files for running on the emulators or hardware.

Conclusion

Using the framebuffer drawing techniques from this tutorial you can develop some simple animations and games. Using the basic touchscreen code that we displayed on the screen in Tutorial One you could even drag items around the screen. A later tutorial will go into this in more depth. Later tutorials will cover double buffering, sound, and other screen modes. Any comments or suggestions are welcomed. See my contact details below.