Przeglądaj źródła

Add GDEH0154Z90 3-color panel descriptor (emitter-derived)

200x200 SSD1681 3-colour (black+red), the first panel brought up via the
descriptor emitter rather than hand-transcription. The emitter reproduced
the 213 Z98c descriptor byte-for-byte as a control, then produced this one
from the vendor GxEPD2_154_Z90c driver; run_154_z90c_emit.cpp regenerates it.

Two real differences from the 213 sibling were caught by the capture and
verified against the vendor source: SWRESET here busy-waits (the 213 uses
fixed 10ms delays) and there's no 0x21 display-update-control in init.
plane_invert={false,false} is carried from the 213 (identical ~data color
write), not emitter-derived - flagged to confirm on hardware. Wired into
MODELS as GxEPD2_154_Z90c (label 0154Z90). Not yet bench-tested - no 1.54"
panel on hand at import time.
ZiB 3 tygodni temu
rodzic
commit
5acc8432c4

+ 1 - 0
components/gxepd2/display.py

@@ -59,6 +59,7 @@ GxEPD2Display = gxepd2_ns.class_("GxEPD2Display", display.DisplayBuffer)
 # style drops the GDEx prefix and the 4-digit size - GxEPD2_213_Z98C is really
 # GDEY0213Z98 - so the class name alone can't reconstruct what's on the tag.
 MODELS = {
+    "GxEPD2_154_Z90c": ("PANEL_154_Z90C", "GDEH0154Z90"),
     "GxEPD2_213_Z98C": ("PANEL_213_Z98C", "GDEY0213Z98"),
     "GxEPD2_213c_GDEY0213F51": ("PANEL_213C_GDEY0213F51", "GDEY0213F51"),
     "GxEPD2_426_GDEQ0426T82": ("PANEL_426_GDEQ0426T82", "GDEQ0426T82"),

+ 1 - 0
components/gxepd2/gxepd2_display.h

@@ -5,6 +5,7 @@
 #include "esphome/components/display/display_buffer.h"
 #include "esphome/components/spi/spi.h"
 #include "cycle_engine.h"
+#include "panel_154_z90c.h"
 #include "panel_213_z98c.h"
 #include "panel_213c_gdey0213f51.h"
 #include "panel_290c_gdey029f51.h"

+ 80 - 0
components/gxepd2/panel_154_z90c.h

@@ -0,0 +1,80 @@
+#pragma once
+
+// GDEH0154Z90, SSD1681 controller, 200x200, black + red (3-colour). Same
+// controller family and command shape as panel_213_z98c.h (SSD1680/1681
+// 3-colour), two 1bpp planes (0x24 black, 0x26 red) sharing one full-frame
+// RAM window.
+//
+// Derived by the descriptor emitter (tools/gxepd2_harness/emit_descriptor.h)
+// observing the real GxEPD2_154_Z90c driver, not hand-transcribed - the first
+// panel brought up that way. The emitter reproduced the 213 Z98c descriptor
+// byte-for-byte as a control, then produced this one; the capture also caught
+// the two real differences from the 213 (verified against the vendor source):
+//   - SWRESET (0x12) here waits on the busy pin, where the 213 brackets it
+//     with fixed 10ms delays instead;
+//   - no 0x21 "display update control" in init (the 213 sets {0x00, 0x80}).
+//
+// plane_invert is NOT emitter-derived (that needs the diag pass); it's set to
+// {false, false} because this driver's writeImage() is byte-identical to the
+// 213's - black via 0x24 as-is, red via 0x26 with _writeData(~data) - and the
+// 213's {false, false} is bench-validated against that exact behaviour. If red
+// and white come out swapped on hardware, flip plane_invert[1] (and see the
+// 213's plane_invert comment for the register/convention coupling).
+//
+// NOT yet bench-tested - no 1.54" panel on hand at import time.
+
+#include "epd_command.h"
+
+namespace esphome::gxepd2 {
+
+inline const uint8_t PANEL_154_Z90C_DRIVER_OUTPUT_CONTROL[] = {0xC7, 0x00, 0x00};  // gate = 0x00C7 = 199 (200 lines)
+inline const uint8_t PANEL_154_Z90C_DATA_ENTRY_MODE[] = {0x03};
+inline const uint8_t PANEL_154_Z90C_BORDER_WAVEFORM[] = {0x05};
+inline const uint8_t PANEL_154_Z90C_TEMP_SENSOR[] = {0x80};
+
+inline const EpdCommand PANEL_154_Z90C_INIT[] = {
+    {0x12, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true},  // SWRESET
+    {0x01, PANEL_154_Z90C_DRIVER_OUTPUT_CONTROL, 3},
+    {0x11, PANEL_154_Z90C_DATA_ENTRY_MODE, 1},
+    {0x3C, PANEL_154_Z90C_BORDER_WAVEFORM, 1},
+    {0x18, PANEL_154_Z90C_TEMP_SENSOR, 1},
+};
+
+inline const uint8_t PANEL_154_Z90C_UPDATE_MODE[] = {0xF7};
+
+inline const EpdCommand PANEL_154_Z90C_REFRESH[] = {
+    {0x22, PANEL_154_Z90C_UPDATE_MODE, 1},
+    {0x20, nullptr, 0, /*delay_before_ms=*/0, /*delay_after_ms=*/0, /*wait_busy_after=*/true},
+};
+
+// _setPartialRamArea(0, 0, 200, 200): SSD1681 byte-column X addressing, no
+// Y-reversal (same layout as the 213 Z98c).
+inline const uint8_t PANEL_154_Z90C_WINDOW_X[] = {0x00, 0x18};              // xe/8 = 199/8 = 24
+inline const uint8_t PANEL_154_Z90C_WINDOW_Y[] = {0x00, 0x00, 0xC7, 0x00};  // ye = 199
+inline const uint8_t PANEL_154_Z90C_WINDOW_XC[] = {0x00};
+inline const uint8_t PANEL_154_Z90C_WINDOW_YC[] = {0x00, 0x00};
+
+inline const EpdCommand PANEL_154_Z90C_WINDOW[] = {
+    {0x44, PANEL_154_Z90C_WINDOW_X, 2},
+    {0x45, PANEL_154_Z90C_WINDOW_Y, 4},
+    {0x4E, PANEL_154_Z90C_WINDOW_XC, 1},
+    {0x4F, PANEL_154_Z90C_WINDOW_YC, 2},
+};
+
+inline const EpdPanelDescriptor PANEL_154_Z90C = {
+    .width = 200,
+    .height = 200,
+    .busy_active_level = true,   // GxEPD2_EPD(..., /*busy_level=*/HIGH, ...)
+    .busy_timeout_ms = 30000,    // vendor full_refresh_time ~14000ms; err long
+    .init_sequence = PANEL_154_Z90C_INIT,
+    .init_sequence_len = sizeof(PANEL_154_Z90C_INIT) / sizeof(PANEL_154_Z90C_INIT[0]),
+    .window_sequence = {PANEL_154_Z90C_WINDOW, nullptr},
+    .window_sequence_len = {sizeof(PANEL_154_Z90C_WINDOW) / sizeof(PANEL_154_Z90C_WINDOW[0]), 0},
+    .plane_count = 2,
+    .plane_write_cmd = {0x24, 0x26},
+    .plane_invert = {false, false},  // see header note (matches 213 Z98c's validated convention)
+    .refresh_sequence = PANEL_154_Z90C_REFRESH,
+    .refresh_sequence_len = sizeof(PANEL_154_Z90C_REFRESH) / sizeof(PANEL_154_Z90C_REFRESH[0]),
+};
+
+}  // namespace esphome::gxepd2

+ 8 - 0
tools/gxepd2_harness/run_154_z90c_emit.cpp

@@ -0,0 +1,8 @@
+#include "emit_descriptor.h"
+
+#include "epd3c/GxEPD2_154_Z90c.h"
+
+int main() {
+  emit::emit_descriptor<GxEPD2_154_Z90c>("GxEPD2_154_Z90c", "PANEL_154_Z90C");
+  return 0;
+}