Skip to content

Partial refresh (0x76) erases all content outside the rect on EP426 — white-fill at partial START relies on panel-LUT behavior that EP426 doesn't have #80

Description

@g4bri3lDev

Summary

A protocol-correct 0x76 partial upload on an EN05 (EP426_800x480 MONO, panel_ic 0x0027, fw 2.0) renders the rect correctly but erases everything outside the rect to white. Every protocol step ACKs (0x76 → 0x71 → 0x72 → 0x73), displayed_etag updates, and the client reports success — the failure is only visible on the physical panel.

Reproduction (fw 2.0, EN05)

  1. Full upload: frame with an 8 px border + black square left → renders correctly.
  2. Partial upload via 0x76: square moves right (rect (120,140,568,201), 8-aligned, old‖new planes, END with refresh mode 2).
  3. Result: square correctly appears at the new position (lighter partial-waveform gray) — but the border and all other content outside the rect is gone (white).

With a small rect (16×7 px changed digit) the effect is "screen wiped except an unreadable sliver". Before/after photos in the comment below.

Root cause

partial_prepare_panel_ram() (display_service.cpp, unchanged since the original partial implementation in #21 / fw 1.8) runs unconditionally at every 0x76 START:

bbepInitIO(...); bbepWakeUp(&bbep);
bbepSendCMDSequence(&bbep, initSeq);
bbepFill(&bbep, BBEP_WHITE, PLANE_1);   // full panel
bbepFill(&bbep, BBEP_WHITE, PLANE_0);   // full panel

Both planes are white-filled across the entire panel; only the rect window then receives real old/new data. The design implicitly assumes the panel's partial waveform treats old==new (white/white) pixels as no-drive, leaving the physical pixels untouched. On EP426's OTP partial waveform (partial_trigger_refreshDISP_CTRL2 = 0xFF, mode 2) that assumption does not hold: white/white pixels are actively driven white, erasing the previously displayed content. Presumably validated on EP397, whose waveform behaves differently — per-panel LUT behavior is why this never shows up in protocol-level testing.

Not client-specific

  • py-opendisplay (current main) and the web client (Web/httpdocs/js/ble-common.js @ fb847ae) both compute minimal 8-aligned diff rects and both send refresh mode 2 at END — both are affected identically.
  • Client wire format cross-checked against firmware (planes, rect encoding, mode byte): correct.

Possible directions

  1. Preserve panel RAM instead of refilling — skip the reinit + white-fill when controller RAM still holds the previous frame (the panel_skips_reinit_on_partial_refresh() special-casing of EP397/EP426 suggests this intent already exists; the blocker is the post-refresh deep-sleep losing RAM).
  2. Gate partial per panel: NACK 0x76 with ERR_PARTIAL_UNSUPPORTED on panels whose waveform drives equal-RAM pixels (EP426), until 1 works.
  3. Protocol extension: client sends the full old frame as plane data (restores correctness everywhere, but defeats the transfer savings that motivate partial updates).

Environment

  • EN05 rev 1, nRF52840, EP426_800x480 MONO, panel_ic 0x0027, encryption enabled
  • fw 2.0 (974e51b1), also expected on 1.8–1.83 (identical partial_prepare_panel_ram)
  • Client: py-opendisplay current main; visual verification via webcam captures after each protocol step

🤖 Investigated and written with Claude Fable (Claude Code)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions