Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
a1500d8
Merge pull request #7 from raffmont/main
raffmont Jun 8, 2026
1459b61
fix: restored screen working configurations
sunshards Jun 8, 2026
e07d726
fix: diffs with old working repo
sunshards Jun 8, 2026
8d43d09
fix(audio): implement software I2S fallback for buzzer-less targets
sunshards Jun 8, 2026
d9b8262
Merge pull request #8 from sunshards/screen-fix
raffmont Jun 8, 2026
11c1977
Merge pull request #9 from sunshards/main
raffmont Jun 8, 2026
ecd67d2
Merge pull request #10 from sunshards/audio-fix
raffmont Jun 8, 2026
298de01
fix: set PRG32_PIN_RGB_LED to 8
sunshards Jun 8, 2026
ab088fd
Merge pull request #11 from sunshards/main
raffmont Jun 8, 2026
49b2eca
Update cartridge store integration
raffmont Jun 8, 2026
674c043
Increase cartridge RAM budget
raffmont Jun 8, 2026
4b4d48f
Update ESP-IDF version from v5.3 to v5.4
raffmont Jun 8, 2026
81904db
Tune store paging and font storage
raffmont Jun 8, 2026
9185cc4
Aligned entire repository to new QEMU flash image 'qemu_flash.bin'
sunshards Jun 9, 2026
3c0bab4
Merge pull request #12 from sunshards/qemu-flash
raffmont Jun 9, 2026
fe8ab72
Fixed store api parsing
sunshards Jun 9, 2026
a58a848
Make cartridge RAM window configurable
raffmont Jun 9, 2026
9d0b41b
Allocate tile helpers lazily
raffmont Jun 9, 2026
c2a4af7
Keep constant assets in flash
raffmont Jun 9, 2026
f6985bf
Add cartridge RAM profiles
raffmont Jun 9, 2026
3a75283
Lazy allocate optional runtime subsystems
raffmont Jun 9, 2026
233c315
Use capability-specific heap allocations
raffmont Jun 9, 2026
bfc06d6
fix(wifi): Fix boot connect timeouts by using active scan and locking…
sunshards Jun 9, 2026
aec8803
feat(store): Set default Cartridge Store URL at boot
sunshards Jun 9, 2026
a5f40a1
fix: re bumped up the store max games to 64
sunshards Jun 9, 2026
133669d
Merge pull request #14 from sunshards/RAM-Optimization-Step-6
raffmont Jun 9, 2026
a6fb2fc
feat: add local environment file support for WiFi credentials
sunshards Jun 9, 2026
b6cab91
Merge pull request #15 from sunshards/env-file
raffmont Jun 9, 2026
44bb955
Add portable cartridge ABI table
raffmont Jun 10, 2026
9932f74
Update portable cartridge tooling
raffmont Jun 11, 2026
9fcfdd7
Add cartridge ABI publishing checks
raffmont Jun 11, 2026
0b5f108
Increase LCD SPI clock
raffmont Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
- name: Run host smoke test
run: bash scripts/ci_smoke_test.sh

- name: Check portable ABI generated files
run: python3 tools/prg32_abi_gen.py --check

esp-idf-build:
name: ESP-IDF firmware build
runs-on: ubuntu-latest
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ Testing/
.cache/
*.prg32

# Local environment configuration
prg32_env.h

