Преглед изворни кода

harness: diagnostic-fill Pattern B (pixel packing + codes)

Derive a packed panel's colour->code palette and intra-byte packing order
by observation, instead of hand-reading color4()/color7() (both private).
run_diagB_harness.h drives the vendor 4C/7C wrapper's fillScreen/drawPixel
and reads the writeNative wire bytes: B2 reads each colour's replicated
byte0 field = its code; B1 places distinct-code pixels in byte0 and reads
the field order (MSB-first vs LSB).

Validated host-only on GxEPD2_290c_GDEY029F51 (2bpp): derived palette
black=0 white=1 red=3 yellow=2 green=2 blue=0 orange=2 and MSB-first
packing (shift=(ppb-1-i)*bpp) - matching color4() exactly and our own
GXEPD2_PALETTE_4C + draw_packed_. Extends to 7-colour (GxEPD2_7C) by
instantiating that wrapper with bpp=4.
ZiB пре 3 недеља
родитељ
комит
15cc4d5a3d

+ 19 - 0
tools/gxepd2_harness/run_290c_gdey029f51_diagB.cpp

@@ -0,0 +1,19 @@
+// Pattern B diagnostic capture for GxEPD2_290c_GDEY029F51 (JD79667, 4-color,
+// packed 2bpp, single region). Driver header first (defines GxEPD_* macros +
+// the WIDTH/HEIGHT GxEPD2_4C.h references unqualified), then the 4C wrapper,
+// then the harness. Host-only; derives the 2bpp palette + packing order.
+
+#include "epd4c/GxEPD2_290c_GDEY029F51.h"
+constexpr int16_t WIDTH = GxEPD2_290c_GDEY029F51::WIDTH;
+constexpr int16_t HEIGHT = GxEPD2_290c_GDEY029F51::HEIGHT;
+#include "GxEPD2_4C.h"
+
+#include "run_diagB_harness.h"
+
+using Driver = DiagBPeek<GxEPD2_290c_GDEY029F51>;
+using Wrapper = GxEPD2_4C<Driver, GxEPD2_290c_GDEY029F51::HEIGHT>;
+
+int main() {
+  run_diagB<Wrapper, Driver>("GxEPD2_290c_GDEY029F51", WIDTH, HEIGHT, 2);
+  return 0;
+}

+ 142 - 0
tools/gxepd2_harness/run_diagB_harness.h

