website statistics

Cookbook

Coding for Arduino

Use PROGMEM for graphics data

As explained on this page, http://www.arduino.cc/playground/Learning/Memory there are two types of memory in the Arduino: Flash and SRAM. Flash is relatively plentiful, so it is much better to put graphics data into the Arduino’s Flash. To do this, use the PROGMEM keyword. Here is a sprite image that uses the PROGMEM modifier to force the data into Flash:

PROGMEM prog_uchar image4[] = {
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x0f,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x28,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x2d,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x60, 0x00, 0x00, 0x8b, 0xe1, 0xe1, 0xe1, 0xe1, 0xbe,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xb6, 0x00, 0x00, 0x00, 0xb6, 0xe1, 0xe1, 0xe1, 0xe1, 0xc3,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x35, 0x0b, 0x00, 0x00, 0x8b, 0xe1, 0xe1, 0xe1, 0xe1, 0xdc,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x8b, 0x00, 0x8b, 0x00, 0x00, 0xb6, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0xe1, 0xbd, 0x00, 0x60, 0xe1, 0x00, 0x00, 0x8b, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0xe1, 0x60, 0x00, 0xb6, 0xe1, 0x00, 0x00, 0xb6, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0x8b, 0x00, 0x8b, 0xe1, 0xe1, 0x00, 0x00, 0x8b, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0x8b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8b, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x00, 0x00, 0x8b, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x00, 0x00, 0xaf, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0x00, 0x00, 0x92, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1, 0xe1,
};

All the Python and online tools generate graphic data in PROGMEM.

Background graphics

Text background color

GD::ascii() loads a text font, and GD::putstr() draws text on the screen. The text is white, but the background color - black by default - is actually transparent. This means that you can change the background color by setting the BG_COLOR register:

GD.ascii();                         // white-on-black
GD.wr16(BG_COLOR, RGB(0, 0, 64));   // relaxing dark blue background

Fade to black

To fade the background graphics to black, you can loop over the character palette RAM, decrementing R,G,B until they reach zero. Spread over a second or so (the outer loop below), this produces a nice fade-to-black effect.:

for (byte i = 0; i < 32; i++) {
  for (int j = RAM_PAL; j < (RAM_PAL + 2048); j += 2) {
    uint16_t pal = GD.rd16(j);
    byte r = 31 & (pal >> 10);
    byte g = 31 & (pal >> 5);
    byte b = 31 & pal;
    if (r) r--;
    if (g) g--;
    if (b) b--;
    pal = (r << 10) | (g << 5) | b;
    GD.wr16(j, pal);
  }
  GD.waitvblank();
  GD.waitvblank();
}

Sprites

Smooth sprite animation

For really smooth sprite animation, you need to synchronize the sprite updates with the vertical refresh. It’s not strictly necessary to do this - for example, sprites256 doesn’t, but asteroids does.

One way is to double-buffer the sprite updates: you write new sprite values into page 0, while displaying page 1, then display page 1 while updating page 0. Here’s the code from asteroids that starts the write into page 0 or 1, depending on whether the frame counter r is odd or even:

  GD.__wstartspr((r & 1) ? 256 : 0);  // write sprites to other frame

and to do the actual flip: wait for vertical refresh then change the SPR_PAGE register:

    GD.waitvblank();
    // swap frames
    GD.wr(SPR_PAGE, (r & 1));

Solid color sprites

Someties you want a sprite to appear as a solid 16x16 pixel block. For example Frogger Tutorial part 3 uses a plain black sprite to cover up screen edges. You can just treat the solid block as a regular graphic, but this uses sprite RAM. One trick is to load the four-color palette with all four colors the same, then draw a sprite with any source image, specifying the palette as PALETTE4A (pal = 8):