# Local Python tooling
.venv/
__pycache__/
Expand Down
26 changes: 13 additions & 13 deletions .vscode/c_cpp_properties.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"name": "PlatformIO",
"includePath": [
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/riscv/include",
"/Users/raffaelemontella/Documents/devel/PRG32/.pio/build/prg32-esp32c6/config",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/.pio/build/prg32-esp32c6/config",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/newlib/platform_include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/freertos/config/include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/freertos/config/include/freertos",
Expand Down Expand Up @@ -166,18 +166,18 @@
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/rt/include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/spiffs/include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/wifi_provisioning/include",
"/Users/raffaelemontella/Documents/devel/PRG32/components/prg32_audio/include",
"/Users/raffaelemontella/Documents/devel/PRG32/managed_components/espressif__esp_websocket_client/include",
"/Users/raffaelemontella/Documents/devel/PRG32/managed_components/espressif__mdns/include",
"/Users/raffaelemontella/Documents/devel/PRG32/components/prg32/include",
"/Users/raffaelemontella/Documents/devel/PRG32/main",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/components/prg32_audio/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/managed_components/espressif__esp_websocket_client/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/managed_components/espressif__mdns/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/components/prg32/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/main",
""
],
"browse": {
"limitSymbolsToIncludedHeaders": true,
"path": [
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/riscv/include",
"/Users/raffaelemontella/Documents/devel/PRG32/.pio/build/prg32-esp32c6/config",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/.pio/build/prg32-esp32c6/config",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/newlib/platform_include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/freertos/config/include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/freertos/config/include/freertos",
Expand Down Expand Up @@ -334,11 +334,11 @@
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/rt/include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/spiffs/include",
"/Users/raffaelemontella/.platformio/packages/framework-espidf/components/wifi_provisioning/include",
"/Users/raffaelemontella/Documents/devel/PRG32/components/prg32_audio/include",
"/Users/raffaelemontella/Documents/devel/PRG32/managed_components/espressif__esp_websocket_client/include",
"/Users/raffaelemontella/Documents/devel/PRG32/managed_components/espressif__mdns/include",
"/Users/raffaelemontella/Documents/devel/PRG32/components/prg32/include",
"/Users/raffaelemontella/Documents/devel/PRG32/main",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/components/prg32_audio/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/managed_components/espressif__esp_websocket_client/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/managed_components/espressif__mdns/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/components/prg32/include",
"/Users/raffaelemontella/PlatformioIOProjects/PRG32/main",
""
]
},
Expand All @@ -349,10 +349,10 @@
"_GLIBCXX_HAVE_POSIX_SEMAPHORE",
"SOC_XTAL_FREQ_MHZ=CONFIG_XTAL_FREQ",
"SOC_MMU_PAGE_SIZE=CONFIG_MMU_PAGE_SIZE",
"PRG32_FIRMWARE_VERSION=\"ab088fd-dirty\"",
"IDF_VER=\"5.4.1\"",
"ESP_PLATFORM",
"PLATFORMIO=60119",
"PRG32_FIRMWARE_VERSION=\"pio-dev\"",
""
],
"cStandard": "gnu17",
Expand Down
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug",
"executable": "/Users/raffaelemontella/Documents/devel/PRG32/.pio/build/prg32-esp32c6/firmware.elf",
"executable": "/Users/raffaelemontella/PlatformioIOProjects/PRG32/.pio/build/prg32-esp32c6/firmware.elf",
"projectEnvName": "prg32-esp32c6",
"toolchainBinDir": "/Users/raffaelemontella/.platformio/packages/toolchain-riscv32-esp/bin",
"internalConsoleOptions": "openOnSessionStart",
Expand All @@ -34,7 +34,7 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug (skip Pre-Debug)",
"executable": "/Users/raffaelemontella/Documents/devel/PRG32/.pio/build/prg32-esp32c6/firmware.elf",
"executable": "/Users/raffaelemontella/PlatformioIOProjects/PRG32/.pio/build/prg32-esp32c6/firmware.elf",
"projectEnvName": "prg32-esp32c6",
"toolchainBinDir": "/Users/raffaelemontella/.platformio/packages/toolchain-riscv32-esp/bin",
"internalConsoleOptions": "openOnSessionStart"
Expand All @@ -43,7 +43,7 @@
"type": "platformio-debug",
"request": "launch",
"name": "PIO Debug (without uploading)",
"executable": "/Users/raffaelemontella/Documents/devel/PRG32/.pio/build/prg32-esp32c6/firmware.elf",
"executable": "/Users/raffaelemontella/PlatformioIOProjects/PRG32/.pio/build/prg32-esp32c6/firmware.elf",
"projectEnvName": "prg32-esp32c6",
"toolchainBinDir": "/Users/raffaelemontella/.platformio/packages/toolchain-riscv32-esp/bin",
"internalConsoleOptions": "openOnSessionStart",
Expand Down
2 changes: 1 addition & 1 deletion .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@
"upload-qemu",
"${input:prg32QemuCartOut}",
"--flash",
"build-qemu/flash_image.bin"
"build-qemu/qemu_flash.bin"
],
"problemMatcher": []
},
Expand Down
47 changes: 40 additions & 7 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ Named contributor metadata used across project docs:
`riscv-prg32/MetricsServer`.
- In-tree setup performance report tooling: `tools/prg32_metrics_paper.py`.
- Uploadable game cartridge tool: `tools/prg32_game.py`.
- Bulk portable example publishing helper:
`tools/prg32_build_portable_examples.py`.
- Legacy resident firmware publishing/flashing helpers:
`tools/prg32_prepare_legacy_firmware.py` and
`tools/prg32_flash_legacy_firmware.py`.
- Media conversion tools: `tools/prg32_image_convert.py`,
`tools/prg32_image_prepare.py`, and `tools/prg32_audio_convert.py`.
- Student VS Code setup: `.vscode` and `PRG32.code-workspace`.
Expand Down Expand Up @@ -223,13 +228,13 @@ Important implementation details:
- Setup mode is entered by holding A+B at boot, by holding `PRG32_PIN_SETUP`
low when that optional pin is wired, when no cartridge is present, or when
multiple cartridges exist without a saved default cartridge.
- Keep `prg32_device_demo_run()` current whenever framework capabilities are
added or changed. The setup-launched device demo should remain a quick
hardware/classroom smoke test covering display, input, audio, Wi-Fi status,
cartridge state, sprites, scrolling/playfields, status bands, RGB LED/audio
VU behavior, arcade-inspired viewport sketches, the tile-engine platformer,
the fixed-point raycaster, the dual-playfield space cockpit, and any new
framework feature.
- Keep the external `riscv-prg32/DeviceDemo` cartridge current whenever
cartridge-facing framework capabilities are added or changed. It should
remain a quick hardware/classroom smoke test covering display, input, audio,
Wi-Fi status, cartridge state, sprites, scrolling/playfields, status bands,
RGB LED/audio VU behavior, arcade-inspired viewport sketches, the tile-engine
platformer, the fixed-point raycaster, the dual-playfield space cockpit, and
any new framework feature.

