Monday, November 1, 2010

Bitmapped Sprites on the GirlTech IMME

The GirlTech IMME is a fine platform for radio hacking and embedded programming, but the LCD of the device is by no means designed for lightning fast graphics. In this brief article, I demonstrate the method by which a sprite library can be constructed which abstracts away the less neighborly minutia of the LCD in favor of a row-wise framebuffer that gets flushed to the LCD as appropriate. This isn't fast enough for a port of Sonic the Hedgehog, but it's certainly sufficient for ZombieGotcha, a network disease simulation game that Eli Skipp and I are prototyping.

To quickly recap, the GirlTech IMME is a children's toy powered by the CC1110F32, which combines an 8051 microcontroller with a versatile sub-GHz radio. The toy also includes a text LCD controller and keyboard, making it a delightful platform for embedded systems hacking and prototyping. Thanks to Dave's article on attaching a debugger and reversing the LCD, it is possible to wire a GoodFET into the IMME, allowing for debugging and reprogramming of the device.

Since the days of the first raster displays, computers have been drawing images as rows, because that's the way that a television's electron stream is traced along the display. The IMME draws rows internally, but because it is intended to draw fonts, its rows are eight pixels tall. For example, the bytes {0x7f, 0x08, 0x08, 0x08, 0x7f, 0x00} are pushed to the LCD in order to draw an uppercase letter H in Dave's LCD Demo for the IMME. Drawing this on graph paper or rendering it by Python, it becomes clear that 0x7F represents a side of the letter, 0x08 represents the bar, and 0x00 is the space following the letter.

Rather than attempting to natively store a framebuffer in the LCD's format, I chose to internally index by row, then to translate during the export of the framebuffer to the LCD. My primary reason for doing this is to maintain portability of the ZombieGotcha code to custom hardware. It also simplifies the transcription of sprites from original bitmaps to C structures.

Having an internal framebuffer is not without cost, however. The buffer is 132 pixels wide and 64 pixels tall. Even with bit-packing, this is more than a kilobyte in size, consuming more than a quarter of the CC1110's 4kB of XDATA RAM. (Programs and sprites are stored in CODE Flash memory, of which there is 32kB, so this limit is not quite so severe as it sounds.)

My sprite toolchain is begun with bitmap images submitted by the artist. I cannot stress enough how important it is that your pixel artist understand pixels. Every sprite must be drawn at native resolution, with proper offsets of each frame and an understanding that the LCD is what it is, regardless of what Photoshop might render.

From the original bitmap sprites, PNM files are produced that are more easily parsed by Perl. This format consists of whitespace-delimited words for the format, width, and height of the sprite. In this case, the sprite consists of three frames, each of them being 24 pixels width and 32 pixels tall. Other sizes are possible, of course, but it is convenient for bit packing that the width is an even multiple of 8, so that the old pixels needn't be read back in.

It is then necessary to use an ugly Perl script to convert the sprite from a PNM to a C array of bytes. Pretty Perl won't work, of course, because such a thing doesn't exist. My script, sprite2c.pl, takes a 24-bit color PNM file and converts it to a 1-bit map that is byte-packed. Each of these is included by the preprocessor as a __code unsigned char[], with the __code keyword implying that the object should be stored in Flash rather than RAM.

Sprite animations are performed by storing the frame count along with the width and height in the C structure. Considering the resource constraints of this system, frame counts change very rarely and are stored within the C code.

Dumping the frame-buffer to the LCD is as simple as writing every stripe of data to the screen. Potentially, as an optimization, you could keep track of which stripes have been invalidated across what horizontal range, updating only where necessary.

Keeping in mind that the LCD is updated as stripes of 8 pixels in height, code such as the following will refresh the entire LCD from the frame-buffer. Be sure not to erase the LCD between writes, as that would cause unnecessary flickering.

The framebuffer elements themselves are grabbed by selecting the pixel index divided by 8, then masking off the selected bit. The following functions work admirably for this purpose.

If greater performance is required, a game should certainly be designed with a custom graphics library that optimizes the few things it does most. Performances can be gained by storing sprites and the frame-buffer natively in the row/stripe format that the LCD expects, simplifying conversion. Mike Ossmann used the technique of basing most graphics around vertical lines in his spectrum analyzer firmware, allowing for channels to be redrawned as they are scanned rather than flushed from a frame-buffer.

As the ZombieGotcha game is largely turn-based and the frame-rate is not a dire concern, I don't expect to optimize the sprite library much beyond what is presented here.

One major addition will be that of screenshots, as I'd like to produce animated GIFs of game action for the website. Screenshots can be produced by the method that I outline in CC1110 Instrumentation with Python, dumping the frame-buffer from XDATA with a GoodFET and writing that to disk. Alternatively, full-speed screenshots could be dumped by sniffing the LCD's SPI bus using a Total Phase Beagle or other SPI protocol analyzer.

For those of you with an IMME, Eli and I will be releasing the ZombieGotcha game at the Twenty-Seventh Chaos Communications Congress in Berlin this winter. We'll bring a bed of nails for reflashing IMME units on the spot, as well as GoodFET kits for modifying your IMME to be a development kit. (The ZombieGotcha will also run on full-custom hardware, but we are maintaining IMME support in parallel.)

As a final note, a teaser of the ZombieGotcha opening screen can be found here in the Intel-Hex format. I'll give a GoodFET40 kit to the first neighbor who sends me an animated GIF of it and a script to generate the same, either by a debugger or an emulator.

Every GoodFET is designed to be bootstrapped by USB. You do not need to already have a JTAG programmer of any kind in order to build it; just place the parts on the board and run 'goodfet.bsl --fromweb' to flash it.