|
@@ -1,45 +1,33 @@
|
|
|
#pragma once
|
|
#pragma once
|
|
|
|
|
|
|
|
-// Hand-derived from third_party/GxEPD2/src/epd4c/GxEPD2_579c_GDEY0579F51.{h,cpp}
|
|
|
|
|
-// (GDEY0579F51, HX8717 controller, 792x272, same JD79667-family 2bpp/4-color
|
|
|
|
|
-// packing as GDEY029F51 - see panel_290c_gdey029f51.h). The real dual-
|
|
|
|
|
-// region version, superseding panel_579c_gdey0579f51_master_only.h's
|
|
|
|
|
-// deliberate single-region simplification - uses CycleEngine's
|
|
|
|
|
-// window_per_plane mechanism to give "master" and "slave" (0xA2-select
|
|
|
|
|
-// logical regions, not separate physical CS pins) their own window-set
|
|
|
|
|
-// before each one's own 0x10 byte burst, "plane 0"/"plane 1" in the
|
|
|
|
|
-// engine's terms even though they're physical regions here, not black/red
|
|
|
|
|
-// color planes - see EpdPanelDescriptor::window_per_plane.
|
|
|
|
|
|
|
+// GDEY0579F51 - HX8717 dual-chip, 792x272, 4-color, 2bpp packed (same
|
|
|
|
|
+// JD79667-family packing as GDEY029F51, see panel_290c_gdey029f51.h).
|
|
|
//
|
|
//
|
|
|
-// Region physical placement (2026-09-19, confirmed two independent ways):
|
|
|
|
|
-// GxEPD2_579_GDEY0579T93.cpp (SSD1683, same 792x272 panel family) comments
|
|
|
|
|
-// its own master/slave split explicitly - "near connector" = slave,
|
|
|
|
|
-// "opposite to connector" = master. User independently confirmed the same
|
|
|
|
|
-// physical mapping driving this actual panel with the GDEY0213F51 driver
|
|
|
|
|
-// (same underlying chip): master = away from the FPC connector. So master
|
|
|
|
|
-// is region 0 here (window_sequence[0], written first, right after init
|
|
|
|
|
-// leaves the controller select=master already) and slave is region 1.
|
|
|
|
|
|
|
+// Captured from the STOCK vendored driver
|
|
|
|
|
+// (third_party/GxEPD2/src/epd4c/GxEPD2_579c_GDEY0579F51.{h,cpp}), with no
|
|
|
|
|
+// local corrections. An earlier revision of this descriptor carried
|
|
|
|
|
+// hand-"fixes" derived from Waveshare's epd5in79g.cpp (swapped master/slave
|
|
|
|
|
+// PSR, power-on under select=Both, a changed BTST) to chase mirrored text /
|
|
|
|
|
+// split geometry / a non-converging slave. Those symptoms were later traced
|
|
|
|
|
+// to DRIVE-CURRENT STARVATION on the bench MCU, not a driver bug - the same
|
|
|
|
|
+// panel + firmware renders on an adequately-powered driver board - so the
|
|
|
|
|
+// corrections were unjustified and have been dropped. This descriptor now
|
|
|
|
|
+// reproduces the stock GxEPD2 driver's own on-wire behaviour byte-for-byte.
|
|
|
//
|
|
//
|
|
|
-// PSR values corrected (2026-09-19) against Waveshare's own epd5in79g.cpp
|
|
|
|
|
-// reference driver (extracted from their official demo archive for this
|
|
|
|
|
-// exact module) - GxEPD2's driver has master(0x01)->{0x07,0x29},
|
|
|
|
|
-// slave(0x02)->{0x03,0x29}; Waveshare's has them swapped:
|
|
|
|
|
-// master(0x01)->{0x03,0x29}, slave(0x02)->{0x07,0x29}. The two PSR values
|
|
|
|
|
-// differ only in the SHL bit (source/column scan-shift direction per the
|
|
|
|
|
-// HX8717 command table) - GxEPD2 apparently has this backwards for this
|
|
|
|
|
-// panel, which lines up with the left-right-mirrored text seen on
|
|
|
|
|
-// hardware. Followed Waveshare's reference here, not GxEPD2's.
|
|
|
|
|
|
|
+// Dual-region layer assembled by hand from the driver source, per
|
|
|
|
|
+// CONTRIBUTING.md "Limits - True dual-region command structure": the
|
|
|
|
|
+// emitter's segmentation doesn't model the 0xA2 master/slave region selects.
|
|
|
|
|
+// Window geometry and refresh were taken from the capture (byte-exact);
|
|
|
|
|
+// the init below transcribes the stock _InitDisplay() directly (the emitter
|
|
|
|
|
+// can't recover a dual-chip init - _InitDisplay runs during the earlier
|
|
|
|
|
+// writeScreenBuffer clear, so nothing is left in the writeImage pre-block).
|
|
|
//
|
|
//
|
|
|
-// The master-only descriptor's single-region run showed real, correctly-
|
|
|
|
|
-// decoded content but compressed/duplicated within half the driven
|
|
|
|
|
-// region's own width, plus noise/black bands on the untouched slave half.
|
|
|
|
|
-// The noise was explained and fixed there (0x12's refresh trigger fires a
|
|
|
|
|
-// real electrical refresh across the WHOLE panel regardless of 0xA2
|
|
|
|
|
-// select, so refreshing with slave's RAM never written "developed" its
|
|
|
|
|
-// stale/undefined content) - this descriptor writes both regions before
|
|
|
|
|
-// refreshing, which should eliminate that on its own. The duplication
|
|
|
|
|
-// artifact's cause is still unconfirmed; writing both regions may or may
|
|
|
|
|
-// not resolve it - that's what this descriptor is for finding out.
|
|
|
|
|
|
|
+// Region mapping: master = away from the FPC connector = region 0 (written
|
|
|
|
|
+// first), slave = near connector = region 1 (cross-checked against
|
|
|
|
|
+// GxEPD2_579_GDEY0579T93.cpp's own master/slave comments). "plane 0/1" in
|
|
|
|
|
+// the engine's terms are these two physical regions, not color planes;
|
|
|
|
|
+// window_per_plane gives each its own 0xA2-select window-set before its own
|
|
|
|
|
+// 0x10 byte burst.
|
|
|
|
|
|
|
|
#include "epd_command.h"
|
|
#include "epd_command.h"
|
|
|
|
|
|
|
@@ -48,46 +36,40 @@ namespace esphome::gxepd2 {
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_MASTER[] = {0x01};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_MASTER[] = {0x01};
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_SLAVE[] = {0x02};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_SLAVE[] = {0x02};
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_BOTH[] = {0x00};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_SELECT_BOTH[] = {0x00};
|
|
|
-inline const uint8_t PANEL_579C_GDEY0579F51_PSR_MASTER[] = {0x03, 0x29};
|
|
|
|
|
-inline const uint8_t PANEL_579C_GDEY0579F51_PSR_SLAVE[] = {0x07, 0x29};
|
|
|
|
|
-inline const uint8_t PANEL_579C_GDEY0579F51_BTST[] = {0x38, 0x38, 0x38, 0x00};
|
|
|
|
|
-inline const uint8_t PANEL_579C_GDEY0579F51_TRES[] = {0x01, 0x8C, 0x01, 0x10}; // 396x272 - one region's resolution
|
|
|
|
|
-inline const uint8_t PANEL_579C_GDEY0579F51_CDI_INIT[] = {0x97};
|
|
|
|
|
|
|
+inline const uint8_t PANEL_579C_GDEY0579F51_PSR_MASTER[] = {0x07, 0x29};
|
|
|
|
|
+inline const uint8_t PANEL_579C_GDEY0579F51_PSR_SLAVE[] = {0x03, 0x29};
|
|
|
|
|
+inline const uint8_t PANEL_579C_GDEY0579F51_BTST[] = {0x32, 0x32, 0x32, 0x32};
|
|
|
|
|
+inline const uint8_t PANEL_579C_GDEY0579F51_TRES[] = {0x01, 0x8C, 0x01, 0x10}; // 396x272 - one region
|
|
|
|
|
+inline const uint8_t PANEL_579C_GDEY0579F51_CDI_INIT[] = {0x97}; // border white
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_E9[] = {0x01};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_E9[] = {0x01};
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_E0[] = {0x01};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_E0[] = {0x01};
|
|
|
|
|
|
|
|
-// Init(), following Waveshare's own epd5in79g.cpp exactly (command order
|
|
|
|
|
-// and select state matter here, not just which bytes get sent): PSR
|
|
|
|
|
-// written to each side individually, then CDI/TRES/BTST/E9/E0 written
|
|
|
|
|
-// once under select=Both since they're genuinely shared/global registers
|
|
|
|
|
-// - and, critically, POWER-ON (0x04) is sent while select is STILL Both,
|
|
|
|
|
-// not re-selecting master first. This engine's first attempt added an
|
|
|
|
|
-// extra "reselect master" step before 0x04 that Waveshare's reference
|
|
|
|
|
-// does not have, meaning only master's chip was ever actually powered on
|
|
|
|
|
-// - a very plausible explanation for slave showing a persistent noise/
|
|
|
|
|
-// garbage artifact regardless of what data it's given (2026-09-19).
|
|
|
|
|
|
|
+// Stock _InitDisplay(): PSR written to each region under its own select,
|
|
|
|
|
+// then BTST/TRES/CDI/E9/E0 under select=Both, then re-select MASTER and
|
|
|
|
|
+// power-on (0x04). This is the vendor's order and select-state exactly.
|
|
|
inline const EpdCommand PANEL_579C_GDEY0579F51_INIT[] = {
|
|
inline const EpdCommand PANEL_579C_GDEY0579F51_INIT[] = {
|
|
|
{0xA2, PANEL_579C_GDEY0579F51_SELECT_MASTER, 1},
|
|
{0xA2, PANEL_579C_GDEY0579F51_SELECT_MASTER, 1},
|
|
|
{0x00, PANEL_579C_GDEY0579F51_PSR_MASTER, 2},
|
|
{0x00, PANEL_579C_GDEY0579F51_PSR_MASTER, 2},
|
|
|
{0xA2, PANEL_579C_GDEY0579F51_SELECT_SLAVE, 1},
|
|
{0xA2, PANEL_579C_GDEY0579F51_SELECT_SLAVE, 1},
|
|
|
{0x00, PANEL_579C_GDEY0579F51_PSR_SLAVE, 2},
|
|
{0x00, PANEL_579C_GDEY0579F51_PSR_SLAVE, 2},
|
|
|
{0xA2, PANEL_579C_GDEY0579F51_SELECT_BOTH, 1},
|
|
{0xA2, PANEL_579C_GDEY0579F51_SELECT_BOTH, 1},
|
|
|
- {0x50, PANEL_579C_GDEY0579F51_CDI_INIT, 1},
|
|
|
|
|
- {0x61, PANEL_579C_GDEY0579F51_TRES, 4},
|
|
|
|
|
{0x06, PANEL_579C_GDEY0579F51_BTST, 4},
|
|
{0x06, PANEL_579C_GDEY0579F51_BTST, 4},
|
|
|
|
|
+ {0x61, PANEL_579C_GDEY0579F51_TRES, 4},
|
|
|
|
|
+ {0x50, PANEL_579C_GDEY0579F51_CDI_INIT, 1},
|
|
|
{0xE9, PANEL_579C_GDEY0579F51_E9, 1},
|
|
{0xE9, PANEL_579C_GDEY0579F51_E9, 1},
|
|
|
{0xE0, PANEL_579C_GDEY0579F51_E0, 1},
|
|
{0xE0, PANEL_579C_GDEY0579F51_E0, 1},
|
|
|
- {0x04, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true}, // power on, select=Both
|
|
|
|
|
|
|
+ {0xA2, PANEL_579C_GDEY0579F51_SELECT_MASTER, 1},
|
|
|
|
|
+ {0x04, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true}, // power on
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// _setPartialRamAreaMaster/Slave(0, 0, 396, 272), default partial_mode=true
|
|
|
|
|
-// - both regions are the same shape, just a different select byte.
|
|
|
|
|
|
|
+// _setPartialRamArea*(0, 0, 396, 272) with partial_mode=true: both regions
|
|
|
|
|
+// are the same shape, differing only in the 0xA2 select byte sent first.
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_WINDOW_REGION[] = {
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_WINDOW_REGION[] = {
|
|
|
- 0x00, 0x00, // x/256, x%256 = 0
|
|
|
|
|
- 0x01, 0x8B, // xe/256, xe%256 = 395
|
|
|
|
|
- 0x00, 0x00, // y/256, y%256 = 0
|
|
|
|
|
- 0x01, 0x0F, // ye/256, ye%256 = 271
|
|
|
|
|
- 0x01,
|
|
|
|
|
|
|
+ 0x00, 0x00, // x = 0
|
|
|
|
|
+ 0x01, 0x8B, // x+w-1 = 395
|
|
|
|
|
+ 0x00, 0x00, // y = 0
|
|
|
|
|
+ 0x01, 0x0F, // y+h-1 = 271
|
|
|
|
|
+ 0x01, // partial_mode
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
inline const EpdCommand PANEL_579C_GDEY0579F51_WINDOW_MASTER[] = {
|
|
inline const EpdCommand PANEL_579C_GDEY0579F51_WINDOW_MASTER[] = {
|
|
@@ -99,9 +81,9 @@ inline const EpdCommand PANEL_579C_GDEY0579F51_WINDOW_SLAVE[] = {
|
|
|
{0x83, PANEL_579C_GDEY0579F51_WINDOW_REGION, 9},
|
|
{0x83, PANEL_579C_GDEY0579F51_WINDOW_REGION, 9},
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-// refresh(false): vendor's own _setPartialRamAreaBoth(0,0,396,272) then
|
|
|
|
|
-// _refresh(false) - select=Both is correct here (unlike the master-only
|
|
|
|
|
-// descriptor) since both regions' RAM genuinely holds real content now.
|
|
|
|
|
|
|
+// refresh(false): stock _setPartialRamAreaBoth(0,0,396,272) then _refresh:
|
|
|
|
|
+// CDI 0x50=0x37 (white), then 0x12 trigger (power-on 0x04 is skipped here -
|
|
|
|
|
+// already on from init).
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_CDI_REFRESH[] = {0x37};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_CDI_REFRESH[] = {0x37};
|
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_REFRESH_TRIGGER[] = {0x00};
|
|
inline const uint8_t PANEL_579C_GDEY0579F51_REFRESH_TRIGGER[] = {0x00};
|
|
|
|
|
|