5.9 KiB
E-ink refresh latency vs rows driven
Model: on this GDEY0579T93 (SSD1683 dual-controller) panel, refresh time is set by how many gate lines (rows, the Y axis) are driven and which waveform LUT runs — not by the pixel column width. Three refresh modes fall out of that: full refresh (~1870 ms), full-area partial (~630 ms), and windowed-Y partial (~100–130 ms for one text line). This is the cost model behind per-keystroke typing, the boot splash→editor swap (
../notes/boot-time-budget.md), and the scroll/gutter spikes.Tradeoff-curves index:
README.md. Docs index:../README.md. Driver:../../firmware/src/epd.rs. Bench origin:../spikes.md(Spikes 5 + 8).
The model
A refresh is three serial costs:
set RAM window → clock the pixels out over SPI → run the update waveform
(fixed) (scales with bytes = rows) (scales with gate lines
AND with which LUT)
Two things do not help, and one thing does:
- Column width (X) is free to keep full. The panel is a master (
0x00) + slave (0x80) pair with the framebuffer split at the seam; every refresh drives both controllers full width so the seam/mirror math stays intact (update_part/write_frame_bankinepd.rs). Narrowing the column saves nothing — "the waveform time dominates, not the data clock-out." - Row count (Y) is the knob. E-paper drive time scales with the number of gate lines transitioned, so restricting the refresh to a horizontal band of rows is the real win — a one-line band is far cheaper than the whole panel.
- The LUT sets the tier. A partial update (
0x22←0xFF) runs a short waveform that only nudges pixels that changed. A full update (0x22←0xD7, the fast-full LUT) runs ~3× as many frames per pixel to fully clear-and-set — slower, but it erases ghosting and re-establishes a known image.
Within partial mode the latency is roughly linear in rows:
t_partial(rows) ≈ 90 ms + 2 ms · rows
└ floor ┘ └ per-gate-line slope ┘
Floor ≈ fixed SPI setup + border/VCOM commands + waveform ramp; slope ≈ the per-row waveform drive. Full refresh sits in its own flat tier (~1870 ms, all 272 rows, full-clear LUT) and cannot be windowed — the clear waveform needs the whole panel.
The curve
Partial-refresh latency vs rows driven (Y-band) t ≈ 90 ms + 2 ms · rows
ms
1870 |========================================= FULL refresh — separate tier:
| all 272 rows, full-clear LUT,
| un-windowable. ~3× the partial
| waveform. De-ghosts + seeds a
| known image.
|
630 | * ← full-area partial (272 rows)
| . ·
| . ·
| . · slope ≈ 2 ms / gate line
300 | . ·
| . ·
110 | * ← one text line (~10 rows): the per-keystroke path
90 | * ← floor (SPI setup + border/VCOM + waveform ramp)
+----+----+----+----+----+----+----+----+----+----+----+---
0 25 50 75 100 125 150 175 200 225 250 272 rows
| Mode | Rows driven | Latency | LUT | Used for |
|---|---|---|---|---|
| Full refresh | 272 (all) | ~1870 ms (measured) | full-clear | first cold-boot image; periodic de-ghost (every FULL_REFRESH_EVERY = 64 updates) |
| Full-area partial | 272 | ~630 ms (measured; 680 ms at boot) | partial | deletes, caret moves, mode switches, the snackbar, and the boot splash→editor swap |
| Windowed-Y partial | ~10 (1 line) | ~100–130 ms (estimated¹) | partial | additive per-keystroke typing |
¹ The single-line windowed figure is projected from the floor+slope model and the
Spike 5 full-area measurement; the exact bench number is still to be confirmed
from the on-device refresh log ({mode} refresh #N … {ms} ms in
main.rs). The 1870 ms full and 630 ms full-area
figures are measured.
Two things that bound it
Ghosting caps the partial streak. Partial updates leave faint residue, so a full refresh every 64 updates resets clarity and panel state. You can't "always partial" — the ~1870 ms tier is a periodic tax paid for longevity, not a mode you can retire.
The first cold-boot image must be a full refresh. After power-on the 0x26
"previous" bank holds garbage, and a partial refresh diffs against it — so the
very first clean paint has to be the full tier. This is why boot pays exactly one
unavoidable ~1.9 s full refresh, and why the splash (which rides it) is nearly
free while the editor's first frame can be a cheap partial on top. Full
derivation: ../notes/boot-time-budget.md.
What it decides
- Per-keystroke typing → windowed-Y partial. Only the touched line's band is driven (~100–130 ms), so typing keeps up with the keyboard; the panel never repaints per keystroke off that line.
- Boot splash→editor → full-area partial (~630 ms), not a second full refresh (~1870 ms). The splash already seeded the baseline, so the editor rides in on a partial — the ~1.25 s cold-boot win recorded in the boot-time budget.
- Deletes, caret moves, mode flips, snackbar → full-area partial. These erase ink or change the panel off the cursor line, which a windowed band would ghost, so they take the whole-panel partial rather than a windowed one.
- Splash + periodic → full refresh. The unavoidable first image and the every-64 de-ghost.