Skip to content

fix(epd7in5_v2): match Waveshare reference framebuffer polarity#258

Open
m5r wants to merge 1 commit into
rust-embedded-community:mainfrom
m5r:fix-7in5-v2-polarity
Open

fix(epd7in5_v2): match Waveshare reference framebuffer polarity#258
m5r wants to merge 1 commit into
rust-embedded-community:mainfrom
m5r:fix-7in5-v2-polarity

Conversation

@m5r
Copy link
Copy Markdown

@m5r m5r commented Apr 25, 2026

Fixes #256.

Summary

update_frame in Epd7in5 (V2) was sending the user buffer verbatim to DTM2 (0x13), causing every framebuffer to render with bit polarity inverted on the actual panel: Color::White (bit 1) showed as black, Color::Black (bit 0) showed as white. Symmetric test patterns hide this; any image with asymmetric content does not.

Issue #256 documents the same symptom — the reporter suggested buffer_mut().iter_mut().for_each(|b| *b = !*b) as a workaround, which is essentially what this PR does inside the driver.

Root cause

Waveshare's reference C demo EPD_7IN5_V2_Display() sends the user buffer raw to DTM1 (0x10) and bitwise-NOT to DTM2 (0x13). The Python driver display() does the same on the wire, just with the opposite user-facing buffer convention (it pre-inverts in getbuffer per its own e-paper world 0=white, 1=black comment).

The panel's DTM2 register expects bit 0 = white per the UC8179 datasheet §22, KW mode with NEW/OLD, DDX=00: {NEW=0, OLD=0} -> LUTWW. epd-waveshare's Color::White → bit 1 encoding means the buffer must be inverted before reaching 0x13.

Changes

  • DisplayInterface::data_inverted — new method, streams ~data via a 256-byte stack chunk (no heap allocation, fits the 48 KB framebuffer comfortably).
  • Epd7in5::update_frame — writes DTM1 raw + DTM2 inverted, matching the C demo. Writing both forces a full LUTKW/LUTWK transition for every pixel, producing strong contrast.
  • Epd7in5::clear_frame — writes DTM1=0xFF + DTM2=0x00, matching EPD_7IN5_V2_Clear. Previously both were 0x00, which goes through LUTWW (stays white) — works for a freshly-powered panel but doesn't force a clean transition from a prior image.

Verification

Tested on real hardware: ESP32-S3 driving a Waveshare 7.5" e-Paper V2 (G) panel via the Universal Driver HAT Rev 2.3, using esp-hal 1.0 + embedded-hal 1.0. Before the patch, a simple clear(Color::White) + Text "hello world" in Color::Black framebuffer rendered as solid black with white text. After the patch, it renders correctly white-bg-black-text without any application-side color swap.

cargo fmt --check, cargo check --lib, and RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps all pass.

Test plan

  • Build clean (cargo check --lib)
  • Format clean (cargo fmt --check)
  • Docs clean (RUSTDOCFLAGS=-Dwarnings cargo doc --no-deps)
  • Real hardware: 7.5" V2 (G) panel renders white background + black text with no app-side inversion
  • Real hardware: plain 7.5" V2 (non-(G)) — would appreciate a second confirmation from anyone with that variant on hand

`update_frame` was sending the buffer verbatim to DTM2 (0x13), causing
every framebuffer to render with bit polarity inverted on the actual
panel: `Color::White` (bit 1) showed as black, `Color::Black` (bit 0)
showed as white. Symmetric test patterns hide this; any image with
asymmetric content does not.

Waveshare's reference C demo
`RaspberryPi_JetsonNano/c/lib/e-Paper/EPD_7in5_V2.c::EPD_7IN5_V2_Display`
sends the user buffer raw to DTM1 (0x10) and bitwise-NOT to DTM2 (0x13).
The Python driver `epd7in5_V2.py::display` does the same on the wire,
just with the opposite user-facing buffer convention (it pre-inverts in
`getbuffer` per its own `e-paper world 0=white, 1=black` comment). The
panel's DTM2 register expects bit 0 = white per the UC8179 datasheet
§22, KW mode with NEW/OLD, DDX=00: `{NEW=0, OLD=0} -> LUTWW`.

This patch:
- adds `DisplayInterface::data_inverted` that streams `~data` via a 256-
  byte stack chunk, avoiding heap allocation for the 48 KB framebuffer.
- changes `Epd7in5::update_frame` to write DTM1 raw + DTM2 inverted,
  matching the C demo. Writing both forces a full LUTKW/LUTWK transition
  for every pixel, producing strong contrast.
- changes `Epd7in5::clear_frame` to write DTM1=0xFF + DTM2=0x00, matching
  `EPD_7IN5_V2_Clear`. Previously both were 0x00 which goes through
  LUTWW (stays white) — works for a freshly-powered panel but doesn't
  force a clean transition from a prior image.

Verified on real hardware: ESP32-S3 driving a Waveshare 7.5" e-Paper V2
(G) panel via the Universal Driver HAT Rev 2.3. Before the patch, the
smoke binary's white-bg + black-text framebuffer rendered as black-bg
with white text. After the patch, it renders correctly without any
app-side color swap.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

The colors for 7in5v2 are inverted

1 participant