- Platform: Arduino
- Language: C++
- Other used: Adafruit GFX library
This project is similar to my previous web based Pacman clock but instead of JS it's written in C++, and is running on an Arduino.
Because of the hard limitations of the Arduino hardware, porting the project had many difficulties.
The original JS source is ~800Kb in size, and usually runs on a desktop pc with a 1-2 Ghz CPU, and decent amount of ram.
Let's see the Arduino's specs:
- CPU speed: 16Mhz
- Flash ROM (max code size): 16 Kb
- RAM: 2 Kb
Also I have a display what isn't so fast, a full screen redraw takes about 2 secs to finish. (It's 0.5 frame/sec!) With this display speed it's almost impossible to make enjoyable fullscreen animations.
All of the above lead to a new way of rethinking how things should be done, as efficiently as I can do.
I wasn't afraid of the 16Kb code limit, because C++ is a compiled language, so it's possible to write small binary code. The CPU speed shouldn't be a big issue too, because I remember when I owned a 286 Pc with a 16Mhz CPU, and I could write programs/games that moved sprites on the screen smoothly.
Those old PCs had also dedicated video cards, and these early "GPUs" took over the hard work of displaying graphics from the CPU. Arduino doesn't have anything like a GPU, so all of the graphics must be done using raw CPU power.
The 16Kb code must contain ALL of the sprites, and their animation phases too, so I could run out of memory quickly.
Not to mention the 2Kb RAM limit, because all of the animations must be done with the CPU, so maybe I must use the RAM to push out the needed graphics to the display.
As you can see the main issue is the display. Because of graphics size, limited CPU power, and screen slowliness.
To spare with usable memory, I decided to use simple 2 color sprites (1 background color for transparent pixels, and 1 for the main color). These 2 colors can be easily stored in 1 bit. If I would like to use 16x16 pixels wide&tall sprites, I will need 16x16=256 bits to store one. This means 1 sprite will cost me only 32 bytes (!) of memory. And for example, if I want an animation with 4 phases, it will use 128 bytes, and that's not bad at all :)
The easiest way to store these sprites in memory in a binary format is... a binary format! :)
Here the ones mean the colorized pixel, and zeroes are where there shuld be transparent the sprite. Easy!
To spare some cpu time, we must draw only the pixels where there are 1 in our sprite matrix, and leave alone where there are the 0s. This could speed up screen drawing because we don't need to paint all the 256 pixels, just the ones which has color.
This method has it's own disadvantage, because if we move the sprite around the screen to a new position, it will leave its previous state here, making a snail-like pattern behind itself. Do you remember this:
A solution should be to first draw a 16x16 pixel black box on the previous place (to clear it on the screen), then paint out the whole sprite on its new position... but with this slow screen redraw scpeed it will cause noticable sprite flickering, and remember... we are trying to spare with CPU too, so we must paint as few pixels as possible.
So we need tricks. Some mask, what only clears out the remaining part of the previous position, to minimize the screen draws. It could be done with the below mask:
With this trick, if the pacman moves to the right, our mask will clear out the leftmost 2 pixels, and will clear the inside in his mouth, because he has yawning animation too. We could use different masks for each animation frame, but that will be more difficult, and we will need more mask sprites -> more memory usage.
Another interesting issue is the multicolor sprites' problem. The Pacman uses only yellow, but ghosts have multiple colors, how can we implement this with 1 bit bitmaps? Of course with splitting up the ghosts into multiple 1 color sprites. One part for the ghost's body, one for the white eyes, and one for the blue pupils. Also with correct positioning, we don't have to draw manually the "looking direction" sprites one by one, it can be done programmatically.
And our ghosts, and all of their animation frames only consumes 210 bytes of memory!
Glue the whole thing together
All we need is now to construct the screen with walls (they are at mostly constant positions), and move only our actors on the screen. Because we must change walls as time goes on, it must be stored in the 2Kb RAM, so it have to be as small as possible. I've choosed a simple char matrix for this:
With this method, it takes only 391 bytes of RAM.
Now all we have to do is to check regularly the current time, and when it changes, update the relevant part of this matrix. And in every minute we have to redraw the walls again. It can take up about 1 sec, but it isn't so annoying, so I think I can live with it.