Skip to content

DavidVentura/PicoPico

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Pico Pico

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.

Demo

In emulator (SDL/rawdraw backed)

out.mp4

Celeste in ESP32

doc_2022-05-14_09-46-23.mp4

In Raspberry pi Pico (port abandoned, not enough RAM)

out.mp4

On Android

android_pico.mp4

On 3DS

3ds_noa.mp4

Hardware

BOM

  • 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

Goals

  1. βœ… Make hello_world work without regressions )).
    • All letters are white (lazy palette evaluation was missing a palette indirection (screen vs draw))
  2. βœ… Make rockets fully playable (without music // complete SFX).
    • Points go up too fast (time() was returning millis)
  3. βœ… 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
  4. 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
  5. Make valdi fully playable (without music // complete SFX)
    • Renders offset when looking to the left (wasn't calculating the sprite width accordingly in spr())
    • fillp not implemented (background)
    • Super slow after a few resets? (gfx_line was missing a bounds check, overwriting the delay between frames)
  6. Make awake fully playable (without music // complete SFX)
    • Colors are super glitched, level2 is also glitched

Basic analysis / feasibility:

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

Performance

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 screen happens "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

TODO

Immediate:

  • Get basic rendering on the Pico
    • split backends properly
  • Read code from p8 file instead of having split files
  • Implement map
  • Implement camera
  • Lua dialect (using z8lua)
  • Unify the build systems (Make for pc / CMake for pico)
  • Pre-encode the palette colors as a RGB565 uint16_t; makes no sense to shift them on every pixel write
  • SFX
  • Measure 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 be nil but not ❎

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

Other stuff

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

Sound

I yoinked zepto8's synth and converted it to fix32; an example SFX went from ~25ms to ~2ms on the ESP32.

API Support

Graphics

Function Supported Notes
camera βœ…
circ βœ…
circfill βœ…
oval βœ…
ovalfill βœ…
clip βœ…
cls βœ…
color βœ…
cursor ❌
fget βœ…
fillp ❌
fset ❌
line βœ…
pal ⚠️ Only "draw palette" is implemented
palt βœ…
pget βœ…
print ⚠️ Does not automatically scroll
pset βœ…
rect βœ…
rectfill βœ…
sget βœ…
spr βœ…
sset βœ…
sspr ⚠️
tline ❌

Tables

All implemented (z8lua)

Input

btn implemented

Sound

Function Supported Notes
sfx ⚠️ Offset is not implemented
music ❌

Map

All implemented

Memory

Not implemented

Math

All implemented (z8lua)

Cartridge data

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.

Coroutines

Implemented by aliasing (z8lua)

Strings

Implemented (z8lua)

Values and objects

Implemented (z8lua)

Hardware

Build something like the PicoSystem ?

Development setup

sudo apt install cmake g++ libsdl2-dev
git submodule update --init
mkdir pc_pico && cd pc_pico
cmake -DBACKEND=PC ..

Development on RP2040 (without a second Pico running OpenOCD)

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.

Tests

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]

Useful links