velxio/test/test_epaper/autosearch/03_spi_protocol.md

4.9 KiB
Raw Blame History

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, 420 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