Selaa lähdekoodia

harness: make the window-encoder sweep a standard capture step

Factor the coordinate sweep into a shared window_sweep.h and run it from
run_harness.h after the full update, so every panel's capture derives and
cross-checks its coordinate -> window-byte encoder by observation (the
full-frame rect cross-checks against the full-refresh window). This is the
general de-risking of hand-derived window math - the class of bug behind
the GDEQ0583T31 0x8F-vs-0x87 smear. run_partial_harness.h now reuses the
same helper instead of its inline rect list.

Verified on GxEPD2_290_T94: the sweep reproduces the SSD1680 encoder across
all rects, incl. the high-y-byte (y=288 -> 0x20,0x01) and non-round
inclusive-end (8,3,8,5 -> y+h-1=7) cases.
ZiB 3 viikkoa sitten
vanhempi
sitoutus
c0c21c11cd

+ 9 - 1
tools/gxepd2_harness/run_harness.h

@@ -4,7 +4,10 @@
 // GxEPD2_BW/GxEPD2_3C wrapper templates make for a single non-paged full
 // GxEPD2_BW/GxEPD2_3C wrapper templates make for a single non-paged full
 // update (writeImage() then refresh()) - bypassing the wrapper templates
 // update (writeImage() then refresh()) - bypassing the wrapper templates
 // themselves (and their Adafruit_GFX dependency) since we only need the
 // themselves (and their Adafruit_GFX dependency) since we only need the
-// driver-level trace, not drawing helpers.
+// driver-level trace, not drawing helpers. After the full update it also
+// runs the window-encoder sweep (window_sweep.h) - adopted as the standard
+// capture step for every panel, to derive and cross-check the coordinate ->
+// window-byte math by observation rather than by hand.
 
 
 #include <cstdio>
 #include <cstdio>
 #include <cstring>
 #include <cstring>
@@ -12,6 +15,7 @@
 #include "spy/Arduino.h"
 #include "spy/Arduino.h"
 #include "spy/SPI.h"
 #include "spy/SPI.h"
 #include "trace.h"
 #include "trace.h"
+#include "window_sweep.h"
 
 
 // Exposes GxEPD2_EPD's protected busy_level/reset_duration without needing
 // Exposes GxEPD2_EPD's protected busy_level/reset_duration without needing
 // to parse anything - a subclass can read its own base class's protected
 // to parse anything - a subclass can read its own base class's protected
@@ -56,6 +60,10 @@ template<typename Driver> void run_harness(const char *name) {
   printf("--- refresh(false) ---\n");
   printf("--- refresh(false) ---\n");
   driver.refresh(false);
   driver.refresh(false);
 
 
+  // Standard window-encoder sweep for every panel (runs after the initial
+  // full refresh above, which satisfies run_window_sweep's precondition).
+  run_window_sweep(driver, Driver::WIDTH, Driver::HEIGHT);
+
   delete[] black;
   delete[] black;
   delete[] color;
   delete[] color;
 }
 }

+ 5 - 23
tools/gxepd2_harness/run_partial_harness.h

@@ -32,6 +32,7 @@
 #include "spy/Arduino.h"
 #include "spy/Arduino.h"
 #include "spy/SPI.h"
 #include "spy/SPI.h"
 #include "trace.h"
 #include "trace.h"
