Vendored the panel's init/LUT/refresh register sequence from three
independent Waveshare reference drivers for this exact panel+controller
(RaspberryPi/c, ESP32, and the ESP32-S3-ePaper-13.3E6 ESP-IDF example),
which all agree byte-for-byte. The epd13in3e.c #error is gone; it
compiles clean and links (verified via /build-firmware ee02).
That vendor code also revealed the panel's SPI wire raster is a native
1200x1600 (portrait), not 1600x1200 as previously assumed -- rotated 90
degrees from the panel's landscape mount/marketing size. The old
assumption wasn't just a rotation bug: 1600x1200 and 1200x1600 don't
share a row stride, so packing at the wrong one would have shredded
images into a repeating diagonal garble on real hardware, not just
displayed them sideways. Fixed with a new PANEL_WIRE_TRANSPOSE in
image_pipeline.py, applied after the existing per-frame
ORIENTATION_TRANSPOSE, with a direction-agnostic regression test that
catches the stride bug specifically (a byte-count check alone can't,
since both orientations pack to the same total size).
A full ee02 build still fails, but no longer because of this driver --
main/{back,next,combo}_button.c call an ESP32-C6-only deep-sleep
GPIO-wakeup API with no ESP32-S3 fallback, a separate pre-existing gap
that was simply hidden behind the panel driver's old #error. See
docs/hardware.md for details; CI's continue-on-error on this board
stays in place until that's fixed too.
101 lines
4.5 KiB
C
101 lines
4.5 KiB
C
#pragma once
|
|
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include "esp_err.h"
|
|
|
|
/* Waveshare 13.3" e-Paper (E) Spectra 6 panel, driven by Seeed's EE02
|
|
* board (XIAO ESP32-S3 Plus). The panel is marketed/mounted as a
|
|
* 1600x1200 landscape rectangle (270.40x202.80mm), but its SPI
|
|
* controller addresses a native raster of 1200 columns x 1600 rows --
|
|
* i.e. the wire format is portrait, rotated 90 degrees from how the
|
|
* panel physically hangs. Confirmed identically across three independent
|
|
* vendor sources: Waveshare's RaspberryPi/c and ESP32 reference drivers
|
|
* for this exact panel (E-paper_Separate_Program/13.3inch_e-Paper_E in
|
|
* waveshare/e-Paper), and Waveshare's own ESP-IDF example for their
|
|
* ESP32-S3-ePaper-13.3E6 driver board (a *different* carrier board than
|
|
* Seeed's EE02, but the same panel+controller, hence the same command
|
|
* bytes/geometry -- only the GPIO numbers differ, and those come from
|
|
* EE02-specific sources, see this component's Kconfig). All three define
|
|
* EPD_WIDTH=1200/EPD_HEIGHT=1600 and split each row into two 600-byte
|
|
* (300px) halves sent to independent chip-selects: EPD_PIN_CS_MASTER
|
|
* gets the left half, EPD_PIN_CS_SLAVE the right -- see epd13in3e.c.
|
|
*
|
|
* Getting this backwards (assuming the wire raster matches the
|
|
* 1600x1200 mount/marketing size) doesn't just rotate the image -- 1600
|
|
* and 1200 don't share a row stride with 1200 and 1600 the other way
|
|
* (800 bytes/row x 1200 rows vs 600 bytes/row x 1600 rows), so a mismatch
|
|
* here slices real image rows at the wrong byte offsets and shreds the
|
|
* picture into a repeating diagonal garble, not a clean rotation.
|
|
* server/app/image_pipeline.py's PANEL_WIRE_TRANSPOSE handles the
|
|
* corresponding rotation server-side before packing bytes for this
|
|
* panel_type -- this header and that dict must agree on which axis is
|
|
* native. */
|
|
#define EPD_WIDTH 1200
|
|
#define EPD_HEIGHT 1600
|
|
#define EPD_BYTES_PER_ROW ((EPD_WIDTH + 1) / 2)
|
|
#define EPD_FRAME_BYTES (EPD_BYTES_PER_ROW * EPD_HEIGHT)
|
|
|
|
/* Same 6-ink Spectra family as the 7.3" panel, and (now confirmed by the
|
|
* same three vendor sources as the geometry above) the same 4-bit nibble
|
|
* codes as epd7in3e.h's epd_color_t -- matches
|
|
* server/app/image_pipeline.py's PANEL_CODES unconditionally, no
|
|
* panel-specific table needed there. */
|
|
typedef enum {
|
|
EPD_COLOR_BLACK = 0x0,
|
|
EPD_COLOR_WHITE = 0x1,
|
|
EPD_COLOR_YELLOW = 0x2,
|
|
EPD_COLOR_RED = 0x3,
|
|
EPD_COLOR_BLUE = 0x5,
|
|
EPD_COLOR_GREEN = 0x6,
|
|
} epd_color_t;
|
|
|
|
/** Configures SPI + GPIO and runs the panel's power-on register init sequence. */
|
|
esp_err_t epd_init(void);
|
|
|
|
/** Fills the whole panel with a single color and refreshes. */
|
|
esp_err_t epd_clear(epd_color_t color);
|
|
|
|
/**
|
|
* Called repeatedly by epd_display_stream() to fill up to chunk_size bytes
|
|
* into chunk. Must return the number of bytes written, or 0 once exhausted.
|
|
*/
|
|
typedef size_t (*epd_read_fn_t)(uint8_t *chunk, size_t chunk_size, void *ctx);
|
|
|
|
/**
|
|
* Streams a full frame (EPD_FRAME_BYTES bytes, packed 2 pixels/byte) to the
|
|
* panel via read_fn and refreshes. Pulling from a caller-supplied source
|
|
* instead of a single buffer lets callers feed the panel directly from an
|
|
* HTTP response without holding the whole ~960KB frame in RAM.
|
|
*/
|
|
esp_err_t epd_display_stream(epd_read_fn_t read_fn, void *ctx);
|
|
|
|
/**
|
|
* Like epd_display_stream(), but writes the frame into the panel's
|
|
* internal buffer over SPI WITHOUT triggering the physical refresh (the
|
|
* visible flash/flicker) -- call epd_turn_on_display() separately to make
|
|
* it visible. Returns ESP_ERR_INVALID_SIZE if read_fn didn't supply
|
|
* exactly EPD_FRAME_BYTES, same as epd_display_stream(); either way
|
|
* nothing is refreshed, so the visible screen is left untouched on
|
|
* error.
|
|
*
|
|
* If out_crc32 is non-NULL, it's set to a CRC32 of the bytes written --
|
|
* lets a caller compare against the last-displayed frame's CRC and skip
|
|
* the refresh entirely when nothing actually changed (e.g. redisplaying
|
|
* the same photo after a reboot).
|
|
*/
|
|
esp_err_t epd_write_frame(epd_read_fn_t read_fn, void *ctx, uint32_t *out_crc32);
|
|
|
|
/**
|
|
* Triggers the panel's physical refresh cycle (power on, refresh, power
|
|
* off) -- the visible flash/flicker sequence. Call after epd_write_frame()
|
|
* to make the written buffer visible.
|
|
*/
|
|
esp_err_t epd_turn_on_display(void);
|
|
|
|
/** Convenience wrapper around epd_display_stream() for an in-memory frame buffer. */
|
|
esp_err_t epd_display_buffer(const uint8_t *frame, size_t len);
|
|
|
|
/** Puts the panel into deep sleep to minimize power draw between refreshes. */
|
|
esp_err_t epd_sleep(void);
|