# 03 — ePaper SPI protocol details (what we have to decode) ## Wiring (the 8-wire interface every Velxio panel will share) Even across families (SSD1681 / UC8159 / Spectra 6) the **physical interface is the same six signals** plus power: | Pin name | Direction (host → panel) | Purpose | |---|---|---| | **VCC** | power | 3.3 V | | **GND** | power | ground | | **CLK** (SCK) | host → panel | SPI clock, mode 0, 4–20 MHz | | **DIN** (MOSI) | host → panel | SPI data | | **CS** | host → panel | active low chip select | | **DC** (D/C, RS) | host → panel | **LOW = command byte, HIGH = data byte** | | **RST** | host → panel | active-low hardware reset (>200 µs) | | **BUSY** | panel → host | **HIGH while controller is working** (poll before next op) | Some panels (the Solomon family with red ink) also expose a MISO line, but neither GxEPD2 nor Adafruit_EPD reads back from it in practice. ## Frame in vs. command in The DC pin distinguishes: ``` ┌── CS asserted ──────────────────────────────────────────────────┐ │ │ │ DC = LOW 1 byte → command opcode │ │ DC = HIGH N bytes → command parameters / pixel data │ │ │ │ CS may stay asserted across the cmd→data transition; many │ │ drivers lower CS once per (cmd, data...) pair instead. │ └──────────────────────────────────────────────────────────────────┘ ``` The state machine the emulator needs: ``` state ∈ { IDLE, WAIT_CMD, COLLECT_DATA(cmd) } on CS falling edge: state ← WAIT_CMD on DC sample at SCK: if DC=LOW → state ← WAIT_CMD if DC=HIGH → state ← COLLECT_DATA(cmd) on byte received: if state == WAIT_CMD: cmd ← byte; state ← COLLECT_DATA(cmd) if state == COLLECT_DATA(cmd): apply parameter or pixel data on CS rising edge: state ← IDLE ``` Note the GxEPD2 / Adafruit_EPD libraries actually re-toggle DC for every write — they don't keep CS asserted for long bursts — so a per-byte DC sample is sufficient. ## BUSY pin semantics After almost every command that triggers physical movement of pigment, the controller pulls BUSY **high**. The libraries poll BUSY in a tight loop (with a 10 ms `delay()` between samples). The emulator must: 1. After accepting `0x20` ACTIVE_DISPLAY_UPDATE: drive BUSY high. 2. After a configurable refresh duration (1 s mono, 12 s Spectra 6), drive BUSY low. 3. During HW reset (RST low) the controller re-asserts BUSY high until it finishes its internal init (~10 ms). For the emulator, "configurable refresh duration" doesn't have to be real seconds — we can shrink it to ~50 ms for snappy testing as long as it's **non-zero** so the firmware's busy-wait loop sees a transition. ## Two independent RAM planes (B/W/R panels) Tri-colour SSD1675/1680 panels have **two** RAM planes: - `0x24` WRITE_BLACK_VRAM — black/white plane (1 = white, 0 = black) - `0x26` WRITE_RED_VRAM — red plane (1 = red, 0 = transparent / underlying B/W) Compositing rule used by every SSD168x driver: **red wins over black**. On flush, for each pixel: ``` if red_plane[x,y] == 1: pixel = RED elif bw_plane[x,y] == 1: pixel = WHITE else: pixel = BLACK ``` UC8159c (5.65" 7-colour ACeP) is different: **single plane, 4 bits/pixel packed 2 px/byte**, mapped to a 7-colour palette. Spectra 6 uses 3 bits/pixel packed 2 px/byte (with a 6-colour palette). ## RAM addressing model (Solomon family) Pixels live in a rectangular window selected by: - `0x44 SET_RAMX_RANGE` — start_col, end_col (bytes; 1 byte = 8 horizontal pixels) - `0x45 SET_RAMY_RANGE` — start_row, end_row (in scanlines; 16 bits each) - `0x4E SET_RAMX_COUNTER` — current X (bytes) - `0x4F SET_RAMY_COUNTER` — current Y (scanlines) - `0x11 DATA_ENTRY_MODE` — bit 0: Y direction (0 = decrement, 1 = increment) bit 1: X direction (0 = decrement, 1 = increment) bit 2: address counter update direction (0 = X first, 1 = Y first) Most drivers use `0x03` (X+, Y+, X-first) which means the RAM auto-increments column-by-column, then jumps to the next row. So a 200×200 panel needs 200/8 = 25 bytes/row × 200 rows = 5000 bytes per `0x24` WRITE. ## Sources - SSD1681 datasheet (commands 0x00–0x4F): see `02_controllers.md` - ESPHome `waveshare_epaper.cpp` (canonical reference for many panels): - GxEPD2 source: - Solomon Systech SSD1681 product page: