Add Waveshare ESP32-S3-ePaper-1.54 board support#53
Open
pritom-kun wants to merge 8 commits into
Open
Conversation
200x200 SSD1681 e-paper; no touch, PMU, or IMU. Follows
docs/porting/adding-a-board.md: a new boards/waveshare_epaper_154/
folder + a PlatformIO env, with a deliberately minimal shared footprint.
Board-local (boards/waveshare_epaper_154/):
- Vendored SSD1681 driver (epd.{h,cpp}) + display.cpp bridging the
RGB565 HAL framebuffer to 1bpp. Boot full-refresh -> partial
steady-state -> 10-min ghost-clear full timer.
- 1bpp legibility: luminance threshold 64 (not 128) so every dark-theme
foreground colour renders as panel-ink (even RED, lum 94) while
backgrounds/tracks stay white. Replaces the need for any ui.cpp colour
overrides.
- MIN_PARTIAL_MS panel rate-limit: LVGL animates into RAM at full speed;
the panel commits at most one partial/sec (early-return without
clearing dirty, so no update is lost). Keeps the slow panel off the
main loop's critical path without threading cadence through shared code.
Sanctioned shared edits (the only ones required):
- BoardCaps gains has_touch. On touchless boards main.cpp lets PWR
dismiss the splash, since AMOLED leaves the splash via a screen tap
that e-paper has no equivalent for.
- ui.cpp compute_layout() gains a 200x200 tier (adding-a-board.md
step 7). The large/compact AMOLED tiers are behaviourally unchanged.
No slow_refresh capability; splash.cpp untouched; no #ifdef BOARD_<name>
in shared code. Builds clean: waveshare_epaper_154 (gating) plus the
amoled_216 / amoled_18 regression envs.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The board-local 1bpp HAL maps dark LVGL pixels to white paper, so the pill's dark COL_BAR_BG fill vanished into the background and only the (now-black) label text remained. Invert the pill on the tiny tier (light fill / dark text in LVGL) so it renders as a solid black pill with white "Current"/"Weekly" text on the e-paper panel. AMOLED tiers keep the original dim COL_BAR_BG chip. Keyed on the existing responsive signal (L.scr_h < 250), not a capability field. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The Waveshare ESP32-S3-ePaper-1.54 uses a momentary-button soft-power latch: the PWR side-button only MOMENTARILY applies the battery rail. The MCU must immediately drive VBAT_PWR (GPIO 17) HIGH to hold the rail on, exactly as the reference 07_BATT_PWR_Test does in user_app_init() before display init. Our port never touched GPIO 17 (board_init was an empty stub, BOARD_HAS_BATTERY=0 -- written assuming USB-only). Symptom: on battery, press-and-hold PWR flashed the panel and left a still splash with no animation. The MCU booted, wrote one full-refresh frame to the bistable e-paper, then power collapsed on button release because the rail was never latched; the e-paper retained that frozen frame. On USB the board is powered by VBUS regardless, so it worked. Fix is board-local: define VBAT_PWR_GPIO=17 / VBAT_PWR_ON_LEVEL=HIGH (active-HIGH, the opposite of the active-LOW EPD_PWR -- confirmed from the reference board_power_bsp.cpp) and drive it HIGH first thing in board_init(), before the SSD1681 receives any byte. Also logs esp_reset_reason() at boot so a brownout loop is distinguishable from a clean boot when validating on battery. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The ePaper-1.54 has no PMU/fuel gauge, so power.cpp reported battery_pct=-1 and the shared 5-state battery icon was stuck on "empty" even with a charged pack connected. The board does expose a battery-voltage sense: Waveshare's own 01_ADC_Test reads ADC1_CH3 = GPIO4 at 12 dB attenuation and reconstructs the pack voltage as (pin_mV * 2), i.e. a 1:2 divider. GPIO4 is in the ADC1 bank (radio-safe with NimBLE active) and is a separate net from the VBAT_PWR=GPIO17 power-enable latch. power.cpp now samples GPIO4 (median-of-5 calibrated analogReadMilliVolts, *2 for the divider), smooths it with an EMA, and maps mV -> percent via a single-cell Li-ion OCV curve. Per-icon-bucket hysteresis keeps a reading hovering on a boundary from flapping and queuing an e-paper partial refresh every 2 s. caps.has_battery / BOARD_HAS_BATTERY flip to true. No UI change needed: ui_update_battery already maps percent to the icon and main.cpp's change-detector feeds it; the icon now tracks the real level. is_charging stays false (no charge-status pin is broken out on this board -- not fabricated). vbus_in stays true so the idle state machine never auto-sleeps and freezes the bistable panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
|
Looks very nice, I assume the |
Author
|
Thanks for taking a look. The panel supports both partial and full refresh capabilities. The spinner updates its RAM-side frame every 130–260 ms, but the panel commits only one frame per second using partial refresh. It always displays the latest RAM state when each 1-second window opens, so intermediate spinner frames are silently skipped. While the display's partial refresh rate is around 300 ms, I've set the spinner refresh rate to 1 second to minimize battery consumption and screen wear. You can easily adjust this using the MIN_PARTIAL_MS constant in the board's display.cpp file. |
- main's 2b4a080 removed SCREEN_BLUETOOTH and ui_cycle_screen; eink follows. - PWR on usage now calls brightness_cycle() (no more screen cycling). - Spinner adopts main's connection-state status line (Connected / Disconnected / Pairing) instead of the always-whimsical messages. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
power_hal.h gained these two edge signals when the hold-to-pair gesture was added in main. The epaper board has no hold-to-pair use yet; add no-op stubs to satisfy the linker without adding unused state. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Touch boards keep splash_next(); touchless boards (has_touch=false, e.g. ePaper-1.54) call ui_show_screen(SCREEN_USAGE) instead — the only way off the boot splash with no tap available. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add Waveshare ESP32-S3-ePaper-1.54 board support
This adds support for the Waveshare ESP32-S3-ePaper-1.54 — a 200×200 monochrome
SSD1681 e-paper panel with no touch, no PMU, and no IMU. It's the first
non-AMOLED, non-color display in the project, so it bumps into a few assumptions
the HAL was built around. I've kept the shared footprint as small as I could and
flagged every place I had to step outside the "just add a board folder" model.
What lives in the board folder (the bulk of the change)
Everything specific to this panel is in
firmware/src/boards/waveshare_epaper_154/,as the porting guide intends:
epd.h/epd.cpp) plus adisplay.cppthatbridges the HAL's RGB565 framebuffer down to the panel's 1-bit-per-pixel format.
look wrong on e-paper, so the driver inverts as it packs pixels: light UI pixels
become black ink, dark pixels become white paper. The result is black-on-white
without the UI code knowing anything about it.
foreground color in the palette — including the red used for the "disconnected"
state, which sits at the low end — still renders as visible ink, while the
background and card fills stay white. This is what let me avoid scattering
e-paper-specific color overrides through the shared UI.
refreshes during use, and a periodic full refresh (every 10 minutes) to clear
ghosting. A rate-limit keeps the panel to at most one partial per second, so the
UI's normal animation speed doesn't thrash a display that physically needs
~300 ms per update.
The touch/IMU/power stubs are trivial, and there's a new PlatformIO env. None of
this touches shared code.
Files outside the board folder, and why they were necessary
The porting guide says you shouldn't need to edit
main.cpp,ui.cpp, oranything under
hal/, and to open an issue if you do because it points at a HALgap. E-paper surfaced a couple of real gaps. Here's each one so you can decide
whether you'd prefer a different shape.
hal/board_caps.handmain.cpp— leaving the splash screen without a touchscreen.On the AMOLED boards you dismiss the boot splash by tapping it. This board has no
touch panel, and its only inputs are the BOOT button (a HID key) and the PWR
button (which, on the splash, cycles through animations). With zero shared edits a
touchless board boots to the splash and can never reach the usage or Bluetooth
screens — it's genuinely stuck. There was no runtime way to express "this board
has no touch," so I added a single
has_touchboolean toBoardCapsand onebranch in the PWR handler: touch boards keep cycling animations (you leave via
tap), touchless boards use PWR to advance off the splash. That's the whole change
— one struct field and one
if.ui.cpp— a layout tier for 200×200.The smallest existing layout targets 368×448 and overflows badly on 200×200.
Step 7 of the porting guide explicitly anticipates this and says to add a
breakpoint to
compute_layout()for a new screen size, which is what I did: aself-contained "tiny" tier alongside the existing two. The screen-builder
functions pull their fonts and sizes from the active tier, so the AMOLED layouts
are unchanged. (One related touch in this file: the "Current"/"Weekly" pill is
inverted on this tier — light fill, dark text — so that after the 1-bit inversion
it shows as a solid black pill with white text instead of disappearing into the
white background.)
The other boards'
caps.cpp/board.h, andcapability-flags.md.Adding the
has_touchfield meant the three existing boards and the template hadto set it; otherwise the designated-initializer default would leave them at
falseand silently change their splash behavior. So each gets a one-linehas_touch = true. The capability-flags doc gets a matching row, in keeping withthe existing convention of holding that table in sync with the struct.
Testing
The new
waveshare_epaper_154env builds and links cleanly, and both ESP32-S3AMOLED envs still build unchanged as a regression check. The ESP32-C6 env's
RISC-V toolchain isn't installed locally, so its
has_touchwiring was verifiedstatically rather than with a full build. On-device navigation (PWR moving off the
splash into usage/Bluetooth), black-on-white rendering, and the inverted pill have
been checked on the physical panel.
🤖 Generated with Claude Code