Skip to content

feat: M5Stack PaperS3 support (panel-IC 3004, FastEPD parallel path) - #159

Merged
jonasniesner merged 4 commits into
OpenDisplay:mainfrom
AsLY4:feature/papers3-support
Sep 4, 2026
Merged

feat: M5Stack PaperS3 support (panel-IC 3004, FastEPD parallel path)#159
jonasniesner merged 4 commits into
OpenDisplay:mainfrom
AsLY4:feature/papers3-support

Conversation

@AsLY4

@AsLY4 AsLY4 commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Adds M5Stack PaperS3 (ESP32-S3, 960x540 ED047TC1-class parallel e-paper, 16-gray) as a config-only device.

Closes #16.
Depends on OpenDisplay/opendisplay-protocol#14 (canonical enum + parity restore; the vendored headers on this branch are byte-identical to that branch — sync_protocol_header.py --check: 2 in sync).

Firmware changes (deliberately minimal)

  • display_fastepd.cpp: map panel-IC 3004 -> BB_PANEL_M5PAPERS3 (incl. the FastEPD-dispatch guard), use CLEAR_SLOW for the full refresh on this panel, and re-assert the config-driven pixel mode on warm transfer paths (a warm MONO<->GRAY16 config switch previously interpreted transfers at the wrong depth).
  • platformio.ini: pin FastEPD to exactly 2.2.0 with a defect-history note — FastEPD main's deInit tears down the parallel bus, which hangs this firmware's init/deinit-per-refresh pattern (filed upstream as deInit() on main tears down the parallel bus (behavior change vs 2.2.0) bitbank2/FastEPD#38).
  • Existing env esp32-s3-N16R8 matches the hardware (16 MB QIO flash, OPI PSRAM, native USB CDC); no new environment.

Everything else — GT911 touch, buzzer, battery telemetry, deep-sleep/timer-wake lifecycle — is stock firmware driven by configuration. A simple-config preset for opendisplay.org will follow separately once a hosted build exists.

Validation

Validated on a physical PaperS3 with an automated acceptance suite, 24 scenarios green on this exact branch (rebased on current main):

  • MONO and GRAY16 full-frame rendering over both direct-write and pipe-write, full refresh 0.7-1.4 s, 16-step grayscale staircase visually confirmed
  • GT911 touch events in the advertising MSD, buzzer patterns, battery telemetry (calibrated within 5% of the Li-ion charge plateau)
  • Idle deep sleep -> timer wake with the image retained, host-commanded CMD_DEEP_SLEEP 0x0053, and the documented CMD_POWER_OFF 0x0052 NACK contract on this latch-less board
  • Full erase -> rebuild -> reflash -> reconfigure -> first image, from published artifacts only

Notes for reviewers

Happy to split this into smaller commits or adjust anything on request.

Applied via opendisplay-protocol tools/sync_protocol_header.py --push
--artifact protocol --only Firmware. Comment-only (0x0043 response
layout changelog note); zero wire change. sync --check now green for
both vendored headers.
- vendored structs header synced from canonical (enum 3004, structs 2.1,
  config minor 1.5) via sync_protocol_header.py --push
- fastepd_parallel_panel(): map 3004 -> BB_PANEL_M5PAPERS3 (FastEPD
  2.2.0 native parallel driver owns bus/power/waveforms)
- fastepd_driver_used(): include 3004 in the native-parallel set
- esp32-s3-N16R8: pin FastEPD exactly to 2.2.0 during bring-up
  (positional BB_PANEL_* enum upstream)
…stream PaperS3 defect

- fastepd_apply_mode(): the effective 1bpp/4bpp mode follows the runtime
  config (color_scheme) but FastEPD's mode was sticky per hardware init;
  a MONO<->GRAY16 config change between transfers left incoming data
  interpreted at the previous depth (recognizable-but-distorted output).
  Re-asserted on every warm prepare (direct, partial, warm begin).
- FastEPD pinned to 2ead295d (2.2.0 + row-timing fix) with the upstream
  defect documented: alternating-column pinstripes on the tested PaperS3
  unit, reproduced library-only on 2.2.0 and main; M5GFX renders cleanly
  (panel exonerated). main also hangs the refresh in this firmware.
…D pins

Root cause (proven by an 8-step standalone-vs-firmware bisection with
serial traces, framebuffer checksums and library state dumps): with no
DataBus config, initOrRestoreWireForOpenDisplay() falls back to
Wire.begin() on the ESP32-S3 default I2C pins GPIO 8/9 — two of the
PaperS3's eight EPD parallel data lines. The I2C driver fought the LCD
bus on those bits: alternating-column corruption and fine dark stripes
in both 1bpp and 4bpp, firmware-only (library-only sketches were clean
once actually executing). Fixed configuration-side: the PaperS3 presets
now carry the board's real I2C bus (DataBus 0x24, SCL=42 SDA=41).

Reverted to the validated FastEPD 2.2.0 exact pin and stock glue
(single-arg initPanel, unconditional deInit): the 16 MHz clock,
bus-keep-alive and main-branch experiments were compensating for the
Wire collision, not a library defect. The config-driven pixel-mode
re-assert (fastepd_apply_mode) remains — a real, separate bug.

Also observed for upstream consideration: FastEPD main (4ab71558)
changes deInit() to tear down the parallel bus devices, which breaks
warm einkPower(1) reuse patterns; and a failed initPanel currently
leaves transfers silently unanswered (no NACK) in this firmware.
@jonasniesner

Copy link
Copy Markdown
Member

Thank you for your contribution. No need to split it, I will do a more in depth review once OpenDisplay/opendisplay-protocol#14 is merged

@AsLY4

AsLY4 commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

Hello @jonasniesner ,
The OpenDisplay/opendisplay-protocol#14 is now merged, I'll let you tell me if what I need to change this PR.
By the way, thanks in advance the review.

@jonasniesner
jonasniesner merged commit 9b915c9 into OpenDisplay:main Sep 4, 2026
13 checks passed
@jonasniesner

jonasniesner commented Sep 4, 2026

Copy link
Copy Markdown
Member

Thank you, merged. I will review the preset once you open the PR for it ;)

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.

Support for M5PaperS3 ESP32S3

2 participants