This is a MicroPython library that provides the means for drawing sprites with tiled background on 16-bit SPI-based displays. It consists of a small part written in C that needs to be compiled into the firmware, and the rest written in Python, usually also included in the firmware as frozen modules.
This library was originally created in CircuitPython for #µGame, but has since been ported onto other platforms.
MicroPython 1.12 is released, and the new release includes an interesting feature: the .mpy modules can now contain native code compiled with C or any other language that produces standard object files. Of course such .mpy files are then architecture-specific. This is great news for the stage library, because it means that you won't need to compile it into the firmware anymore — you should be able to just copy the .mpy file to the filesystem and import it as any other Python module.
The first, naive, try failed badly. Just removing the module initialization code and replacing it with mpy_init with equivalent functions is maybe enough for the factorial example, but not for my library — in particular, the dicts that act as namespaces for the two classes that I defined fail to compile, because they would normally go to fixed code, and that is not supported with dynamic loading. So I started to dig through the examples, and figured out from the framebuf and regexp modules how to dynamically declare those classes. So far so good. Now the mpy_ld.py script crashes. Great. After naively fixing the script, the compilation still fails, because it can't locate the mp_obj_get_array function. Which is super-weird, because it has no problem locating other functions from the py/obj.c file.
I added support for the buttons on the game face of M5Stack, (it's very similar to my #D1 Mini X-Pad Shield, I basically just had to change the numbers for the button maping, the I²C address, and negate the output). Together with the automatic 2× scaling, it means you can play the µGame games on it now (and, what is more important, easily make your own).
What I don't like is the noise from the speakers — for some reason that happens only when the face is connected, and not on USB — perhaps grounding issues? There is no sound from the game, because so far MicroPython doesn't support it (though technically it's possible, someone just needs to write the code).
One problem with supporting devices other than #µGame is that they use different displays. The 160×128 version of ST7735 is not that bad — there is just a strip of 32 unused pixels — but the ILI9341 is almost twice as large, with 320×240 pixels. So you either play the game on a tiny postage stamp, or you adapt it to the platform — but the 16×16 tiles and sprites are still too small.
That's why I just added a "scale" optional parameter to the Stage object in the library, that allows you to scale the display 2× or more, giving you back the chunky pixels we so love.
While the CircuitPython port of this library is being used both on the #µGame and a number of Adafruit boards (see #PyBadge Hacking), and is included in the official repository, the MicroPython port is not as well supported. In fact, if you wanted to use it on MicroPython, you were up for a bit of a challenge, figuring out what files to modify in order to have the C portion of the library compiled in your firmware. But that is no more.
The branch is a little bit dated, since I didn't upgrade it in a while, but if you look at the three last commits, you will see all the required code. It's not much, just enough to make the plain python version fast enough. Enjoy!
For now I just tested the default firmware, which contains all the emulators. It works as advertised, though I have to say that the buttons feel rather cheap — much worse than on the #µGame. The sound is super-loud and kinda annoying, but you can mute it.
Looks like my decision to abandon #µGame Turbo and instead focus on improving this library and making it work better on the original #µGame might have been right. New devices are popping up left and right. Adafruit has recently leaked some information about the Arcade FeatherWing they are working on, and now @Jarrett alerted me about a new device made by Odroid: https://wiki.odroid.com/odroid_go/odroid_go#arduino
From the point of view of hardware, it uses the same stuff as the M5Stack, so this library should already work on it. I ordered one ($48 with shipping for me), and of course will attempt to get my games to run on it. Still a bit more than the $11 I paid for the TTGO 1.0, but this one has the button and a case already. We will see how good those buttons are...
After a bit of work, I can now use the whole 320×240 screen of the M5Stack — unfortunately I had to change the C sources a bit, to use 16-bit variables for the coordinates. That was a lack of foresight on my side, but it's now fixed and the pull requests for the change are up.
I also optimized the sprite drawing routines a little bit, and fixed a bug that would sometimes make the library fail to refresh some sprites. You can now also use floating point values for the sprite positions, and they will be rounded during the rendering.
One of the nicer MicroPython-capable boards with integrated color display out there is the M5Stack. It's a bit pricey, but you get a really nice enclosure, and if you get the "faces" thing, even several keyboards. So I decided to try porting the Stage library to it first. After a day of work, mostly spent writing the driver for the ILI9341 display, I ended up with something like this:
The small square area is the 128×128 pixels for which the library was originally written. Of course with a bigger screen you can make games that use a larger area. But the rendering doesn't look quite right. After half a day of debugging, I considerably simplified the driver, but I didn't solve the issue until I tried to reduce the speed of the SPI clock down to 40MHz. I have forgotten that ESP32 has really fast SPI, which apparently this display can't handle reliably. With that, I have proper rendering:
Now, the buttons. The keyboard attachments all use an ATmega328p chip working as an I2C slave on address 0x08 and reporting the pressed keys as a simple bit mask — pretty much like #D1 Mini X-Pad Shield or #LAMEBOY - another ESP12 handheld. That's a problem, because I have to poll the bus, and if I do it only once a frame, I might lose keypresses. For now I used the naive code that just returns the current state of the buttons. I will need to see if I can use a timer to have a proper button handling, with a buffer and all that.
I also didn't explore the sound yet. The ESP32 has a DAC, and the M5Stack has an amplifier and a speaker — you can tell, because you can hear every single GPIO toggle in that thing. But there is no ready-to-use functionality in MicroPython to simply play WAV files in the background, like there is in CircuitPython, so I might need to do some work there.