|
@@ -0,0 +1,93 @@
|
|
|
|
|
+#pragma once
|
|
|
|
|
+
|
|
|
|
|
+// Hand-derived from vendor/src/gdeq/GxEPD2_583_GDEQ0583T31.{h,cpp}
|
|
|
|
|
+// (GDEQ0583T31, UC8179 controller, 648x480, monochrome).
|
|
|
|
|
+//
|
|
|
|
|
+// Structurally the oddest of the panels covered so far: UC8179 has no
|
|
|
|
|
+// SSD1680/1677-style 0x44/0x45/0x4E/0x4F window registers at all - a single
|
|
|
|
|
+// 0x90 command carries the whole region, bracketed by 0x91 "partial in" /
|
|
|
|
|
+// 0x92 "partial out" markers that live *around* the plane write, not just
|
|
|
|
|
+// before it. 0x91+0x90 fits this engine's existing window_sequence slot
|
|
|
|
|
+// fine; 0x92 has nowhere else to go but the front of refresh_sequence,
|
|
|
|
|
+// since it must fire after the plane byte burst and before the refresh
|
|
|
|
|
+// trigger, and those are the only two sequence slots this engine has.
|
|
|
|
|
+//
|
|
|
|
|
+// Also the only panel here where _InitDisplay() (0x00 panel setting + 0x50
|
|
|
|
|
+// VCOM/data-interval) genuinely runs twice in one real update cycle even
|
|
|
|
|
+// with GxEPD2's own priming dropped: the vendor driver's _using_partial_mode
|
|
|
|
|
+// flag flips between _Init_Part() (before the write, TSSET=0x6E) and
|
|
|
|
|
+// _Init_Full() (before the refresh trigger, TSSET=0x5A) on *every* call, not
|
|
|
|
|
+// just the first - confirmed by reading refresh()/writeImage() themselves,
|
|
|
|
|
+// not just the traced first call. This engine has no persistent
|
|
|
|
|
+// _using_partial_mode to skip either pass, and resets the panel via a real
|
|
|
|
|
+// hardware reset pulse every cycle anyway (see CycleEngine's
|
|
|
|
|
+// request_full_update()), so re-running both init variants every time
|
|
|
|
|
+// matches the vendor driver's own behavior under that same "always reset,
|
|
|
|
|
+// never assume prior state" model, not a shortcut around it.
|
|
|
|
|
+
|
|
|
|
|
+#include "epd_command.h"
|
|
|
|
|
+
|
|
|
|
|
+namespace esphome::gxepd2 {
|
|
|
|
|
+
|
|
|
|
|
+inline const uint8_t PANEL_583_GDEQ0583T31_PANEL_SETTING[] = {0x1F};
|
|
|
|
|
+inline const uint8_t PANEL_583_GDEQ0583T31_VCOM_DATA_INTERVAL[] = {0x29, 0x07};
|
|
|
|
|
+inline const uint8_t PANEL_583_GDEQ0583T31_CASCADE_SETTING[] = {0x02};
|
|
|
|
|
+inline const uint8_t PANEL_583_GDEQ0583T31_TEMP_PARTIAL[] = {0x6E}; // TSSET, _Init_Part's value (110)
|
|
|
|
|
+
|
|
|
|
|
+inline const EpdCommand PANEL_583_GDEQ0583T31_INIT[] = {
|
|
|
|
|
+ // _Init_Part(): _InitDisplay() + cascade/temp-force + _PowerOn().
|
|
|
|
|
+ {0x00, PANEL_583_GDEQ0583T31_PANEL_SETTING, 1},
|
|
|
|
|
+ {0x50, PANEL_583_GDEQ0583T31_VCOM_DATA_INTERVAL, 2},
|
|
|
|
|
+ {0xE0, PANEL_583_GDEQ0583T31_CASCADE_SETTING, 1},
|
|
|
|
|
+ {0xE5, PANEL_583_GDEQ0583T31_TEMP_PARTIAL, 1},
|
|
|
|
|
+ {0x04, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true}, // power on
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// _setPartialRamArea(0, 0, 648, 480): xe = (0+648-1)|0x0007 = 655 (byte-
|
|
|
|
|
+// boundary inclusive), ye = 479; x masked to byte boundary is 0 either way.
|
|
|
|
|
+inline const uint8_t PANEL_583_GDEQ0583T31_WINDOW_REGION[] = {
|
|
|
|
|
+ 0x00, 0x00, // x/256, x%256 = 0, 0
|
|
|
|
|
+ 0x02, 0x8F, // xe/256, xe%256 = 655
|
|
|
|
|
+ 0x00, 0x00, // y/256, y%256 = 0, 0
|
|
|
|
|
+ 0x01, 0xDF, // ye/256, ye%256 = 479
|
|
|
|
|
+ 0x01,
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+inline const EpdCommand PANEL_583_GDEQ0583T31_WINDOW[] = {
|
|
|
|
|
+ {0x91, nullptr, 0}, // partial in
|
|
|
|
|
+ {0x90, PANEL_583_GDEQ0583T31_WINDOW_REGION, 9},
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// _Init_Full() + _Update_Full(): cascade/temp-force (full-context value)
|
|
|
|
|
+// re-run before the trigger, matching what the vendor driver actually does
|
|
|
|
|
+// on every call (see file header comment) - then 0x92 "partial out" closes
|
|
|
|
|
+// the window this engine's window_sequence opened, since it has to fire
|
|
|
|
|
+// after the plane write and there's no other slot for it.
|
|
|
|
|
+inline const uint8_t PANEL_583_GDEQ0583T31_TEMP_FULL[] = {0x5A}; // TSSET, _Init_Full's value (90)
|
|
|
|
|
+
|
|
|
|
|
+inline const EpdCommand PANEL_583_GDEQ0583T31_REFRESH[] = {
|
|
|
|
|
+ {0x92, nullptr, 0}, // partial out
|
|
|
|
|
+ {0x00, PANEL_583_GDEQ0583T31_PANEL_SETTING, 1},
|
|
|
|
|
+ {0x50, PANEL_583_GDEQ0583T31_VCOM_DATA_INTERVAL, 2},
|
|
|
|
|
+ {0xE0, PANEL_583_GDEQ0583T31_CASCADE_SETTING, 1},
|
|
|
|
|
+ {0xE5, PANEL_583_GDEQ0583T31_TEMP_FULL, 1},
|
|
|
|
|
+ {0x12, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true}, // display refresh
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+inline const EpdPanelDescriptor PANEL_583_GDEQ0583T31 = {
|
|
|
|
|
+ .width = 648,
|
|
|
|
|
+ .height = 480,
|
|
|
|
|
+ .busy_active_level = false, // GxEPD2_EPD(..., /*busy_level=*/LOW, ...) - inverted vs. the SSD1677/1680 panels
|
|
|
|
|
+ .busy_timeout_ms = 20000, // full_refresh_time is 1200ms; generous ceiling, matches PANEL_213_Z98C's style
|
|
|
|
|
+ .init_sequence = PANEL_583_GDEQ0583T31_INIT,
|
|
|
|
|
+ .init_sequence_len = sizeof(PANEL_583_GDEQ0583T31_INIT) / sizeof(PANEL_583_GDEQ0583T31_INIT[0]),
|
|
|
|
|
+ .window_sequence = PANEL_583_GDEQ0583T31_WINDOW,
|
|
|
|
|
+ .window_sequence_len = sizeof(PANEL_583_GDEQ0583T31_WINDOW) / sizeof(PANEL_583_GDEQ0583T31_WINDOW[0]),
|
|
|
|
|
+ .plane_count = 1,
|
|
|
|
|
+ .plane_write_cmd = {0x13, 0x00},
|
|
|
|
|
+ .plane_invert = {false, false},
|
|
|
|
|
+ .refresh_sequence = PANEL_583_GDEQ0583T31_REFRESH,
|
|
|
|
|
+ .refresh_sequence_len = sizeof(PANEL_583_GDEQ0583T31_REFRESH) / sizeof(PANEL_583_GDEQ0583T31_REFRESH[0]),
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+} // namespace esphome::gxepd2
|