Contents
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.
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
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(); }
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));
Someties you want a sprite to appear as a solid 16x16 pixel block. For example frogger3 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)
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.
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:
Each image uses 4 bits per pixel:
So both can fit into one sprite page, like this:
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:
and
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.
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 :download:`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 :download:`square440.wav`
Last modified $Date: 2011-10-16 12:05:43 -0700 (Sun, 16 Oct 2011) $