diff --git a/docs/rust-port.md b/docs/rust-port.md index 254f72c..202cc31 100644 --- a/docs/rust-port.md +++ b/docs/rust-port.md @@ -38,8 +38,39 @@ then doesn't care whether the target is a 17×7 mono matrix or a 320×480 colour **Per-board binary crates — `pm-kit`, `pm-explorer`, `pm-grid`:** a thin `main.rs` BSP that instantiates the right concrete drivers and hands them to the generic core: - **Grid** (Scroll Pack): IS31FL3731 over I²C (a `DrawTarget` for a 17×7 mono frame) + 4 GPIO buttons. -- **Explorer / Kit:** ST7789 via `mipidsi` + `embedded-graphics`; GT911 touch (Kit) over I²C; WS2812 - via `ws2812-pio`; I²S to the PCM5102A via PIO. +- **Kit:** ST7796 320×480 over **SPI** — driven by a **custom `St7796` struct** (direct port of + `pico/main.py`), **not** mipidsi, which fought the panel's geometry/CS (see [[rust-st7796-cs-gotcha]]); + UI still renders through an `embedded-graphics` framebuffer. GT911 touch over I²C; WS2812 via + `ws2812-pio`; I²S to the PCM5102A via PIO. +- **Explorer:** ST7789V 320×240 over an **8-bit parallel (8080) bus** via `mipidsi`'s + `ParallelInterface` — a different driver story from the Kit's SPI (see the matrix below). + +### Display driver matrix (researched 2026-06-03) + +Displays are not the gate — every controller has a real Rust path; the buses differ: + +| Form factor | Controller | Bus | Rust driver | Status | +|---|---|---|---|---| +| Kit (`pm-kit`) | ST7796 320×480 | SPI | **custom `St7796`** (port of `pico/main.py`) + `embedded-graphics` framebuffer; **mipidsi dropped** | ✅ on hardware — **but tearing** (see below) | +| Explorer (`pm-explorer`) | ST7789V 320×240 | 8-bit parallel 8080 | `mipidsi` `ParallelInterface` *(start here; be ready to port directly if geometry fights, as on the Kit)* | path proven upstream; not yet built | +| Grid (`pm-grid`) | IS31FL3731 17×7 mono | I²C | `is31fl3731` crate | 🟡 crate works (setup/fill/pixels); **no** `embedded-graphics` — write a ~30-line `DrawTarget` | +| Kit touch | GT911 | I²C | `gt911` or `gt9x` crate | ✅ mature (blocking + async, 5-point) | + +**ST7796 (Kit) — only *partially* working: tearing.** Pixels are correct and the panel boots, but the +image tears badly. The cause is structural, not a bug: `mipidsi` has **no TE-pin / vsync / partial-update +/ double-buffer support** (confirmed against the upstream repo `github.com/almindor/mipidsi` — it offers +only optional draw *batching*), and this ST7796 module doesn't break out the TE (tearing-effect) line to +sync writes against scan-out. So writes race the panel's refresh. Mitigations available to us, none from +the crate: (a) redraw only changed full-width row-bands to shrink the tear window — already done; (b) DMA +each band as one tight burst; (c) sync to a TE GPIO *only if* a module that exposes that pin is sourced. +Treat tearing as an **open hardware/firmware item**, not "display done." See [[rust-st7796-cs-gotcha]]. + +**Explorer parallel bus — correctness and performance are decoupled.** `mipidsi`'s `ParallelInterface` +drives the data pins through an `OutputBus` *trait*. The shipped `Generic8BitBus` is plain GPIO bit-bang +(`embedded-hal` `OutputPin`s) — works immediately, just CPU-bound. For speed, implement `OutputBus` over +**RP2350 PIO** (the PIO supports 8080/6800 bus timing; the C/TFT_eSPI world hits ~4 ms for a full 320×480 +clear this way) — a drop-in swap that leaves mipidsi + `embedded-graphics` + `pm-ui` untouched. Worst case +is "slow but functional," never "impossible," so the bit-bang fallback de-risks the whole Explorer bring-up. **The honest caveat (what the Grid prototype is teaching us):** a 17×7 mono grid and a 320×480 touch TFT are too different for *one* pixel-identical UI. So the clean split is **core engine + @@ -83,10 +114,14 @@ this timeline against the wall clock. ### Stage 3 — drivers (hardware) 🔧 IN PROGRESS (`rust/pm-kit/`) **✅ Milestone 1 (boot) — confirmed on Pico 2:** GP25 blink. Toolchain + RP2350 boot block + flash work. -**✅ Milestone 2 (display) — confirmed on Pico 2:** ST7796 320×480 over SPI0 via `rp235x-hal` + -`mipidsi` + `embedded-graphics`, drawing the shared `pm-ui`. Key fix: **hold CS low for the whole -session** (`NoCs`) — mipidsi toggles CS mid-command and the ST7796 needs it continuous (see -[[rust-st7796-cs-gotcha]]). Diagnosed off-bench with host tools in `rust/uisim`: `uisim` renders +**🟡 Milestone 2 (display) — draws correctly on Pico 2, but TEARS:** ST7796 320×480 over SPI0 via +`rp235x-hal`, drawing the shared `pm-ui` through an `embedded-graphics` framebuffer. Driver is a +**custom `St7796` struct** ported from `pico/main.py` — mipidsi was tried first and **dropped**: its +split-transaction CS and orientation/offset math mangled the geometry; the port uses correct +per-command CS framing (CS low → cmd → params → CS high) and full-width row bands (see +[[rust-st7796-cs-gotcha]]). **Not done:** the image tears badly — no TE/vsync sync is possible because +this module exposes no TE pin, so writes race scan-out (no crate fixes this; see the tearing note in +the display driver matrix above). Open item before the display can be called finished. Diagnosed off-bench with host tools in `rust/uisim`: `uisim` renders pm-ui to PNG; `--bin panelsim` decodes mipidsi's real command/pixel stream into a PNG (proved the protocol correct → bug was physical); `--bin initdump` dumps the init + CASET/RASET sequence.