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

115 lines
4.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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
- SSD1681 datasheet (commands 0x000x4F): see `02_controllers.md`
- ESPHome `waveshare_epaper.cpp` (canonical reference for many panels):
<https://api-docs.esphome.io/waveshare__epaper_8cpp_source>
- GxEPD2 source: <https://github.com/ZinggJM/GxEPD2/tree/master/src>
- Solomon Systech SSD1681 product page:
<https://www.solomon-systech.com/product/ssd1681/>