Przeglądaj źródła

harness: derive plane_invert by capture (probe)

plane_invert[k] = whether plane k's wire byte must be the bit-complement of
the engine's buffer byte. The engine's buffer convention is fixed (plane0 bit
= white, plane1 bit = red), so driving the vendor's two-arg
writeScreenBuffer(black_value, color_value) with white/black/red fills and
recording each plane's wire byte resolves it deterministically - no analogy.

Validated: the probe reproduces the bench-validated 213 Z98c's {false,false},
and gives the 154 Z90c the same, upgrading that descriptor's plane_invert from
an analogy to a capture-derived value. run_213_z98c_probe.cpp is the control.

Prototype; not yet folded into emit_descriptor (that would fill the emitted
descriptor's one remaining hand-set field for 1bpp multi-plane panels).
ZiB 3 tygodni temu
rodzic
commit
54022b2ea1

+ 8 - 7
components/gxepd2/panel_154_z90c.h

@@ -14,14 +14,15 @@
 //     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).
+// plane_invert={false, false} is capture-derived, by the plane-invert probe
+// (tools/gxepd2_harness/probe_plane_invert.h): writeScreenBuffer() white/black/
+// red fills put plane0 wire=0xFF for white and plane1 wire=0xFF for red, which
+// agrees with the engine's buffer convention (plane0 bit=white, plane1 bit=red)
+// => no inversion. The same probe reproduces the bench-validated 213 Z98c's
+// {false, false}, so this is at the same confidence as the rest of the
+// descriptor, not an analogy guess.
 //
-// NOT yet bench-tested - no 1.54" panel on hand at import time.
+// NOT yet bench-tested overall - no 1.54" panel on hand at import time.
 
 #include "epd_command.h"
 

+ 75 - 0
tools/gxepd2_harness/probe_plane_invert.h

@@ -0,0 +1,75 @@
+#pragma once
+
+// Derive plane_invert by capture instead of analogy. plane_invert[k] is "does
+// the wire byte for plane k need to be the bit-complement of the engine's
+// buffer byte". The engine's buffer convention is fixed and known in code
+// (gxepd2_display.h draw_absolute_pixel_internal):
+//   plane 0 bit set = white/blank;  plane 1 bit set = red.
+// So drive the vendor's two-arg writeScreenBuffer(black_value, color_value)
+// with known full-screen fills, record each plane's wire byte, and reconcile:
+//   plane 0: engine sets the bit for WHITE, so if the vendor's wire byte for a
+//            white fill is 0xFF (bits set) the conventions agree => invert=false.
+//   plane 1: engine sets the bit for RED, so if the vendor's wire byte for a
+//            red fill is 0xFF the conventions agree => invert=false.
+// A wire byte of 0x00 for the ink-present fill means the opposite => invert=true.
+//
+// Deterministic and panel-agnostic for 1bpp two-plane (3-colour) panels. For a
+// single-plane panel only plane 0 applies. Prototype companion to
+// emit_descriptor.h; not yet folded into it.
+
+#include <cstdint>
+#include <cstdio>
+
+#include "emit_descriptor.h"  // emit::group, emit::Cmd, emit::Peek, spy includes
+
+namespace emit {
+
+// First data byte of the burst written right after command `op`, or -1.
+inline int burst_byte_after(const std::vector<Cmd> &cmds, uint8_t op) {
+  for (const auto &c : cmds)
+    if (c.cmd == op && c.data.size() >= PLANE_BURST_MIN)
+      return c.data[0];
+  return -1;
+}
+
+template<typename Driver> void probe_plane_invert(const char *name, uint8_t plane0_cmd, uint8_t plane1_cmd, int plane_count) {
+  constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
+  Trace::set_dc_pin(DC);
+  Peek<Driver> driver(CS, DC, RST, BUSY);
+  Trace::last_busy_release_value = driver.busy_level() == HIGH ? LOW : HIGH;
+  driver.selectSPI(SPI, SPISettings());
+  driver.init(0);
+
+  struct Fill {
+    const char *label;
+    uint8_t bv, cv;
+  };
+  // writeScreenBuffer(black_value, color_value): 0xFF = "blank" for each input.
+  const Fill fills[] = {{"white", 0xFF, 0xFF}, {"black", 0x00, 0xFF}, {"red", 0xFF, 0x00}};
+
+  int wire0[3], wire1[3];
+  printf("=== %s plane-invert probe ===\n", name);
+  for (int i = 0; i < 3; i++) {
+    Trace::start_event_record();
+    driver.writeScreenBuffer(fills[i].bv, fills[i].cv);
+    Trace::stop_event_record();
+    auto cmds = group(0, Trace::events.size());
+    wire0[i] = burst_byte_after(cmds, plane0_cmd);
+    wire1[i] = plane_count > 1 ? burst_byte_after(cmds, plane1_cmd) : -1;
+    printf("  fill=%-5s black_value=0x%02X color_value=0x%02X -> plane0 wire=0x%02X  plane1 wire=0x%02X\n",
+           fills[i].label, fills[i].bv, fills[i].cv, wire0[i] & 0xFF, wire1[i] & 0xFF);
+  }
+
+  // plane 0 ink = black; engine bit is set for WHITE. White fill is index 0.
+  bool inv0 = (wire0[0] == 0x00);  // white wire 0xFF => agree(false); 0x00 => invert
+  printf("  => plane_invert[0] = %s  (white-fill plane0 wire=0x%02X; engine bit=white)\n", inv0 ? "true" : "false",
+         wire0[0] & 0xFF);
+  if (plane_count > 1) {
+    // plane 1 ink = red; engine bit is set for RED. Red fill is index 2.
+    bool inv1 = (wire1[2] == 0x00);
+    printf("  => plane_invert[1] = %s  (red-fill plane1 wire=0x%02X; engine bit=red)\n", inv1 ? "true" : "false",
+           wire1[2] & 0xFF);
+  }
+}
+
+}  // namespace emit

+ 3 - 0
tools/gxepd2_harness/run_213_z98c_probe.cpp

@@ -0,0 +1,3 @@
+#include "probe_plane_invert.h"
+#include "epd3c/GxEPD2_213_Z98c.h"
+int main(){ emit::probe_plane_invert<GxEPD2_213_Z98c>("GxEPD2_213_Z98c", 0x24, 0x26, 2); return 0; }