| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153 |
- #pragma once
- #include <cstdint>
- namespace esphome::gxepd2 {
- // One command byte plus its (fixed, compile-time) data bytes, as sent by
- // GxEPD2_EPD::_writeCommand()/_writeData(). delay_before_ms/delay_after_ms
- // and wait_busy_after mirror the fixed delay()s and _waitWhileBusy() calls
- // bracketing specific commands in the vendor source (which command gets
- // which varies per panel - e.g. GxEPD2_213_Z98c delays 10ms on both sides
- // of its SWRESET, while GxEPD2_154_Z90c busy-waits after its SWRESET
- // instead). At most one of delay_after_ms/wait_busy_after is expected to be
- // set on any one command.
- struct EpdCommand {
- uint8_t cmd;
- const uint8_t *data; // nullptr if the command takes no data bytes
- uint8_t data_len;
- uint16_t delay_before_ms{0};
- uint16_t delay_after_ms{0};
- bool wait_busy_after{false};
- };
- // One palette entry for a packed (bits_per_pixel > 1) panel: a reference
- // RGB color (ESPHome Color space, 0-255 per channel) and the wire code the
- // controller uses for it. Classification at draw time picks the entry
- // nearest the drawn color, with exact matches winning - so named colors map
- // deterministically, the way GxEPD2's own color4()/color7() switch does.
- // Several entries may share a code (GxEPD2 maps green and orange both onto
- // the 4-color yellow code, and blue onto black).
- struct EpdPaletteEntry {
- uint8_t r, g, b;
- uint8_t code;
- };
- // Standard GxEPD2 4-color (BWRY) palette - codes per GxEPD2_4C.h color4():
- // black=0x00, white=0x01, yellow/green/orange=0x02, red=0x03, blue->black.
- inline const EpdPaletteEntry GXEPD2_PALETTE_4C[] = {
- {0x00, 0x00, 0x00, 0x00}, // black
- {0xFF, 0xFF, 0xFF, 0x01}, // white
- {0xFF, 0xFF, 0x00, 0x02}, // yellow
- {0x00, 0xFF, 0x00, 0x02}, // green -> yellow code
- {0xFF, 0xA4, 0x00, 0x02}, // orange -> yellow code
- {0xFF, 0x00, 0x00, 0x03}, // red
- {0x00, 0x00, 0xFF, 0x00}, // blue -> black code
- };
- inline const uint8_t GXEPD2_PALETTE_4C_LEN = sizeof(GXEPD2_PALETTE_4C) / sizeof(GXEPD2_PALETTE_4C[0]);
- // Standard ACeP 7-color palette - codes per GxEPD2_7C.h color7(): black=0x00,
- // white=0x01, green=0x02, blue=0x03, red=0x04, yellow=0x05, orange=0x06.
- // Ready for the first 4bpp panel (GxEPD2_565c/730c); no shipped descriptor
- // consumes it yet - see the gxepd2 panel work.
- inline const EpdPaletteEntry GXEPD2_PALETTE_7C[] = {
- {0x00, 0x00, 0x00, 0x00}, // black
- {0xFF, 0xFF, 0xFF, 0x01}, // white
- {0x00, 0xFF, 0x00, 0x02}, // green
- {0x00, 0x00, 0xFF, 0x03}, // blue
- {0xFF, 0x00, 0x00, 0x04}, // red
- {0xFF, 0xFF, 0x00, 0x05}, // yellow
- {0xFF, 0xA4, 0x00, 0x06}, // orange
- };
- inline const uint8_t GXEPD2_PALETTE_7C_LEN = sizeof(GXEPD2_PALETTE_7C) / sizeof(GXEPD2_PALETTE_7C[0]);
- // Everything CycleEngine needs to drive one panel, hand-derived from a
- // GxEPD2 vendor driver's .cpp/.h (see panel_154_z90c.h). Only what this
- // first slice's engine actually uses - the RAM-window-set command layout
- // (0x44/0x45/0x4E/0x4F) is common to every panel inspected so far and is
- // hardcoded in CycleEngine itself rather than made part of this struct;
- // revisit if/when the extraction-tooling phase finds a controller family
- // that lays it out differently.
- struct EpdPanelDescriptor {
- uint16_t width;
- uint16_t height;
- bool busy_active_level; // digitalRead() level meaning "still busy"
- uint32_t busy_timeout_ms; // ceiling for every busy wait (init and refresh alike)
- const EpdCommand *init_sequence; // run once, after the reset pulse
- uint8_t init_sequence_len;
- // Sets the (always full-frame, since this engine has no partial-window
- // support) RAM addressing window. window_sequence[0] runs once after
- // init_sequence and before the first plane's byte burst; index 1 is only
- // used when window_per_plane is true (see below). Was hardcoded into
- // CycleEngine assuming every controller shares SSD1680's
- // 0x44/0x45/0x4E/0x4F byte-column layout - GxEPD2_426_GDEQ0426T82
- // (SSD1677) turned out to address in raw pixels with reversed Y and
- // re-issues its entry-mode command here, and GxEPD2_583_GDEQ0583T31
- // (UC8179) uses one 0x90 command bracketed by 0x91/0x92 markers instead.
- // Made per-panel data, same as init/refresh, rather than special-cased
- // in the engine.
- const EpdCommand *window_sequence[2];
- uint8_t window_sequence_len[2];
- // true if each plane needs window_sequence[plane index] re-run before
- // its own byte burst, instead of one window_sequence[0] covering every
- // plane's burst (the default, e.g. a BW+red panel's shared full-frame
- // window). This is how GxEPD2_579c_GDEY0579F51's real master/slave
- // regions are handled: two genuinely separate physical RAM windows, not
- // just two color planes sharing one - "plane" here just means "one more
- // buffer with its own byte burst," which happens to fit a two-physical-
- // region panel as well as a two-color-plane one. See
- // panel_579c_gdey0579f51.h.
- bool window_per_plane{false};
- uint8_t plane_count; // 1 (BW), 2 (BW + accent color), or 2 physical regions
- uint8_t plane_write_cmd[2]; // e.g. {0x24, 0x26}
- bool plane_invert[2]; // true if the byte must be bit-inverted on write
- // When window_per_plane is true, each plane's own buffer is sized for
- // this width, not the descriptor's overall `width` (which is the full
- // logical canvas ESPHome draws into and get_width_internal() reports) -
- // e.g. 396 for GDEY0579F51's master/slave halves, each 396x272 on its
- // own, not the full 792x272. Unused otherwise.
- uint16_t plane_width{0};
- // true if rows must be sent in interleaved top/bottom-pair order (row 0,
- // row H-1, row 1, row H-2, ...) instead of plain sequential top-to-
- // bottom - GDEY0579F51's HX8717 pair apparently scans each half from
- // both edges toward the middle, per Waveshare's own epd5in79g.cpp
- // reference driver (confirmed 2026-09-19: a bar/ladder/diagonal test
- // pattern came out vertically scrambled with a broken diagonal - the
- // documented signature of a row-reordering bug - sent in plain
- // sequential order). Not assumed for any other packed (bits_per_pixel > 1)
- // panel (e.g. GDEY029F51, a single-chip panel with no master/slave split
- // at all) without the same kind of hardware confirmation.
- bool row_interleave{false};
- // Pixel bit-depth, which also selects the buffer model:
- // 1 = one-bit-per-pixel plane model (mono, or BW + accent as two
- // separate 1bpp planes). plane_invert applies; palette is unused.
- // 2 = packed 4-color: 2 bits/pixel, 4 pixels/byte, MSB-first.
- // 4 = packed 7-color (ACeP): 4 bits/pixel, 2 pixels/byte, MSB-first.
- // For bits_per_pixel > 1 the buffer is a single packed code-per-pixel
- // stream (per region when window_per_plane), sized width*height*bpp/8,
- // and each pixel's wire code comes from `palette` (nearest-color
- // classification at draw time) rather than the 1bpp plane bitmasks;
- // plane_invert is unused. This replaces the old packed_2bpp_plane bool -
- // "is it packed" is now bits_per_pixel > 1, "which codes" is data, so a
- // 4bpp 7-color panel is a palette + bits_per_pixel=4, no new engine flag.
- uint8_t bits_per_pixel{1};
- // Packed-mode (bits_per_pixel > 1) palette and the wire code the buffer is
- // cleared to (the panel's white/blank). Unused when bits_per_pixel == 1.
- // See GXEPD2_PALETTE_4C / GXEPD2_PALETTE_7C.
- const EpdPaletteEntry *palette{nullptr};
- uint8_t palette_len{0};
- uint8_t blank_code{0};
- const EpdCommand *refresh_sequence; // e.g. 0x22/0xF7 then trigger 0x20
- uint8_t refresh_sequence_len;
- };
- } // namespace esphome::gxepd2
|