uint16_t pink = RGB(255, 192, 203);
GD.wr16(PALETTE4A + 0, pink);
GD.wr16(PALETTE4A + 2, pink);
GD.wr16(PALETTE4A + 4, pink);
GD.wr16(PALETTE4A + 6, pink);
GD.sprite(0, 100, 100, 0, 8);   // pink 16x16 square at (100,100)

Hiding sprites

When drawing a screen, you need to hide sprites that aren’t used. You can hide one sprite, say sprite 77, by setting its position to (400,400):

GD.sprite(0, 100, 100, 0, 8);   // pink 16x16 square at (100,100)

but when writing sprites in a block, there is an easier way to clear the sprites. The GD library keeps a counter spr of the current sprite slot. So to hide many sprites:

GD.__wstartspr(0);  // start writing sprites at slot 0
// .. (write visible sprites here ...)
// Now hide all the remaining sprites
while (GD.spr < 200)
  GD.xhide();
GD.__end();

This code draws the sprites that are visible, then quickly hides all other sprites, up to slot number 200. The asteroids game uses this technique.

Making extra 16-color palettes

The asteroids game uses 16-color sprite graphics throughout. The Gameduino sprite system supports two 16-color sprite palettes, PALETTE16A and PALETTE16B. But you can also use the 256 color palettes as 16-color palettes by cunningly loading them with colors in a certain pattern.

In this way you can use the four 256-color palettes to make two more 16-color palettes.

Here’s an illustrative example. Say you have a couple of sprite images that share a 16-color palette:

../_images/fig1.png

Each image uses 4 bits per pixel:

../_images/fig2.png ../_images/fig3.png

So both can fit into one sprite page, like this:

../_images/fig4.png

Of course you can use PALETTE16A to hold the palette, but you can also use two 256-color palettes, loading them with the original 16-color palette pal16:

// Use the first two 256-color palettes as pseudo-16 color palettes
for (int i = 0; i < 256; i++) {

  // palette 0 decodes low nibble, hence (i & 15)
  uint16_t rgb = pgm_read_word_near(pal16 + ((i & 15) << 1));
  GD.wr16(RAM_SPRPAL + 2 * i, rgb);

  // palette 1 decodes nigh nibble, hence (i >> 4)
  rgb = pgm_read_word_near(pal16 + ((i >> 4) << 1));
  GD.wr16(RAM_SPRPAL + 512 + 2 * i, rgb);
}

This loads the two palettes to decode low and high nybbles respectively, so that each gives the correct palette lookup:

../_images/fig5.png ../_images/fig6.png

Monochrome sprites

Gameduino natively supports 8- 4- and 2-bit sprite graphics. 1-bit sprite images might be useful - they would allow 512 sprite images to fit in the 16K sprite image RAM.

Audio

Square and sawtooth waves

The hardware audio voices only generate sine waves and noise - so how do you create the square and sawtooth waves of old video games? By summing up a few sine waves to get a close approximation to that original 8-bit sound.

Sawtooth is simpler; this function uses voices 0-3 to make the sound of a sawtooth wave:

#define SINE 0
#define NOISE 1
void sawtooth_wave(int f0)
{
  GD.voice(0, SINE, f0,     100,    100);
  GD.voice(1, SINE, 2 * f0, 100/2,  100/2);
  GD.voice(2, SINE, 3 * f0, 100/3,  100/3);
  GD.voice(3, SINE, 4 * f0, 100/4,  100/4);
}

Sample at 440Hz is here sawtooth440.wav

and for square waves the math is only slightly more complicated:

void squarewave(int f0)
{
  GD.voice(0, SINE, f0,     100,    100);
  GD.voice(1, SINE, 3 * f0, 100/3,  100/3);
  GD.voice(2, SINE, 5 * f0, 100/5,  100/5);
  GD.voice(3, SINE, 7 * f0, 100/7,  100/7);
}

Sample is here square440.wav

Last modified $Date: 2011-10-16 12:05:43 -0700 (Sun, 16 Oct 2011) $