Note
This manual is intended for users and students writing cartridges using the provided API. It is not intended for developers modifying the underlying ESP-IDF C framework.
PRG32 lets students write game logic in RISC-V assembly or C while a small framework provides hardware access.
Arguments use the standard RISC-V calling convention: a0 to a7 carry
arguments and return values, ra holds the return address, and sp is kept
16-byte aligned around C calls.
PRG32_MODE_UART_ONLY: serial terminal only.PRG32_MODE_LCD_ONLY: text appears on the ILI9341 display.PRG32_MODE_UART_LCD_MIRROR: debug text is sent both to serial and LCD.
prg32_input_read() returns the PRG32 input register. The low bits are the
single local joystick:
PRG32_BTN_LEFTPRG32_BTN_RIGHTPRG32_BTN_UPPRG32_BTN_DOWNPRG32_BTN_APRG32_BTN_BPRG32_BTN_SELECT(PRG32_BTN_STARTis kept as an alias)
Games can use prg32_input_read_player(1) to get the same normalized low-bit
mask. prg32_input_read_player(2) is kept as a source-compatible helper, but
returns 0; multiplayer games should use the PRG32 multiplayer API for remote
players.
QEMU and host-driven tests can inject the same bitmask through
prg32_diag_set_input_state().
Menu/setup helpers call prg32_input_read_menu() to read the local joystick in
the same normalized low-bit mask.
System hotkey:
- A + B + DOWN on the local joystick: restart the ESP32-C6 firmware from anywhere in the PRG32 input path.
prg32_random_number(min, max) returns an unsigned value between min and
max, including both endpoints. It returns min if max <= min and supports
the full 0 through UINT32_MAX range. The
C and assembly demo shows
how to call it and update the display on each A-button press.
The on-screen keyboard lets games and framework setup screens collect short alphanumeric text without a USB keyboard.
Useful calls:
prg32_keyboard_init(keyboard, buffer, capacity)prg32_keyboard_update(keyboard, input_mask)prg32_keyboard_draw(keyboard, x, y)prg32_text_input(buffer, capacity, title)
Controls:
- D-pad: move around the key grid.
- SELECT: select the highlighted on-screen key.
- A: escape back to the previous state.
- B: confirm, equivalent to selecting the on-screen
returnkey.
The keyboard uses a QWERTY layout with explicit delete, shift, and return
keys. shift toggles between lower-case and upper-case/symbol labels. The
ascii key opens a printable ASCII page covering characters 0x20 through 0x7e.
The LCD and QEMU text renderers include distinct glyphs for the full printable
ASCII range; console output also treats tab and DEL/backspace as text controls.
The physical display is 320x240, while the normal game viewport remains
320x200. The firmware splash, setup, Wi-Fi setup, developer menu, and about
screen use the full display. Game and feature-demo drawing calls use the
centered 320x200 viewport so cartridges keep the same coordinate system and
the retro frame. Unless a program sets a band color explicitly, the upper and
lower horizontal bands are filled with the same color passed to
prg32_gfx_clear.
On ESP32-C6 hardware, the 320x200 game surface is a 64,000-byte, 8-bit indexed framebuffer backed by a deterministic 256-entry RGB565 palette. The ILI9341 remains in native 16-bit RGB565 mode: dirty indexed rows are expanded into the existing small RGB565 SPI strip only during presentation. Compared with the previous 128,000-byte framebuffer, this reclaims 63,488 bytes (about 62 KiB) without allocating a second full-size RGB565 surface. Existing RGB565 drawing calls remain source- and ABI-compatible through deterministic system-palette quantization; indexed-native primitives and palette cycling are available for cartridges that need exact palette control. See ILI9341 Hardware and Driver Notes.
The reference Performance Test compares matched RGB565-compatible and full indexed-native workloads. Poing is an application example that renders its procedural scene with 8-bit indices and can publish a 300-frame gameplay result through the same public performance API by pressing SELECT.
The QEMU renderer exposes the same 320x240 physical screen and centers the 320x200 PRG32 game viewport inside it. Student assembly code does not change; only the selected display backend changes.
For classroom debugging, optional helper prg32_debug_overlay_draw can print
x, y, input mask, frame, and tick info on the top scanline.
Display backend selection:
CONFIG_PRG32_DISPLAY_ILI9341: physical ILI9341 SPI TFT, default.CONFIG_PRG32_DISPLAY_QEMU_RGB: QEMU virtual RGB framebuffer.
Use the QEMU defaults file when running on a desktop:
idf.py -B build-qemu -D SDKCONFIG=build-qemu/sdkconfig -D SDKCONFIG_DEFAULTS="profiles/sdkconfig.defaults;profiles/sdkconfig.defaults.qemu" qemu --graphics monitorWhen the QEMU backend is selected, main/prg32_config.h disables physical GPIO
buttons and the buzzer. QEMU builds keep player 1 usable through the UART
console keyboard mapper: arrows or W/A/S/D for the joystick,
Enter/Space for SELECT, J/Z for A, and K/X for B.
The resident firmware shows the PRG32 logo image after display initialization. It can be disabled or timed through Kconfig:
CONFIG_PRG32_SPLASH_ENABLEDCONFIG_PRG32_SPLASH_DURATION_MSCONFIG_PRG32_SPLASH_SOUND_ENABLED
When splash sound is enabled, firmware plays a short welcome phrase through the I2S audio subsystem only when the configured audio pins do not conflict with the reference display/input wiring. Otherwise it uses the passive buzzer when one is configured.
Graphic games can reuse the 320x200 game splash helpers:
prg32_splash_show_game(title, subtitle, duration_ms, bg, fg, accent): draw a game title screen, present it, and wait.prg32_splash_draw_game(title, subtitle, bg, fg, accent): draw a game title screen without delaying, useful inside a title-state loop.
Framework-owned full-screen splash helpers remain available:
prg32_splash_show(title, subtitle, duration_ms, bg, fg, accent): draw, present, and wait on the full 320x240 display.prg32_splash_draw(title, subtitle, bg, fg, accent): draw a splash/title screen without delaying on the full 320x240 display.prg32_splash_show_default(): show the firmware-style PRG32 splash.prg32_gfx_set_fullscreen(enabled): switch between full-screen framework drawing and the centered game viewport.prg32_gfx_set_band_color(color): set a custom color for the game viewport bands.prg32_gfx_use_background_bands(): return to automatic background-colored bands.prg32_gfx_lock()/prg32_gfx_unlock(): optional recursive graphics lock for advanced code that must update several draw calls atomically.prg32_gfx_snapshot_row_rgb565(y, out, pixels): copy one physical 320-pixel framebuffer row as normal RGB565. The HTTP screenshot API uses this helper for both ILI9341 hardware and QEMU.
Assembly programs pass C strings in a0 and a1, duration in a2, and RGB565
colors in a3 to a5.
When the viewport is active, PRG32 owns the 20-pixel band above the game and the 20-pixel band below it. By default they follow the game background color. Games can opt in to status text without changing the 320x200 play area:
prg32_band_set_mode(PRG32_BAND_TOP, mode)prg32_band_set_mode(PRG32_BAND_BOTTOM, mode)prg32_band_set_text(band, text)prg32_band_set_game_info(text)prg32_band_log(message)prg32_band_set_colors(band, fg, bg)prg32_band_use_default_colors(band)
Available modes are PRG32_BAND_MODE_NONE, PRG32_BAND_MODE_FPS,
PRG32_BAND_MODE_WIFI, PRG32_BAND_MODE_GAME,
PRG32_BAND_MODE_DEBUG, and PRG32_BAND_MODE_CUSTOM. The setup developer
menu lets a trainer choose what appears in the top and bottom bands and stores
that choice in NVS.
When the resident HTTP server is reachable, GET /api/screenshot.bmp streams
the current full 320x240 framebuffer as a 24-bit BMP. It is intended for lab
reports, debugging display output, and comparing hardware with QEMU rendering:
curl http://192.168.4.1/api/screenshot.bmp --output screenshot.bmpThe encoder holds the recursive graphics lock while it streams rows. This keeps the BMP internally consistent without allocating a complete second framebuffer in ESP32 RAM.
Optional performance metrics record update, draw, present, heap, input, FPS, and deadline information while a cartridge is running. The feature is disabled by default and controlled through Kconfig:
CONFIG_PRG32_METRICS_ENABLECONFIG_PRG32_METRICS_SERVER_URLCONFIG_PRG32_METRICS_BOARD_IDCONFIG_PRG32_METRICS_SAMPLE_PERIOD_FRAMESCONFIG_PRG32_METRICS_UPLOAD_PERIOD_MSCONFIG_PRG32_METRICS_QUEUE_LEN
The metrics upload queue is allocated only when a metrics run starts. Recording remains non-blocking; if the queue fills, new samples are dropped and reported with the next uploaded batch.
The public API is in prg32_metrics.h:
prg32_metrics_init(config)prg32_metrics_start_run()prg32_metrics_stop_run()prg32_metrics_is_enabled()prg32_metrics_record(sample)prg32_metrics_run_id()
The resident firmware instruments the cartridge update/draw/present loop when
metrics are enabled. prg32_metrics_record only copies into a ring buffer; HTTP
upload is handled asynchronously so the measured frame code does not wait for
the network. See Performance Metrics for
the server, export workflow, and lab exercise.
The optional performancetest cartridge provides an unattended multi-case
benchmark that retains compact per-screen/per-color-mode summaries in RAM
without streaming every frame. Temporary percentile samples are released at
the end of each case. The latest run is
available as /api/performance.json until the next benchmark or reboot. The
cartridge cases isolate clear/fill, text overlay, sprite storm, scrolling, and
mixed-gameplay workloads. Every workload runs through matched RGB565 and
full indexed-native primitives and indexed-color sprite probes; the final screen reports an aggregate summary and
the complete result endpoint. The JSON API preserves the mode on every case
summary. See the Performance Test Guide for the
execution workflow, interpretation rules, and custom ABI tutorial.
screen_count remains five because it counts distinct workloads;
result_count is ten because every workload produces an RGB565 result and an
indexed result. Both modes ultimately present RGB565 pixels, so the comparison
isolates compact-asset decoding rather than LCD wire-format bandwidth.
The resident firmware includes a cartridge loader so games can be replaced without reflashing the whole ESP32-C6 app.
Important constants:
PRG32_CART_MAGIC:.prg32package magic.PRG32_CART_ABI_MAJOR/PRG32_CART_ABI_MINOR: loader ABI version.PRG32_CART_META_MAGIC: optional metadata trailer magic,PRG32META.PRG32_CART_META_ABI: metadata JSON ABI,prg32-metadata-1.0.PRG32_CART_COLOPHON_ABI: colophon JSON ABI,prg32-colophon-1.0.PRG32_CART_MAX_SIZE: maximum.prg32package size, 64 KiB in the default physical and QEMU configurations.PRG32_CART_RAM_SIZE: statically placed executable cartridge RAM window, configured byCONFIG_PRG32_CART_RAM_PROFILE. Physical ESP32-C6 and QEMU builds default to the 64 KiB extended profile. The optionalPRG32_CART_RAM_LARGE_128profile reserves 128 KiB on ESP32-C6 when built withprofiles/sdkconfig.defaults.esp32c6_128k; it requires matching rebuilt cartridges and raises the stored-image limit to 128 KiB. The window remains static because legacy cartridges are linked to the exportedprg32_cart_execaddress; portable cartridges use the ABI table. Host tools assume the 64 KiB window unless told otherwise with--cart-ram-kib; see PRG32 Profiles.PRG32_CART_SLOT_COUNT: number of persistent flash cartridge slots.
Important functions:
prg32_cart_load_addr(): runtime address used by the host linker.prg32_cart_install(image, size, persist): validate, load, and optionally store.prg32_cart_store_slot(slot, image, size): validate and store an image without running it.prg32_cart_install_slot(slot, image, size, persist): install to one ofcart0throughcart3.prg32_cart_select_slot(slot): load a stored cartridge from one slot.prg32_cart_default_slot(): read the saved default boot cartridge.prg32_cart_set_default_slot(slot): save a default slot, or pass-1to clear it.prg32_cart_select_default(): load the saved default slot.prg32_cart_stored_count(): count valid stored cartridges.prg32_cart_get_slot_info(slot, info): inspect one persistent slot.prg32_cart_call_init()prg32_cart_call_update()prg32_cart_call_draw()
The default app automatically calls the current cartridge every frame when one is loaded. Store-ready cartridges may include a metadata trailer after the legacy executable payload. The game colophon is shown after the cartridge is activated, before the player starts a new play. See cartridge_metadata.md, colophon_abi.md, and setup_mode_cartridge_store.md.
For a full guide on connecting the board to a network, see the Network Setup and Wi-Fi Modes documentation.
PRG32 supports three Wi-Fi runtime modes (PRG32_WIFI_MODE_STA, PRG32_WIFI_MODE_AP, PRG32_WIFI_MODE_APSTA).
After the startup splash, the resident ESP32-C6 firmware enters setup mode when A and B are held during boot, whenever no stored cartridge is available, or when multiple cartridges are available but no default cartridge has been saved. If one cartridge is available, it starts automatically. If a default cartridge has been saved, that cartridge starts automatically even when multiple slots are filled.
The setup main menu contains cartridge launch, default cartridge selection, Wi-Fi setup, Cartridge Store configuration and browsing, audio setup, the developer band menu, the performance test, the about screen, and exit. The Cartridge Store integration contract adds manual/discovered store URL entry, browsing, colophon preview, and download-to-slot behavior for future firmware work. Use UP/DOWN to choose, SELECT or A to confirm, and B to cancel/back. The device smoke test is now the external DeviceDemo cartridge, which exercises display, input, audio, sprites, scrolling, playfield rendering, status bands, and small classroom sketches through the same cartridge ABI used by student games.
Normal images autoload their only stored cartridge, or the saved default when
multiple cartridges are present. PRG32_BOOT_SETUP_MODE in idf.py menuconfig
(PRG32 Firmware Features -> Boot Setup Mode) can force setup on every boot for custom classroom
images. If PRG32_PIN_SETUP is wired, holding it low during boot also forces
setup mode.
Useful calls:
prg32_wifi_setup_requested()prg32_wifi_setup_run()prg32_wifi_start_mode(config)prg32_wifi_current_mode()prg32_wifi_current_ip()prg32_wifi_current_ssid()
PRG32 multiplayer is a cartridge-level state-sharing service. A cartridge opts
in by calling prg32_multiplayer_join(signature, flags) from its own code, or
by being packaged with python3 -m prg32 cartridge build --multiplayer. Use short
ASCII signatures such as pong-v1 or mygame:lab. Players only see peers that
joined the same cartridge signature, so different games or different cartridge
revisions do not share a playfield.
The ESP32-C6 transport uses Wi-Fi station mode and WebSocket over TCP:
- ESP32-C6 has native Wi-Fi, so station mode is the right physical network transport for a classroom LAN.
- WebSocket keeps one persistent bidirectional connection, which is lower latency and simpler than repeated HTTP polling.
- The classroom server is Node.js with the
wspackage, which is small enough to run on an instructor laptop.
Useful calls:
prg32_multiplayer_init()prg32_multiplayer_available()prg32_multiplayer_join(signature, flags)prg32_multiplayer_leave()prg32_multiplayer_tick()prg32_multiplayer_set_local_state(x, y, sprite, flags)prg32_multiplayer_set_input(input)prg32_multiplayer_get_peer_count()prg32_multiplayer_get_peer(index, out)
Run the standalone relay server from riscv-prg32/MultiplayerServer:
git clone https://github.com/riscv-prg32/MultiplayerServer.git
cd MultiplayerServer
npm install
npm startConfigure the board-side endpoint in idf.py menuconfig (PRG32 Firmware Features -> Multiplayer Server URL) with
PRG32_MULTIPLAYER_SERVER_URL. QEMU exposes the same API with an offline local
stub: prg32_multiplayer_available() returns true, join succeeds for a
non-empty signature, and peer snapshots are empty by default.
The tile engine exposes a 40x25 grid of 8x8 tiles. This matches a 320x200 retro screen exactly.
Useful calls:
prg32_tile_define(id, bitmap8x8, fg, bg): define a reusable tile.prg32_tile_put(tx, ty, id): place a tile in the simple 40x25 tile map.prg32_tile_present(): draw dirty simple-map tiles and present the frame.
The simple tile map is best for first tile exercises. Use playfields when the lesson needs scrolling, parallax, or two layers.
PRG32 includes two scrollable 64x32 tile playfields. A playfield is larger than the visible 40x25 tile viewport, so it can scroll horizontally and vertically.
Useful calls:
prg32_playfield_clear(layer, tile_id): fill one playfield with a tile.prg32_playfield_put(layer, tx, ty, id): place a tile in one playfield.prg32_playfield_scroll(layer, x, y): set pixel scroll for one layer.prg32_playfield_scroll_by(layer, dx, dy): move one layer by a delta.prg32_playfield_camera(x, y): set a shared camera position.prg32_playfield_parallax(layer, x_q8, y_q8): set camera scale per layer.prg32_playfield_draw(layer, transparent_zero): draw one layer.prg32_playfield_draw_dual(): draw layer 0 opaque and layer 1 transparent.
Parallax factors use Q8 fixed point:
256 = 1.0x camera speed
128 = 0.5x camera speed
64 = 0.25x camera speed
For a parallax background, set layer 0 to a smaller factor and layer 1 to
PRG32_PARALLAX_1X. The foreground layer treats tile 0 as transparent when
drawn through prg32_playfield_draw_dual().
The platform helpers build on playfields by assigning behavior flags to tile IDs and moving an actor rectangle through the flagged world.
Tile flags:
PRG32_TILE_FLAG_SOLID: blocks movement from every side.PRG32_TILE_FLAG_PLATFORM: one-way floor, useful for ledges.PRG32_TILE_FLAG_HAZARD: marks spikes, enemies, or damage tiles.PRG32_TILE_FLAG_COLLECT: marks collectible tiles.
Actor state bits:
PRG32_PLATFORM_ON_GROUNDPRG32_PLATFORM_HIT_LEFTPRG32_PLATFORM_HIT_RIGHTPRG32_PLATFORM_HIT_HEADPRG32_PLATFORM_HAZARDPRG32_PLATFORM_COLLECT
Useful calls:
prg32_platform_tile_flags(tile_id, flags): define tile behavior.prg32_platform_actor_init(actor, layer, x, y, w, h): create an actor.prg32_platform_actor_step(actor, input, speed, jump, gravity, max_fall): apply left/right movement, jump, gravity, and tile collision.prg32_platform_camera_follow(actor, deadzone_x, deadzone_y): follow an actor inside the playfield world.
The platform engine intentionally uses integer pixels and small rectangles so students can inspect every value from C or RISC-V assembly.
Assembly labs can use the PRG32_PLATFORM_ACTOR_*_OFFSET macros or treat
prg32_platform_actor_t as a 24-byte record:
0:x 4:y 8:vx 12:vy 16:w 18:h 20:state 22:layer
The sprite layer provides simple bitmap drawing and axis-aligned bounding-box collision detection.
Useful calls:
prg32_sprite_draw_8x8(x, y, bits, fg, bg): draw a monochrome sprite.prg32_sprite_draw_16x16(x, y, rgb565): draw a 16x16 RGB565 sprite.prg32_sprite_draw_24x24(x, y, rgb565): draw a 24x24 RGB565 sprite from24 * 24contiguous halfwords.prg32_sprite_hitbox(...): test two axis-aligned rectangles.prg32_sprite_anim_frame(now_ms, frame_count, frame_ms): compute a frame.prg32_sprite_draw_frame(...): draw one frame from a sprite sheet.prg32_sprite_draw_indexed(...): draw a packed 1/2/4/8-bpp palette frame.prg32_sprite_draw_bitplanes(...): draw a planar 1/2/4/8-bpp palette frame.prg32_gfx_pixel_indexed(...),prg32_gfx_rect_indexed(...), andprg32_gfx_clear_indexed(...): write system-palette indices directly.prg32_palette_set(...)/prg32_palette_get(...): change or inspect a system-palette entry; changing it recolors existing indexed pixels.
The 16x16 and 24x24 helpers treat PRG32_COLOR_WHITE as transparent. For other
sizes or another transparency key, prg32_sprite_draw_frame accepts width,
height, a pointer to contiguous RGB565 frames, the frame index, and a
transparent color. This keeps animated sprites usable from assembly without
requiring a C object.
Compact sprites use prg32_indexed_sprite_t, which contains pointers to packed
pixel data and an RGB565 palette plus width, height, frame count, bit depth, and
an optional transparent palette index. They save cartridge RAM and flash for
graphics and animations. On ILI9341 builds their local RGB565 palettes are
mapped once per draw to the native 8-bit framebuffer; RGB565 expansion happens
later in the dirty SPI strip. Existing RGB565 signatures and transparency
behavior are unchanged, although non-system colors are deterministically
quantized to the 6x6x6 system cube.
The asset converter emits a tagged alias for each descriptor. That alias works through the existing 16x16, 24x24, arbitrary-frame, and animation entry points; the animation initializer takes dimensions and frame count from the descriptor, and the existing animation draw call expands the selected frame. No additional framebuffer or runtime decompression buffer is allocated.
Every sprite renderer clips once, holds the graphics mutex once, and records at most one dirty rectangle. Indexed 1/2/4/8-bpp and bitplane sources decode to 8-bit destination indices on ILI9341 builds. QEMU retains its RGB565 host surface while exposing the same public palette API.
See examples/games/frogger/graphics/game.S for the assembly call sequence and
examples/games/frogger/c/game.c for a fuller game that pairs the 24x24 sprite
with prg32_sprite_hitbox.
PRG32 has two audio layers.
Warning
Cartridge Audio Best Practices: Please don't use the legacy buzzer functions since the physical buzzer is no longer used by default. Just use the new prg32_audio_note whenever necessary, the prg32_audio_note_on and _off if you need to leave something on, the sample functions if you actually need to play a sample, and the track functions if there is a tracker sequence.
The legacy teaching helpers still use PWM to drive a passive buzzer:
prg32_buzzer_tone(hz, ms, duty): PWM tone with explicit duty cycle (512 is 50%).prg32_audio_note(channel, instrument, note, volume, duration_ms): play an asynchronous audio note playing via I2S on a speaker.prg32_audio_notes(channel, instrument, volume, notes, count): play a blocking sequence of notes wherenotesis an array ofprg32_midi_note_t.prg32_buzzer_play_notes(notes, count): blocking sequence of notes/rests.prg32_audio_note_on(channel, instrument, note, volume): start a PCM or procedural instrument note.prg32_buzzer_sample_u8(samples, count, rate): play unsigned 8-bit samples via buzzer through PWM.
The I2S audio runtime lives in the prg32_audio component and targets
MAX98357A DAC/amplifier boards:
- mono mode: one MAX98357A, default, 22050 Hz, 8 voices
- stereo mode: two MAX98357A boards, optional PRG32 Audio Plus, panned voices
- PCM and SID-like triangle, saw, pulse, and deterministic noise voices
- per-synth-voice ADSR and resonant low-pass filtering
Useful calls:
prg32_audio_init(config): start the I2S mixer runtime.prg32_audio_get_mode(): return mono or stereo.prg32_audio_register_sample(...): register unsigned 8-bit PCM.prg32_audio_play_sample(sample_id, volume, pitch): trigger a sample.prg32_audio_play_sample_pan(sample_id, volume, pitch, pan): trigger with pan.prg32_audio_note_on(channel, instrument, note, volume): start a PCM or procedural instrument note.prg32_audio_note_on_pan(...): start a note with a stereo pan override.prg32_audio_note_off(channel): stop PCM immediately or release a synth note.prg32_audio_play_track(track_id): start tracker event playback.
Pitch 1024 means natural sample speed. Volumes use 0..255. Pan uses
-64..+63; mono mode accepts pan calls but outputs mono.
See docs/tools/audio.md for wiring, synth-ID encoding,
ADSR/filter behavior, examples, and the cartridge AUDIO block format.
The setup audio menu auto-detects the active output path:
- none
- PWM buzzer
- mono I2S
- stereo I2S
It lets trainers set the test volume, play a short tune, and toggle the onboard RGB LED as a spectrum-style VU meter when the LED GPIO is available.
PRG32 exposes a small addressable RGB LED API:
prg32_rgb_led_init(gpio): initialize the board LED on a free GPIO.prg32_rgb_led_available(): return whether the LED is ready.prg32_rgb_led_set(red, green, blue): set 8-bit RGB intensity.prg32_rgb_led_off(): turn the LED off.prg32_rgb_led_vu(level): map a 0-255 level to a blue/green/yellow/red spectrum color.prg32_audio_led_vu_enable(enabled): let the audio test and PWM helpers drive the LED as a VU meter.
The reference ILI9341 wiring uses GPIO8 for LCD D/C. Many ESP32-C6 development
boards also use GPIO8 for the onboard RGB LED, so PRG32_PIN_RGB_LED defaults
to -1 in idf.py menuconfig. Set it only when the LED pin is free on the
chosen board wiring.
Important
This information is only intended for developers of the PRG32 framework.
The public ABI is components/prg32/include/prg32.h. Keep it small, stable, and
friendly to RISC-V assembly callers.
When editing framework code:
- Keep dependencies in
components/prg32/CMakeLists.txt. - Keep
REQUIRESandPRIV_REQUIRESindependent ofCONFIG_*choices; ESP-IDF expands component requirements before configuration-dependent source choices. - Configure app-specific pins and features using
idf.py menuconfig(under PRG32 framework). - Preserve
prg32_init()as the one-call framework initializer. - Do not expose ESP-IDF-only types in the public ABI unless absolutely needed.
- Return simple
intstatus codes for APIs called from assembly. - Check pointer inputs in helpers that can be called from student code.
- Keep comments short and educational where they clarify hardware or ABI behavior.