7.9 KiB
ePaper / e-Ink emulation in Velxio
Velxio emulates the SSD168x family of mono ePaper panels (Solomon Systech
SSD1681 / SSD1675A / SSD1680 / SSD1683 + UltraChip UC817x clones). One
component, one decoder, one Web Component — parameterised by panelKind.
Phase 1 = mono only. Tri-colour (B/W/R), 7-colour ACeP, and the user-cited 13.3" Spectra 6 are roadmap items — see "Phase 2 / out of scope" below.
Supported panels
panelKind (metadata id) |
Size | Resolution | Palette | Controller IC | AVR Uno? | RP2040? | ESP32? |
|---|---|---|---|---|---|---|---|
epaper-1in54-bw |
1.54" | 200×200 | B/W | SSD1681 | ✅ paged | ✅ | ✅ |
epaper-2in13-bw |
2.13" | 250×122 | B/W | SSD1675A / IL3897 | ✅ paged | ✅ | ✅ |
epaper-2in13-bwr |
2.13" | 250×122 | B/W/R | SSD1680 (3-colour) | ⚠️ tight | ✅ | ✅ |
epaper-2in9-bw |
2.9" | 296×128 | B/W | SSD1680 | ⚠️ tight | ✅ | ✅ |
epaper-2in9-bwr |
2.9" | 296×128 | B/W/R | SSD1680 (3-colour) | ❌ flash | ✅ | ✅ |
epaper-4in2-bw |
4.2" | 400×300 | B/W | SSD1683 / UC8176 | ❌ flash | ✅ | ✅ |
epaper-7in5-bw |
7.5" | 800×480 | B/W | UC8179 / GD7965 | ❌ | ⚠️ tight | ✅ |
epaper-5in65-7c |
5.65" | 600×448 | ACeP 7-colour | UC8159c | ❌ flash | ⚠️ tight | ✅ |
The 7-colour epaper-5in65-7c panel uses a different controller family
(UltraChip UC8159c). The decoder lives at
frontend/src/simulation/displays/UC8159cDecoder.ts and the matching
backend slave is Uc8159cEpaperSlave in esp32_spi_slaves.py. The hook
(EPaperPart.ts) picks the decoder via cfg.controllerFamily so the same
component code drives all panels.
"AVR ⚠️ tight" means the GxEPD2 paged build fits but Adafruit_GFX font selection matters; "❌" means the binary blows past 32 KB at any sane config.
Wiring (every panel — same pinout)
┌──────────────┐
│ ePaper │
│ │
│ GND VCC │ GND → board GND
│ │ VCC → board 3V3
│ SCK SDI │ SCK → SPI clock (SCK)
│ │ SDI → SPI MOSI
│ CS DC │ CS → any digital
│ │ DC → any digital (LOW = command)
│ RST BUSY │ RST → any digital (active LOW)
│ │ BUSY → any digital (input — HIGH while refreshing)
└──────────────┘
Pin order in GxEPD2 constructors: (CS, DC, RST, BUSY). Use those
exact pin numbers in the sketch.
How the emulator works
There are three rendering paths, picked automatically at runtime by
EPaperPart.attachEvents() based on which simulator owns the board:
| Board family | Decoder location | Plumbing |
|---|---|---|
| AVR (Uno / Nano / Mega) | Browser, frontend/src/simulation/displays/SSD168xDecoder.ts |
Hooks simulator.spi.onByte. CS / DC / RST tracked via pinManager.onPinChange. |
| RP2040 (Pico / Pico W) | Browser, same decoder | Hooks rp2040.spi[bus].onTransmit. Same pin tracking. |
| ESP32 family | Backend, backend/app/services/esp32_spi_slaves.py::Ssd168xEpaperSlave |
Worker subprocess decodes SPI synchronously inside the QEMU thread; emits epaper_update WS event with the latched framebuffer (base64 palette buffer). |
For all three paths:
- Latched RAM: pixels written via
0x24 WRITE_BLACK_VRAMand0x26 WRITE_RED_VRAMonly become visible after0x20 MASTER_ACTIVATION. - BUSY pin: driven HIGH for
refreshMs(default 50 ms) after every activation, then back LOW. Real hardware is 1–5 s; the default is shrunk for snappy testing. Bump it via therefreshMsproperty. - Auto-RAM-window auto-increment honours
0x11 DATA_ENTRY_MODE(default 0x03 = X+, Y+, X-first).
Library compatibility matrix
| Library | AVR | RP2040 | ESP32 | Notes |
|---|---|---|---|---|
| GxEPD2 (Jean-Marc Zingg) | ✅ ≤ 2.13" only | ✅ | ✅ | The de-facto Arduino library; tested against. |
| Adafruit_EPD | ❌ RAM | ✅ | ✅ | Should work — the SSD168x command set is identical. |
ESPHome waveshare_epaper |
n/a | n/a | ✅ | Generates the same SPI traffic as Adafruit_EPD; tested on real hardware. |
The example sketches use GxEPD2. Both GxEPD2 and Adafruit GFX Library are auto-installed by the editor when you load any example
that lists them in libraries: [...].
Try it
- Open
/examplesand pick ePaper 1.54" Hello — Arduino Uno. - Hit Run. After ~1 s the panel "refreshes" (you'll see the BUSY shimmer overlay) and the 1.54" canvas shows "Velxio / ePaper / OK!" in black on the off-white paper background.
- Try the 2.9" ESP32 Weather example next — that path goes through
the backend SSD168x slave (
epaper_updateevents arriving over the WebSocket), not the in-browser decoder. Same UX from the user's POV.
Phase 2 / out of scope (deliberately deferred)
| Feature | Why not yet |
|---|---|
✅ shipped — epaper-2in13-bwr, epaper-2in9-bwr panel kinds. The decoder's red RAM plane (0x26 WRITE_RED_VRAM) was always there; we just enabled it for the SSD1680 3-colour panels. |
|
✅ shipped — epaper-5in65-7c panel kind. New decoder family, same hook + Web Component. |
|
| Other UC81xx panels (UC8176 4.2" alt, UC8179 7.5" full driver) | The SSD168x driver covers these panels' GxEPD2 path today (those Arduino driver classes emit SSD168x-compatible traffic for B/W). Only matters if someone uses a panel whose GxEPD2 class strictly emits UC81xx commands. |
| E Ink Spectra 6 13.3" 1200×1600 (Seeed 6569) | Reverse-engineered command set; ship after Phase 1 proves the scaffold and we can capture real SPI traces from a Seeed EE02. |
| IT8951 Carta panels | Different protocol entirely (SPI packet stream). |
LUT (0x32) waveform validation |
Accept silently; never validate. Real panels sometimes send vendor-specific LUTs that aren't worth fingerprinting. |
| Real-time partial-window refresh | Phase 1 always does a full-frame refresh. |
| MicroPython on Pi Pico W | Phase 2.5 — no current epaper MicroPython driver tested in Velxio. |
Code map
| File | Purpose |
|---|---|
frontend/src/simulation/displays/SSD168xDecoder.ts |
TS port of the reference Python decoder |
frontend/src/simulation/displays/EPaperPanels.ts |
Per-panel geometry + controller assignments |
frontend/src/components/velxio-components/EPaperElement.ts |
<velxio-epaper> Web Component |
frontend/src/components/velxio-components/EPaper.tsx |
Thin React wrapper |
frontend/src/simulation/parts/EPaperPart.ts |
attachEvents factory — branches AVR / RP2040 / ESP32 |
frontend/src/data/examples-displays-epaper.ts |
5 gallery examples |
frontend/public/components-metadata.json |
5 picker entries (category: displays) |
backend/app/services/esp32_spi_slaves.py |
Ssd168xEpaperSlave for the ESP32 path |
test/test_epaper/ssd168x_decoder.py |
Reference Python decoder (the spec) |
test/test_epaper/test_ssd168x_protocol.py |
11 pure-Python protocol tests |
frontend/src/__tests__/ssd168x-decoder.test.ts |
Vitest port of the same tests |
test/test_epaper/sketches/ |
5 canonical "hello world" sketches |
See also
- Research dossier — the pre-implementation research notes covering Seeed catalog, controllers, SPI protocol, library compatibility, SVG layouts, and the phased plan.
- Custom chips (ESP32 backend runtime) — the same backend-runs-the-peripheral pattern that the ePaper SSD168x slave mirrors.