+#include "window_sweep.h"
 
 
 template<typename Driver> struct PartialPeek : Driver {
 template<typename Driver> struct PartialPeek : Driver {
   using Driver::Driver;
   using Driver::Driver;
@@ -76,29 +77,10 @@ template<typename Driver> void run_partial_harness(const char *name) {
   d.writeImage(frame1, 0, 0, Driver::WIDTH, Driver::HEIGHT);
   d.writeImage(frame1, 0, 0, Driver::WIDTH, Driver::HEIGHT);
   d.refresh(16, 24, 32, 48);
   d.refresh(16, 24, 32, 48);
 
 
-  // [PHASE D] Window-encoder sweep via refresh(x,y,w,h). Distinct rects with
-  // x/w multiples of 8 (so the driver's rounding is a no-op and the window
-  // bytes are exactly the rect); y/h deliberately non-round to expose the
-  // y encoding and the inclusive (y+h-1) end math. D0 is the full frame, a
-  // cross-check against the already-captured full window. Each refresh also
-  // emits an _Update_Part waveform (expected trailing noise); the window
-  // bytes are what appear between the ">>> SWEEP" line and the 0x22 0xFC.
-  printf("\n--- [PHASE D] window-encoder sweep (refresh(x,y,w,h) per rect) ---\n");
-  struct Rect {
-    uint16_t x, y, w, h;
-    const char *label;
-  };
-  const Rect rects[] = {
-      {0, 0, Driver::WIDTH, Driver::HEIGHT, "D0 full (0,0,W,H) - cross-check vs captured full window"},
-      {0, 0, 8, 8, "D1 origin-min (0,0,8,8)"},
-      {16, 24, 8, 8, "D2 offset (16,24,8,8)"},
-      {32, 40, 40, 48, "D3 block (32,40,40,48)"},
-      {120, 288, 8, 8, "D4 far-corner (120,288,8,8) - high y byte"},
-  };
-  for (const auto &r : rects) {
-    printf(">>> SWEEP %s\n", r.label);
-    d.refresh(r.x, r.y, r.w, r.h);
-  }
+  // [PHASE D] Window-encoder sweep - the shared run_window_sweep (window_sweep.h),
+  // same routine the standard full-refresh capture now runs for every panel.
+  // Here it also reveals each rect's _Update_Part waveform (0x22 0xFC).
+  run_window_sweep(d, Driver::WIDTH, Driver::HEIGHT);
 
 
   delete[] frame0;
   delete[] frame0;
   delete[] frame1;
   delete[] frame1;

+ 49 - 0
tools/gxepd2_harness/window_sweep.h

@@ -0,0 +1,49 @@
+#pragma once
+
+// Reusable window-encoder sweep, shared by run_harness.h (every panel) and
+// run_partial_harness.h. Calls the driver's PUBLIC refresh(x,y,w,h) at
+// several distinct in-bounds rects, with x/w kept multiples of 8 so the
+// driver's own coordinate rounding is a no-op and the window bytes in each
+// trace are exactly the rect. Diff the per-rect traces to DERIVE the
+// coordinate -> window-byte encoder by observation, instead of hand-
+// computing it (the latter produced the GDEQ0583T31 0x8F-vs-0x87 smear).
+//
+// Not fast-partial-specific: every panel sets a RAM window from
+// coordinates, so this works for any driver. On a panel whose refresh
+// ignores the rect and always refreshes full, every sweep point simply
+// emits the same (full) window - itself the informative result ("no
+// coordinate-parameterized window here"). The full (0,0,W,H) point is a
+// built-in cross-check against the window the normal full-refresh capture
+// already records.
+//
+// Precondition: run AFTER an initial full refresh - some drivers force the
+// very first refresh to be full regardless of the rect (e.g. the
+// _initial_refresh gate in GxEPD2_290_T94::refresh).
+
+#include <cstdint>
+#include <cstdio>
+
+template<typename Driver> void run_window_sweep(Driver &d, uint16_t W, uint16_t H) {
+  auto align8_down = [](uint16_t v) -> uint16_t { return v & ~uint16_t(7); };  // to multiple of 8
+  struct Rect {
+    uint16_t x, y, w, h;
+    const char *label;
+  };
+  const Rect rects[] = {
+      {0, 0, W, H, "full (0,0,W,H) - cross-check vs full-refresh window"},
+      {0, 0, 8, 8, "origin-unit (0,0,8,8)"},
+      {8, 3, 8, 5, "small-offset (x/8=1, non-round y/h -> inclusive-end math)"},
+      {align8_down(static_cast<uint16_t>(W / 2)), static_cast<uint16_t>(H / 2), 16, 24, "mid block"},
+      {align8_down(static_cast<uint16_t>(W - 8)), static_cast<uint16_t>(H - 8), 8, 8, "far-corner - high bytes"},
+  };
+
+  printf("\n--- [WINDOW SWEEP] refresh(x,y,w,h) per rect (window bytes precede each refresh trigger) ---\n");
+  for (const auto &r : rects) {
+    if (r.w == 0 || r.h == 0 || r.x + r.w > W || r.y + r.h > H) {
+      printf(">>> SWEEP skip %s = (%u,%u,%u,%u) - out of bounds for %ux%u\n", r.label, r.x, r.y, r.w, r.h, W, H);
+      continue;
+    }
+    printf(">>> SWEEP %s = (%u,%u,%u,%u)\n", r.label, r.x, r.y, r.w, r.h);
+    d.refresh(r.x, r.y, r.w, r.h);
+  }
+}