4.0 KiB
05 — How Velxio currently wires SPI/I2C displays (the pattern to follow)
We already have two display emulators that Phase 1 ePaper should mirror.
SSD1306 — I2C OLED
- Web Component:
wokwi-ssd1306(third-party, comes from@wokwi/elements) - Simulation hook:
frontend/src/simulation/parts/ProtocolParts.ts:312registers asssd1306and listens on the simulator's I2C bus. - Render path: maintains a 1-bit GDDRAM buffer in JS, pushes it to the
Web Component via
setPixels(uint8Array, width, height).
ILI9341 — SPI TFT (the closest precedent for ePaper)
- Web Component:
wokwi-ili9341(built-in canvas) - Simulation hook:
frontend/src/simulation/parts/ComplexParts.ts:795registers asili9341. Plumbing:
const pinManager = (avrSimulator as any).pinManager;
const spi = (avrSimulator as any).spi;
const pinDC = getArduinoPinHelper('D/C');
pinManager.onPinChange(pinDC, (_, state) => { dcState = state; });
spi.onByte = (value) => {
if (!dcState) processCommand(value); // DC=LOW → command opcode
else processData(value); // DC=HIGH → parameter / pixel
spi.completeTransfer(0xff); // unblock the CPU immediately
};
The simulation maintains:
colStart, colEnd, rowStart, rowEnd, curX, curY— RAM window statecurrentCmd, dataBytes[]— collecting parameters for the active command- A single
ImageDatabuffer batched torequestAnimationFrameso we don't thrash the canvas on every WRITE_RAM byte. - A
getArduinoPinHelper('D/C')lookup — pulls the DC pin number from the user's wiring in the diagram.
This is exactly what we need for ePaper, with these additions:
| Concern | ILI9341 today | ePaper additions |
|---|---|---|
| DC pin | yes | yes (same) |
| RST pin | optional, ignored | mandatory (clear framebuffer) |
| BUSY pin | n/a | emulator drives it back to firmware |
| Frame model | streaming pixel writes (visible immediately) | latched — held off-screen until 0x20 ACTIVATION |
| Refresh latency | none | configurable (default 1 s) — drive BUSY high during |
| Framebuffer planes | one (16-bit RGB) | one or two (B/W, +R) or seven (Spectra/ACeP) |
| Wire-up | Adafruit_GFX tft.begin() |
GxEPD2/Adafruit_EPD display.init() |
What "drive BUSY back to the firmware" means in code
ESP32 boards: Esp32Bridge.sendPinEvent(busyGpio, state) exists today
(used for DHT22/HC-SR04). For browser-side simulators (AVR, RP2040) the
PinManager already supports triggerPinChange(gpio, state) and the
simulator's GPIO peripheral picks it up as an INPUT read.
ePaper emulator pseudocode:
function flushFrameAndPulseBusy() {
// 1. Compose the latched RAM planes into the canvas
ctx.putImageData(composeBwAndRedPlanes(), 0, 0);
// 2. Drive BUSY HIGH so the firmware's GxEPD2 _PowerOn() blocks
pinManager.triggerPinChange(busyGpio, true);
// 3. After REFRESH_MS, drive BUSY LOW
setTimeout(() => pinManager.triggerPinChange(busyGpio, false), REFRESH_MS);
}
REFRESH_MS is 50 ms by default (snappy emulation) but settable per-panel
attribute for users who want to feel the real ~12 s Spectra 6 latency.
Where the new files will live
frontend/src/components/velxio-components/EPaperElement.ts ← Web Component (per CLAUDE.md §6a)
frontend/src/components/velxio-components/EPaper.tsx ← React wrapper
frontend/src/components/simulator/BoardOnCanvas.tsx ← (no changes — components flow through ComponentRegistry)
frontend/src/services/ComponentRegistry.ts ← register the metadataId(s)
frontend/src/simulation/parts/ComplexParts.ts ← OR a new EPaperPart.ts in parts/
Per CLAUDE.md §6a (added 2026-04), the visual component must be a Web
Component with a pinInfo getter so wires snap to pin tips, not the
corner. We'll author one Web Component (<velxio-epaper>) parameterised by
a panel-kind attribute (epaper-1in54-bw, epaper-2in13-bwr, etc.) so
all sizes share the same code.