## Assembly Example Guidelines

Expand Down Expand Up @@ -428,6 +433,34 @@ Companion repository: https://github.com/riscv-prg32/CartridgeStore
| `tools/prg32_game.py` | `publish`, `pack-bundle`, `publish-bundle`, `store-*` |
| `docs/cartridge_store.md` | End-user integration guide |

## PRG32 Cartridge ABI Compatibility

- Treat `tools/prg32_abi.json` as the single source of truth for the portable
cartridge ABI.
- Do not edit generated ABI files manually. Regenerate or check them with
`python3 tools/prg32_abi_gen.py` and `python3 tools/prg32_abi_gen.py --check`.
- ABI function indices are append-only. Never reorder, remove, or change
existing function prototypes within the same ABI major version.
- Any incompatible ABI change requires increasing `PRG32_ABI_MAJOR`.
- Additive functions or optional feature bits may increase `PRG32_ABI_MINOR`.
- Portable cartridges must not link against firmware-specific symbol addresses.
- Legacy absolute-import cartridges may remain supported, but new examples and
documentation should prefer portable ABI-table cartridges.
- Upload, QEMU staging, CartridgeStore downloads, and firmware setup downloads
should reject incompatible cartridges before deployment whenever the ABI
contract can be checked.

For cartridge/ABI work, run the relevant available checks before finishing:

```bash
python3 tools/prg32_abi_gen.py --check
python3 tools/prg32_game.py summary build/<example>.prg32
git diff --check
```

If `pytest`, ESP-IDF, or the RISC-V toolchain is unavailable, state exactly
which checks could not be run.

## Metadata Consistency Rules

When changing authorship or academic attribution, keep these files in sync:
Expand Down
23 changes: 23 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
cmake_minimum_required(VERSION 3.16)

set(PRG32_FIRMWARE_VERSION_FALLBACK "0.0.0-dev")
find_package(Git QUIET)
if(GIT_FOUND)
execute_process(
COMMAND "${GIT_EXECUTABLE}" describe --tags --dirty --always
WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}"
OUTPUT_VARIABLE PRG32_FIRMWARE_VERSION
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_QUIET
)
endif()
if(NOT PRG32_FIRMWARE_VERSION)
set(PRG32_FIRMWARE_VERSION "${PRG32_FIRMWARE_VERSION_FALLBACK}")
endif()
set(PROJECT_VER "${PRG32_FIRMWARE_VERSION}")

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(PRG32)

