Эх сурвалжийг харах

harness: fold plane_invert + 1bpp/packed detection into the emitter

The emitter now runs the fill-capture probe itself and emits a real
plane_invert (no more {false,false} TODO) for 1bpp panels, and distinguishes
1bpp from packed off the white-fill wire byte: an all-bits byte (0x00/0xFF)
means a 1bpp plane model (bits_per_pixel=1, plane_invert probe-derived),
anything else (e.g. 0x55) means a packed panel whose depth/palette/blank_code
stay the diag pass's job. Fields are emitted in struct-declaration order so
the generated designated initializer compiles.

probe_plane_invert.h is now a thin print wrapper over the shared
emit::probe_plane_invert_values. Re-validated: INIT/WINDOW/REFRESH still match
hand for F51/426/583/213/154, plane_invert comes out {false,false} for the
mono and 3-colour panels (213 matches its bench-validated value), F51 is
correctly flagged packed, and every generated header compiles as a descriptor.
ZiB 3 долоо хоног өмнө
parent
commit
db1cd5b6ec

+ 90 - 7
tools/gxepd2_harness/emit_descriptor.h

@@ -25,6 +25,8 @@
 #include <cstdio>
 #include <cstdlib>
 #include <set>
+#include <type_traits>
+#include <utility>
 #include <vector>
 
 #include "spy/Arduino.h"
@@ -124,6 +126,66 @@ inline void print_cmd(const char *macro, const char *suffix, const Cmd &c) {
   printf("},\n");
 }
 
+// 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;
+}
+
+// Detects the two-arg writeScreenBuffer(black_value, color_value) that colour
+// (plane_count > 1) drivers have and mono drivers don't.
+template<typename T, typename = void> struct has_wsb2 : std::false_type {};
+template<typename T>
+struct has_wsb2<T, std::void_t<decltype(std::declval<T &>().writeScreenBuffer(uint8_t{}, uint8_t{}))>>
+    : std::true_type {};
+
+struct PlaneInvert {
+  bool inv0 = false, inv1 = false;
+  bool plane1_valid = false;
+  bool is_1bpp = false;  // white fill => an all-bits-same wire byte (not packed)
+  int w0_white = -1, w1_red = -1;
+};
+
+// plane_invert by capture (see probe_plane_invert.h for the rationale): drive
+// known full-screen fills and read each plane's wire byte, reconciling against
+// the engine's fixed buffer convention (plane0 bit=white, plane1 bit=red). A
+// white-fill plane0 wire that isn't 0x00/0xFF means a packed (>1bpp) panel,
+// where plane_invert is unused and bits_per_pixel/palette come from the diag
+// pass instead.
+template<typename Driver> PlaneInvert probe_plane_invert_values(uint8_t p0cmd, uint8_t p1cmd, size_t plane_count) {
+  constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
+  Trace::set_dc_pin(DC);
+  Peek<Driver> d(CS, DC, RST, BUSY);
+  Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
+  d.selectSPI(SPI, SPISettings());
+  d.init(0);
+
+  PlaneInvert r;
+  auto cap0 = [&](uint8_t value) -> int {
+    Trace::start_event_record();
+    d.writeScreenBuffer(value);  // 1-arg form all drivers have; colour defaults blank
+    Trace::stop_event_record();
+    return burst_byte_after(group(0, Trace::events.size()), p0cmd);
+  };
+  r.w0_white = cap0(0xFF);
+  r.is_1bpp = (r.w0_white == 0x00 || r.w0_white == 0xFF);
+  r.inv0 = (r.w0_white == 0x00);  // engine plane0 bit=white; white wire 0x00 => invert
+
+  if (plane_count > 1) {
+    if constexpr (has_wsb2<Driver>::value) {
+      Trace::start_event_record();
+      d.writeScreenBuffer((uint8_t) 0xFF, (uint8_t) 0x00);  // all-red fill
+      Trace::stop_event_record();
+      r.w1_red = burst_byte_after(group(0, Trace::events.size()), p1cmd);
+      r.plane1_valid = (r.w1_red == 0x00 || r.w1_red == 0xFF);
+      r.inv1 = (r.w1_red == 0x00);  // engine plane1 bit=red; red wire 0x00 => invert
+    }
+  }
+  return r;
+}
+
 template<typename Driver> void emit_descriptor(const char *class_name, const char *macro) {
   constexpr int16_t CS = 10, DC = 11, RST = 12, BUSY = 13;
   Trace::set_dc_pin(DC);
@@ -287,6 +349,11 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
   const size_t cip = plane_index(init_stage);
   const uint8_t clear_fill = (cip < init_stage.size() && !init_stage[cip].data.empty()) ? init_stage[cip].data[0] : 0x00;
 
+  // plane_invert + 1bpp/packed detection, derived by the fill-capture probe.
+  const uint8_t p0cmd = plane_cmds.empty() ? 0x00 : plane_cmds[0];
+  const uint8_t p1cmd = plane_cmds.size() > 1 ? plane_cmds[1] : 0x00;
+  const PlaneInvert pv = probe_plane_invert_values<Driver>(p0cmd, p1cmd, nplanes);
+
   // Refresh = post-write commands after the LAST burst (e.g. UC8179's 0x92),
   // then the refresh call minus any window-set it repeats.
   std::set<uint8_t> all_wops;
@@ -348,15 +415,31 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
     printf("    .window_sequence_len = {sizeof(%s_WINDOW_SEQ0) / sizeof(%s_WINDOW_SEQ0[0]), 0},\n", macro, macro);
   }
   printf("    .plane_count = %zu,\n", nplanes);
