Přeskočit na obsah

Spuštění picogame na reálném hardwaru (PicoPad / RP2040)

Simulátor (sim/) má neomezenou RAM, shovívavé čistě Pythonové API a je nejrychlejší způsob, jak iterovat — ale skrývá několik věcí, které vás na zařízení nepříjemně překvapí. Tento dokument je checklist všeho, co potřebujete, aby hra běžela na skutečném PicoPadu, naučeno tou těžší cestou. Přečtěte si ho, než budete flashovat.

TL;DR nástrah: žádný celoobrazovkový Canvas; HUD = Label, ne pruh z Canvasu; display backend si postavte sami; nasazujte .mpy, ne velké .py; velká RGB565 data ukládejte jako PAL8 bytes, ne jako literál array; gc.collect() mezi scénami; a firmware musí danou funkci, kterou voláte, opravdu obsahovat.

Limity hodin / SPI / rychlosti displeje (jak jádrový clock řídí display SPI, strop ST7789, overclocking RP2350, jak to testovat) najdete v HARDWARE_LIMITS.md.


1. Rozpočet RAM (to je to skutečné omezení)

Sekce “1. Rozpočet RAM (to je to skutečné omezení)”

RP2040 má 264 KB SRAM. Firmware spotřebuje ~72 KB staticky (~27 %), takže zbývá zhruba ~150–190 KB Python heapu pro vaši hru. To se zaplní rychle:

PoložkaCena
strip buffery z picogame_game.setup() (2 × 320×strip_h×2)strip_h=2430 KB, strip_h=1215 KB
celoobrazovkový Canvas(320, 240)150 KB ⚠️ v podstatě celý heap
Canvas(320, 130) (např. pseudo-3D silnice)83 KB — samostatně OK (viz microrace), ale ne navíc k mnoha dalšímu
stavový řádek Canvas(320, 20)12,8 KB
Bitmap dlaždice/spritešířka×výška×snímky × (1 B PAL8 / 2 B RGB565)

Důsledky:

  • Nikdy nealokujte celoobrazovkový Canvas. Pokud potřebujete vlastní raster (silnice, pole tvarů), držte ho tak malý, jak je obsah (rozdělte ho na pruhy), nebo použijte Tilemap pro velké rolovací plochy (1 bajt/buňka místo 2 bajtů/pixel — 320×960 šumové nebe je 600 KB jako Canvas, ale ~5 KB jako odstínový Tilemap).
  • HUD = picogame_font.Label / picogame_ui.SceneLabel (text), ne celoširoký pruh z Canvasu. HW ověřené hry to dělají takto.
  • Snižte strip_h na 12 v napjatých scénách pro ~15 KB rezervy (o něco více refresh průchodů, stále v pohodě).
  • gc.collect() mezi scénami/úrovněmi, aby se buffery předchozí scény uvolnily, než další alokuje.
  • Pokud je hra příliš velká jako jeden program, rozdělte ji (viz §4).

MemoryError: memory allocation failed, allocating N bytes = překročili jste rozpočet; poznamenejte si, která scéna/řádek, a zmenšete tam ten největší buffer (obvykle Canvas).

Fragmentace, ne jen celkový volný prostor. Dlouhá session, která alokuje a uvolňuje velké buffery, fragmentuje heap: gc.mem_free() může ukazovat ~90 KB, zatímco alokace 51 KB stále selže (žádný souvislý úsek). Pokud monolit umírá na velkém Canvasu, i když „je tam spousta volného místa”, je to právě tohle. Řešením je předalokovaná arena (lib/picogame_arena.py

  • firmwarový argument Canvas(..., buffer=)) — naberte jeden velký buffer předem a krájejte ho. Obecný popis (se sondou na největší souvislý blok a síťovým příkladem) je v MEMORY.md.

2. Rozdíly v API mezi zařízením a simulátorem

Sekce “2. Rozdíly v API mezi zařízením a simulátorem”

Simulátor je tolerantní; C firmware je přísnější. Tohle všechno „funguje v simu, spadne na zařízení”:

Sim přijmeZařízení potřebujeProjev na zařízení
scene0.display (sim Scene ho má)postavte backend: DISP = pg.Display(board.DISPLAY)AttributeError: 'Scene' object has no attribute 'display'

C Scene nemá .display. Scene.add má příznak fixed jen jako keyword (scene.add(item, fixed=True)) v simu i na zařízení. Konstruktory noise/Canvas/Particles keyword argumenty BEROU (mp_arg_parse_all).

Jak si postavit display backend sami (co picogame_game.setup dělá interně):

import board, picogame as pg
d = board.DISPLAY
d.auto_refresh = False
try: d.root_group = None
except Exception: pass
DISP = pg.Display(d) # backend, který Scene(...) chce jako první argument
bufA = bytearray(320 * 12 * 2); bufB = bytearray(320 * 12 * 2)
scene = pg.Scene(DISP, bufA, bufB, background=background)

3. Pasti při importu / kompilaci

Sekce “3. Pasti při importu / kompilaci”
  • Velký .py jako code.py → MemoryError při importu. CircuitPython kompiluje zdroj code.py při bootu; parse strom velkého souboru je velký krátkodobý nárůst RAM. Nasazujte vše jako .mpy (zkompilované přes mpy-cross odpovídající firmwaru — aktuálně mpy v6.3 / CircuitPython 10.3.0-alpha) a použijte malinký launcher code.py (import my_scene). .mpy je také menší a šetrnější k RAM.
  • Obří list literály → RuntimeError: pystack exhausted. Literál array.array('H', [7168, ...]) tlačí tisíce prvků na VM stack (a vytvoří ~28 KB krátkodobý list). Pro velké RGB565 tilesety je bakejte jako PAL8 s DATA = b'...' (jediná bytes konstanta: poloviční velikost, žádný list a bajtová data jsou zarovnáním bezpečná na Cortex-M0+). Rezervujte index palety 0 = průhledná.

