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.
out.mp4
doc_2022-05-14_09-46-23.mp4
out.mp4
android_pico.mp4
3ds_noa.mp4
- 1x THT Power switch TME PDF
- 8x SMD button TME PDF
- 1x 10k horixontal potentiometer TME PDF
- 8x SMD 10k resistor
- MAX98357 I2S audio amplifier ALI
- ESP32 "Wrover" with 4MB PSRAM Amazon
- 1.77" SPI ST7735 128x160 Display (128x128 used for game, 16px for UI, 16px padding) ALI
- ✅ Make
hello_world
work without regressions )).All letters are white(lazy palette evaluation was missing a palette indirection (screen vs draw))
- ✅ Make
rockets
fully playable (without music // complete SFX).Points go up too fast(time()
was returning millis)
- ✅ Create some basic automated testing.
- Tests are at
tests/
, they run one (or a few) frames and get output. - Need to integrate better into the project; exclusive
ifdef
for header selection
- Tests are at
- Make
celeste
fully playable (without music // complete SFX)Clouds suddenly appear(bad rectfill types, took uint instead of int)- Sometimes 2 celestes appear?? mostly on level crossings
- Make
valdi
fully playable (without music // complete SFX)Renders offset when looking to the left(wasn't calculating the sprite width accordingly inspr()
)fillp
not implemented (background)Super slow after a few resets?(gfx_line
was missing a bounds check, overwriting the delay between frames)
- Make
awake
fully playable (without music // complete SFX)- Colors are super glitched, level2 is also glitched
Memory requirements:
- Spritesheet, at 128x128 in size = 16KB
- Can move to flash
- Fontsheet, at 128x128 in size = 16KB
- Can move to flash
- Map, at 64x128 = 8 KB
- Can move to flash
- Flags, at 2x128 = 256B
- Can move to flash
- Sound effects, 64x84 = 5376B (~5KB)
- Can move to flash
- Music patterns, 64x6 = 384B
- Can move to flash
- Front buffer 128x128/2 = 8KB (x/y position -> palette)
- Back buffer 128x128x2 = 32KB (x/y position -> color, at 16bpp)
- HUD buffer 16x128x2 = 4KB
- Can squeeze to 8x64x2 = 1KB, or even 7 sprites (896b)
- Dedicated "Game memory" = 32KB
Totals ~130KB (of which 76KB for screen buffers and game memory have to stay).
And most importantly 2MB for game RAM (Lua memory). This varies based on each game, but on the PICO the 264KB ran out pretty fast. There's this thing to use very slow, very cursed external RAM. Some games
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- Deal with warnings when building for ESP
Figure out why they don't show up when building with SDL backend
Move hardcoded pin for ESP32 to sdkconfig- Extract current values for docs WIP
- Implement more complete SFX
- Generate board captures automatically
Add support for short-hand print ('?"x"' == print("x"))- Implement fillp https://pico-8.fandom.com/wiki/Fillp
- Lua error: bad argument #1 to 'split' (string expected, got nil)
- Lua error: bad argument #1 to 'btn' (number expected, got nil)
- z8lua considers literal circle (
🅾️ ) to benil
but not ❎
- z8lua considers literal circle (
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
- Enable
-Wconversion
- Console-based UI game
- Virtual keyboard (original, no lowercase)
- Wifi menu
- Cart explorer?
Resources are parsed from plaintext into a header by the to_c.py
script, this also covers converting source code to byte-code.
Having byte-code compiled in a pre-processing stage makes parsing faster (112ms -> 18ms for a large cart), uses less memory (bytecode
stays in flash, not necessary to load to RAM) and enables future bytecode-level optimization
I yoinked zepto8's synth and converted it to fix32
; an example SFX went
from ~25ms to ~2ms on the ESP32.
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
Function | Supported | Notes |
---|---|---|
sfx | Offset is not implemented | |
music | ❌ |
All implemented
Not implemented
All implemented (z8lua)
Function | Supported | Notes |
---|---|---|
cartdata | Not persistent | |
dget | Not persistent | |
dset | Not persistent | |
cstore | ❌ | |
reload | ❌ | |
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.
There's a CMake backend (TEST) that you can use with cmake -DBACKEND=TEST
; in that backend, the command make test
will run some tests.
Some of the tests are for the internal APIs, and are written in C. Generally though, most tests should be written as p8 cartridges and placed in tests/regression/*.p8
.
Internal API tests usually compare the output with some expected, known-good frontbuffer values (a "screenshot").
Whenever these need to change, the specific test can be re-run with the environment variable OVERWRITE_TEST_BUF
set to any value.
This will overwrite tests/data/buf_*.bin
. To look at these buffers, you can use the command bin_to_png
.
> make test
Running tests...
Test project /Users/tati/git/PicoPico/tests_build
Start 1: test_hello_world
1/5 Test #1: test_hello_world ................. Passed 0.03 sec
Start 2: test_hud
2/5 Test #2: test_hud ......................... Passed 0.03 sec
Start 3: test_menu
3/5 Test #3: test_menu ........................ Passed 0.03 sec
Start 4: test_primitives
4/5 Test #4: test_primitives .................. Passed 0.03 sec
Start 5: test_regression
5/5 Test #5: test_regression .................. Passed 0.03 sec
100% tests passed, 0 tests failed out of 5
Total Test time (real) = 0.16 sec
> ./test_regression
Testing regression_api_p8
test_map_no_args [ OK]
test_print_p8scii_color [ OK]
Testing regression_asan_p8
test_sspr_same_size_is_maintained [ OK]
test_circ_top_left_edge [ OK]
Testing regression_other_p8
test_nothing [ OK]