-  printf("    .plane_write_cmd = {0x%02X, 0x%02X},\n", plane_cmds.empty() ? 0x00 : plane_cmds[0],
-         plane_cmds.size() > 1 ? plane_cmds[1] : 0x00);
-  printf("    .plane_invert = {false, false},  // TODO: diag pass\n");
-  printf("    .bits_per_pixel = 1,  // TODO: diag pass (clear fill byte observed: 0x%02X)\n", clear_fill);
-  printf("    .palette = nullptr,  // TODO: diag pass\n");
-  printf("    .palette_len = 0,\n");
+  printf("    .plane_write_cmd = {0x%02X, 0x%02X},\n", p0cmd, p1cmd);
+  // Fields below are emitted in struct-declaration order (plane_invert,
+  // plane_width, row_interleave, bits_per_pixel, palette, palette_len,
+  // blank_code) so the designated initializer stays well-formed.
+  if (pv.is_1bpp) {
+    const bool i1 = (nplanes > 1 && pv.plane1_valid) ? pv.inv1 : false;
+    printf("    .plane_invert = {%s, %s},  // probe-derived\n", pv.inv0 ? "true" : "false", i1 ? "true" : "false");
+  } else {
+    printf("    .plane_invert = {false, false},  // unused: packed panel\n");
+  }
   if (per_plane)
     printf("    .plane_width = 0,  // TODO: diag pass (separate-region width)\n");
-  printf("    .blank_code = 0x00,  // TODO: derive from clear fill 0x%02X\n", clear_fill);
+  if (pv.is_1bpp) {
+    // 1bpp plane model: depth confirmed by the probe; palette/blank_code unused.
+    printf("    .bits_per_pixel = 1,  // probe: white fill => all-bits wire byte 0x%02X\n", pv.w0_white & 0xFF);
+    printf("    .palette = nullptr,\n");
+    printf("    .palette_len = 0,\n");
+  } else {
+    // Packed panel: depth/palette/blank_code are the diag pass's job.
+    printf("    .bits_per_pixel = 2,  // TODO: diag pass - PACKED (white fill wire 0x%02X, not 1bpp)\n",
+           pv.w0_white & 0xFF);
+    printf("    .palette = nullptr,  // TODO: diag pass\n");
+    printf("    .palette_len = 0,\n");
+    printf("    .blank_code = 0x00,  // TODO: diag pass (clear fill observed 0x%02X)\n", clear_fill);
+  }
   printf("    .refresh_sequence = %s_REFRESH,\n", macro);
   printf("    .refresh_sequence_len = sizeof(%s_REFRESH) / sizeof(%s_REFRESH[0]),\n", macro, macro);
   printf("};\n\n}  // namespace esphome::gxepd2\n");

+ 19 - 59
tools/gxepd2_harness/probe_plane_invert.h

@@ -1,74 +1,34 @@
 #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.
+// Standalone print wrapper around the emitter's plane-invert probe
+// (emit::probe_plane_invert_values, in emit_descriptor.h). plane_invert[k] is
+// "does plane k's wire byte need to be the bit-complement of the engine's
+// buffer byte"; the probe drives known full-screen fills and reads each
+// plane's wire byte, reconciling against the engine's fixed buffer convention
+// (plane0 bit=white, plane1 bit=red). See emit_descriptor.h for the detail.
+// This header just prints the result for eyeballing; the emitter uses the
+// values directly to fill plane_invert in the generated descriptor.
 
-#include <cstdint>
 #include <cstdio>
 
-#include "emit_descriptor.h"  // emit::group, emit::Cmd, emit::Peek, spy includes
+#include "emit_descriptor.h"
 
 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];
+  const PlaneInvert r = probe_plane_invert_values<Driver>(plane0_cmd, plane1_cmd, (size_t) plane_count);
   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);
+  printf("  white-fill plane0 wire=0x%02X -> is_1bpp=%s\n", r.w0_white & 0xFF, r.is_1bpp ? "true" : "false");
+  if (!r.is_1bpp) {
+    printf("  => packed panel (bits_per_pixel>1): plane_invert unused, see diag pass\n");
+    return;
   }
-
-  // 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);
+  printf("  => plane_invert[0] = %s\n", r.inv0 ? "true" : "false");
   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);
+    if (r.plane1_valid)
+      printf("  => plane_invert[1] = %s  (red-fill plane1 wire=0x%02X)\n", r.inv1 ? "true" : "false", r.w1_red & 0xFF);
+    else
+      printf("  => plane_invert[1] = (undetermined; no 2-arg writeScreenBuffer)\n");
   }
 }