idf_build_set_property(
COMPILE_DEFINITIONS
"PRG32_FIRMWARE_VERSION=\"${PRG32_FIRMWARE_VERSION}\""
APPEND
)
84 changes: 68 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ brew install git cmake ninja dfu-util ccache libusb python

# 2) ESP-IDF
cd $HOME
git clone -b v5.3 --recursive https://github.com/espressif/esp-idf.git
git clone -b v5.4 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32c3,esp32c6
. ./export.sh
Expand All @@ -42,11 +42,11 @@ cd <path_to_PRG32>
. $HOME/esp-idf/export.sh
python3 tools/prg32_game.py build \
examples/games/asteroids/graphics/game.S \
--firmware-elf build-qemu/PRG32.elf \
--portable \
--entry-prefix asteroids_graphics \
--name asteroids \
--out build-qemu/asteroids.prg32
python3 tools/prg32_game.py upload-qemu build-qemu/asteroids.prg32 --flash build-qemu/flash_image.bin
python3 tools/prg32_game.py upload-qemu build-qemu/asteroids.prg32 --flash build-qemu/qemu_flash.bin
```

Run everything with one command next time:
Expand Down Expand Up @@ -161,7 +161,7 @@ Recommended reading paths:
```bash
brew install git cmake ninja dfu-util ccache libusb python
cd $HOME
git clone -b v5.3 --recursive https://github.com/espressif/esp-idf.git
git clone -b v5.4 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32c3,esp32c6
. ./export.sh
Expand Down Expand Up @@ -251,7 +251,7 @@ Install ESP-IDF:

```bash
cd $HOME
git clone -b v5.3 --recursive https://github.com/espressif/esp-idf.git
git clone -b v5.4 --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh esp32c3,esp32c6
. ./export.sh
Expand Down Expand Up @@ -309,7 +309,7 @@ Linux notes:

Open the repository root in PlatformIO. The checked-in `platformio.ini` default
environment is `prg32-esp32c6`, which targets the ESP32-C6 DevKitC-1 with
ESP-IDF, reuses the standard `main` component, and applies
ESP-IDF, reuses the standard `main` component and applies
`partitions_prg32.csv` plus `sdkconfig.defaults`.

CLI equivalents:
Expand All @@ -321,7 +321,7 @@ pio device monitor -b 115200
```

The ESP32-C6 build keeps UART0 as the primary ESP-IDF console and enables
native USB Serial/JTAG as secondary output for PlatformIO Monitor. A healthy
native USB Serial/JTAG as a secondary output for PlatformIO Monitor. A healthy
boot logs the configured `prg32_lcd` ILI9341 pins before drawing the splash.

