Files
typewriter/docs/tradeoff-curves/epd-refresh-latency.md

5.9 KiB
Raw Permalink Blame History

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 runsnot 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 (~100130 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_bank in epd.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 (0x220xFF) runs a short waveform that only nudges pixels that changed. A full update (0x220xD7, 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) ~100130 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 (~100130 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.