Project to attempt to create hardware for the Pico-8.
I'd like to eventually get full game running on a Raspberry Pi Pico ESP32
Be aware, the code will be terrible, this is a project to learn:
- C
- SDL
- Embedded
- CMake
- Lua
- And how to adapt / modify languages
- JIT-ting ??
This project uses z8lua to implement Pico8's Lua dialect; I've whacked the original repo to make it build with the Pico as a target, it was all implicit casting, which I hopefully got right.
This project also gets some "inspiration" from tac08 - ideas for things I don't know how to solve, and the "firmware.lua" file for basic implementations.
doc_2022-05-14_09-46-23.mp4
out.mp4
- ESP32 "Wrover" with 4MB PSRAM
- MAX98357 I2S audio amplifier
- 1.77" SPI ST7735 128x160 Display (128x128 used for game, 16px for UI, 16px padding)
Memory requirements:
- Spritesheet, at 128x128 in size = 16KB
- Fontsheet, at 128x128 in size = 16KB
- Map, at 32x128 = 4 KB
- Flags, at 2x128 = 256B
- Sound effects, 64x84 = 5376B (~5KB)
- Music patterns, 64x6 = 384B
- Front buffer 128x128 = 16KB (x/y position -> palette; could be 8KB if using only a nibble per pixel)
- Back buffer 128x128x2 = 32KB (x/y position -> color, at 16bpp)
- Game memory = 32KB
- Lua = ???KB
- how to measure?
~130KB, should have plenty of space for things going wrong.
Both Spritesheet (16KB), and Map (4KB) can be squeezed to half, as they need a nibble per pixel, instead of a byte.
SFX can also be squeezed drastically - each of the 32 nots in the 64 SFX currently take 4 bytes instead of 2, could reclaim 4KB more DRAM.
And most importantly 2MB for game RAM (Lua memory). This varies based on each game, but on the PICO the 256KB ran out way too fast. There's this thing to use very slow, very cursed external RAM
In RPI Pico:
Running hello_world.lua
:
-
Normal: 13ms / frame; of which:
- Lua: ~9ms / frame
- Copying backbuffer to screen (
uint8_t
): ~4ms / frame
-
Display on 2nd core: 7.5ms / frame; of which:
- Lua: ~7ms / frame
Copying backbuffer to screenhappens "for free" in the other core
-
Multicore + overclock to 260MHz: 3.5ms/frame
In ESP32:
Celeste takes about 9ms / frame (rendering happens on the second core), including SFX
Immediate:
Get basic rendering on the Picosplit backends properly
Read code from p8 file instead of having split filesImplement mapImplement cameraLua dialect(using z8lua)Unify the build systems (Make for pc / CMake for pico)Pre-encode the palette colors as a RGB565uint16_t
; makes no sense to shift them on every pixel writeSFXMeasure and output the correct number of samples out of the audio buffer, currently it's a (badly) guessed number.- Get reasonable audio quality out of SFX
Later:
Clock rate on SPI?set to 62.5MHz; not sure if it can go higher- Implement flash
- cartdata command could use it
- carts could be stored in flash instead of static data
- Investigate pushing pixels to display via DMA
- worth it? the second core is idle anyway
- Music
- Look at optimizing lua bytecode for "fast function calls", for "standard library"
- Use headers instead of stupid ifdefs
- Console-based UI game
- Virtual keyboard (original, no lowercase)
- Wifi menu
- Cart explorer?
There's a basic RLE encoding mechanism in place, to compress:
- Font data (16.5KB -> 5KB)
- Examples:
- Hello wolrd: 4KB -> 1KB
- Dice: 25KB -> 8KB
- Tennis: 21KB -> 4KB
Not entirely sure yet why I was running out of memory, even with 40KB of (font+dice), it should be enough.
This whole thing (compression) is a bit of a hack -- it is nice for development to have full carts accessible; there is no code to read/write flash, nor any way to store the actual carts on flash yet.
I yoinked zepto8's synth and converted it to fix32
; an example SFX went
from ~25ms to ~2ms on the ESP32. It works fine in SDL, and sounds like a banshee when using the ESP32's internal DAC.
Function | Supported | Notes |
---|---|---|
camera | ✅ | |
circ | ✅ | |
circfill | ✅ | |
oval | ❌ | |
ovalfill | ❌ | |
clip | ✅ | |
cls | ✅ | |
color | ✅ | |
cursor | ❌ | |
fget | ✅ | |
fillp | ❌ | |
fset | ❌ | |
line | ❌ | |
pal | Only "draw palette" is implemented | |
palt | ✅ | |
pget | ✅ | |
Does not automatically scroll | ||
pset | ✅ | |
rect | ✅ | |
rectfill | ✅ | |
sget | ✅ | |
spr | ✅ | |
sset | ❌ | |
sspr | ||
tline | ❌ |
All implemented (z8lua)
btn
implemented; btnp
is just an alias to btn
Function | Supported | Notes |
---|---|---|
sfx | There is only 1 channel; and offset is not implemented | |
music | ❌ |
All implemented
Not implemented
All implemented (z8lua)
cartdata/dget/dset are technically implemented, there's no persistence layer though.
cstore/reload are not implemented.
Implemented by aliasing (z8lua)
Implemented (z8lua)
Implemented (z8lua)
Build something like the PicoSystem ?
sudo apt install cmake g++ libsdl2-dev
git submodule update --init
mkdir pc_pico && cd pc_pico
cmake -DBACKEND=PC ..
Add this block to a udev rule (adjust the paths to point to this repo)
SUBSYSTEM=="block", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", ACTION=="add", SYMLINK+="rp2040upl%n"
SUBSYSTEM=="block", ATTRS{idVendor}=="2e8a", ATTRS{idProduct}=="0003", ACTION=="add", RUN+="/home/david/git/luatest/udev_build.sh rp2040upl%n"
Then having an open minicom shell
sudo minicom -b 115200 -D /dev/ttyACM1
you can press r
on it to reboot into mass-storage mode; which will trigger the udev rules after a second or so.