@@ -0,0 +1,142 @@
+#pragma once
+
+// Diagnostic-fill harness, Pattern B (pixel packing + codes) - see the
+// gxepd2-diag-fill-spec memory. Packing geometry and the colour->code table
+// live in the vendor wrapper's draw path (color4()/color7() and _pixel_buffer
+// are private), so we derive them by observation: drive the wrapper's
+// fillScreen/drawPixel and read the emitted wire bytes (writeNative output).
+// Host-only, no hardware.
+//
+//   B2 colour->code: fillScreen(colour) -> every byte is that colour's code
+//      replicated across its pixel fields; read byte0, all fields equal = the
+//      code. The set of codes across the named colours IS the palette.
+//   B1 intra-byte order: fill background=black, set pixels 0..ppb-1 of row 0
+//      to distinct-code colours; byte0's fields (read MSB-first) reveal which
+//      pixel lands in which field -> MSB-first vs LSB-first, and confirms ppb.
+
+#include <cstdint>
+#include <cstdio>
+#include <vector>
+
+#include "spy/Arduino.h"
+#include "spy/SPI.h"
+#include "trace.h"
+
+template<typename Driver> struct DiagBPeek : Driver {
+  using Driver::Driver;
+  int busy_level() const { return this->_busy_level; }
+};
+
+// First plane-sized burst from the record (any content - unlike Pattern A we
+// do NOT skip uniform bursts, since a fillScreen burst is what B2 reads).
+inline std::vector<uint8_t> diagB_first_burst(uint32_t plane_bytes) {
+  std::vector<uint8_t> run, best;
+  auto flush = [&]() {
+    if (run.size() == plane_bytes && best.empty())
+      best = run;
+    run.clear();
+  };
+  for (const auto &ev : Trace::record) {
+    if (ev.first)
+      flush();
+    else
+      run.push_back(ev.second);
+  }
+  flush();
+  return best;
+}
+
+// Decode a byte's pixel fields, MSB field first (field 0 = highest bits).
+inline void diagB_fields(uint8_t byte, int bpp, int ppb, uint8_t out[]) {
+  uint8_t mask = (uint8_t) ((1u << bpp) - 1);
+  for (int j = 0; j < ppb; j++)
+    out[j] = (byte >> ((ppb - 1 - j) * bpp)) & mask;
+}
+
+template<typename WrapperT, typename DriverT> void run_diagB(const char *name, uint16_t W, uint16_t H, int bpp) {
+  const int ppb = 8 / bpp;
+  const uint32_t plane_bytes = (uint32_t) W * H * bpp / 8;  // per region/page
+  printf("=== %s: DIAGNOSTIC FILL (Pattern B: packing + codes) bpp=%d ppb=%d ===\n", name, bpp, ppb);
+
+  WrapperT display(DriverT(10, 11, 12, 13));
+  Trace::set_dc_pin(11);
+  Trace::last_busy_release_value = display.epd2.busy_level() == HIGH ? LOW : HIGH;
+  display.epd2.selectSPI(SPI, SPISettings());
+  display.init(0);
+  display.setFullWindow();
+
+  // Prime (untracked) so the first-page screen-buffer prime doesn't land in a
+  // recorded burst.
+  display.firstPage();
+  do {
+    display.fillScreen(GxEPD_WHITE);
+  } while (display.nextPage());
+
+  struct NC {
+    uint16_t color;
+    const char *label;
+  };
+  const NC colors[] = {{GxEPD_BLACK, "black"},   {GxEPD_WHITE, "white"}, {GxEPD_RED, "red"}, {GxEPD_YELLOW, "yellow"},
+                       {GxEPD_GREEN, "green"},   {GxEPD_BLUE, "blue"},   {GxEPD_ORANGE, "orange"}};
+  const int NCOL = (int) (sizeof(colors) / sizeof(colors[0]));
+  uint8_t code_of[16] = {0};
+
+  printf("  [B2] colour -> code (fillScreen, byte0 replicated field):\n");
+  for (int k = 0; k < NCOL; k++) {
+    Trace::start_record();
+    display.firstPage();
+    do {
+      display.fillScreen(colors[k].color);
+    } while (display.nextPage());
+    Trace::stop_record();
+    auto b = diagB_first_burst(plane_bytes);
+    uint8_t byte0 = b.empty() ? 0 : b[0];
+    uint8_t f[8];
+    diagB_fields(byte0, bpp, ppb, f);
+    bool uniform = true;
+    for (int j = 1; j < ppb; j++)
+      if (f[j] != f[0])
+        uniform = false;
+    code_of[k] = f[0];
+    printf("    %-7s byte0=0x%02X  code=%u%s\n", colors[k].label, byte0, f[0],
+           uniform ? "" : "  (WARN fields not uniform: bpp/ppb mismatch)");
+  }
+
+  auto code_for = [&](uint16_t col) -> int {
+    for (int k = 0; k < NCOL; k++)
+      if (colors[k].color == col)
+        return code_of[k];
+    return -1;
+  };
+
+  printf("  [B1] intra-byte order (bg=black; px 0..%d = distinct-code colours):\n", ppb - 1);
+  const uint16_t probe[4] = {GxEPD_RED, GxEPD_YELLOW, GxEPD_WHITE, GxEPD_BLACK};  // codes 3,2,1,0 on 4-colour
+  Trace::start_record();
+  display.firstPage();
+  do {
+    display.fillScreen(GxEPD_BLACK);
+    for (int i = 0; i < ppb; i++)
+      display.drawPixel(i, 0, probe[i % 4]);
+  } while (display.nextPage());
+  Trace::stop_record();
+  auto b = diagB_first_burst(plane_bytes);
+  uint8_t byte0 = b.empty() ? 0 : b[0];
+  uint8_t f[8];
+  diagB_fields(byte0, bpp, ppb, f);
+  printf("    byte0=0x%02X  fields MSB..LSB:", byte0);
+  for (int j = 0; j < ppb; j++)
+    printf(" %u", f[j]);
+  printf("\n");
+
+  bool msb_first = true, lsb_first = true;
+  for (int i = 0; i < ppb; i++) {
+    int want = code_for(probe[i % 4]);
+    if ((int) f[i] != want)
+      msb_first = false;           // pixel i in field i (MSB-first)
+    if ((int) f[ppb - 1 - i] != want)
+      lsb_first = false;           // pixel i in field (ppb-1-i)
+  }
+  printf("    => %s\n", msb_first   ? "MSB-first: pixel i in field i (shift=(ppb-1-i)*bpp)"
+                        : lsb_first ? "LSB-first: pixel i in low fields"
+                                    : "UNKNOWN packing order");
+}