Kompilace modulu:

circuitpython/mpy-cross/build/mpy-cross mymodule.py -o mymodule.mpy

4. Vzor nasazení: jeden program na scénu

Sekce “4. Vzor nasazení: jeden program na scénu”

Demo typu „ukaž všechno”, které drží všechna assets + veškerý kód scén v jednom modulu, se nevejde. Vzor, který funguje:

  • dj_common.mpy — sdílené lešení + pomocníci (nastavení displeje, new_scene, status_bar jako Label, play() bez Canvasu kryjícího přechod, bitmap pomocníci). import * z něj.
  • scene_<name>.mpy — jeden program na scénu; importuje jen assets, které potřebuje, takže v RAM žije vždy jen jedna scéna. Spouští vlastní while True: seg(); gc.collect().
  • code.py — jednořádkový launcher (import scene_intro); upravte/přejmenujte pro přepínání scén, nebo postavte malé tlačítkové menu.

Sim/video build může zůstat jediným monolitem (má na to RAM); HW rozdělení držte oddělené. Konkrétní příklad: examples/journey_hw/ (dj_common.py + scene_*.py, plus journey_mono.py — varianta v jednom souboru se StripDraw, nulový pixel buffer / bez areny) vs. sim/video monolit examples/picogame_demo_journey.py (se zvukem + přechodem). Viz examples/journey_hw/README.md.

Rozložení na zařízení:

CIRCUITPY/
code.py # import scene_<name>
scene_*.mpy # jedna na scénu
dj_common.mpy # sdílení pomocníci
<assets>.mpy # dj_hero, dj_town, ... (jen to, co scény importují)
lib/picogame_*.mpy # Python pomocníci enginu

(Žádný zvuk na zařízení, pokud nezapojíte picogame_audio; chiptune dema je pouze offline, zapečený do nahraného videa.)


5. Firmware musí obsahovat funkci, kterou voláte

Sekce “5. Firmware musí obsahovat funkci, kterou voláte”

Projevy jako AttributeError: ... has no attribute 'X' nebo can't set attribute 'X' obvykle znamenají, že naflashovaný firmware je starší než kód, který X používá. Např. starý build měl Sprite.scale jen pro čtení → can't set attribute 'scale'.

  • Build: viz PICOGAME.md §„Building the firmware” a [[picopad-build-env]] (toolchain ARM GCC ≥ 14 + venv; make BOARD=pajenicko_picopad -j$(nproc)). Výstup: circuitpython/ports/raspberrypi/build-pajenicko_picopad/firmware.uf2.
  • Ověření, že je symbol přítomen, bez flashování: arm-none-eabi-nm build-.../firmware.elf | grep sprite_set_scale.
  • Flash: vstupte do bootloaderu RP2040 (disk RPI-RP2), zkopírujte firmware.uf2. Souborový systém CIRCUITPY (vaše .mpy soubory) zůstane zachován i přes flash firmwaru.

Aktuální firmware obsahuje: settery Sprite.scale/angle/shadow, kompletní sadu primitiv Canvas (triangle/ellipse/ring/fill_round_rect/frame3d) a C noise (value2d/value1d/fbm2d/fbm1d + varianty _fx v pevné řádové čárce).


  • Noise je v C a v pevné řádové čárce. Pomalou částí byl čistě Pythonový noise (~1–2 s zádrhel při generování nebe); C fbm2d to dělá zanedbatelným. Po benchmarku byla varianta v pevné řádové čárce ~1,8× rychlejší než float na zařízení (float 1,186 s vs. fixed 0,649 s pro 5000 fbm2d), proto byla float verze vyřazena (uvolnila ~1,8 KB flash) — value2d/ value1d/fbm2d/fbm1d jsou teď implementace v pevné řádové čárce (souřadnice Q16.16, hodnoty Q0.16). Staré názvy *_fx zmizely (není s čím kontrastovat). Float referenci ponecháváme vypnutou za #if 0 ve zdroji C pro případ, že by byla někdy znovu potřeba.
  • Kreslení Canvasu je v C (fill_rect, frame3d, …) — Python jen vydává volání. Co stojí, je buffer Canvasu (RAM), ne kreslení.
  • StripDraw = immediate mode, nulový buffer. Pro celosnímkové animované plochy (pseudo-3D silnice, gradientní nebe, procedurální pozadí) použijte pg.StripDraw(callback, …) místo Canvasu: kreslí přímo do každého render stripu, takže stojí 0 bajtů RAM plochy (vs. 150 KB pro celoobrazovkový Canvas). Překresluje se každý snímek, takže je pro animovaný obsah, ne pro statickou grafiku. Přidáno do firmwaru za +216 B flash (znovu využívá primitiva Canvasu jako pohled na jednotlivé stripy). Viz PICOGAME.mdStripDraw a examples/picogame_stripdraw_demo.py. To je skutečné řešení problému fragmentace velkých bufferů (MEMORY.md) — buffer prostě neexistuje.
  • Dirty-rect znamená, že převážně statická scéna je levná; celoobrazovkové rolování překresluje všechno každý snímek. ~50 pohybujících se spritů ≈ 25 FPS na RP2040.

Viz také: PICOGAME.md (API), ENGINE_ERGONOMICS.md (zdůvodnění návrhu), SCENE_FORMAT.md (deklarativní úrovně), tutorials/ (krok za krokem), examples/ (žánrové porty — microrace dokazuje, že 83 KB Canvas je na zařízení v pořádku).