The PlatformIO environment is for the physical ESP32-C6 classroom board. Keep
Expand Down Expand Up @@ -353,6 +353,52 @@ Flow:
.S source -> riscv toolchain -> .prg32 cartridge -> PRG32 runtime -> init/update/draw loop
```

## Portable Cartridges

PRG32 cartridges are portable across firmware builds that implement the same
cartridge ABI. Portable cartridges call firmware services through a versioned
ABI table instead of absolute firmware symbol addresses.

Build a portable cartridge:

```bash
python3 tools/prg32_game.py build examples/games/pong/ascii/game.S \
--entry-prefix pong_ascii \
--portable \
--out build/pong.prg32
```

Inspect it:

```bash
python3 tools/prg32_game.py summary build/pong.prg32
```

The summary shows ABI major/minor, ABI hash, import model, and required or
optional feature bits. Legacy absolute-import cartridges are still supported for
old workflows, but they are tied to the firmware image used at build time and
are not guaranteed to run on another firmware. ABI hash mismatches, missing
required features, and incompatible legacy cartridges are rejected by the
runtime, store download path, QEMU staging path, and HTTP upload tool with a
diagnostic message.

Build all checked-in examples as portable cartridges and CartridgeStore bundles:

```bash
python3 tools/prg32_build_portable_examples.py --clean
```

Prepare or flash a published single-file legacy firmware image:

```bash
python3 tools/prg32_prepare_legacy_firmware.py
python3 tools/prg32_flash_legacy_firmware.py \
publish/legacy-firmware/PRG32-legacy-esp32c6.json \
--port /dev/cu.usbmodem5ABA0099241
```

See [docs/publishing_and_flashing_legacy_firmware.md](docs/publishing_and_flashing_legacy_firmware.md).

Cartridge metadata and store publishing are documented in
[docs/cartridge_metadata.md](docs/cartridge_metadata.md),
[docs/colophon_abi.md](docs/colophon_abi.md),
Expand Down Expand Up @@ -386,7 +432,7 @@ download server is the standalone **Cartridge Store** in
- QEMU runs but the game does not move: focus the terminal running QEMU. Use
arrow keys or `W`/`A`/`S`/`D` for joystick 1, `Enter`/`Space` for SELECT,
`J`/`Z` for A, and `K`/`X` for B.
- Cartridge upload fails: `build-qemu/flash_image.bin` is missing/invalid, or the
- Cartridge upload fails: `build-qemu/qemu_flash.bin` is missing/invalid, or the
cartridge is too large. Run QEMU once, then rerun `upload-qemu`.
- `riscv32-esp-elf-gcc` missing: re-run `./install.sh esp32c3,esp32c6` and
source the ESP-IDF export script.
Expand All @@ -396,7 +442,7 @@ download server is the standalone **Cartridge Store** in
## Learning Path

1. Build and flash the resident firmware.
2. Open setup with A+B at boot, run the device demo, and upload a cartridge.
2. Open setup with A+B at boot, configure Wi-Fi or CartridgeStore, and upload a cartridge.
3. Read `docs/tutorial.md` for assembly or `docs/tutorial_c_game.md` for C.
4. Complete the labs in `docs/labs`.
5. Modify one example game under `examples/games`.
Expand Down Expand Up @@ -455,13 +501,13 @@ cartridge is stored, or when multiple cartridges exist without a default. The
setup menu can run a cartridge, set the default boot cartridge, configure Wi-Fi,
configure CartridgeStore access, browse the store, open the audio setup menu,
open the developer status-band menu, launch the unattended performance test,
show the about screen, or launch the device demo.
or show the about screen.
Setup screens show the active Wi-Fi mode and current IP address,
and the local joystick can navigate them with SELECT/B to confirm and A to go
back. The device demo includes 320x200 sketches inspired by Pong, Breakout,
Space Invaders, Pacman, Tetris, Pole Position, Asteroids, a side-scrolling
platform game, a Doom-style raycaster, and a space cockpit that demonstrates
dual playfields. When the audio configuration is usable for the current board,
back. The former setup device demo now lives as the
[DeviceDemo cartridge](https://github.com/riscv-prg32/DeviceDemo), which can be
built, uploaded, and published through CartridgeStore like the teaching games.
When the audio configuration is usable for the current board,
the splash plays a short welcome sound; otherwise it falls back to the passive
buzzer when configured.

Expand Down Expand Up @@ -544,9 +590,15 @@ for a step-by-step scientific-paper measurement workflow with screenshots.
- `tools/prg32_game.py store-discover`: find CartridgeStore instances via mDNS.
- `tools/prg32_game.py store-list`: print a CartridgeStore catalog table.
- `tools/prg32_game.py store-download`: download a `.prg32` from a store.
- `tools/prg32_game.py publish`: build a cartridge and publish a store bundle.
- `tools/prg32_game.py publish`: build a cartridge and submit a store bundle.
- `tools/prg32_game.py pack-bundle`: create a flat multi-architecture zip.
- `tools/prg32_game.py publish-bundle`: upload a prepared bundle.
- `tools/prg32_game.py publish-bundle`: submit a prepared bundle.
- `tools/prg32_build_portable_examples.py`: build every checked-in example as
portable `.prg32` cartridges and CartridgeStore bundles.
- `tools/prg32_prepare_legacy_firmware.py`: merge a physical firmware build into
one publishable binary.
- `tools/prg32_flash_legacy_firmware.py`: flash a published single-file legacy
firmware image.

See [docs/assets.md](docs/assets.md).

Expand Down
2 changes: 1 addition & 1 deletion components/prg32/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ endif()
idf_component_register(
SRCS
"prg32_abi_exports.c"
"prg32_abi_table.c"
"prg32_audio.c"
"assets/prg32_splash_logo.c"
"prg32_bands.c"
"prg32_cart.c"
"prg32_console.c"
"prg32_controller.c"
"prg32_debug_overlay.c"
"prg32_device_demo.c"
"prg32_diag.c"
"prg32_http_games.c"
"prg32_http_scores.c"
Expand Down
Loading
Loading