Просмотр исходного кода

harness: descriptor emitter handles 2-plane shared-region panels

Extend the phase-segmenter to multi-plane panels. It detects N pixel-plane
bursts in the image call and recovers each plane's window set in either
observed layout: interleaved ([init][W0][burst0][W1][burst1]) or batched
([init][W][W][burst0][burst1], e.g. SSD1680 3-colour, which sets the black
and red RAM windows before bursting both planes). The window is the block
that repeats once per plane; the non-repeating prefix is init, so this no
longer depends on writeScreenBuffer having triggered init. Per-plane windows
that match collapse to a single shared window (window_per_plane=false);
windows that differ mark separate regions (window_per_plane=true).

Now byte-for-byte against the hand descriptors for four panels across four
controllers: 290c (JD79661 4-colour 1 plane), 426 (SSD1677 mono), 583
(UC8179 mono), and 213 Z98c (SSD1680 3-colour, 2 shared-region planes,
plane_write_cmd {0x24,0x26}).

Still out of scope: true dual-region panels (579 master/slave) whose init
and refresh carry 0xA2 region-selects per region - that needs region-model
awareness, not just plane counting. plane_width / row_interleave / bpp /
palette continue to come from the diag passes. EMIT_DEBUG=1 dumps the
image-stage command/burst structure.
ZiB 3 недель назад
Родитель
Сommit
65c7a715ac
1 измененных файлов с 157 добавлено и 33 удалено
  1. 157 33
      tools/gxepd2_harness/emit_descriptor.h

+ 157 - 33
tools/gxepd2_harness/emit_descriptor.h

@@ -23,6 +23,7 @@
 
 #include <cstdint>
 #include <cstdio>
+#include <cstdlib>
 #include <set>
 #include <vector>
 
@@ -158,43 +159,153 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
   auto image_stage = group(s1, s2);
   auto refresh_stage = group(s2, s3);
 
-  const size_t ip = plane_index(init_stage);
-  const size_t wp = plane_index(image_stage);
-
-  // Window set = the commands the image call issues before its plane burst.
-  std::vector<Cmd> window_seq(image_stage.begin(), image_stage.begin() + wp);
-  std::set<uint8_t> window_ops;
-  for (const auto &c : window_seq)
-    window_ops.insert(c.cmd);
-
-  // Init = init-stage commands before its plane burst, BUT cut at the first
-  // window-set command: SSD16xx-style drivers set the RAM window inside
-  // writeScreenBuffer too, and that belongs in window_sequence, not init.
-  size_t init_end = ip;
-  for (size_t i = 0; i < ip; i++)
-    if (window_ops.count(init_stage[i].cmd)) {
-      init_end = i;
-      break;
+  auto cmds_equal = [](const std::vector<Cmd> &a, const std::vector<Cmd> &b) {
+    if (a.size() != b.size())
+      return false;
+    for (size_t i = 0; i < a.size(); i++)
+      if (a[i].cmd != b[i].cmd || a[i].data != b[i].data)
+        return false;
+    return true;
+  };
+
+  // Every pixel-plane burst in the image call. One => single-plane panel;
+  // N => N planes (two colour planes sharing a region, or two physical
+  // regions - told apart below by whether their window-sets match).
+  std::vector<size_t> bursts;
+  for (size_t i = 0; i < image_stage.size(); i++)
+    if (image_stage[i].data.size() >= PLANE_BURST_MIN)
+      bursts.push_back(i);
+  const size_t nplanes = bursts.empty() ? 1 : bursts.size();
+
+  if (getenv("EMIT_DEBUG")) {
+    fprintf(stderr, "image_stage %zu cmds; bursts:", image_stage.size());
+    for (auto b : bursts)
+      fprintf(stderr, " [%zu]=0x%02X", b, image_stage[b].cmd);
+    fprintf(stderr, "\n");
+    for (size_t i = 0; i < image_stage.size(); i++)
+      fprintf(stderr, "  [%zu] 0x%02X data=%zu\n", i, image_stage[i].cmd, image_stage[i].data.size());
+  }
+
+  // pre_block(k) = commands after burst k-1 (or the start) up to, but not
+  // including, burst k's own plane-write command - i.e. the window set for
+  // plane k, possibly with the lazily-run _InitDisplay in front for k==0.
+  auto pre_block = [&](size_t k) {
+    size_t lo = (k == 0) ? 0 : bursts[k - 1] + 1;
+    return std::vector<Cmd>(image_stage.begin() + lo, image_stage.begin() + bursts[k]);
+  };
+
+  std::vector<Cmd> init_seq, window0, window1;
+  std::vector<uint8_t> plane_cmds;
+  bool per_plane = false;
+
+  if (bursts.size() >= 2) {
+    // Multi-plane. Two observed layouts:
+    //   interleaved: [init][W0][burst0][W1][burst1]  - each plane's window
+    //                sits between its burst and the previous one (dual region);
+    //   batched:     [init][W][W][burst0][burst1]    - all windows precede all
+    //                bursts (SSD1680 3-colour: writeImage sets the black and
+    //                red RAM windows, then bursts both planes).
+    for (size_t k = 0; k < bursts.size(); k++)
+      plane_cmds.push_back(image_stage[bursts[k]].cmd);
+    const size_t n = bursts.size();
+    std::vector<Cmd> pre = pre_block(0);
+    std::vector<std::vector<Cmd>> windows;
+
+    bool interleaved = false;
+    for (size_t k = 1; k < n; k++)
+      if (bursts[k] - bursts[k - 1] > 1) {
+        interleaved = true;
+        break;
+      }
+
+    if (interleaved) {
+      std::vector<Cmd> gap0(image_stage.begin() + bursts[0] + 1, image_stage.begin() + bursts[1]);
+      const size_t wl = gap0.size();
+      if (wl <= pre.size()) {
+        windows.push_back(std::vector<Cmd>(pre.end() - wl, pre.end()));
+        init_seq.assign(pre.begin(), pre.end() - wl);
+      } else {
+        windows.push_back({});
+        init_seq = pre;
+      }
+      for (size_t k = 1; k < n; k++)
+        windows.push_back(std::vector<Cmd>(image_stage.begin() + bursts[k - 1] + 1, image_stage.begin() + bursts[k]));
+    } else {
+      // Batched: pre = init + n copies of the window. Peel the smallest block
+      // that repeats n times at the tail; the remainder is init.
+      size_t L = 0;
+      for (size_t cand = 1; cand * n <= pre.size(); cand++) {
+        bool ok = true;
+        for (size_t b = 0; b < n && ok; b++)
+          for (size_t j = 0; j < cand && ok; j++) {
+            const Cmd &a = pre[pre.size() - cand * n + b * cand + j];
+            const Cmd &c = pre[pre.size() - cand + j];
+            if (a.cmd != c.cmd || a.data != c.data)
+              ok = false;
+          }
+        if (ok) {
+          L = cand;
+          break;
+        }
+      }
+      if (L > 0) {
+        for (size_t b = 0; b < n; b++)
+          windows.push_back(std::vector<Cmd>(pre.end() - L * (n - b), pre.end() - L * (n - b - 1)));
+        init_seq.assign(pre.begin(), pre.end() - L * n);
+      } else {
+        windows.push_back({});
+        init_seq = pre;
+      }
     }
-  std::vector<Cmd> init_seq(init_stage.begin(), init_stage.begin() + init_end);
 
-  const uint8_t plane_cmd = ip < init_stage.size() ? init_stage[ip].cmd : 0x00;
-  const uint8_t clear_fill = (ip < init_stage.size() && !init_stage[ip].data.empty()) ? init_stage[ip].data[0] : 0x00;
+    window0 = windows.empty() ? std::vector<Cmd>{} : windows[0];
+    window1 = windows.size() > 1 ? windows[1] : std::vector<Cmd>{};
+    for (size_t k = 1; k < windows.size(); k++)
+      if (!cmds_equal(windows[0], windows[k]))
+        per_plane = true;  // per-plane windows differ => separate regions
+  } else {
+    // Single plane: window = the image call's pre-burst block; init comes
+    // from the writeScreenBuffer stage, cut at the first window-set command
+    // (SSD16xx sets the window there too - that belongs in window_sequence).
+    const size_t wp = plane_index(image_stage);
+    window0.assign(image_stage.begin(), image_stage.begin() + wp);
+    std::set<uint8_t> wops;
+    for (const auto &c : window0)
+      wops.insert(c.cmd);
+    const size_t ip = plane_index(init_stage);
+    size_t cut = ip;
+    for (size_t i = 0; i < ip; i++)
+      if (wops.count(init_stage[i].cmd)) {
+        cut = i;
+        break;
+      }
+    init_seq.assign(init_stage.begin(), init_stage.begin() + cut);
+    plane_cmds.push_back(wp < image_stage.size() ? image_stage[wp].cmd : 0x00);
+  }
 
-  // Refresh = post-write commands (issued after the image's plane burst but
-  // before any next burst, e.g. UC8179's 0x92 "partial out" - they fire after
-  // the byte burst and before the trigger, so they lead refresh), then the
-  // refresh call itself minus any window-set it repeats.
+  // blank/clear fill byte, observed from the writeScreenBuffer clear burst.
+  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;
+
+  // 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;
+  for (const auto &c : window0)
+    all_wops.insert(c.cmd);
+  for (const auto &c : window1)
+    all_wops.insert(c.cmd);
   std::vector<Cmd> refresh_seq;
-  for (size_t i = wp + 1; i < image_stage.size() && image_stage[i].data.size() < PLANE_BURST_MIN; i++)
-    refresh_seq.push_back(image_stage[i]);
+  if (!bursts.empty())
+    for (size_t i = bursts.back() + 1; i < image_stage.size() && image_stage[i].data.size() < PLANE_BURST_MIN; i++)
+      refresh_seq.push_back(image_stage[i]);
   for (const auto &c : refresh_stage)
-    if (!window_ops.count(c.cmd))
+    if (!all_wops.count(c.cmd))
       refresh_seq.push_back(c);
 
   // --- emit ---
   printf("// GENERATED by emit_descriptor from %s - candidate, segmentation only.\n", class_name);
-  printf("// bits_per_pixel/palette/plane_invert still come from the diag passes.\n");
+  printf("// bits_per_pixel/palette/plane_invert/plane_width/row_interleave still\n");
+  printf("// come from the diag passes.\n");
   printf("#pragma once\n#include \"epd_command.h\"\n\nnamespace esphome::gxepd2 {\n\n");
 
   auto emit_seq = [&](const char *seqsuffix, std::vector<Cmd> &cmds) {
@@ -214,7 +325,9 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
   };
 
   emit_seq("INIT", init_seq);
-  emit_seq("WINDOW_SEQ", window_seq);
+  emit_seq("WINDOW_SEQ0", window0);
+  if (per_plane)
+    emit_seq("WINDOW_SEQ1", window1);
   emit_seq("REFRESH", refresh_seq);
 
   printf("inline const EpdPanelDescriptor %s = {\n", macro);
@@ -224,14 +337,25 @@ template<typename Driver> void emit_descriptor(const char *class_name, const cha
   printf("    .busy_timeout_ms = %u,\n", (unsigned) Driver::full_refresh_time + 5000u);
   printf("    .init_sequence = %s_INIT,\n", macro);
   printf("    .init_sequence_len = sizeof(%s_INIT) / sizeof(%s_INIT[0]),\n", macro, macro);
-  printf("    .window_sequence = {%s_WINDOW_SEQ, nullptr},\n", macro);
-  printf("    .window_sequence_len = {sizeof(%s_WINDOW_SEQ) / sizeof(%s_WINDOW_SEQ[0]), 0},\n", macro, macro);
-  printf("    .plane_count = 1,\n");
-  printf("    .plane_write_cmd = {0x%02X, 0x00},\n", plane_cmd);
+  if (per_plane) {
+    printf("    .window_sequence = {%s_WINDOW_SEQ0, %s_WINDOW_SEQ1},\n", macro, macro);
+    printf("    .window_sequence_len = {sizeof(%s_WINDOW_SEQ0) / sizeof(%s_WINDOW_SEQ0[0]), "
+           "sizeof(%s_WINDOW_SEQ1) / sizeof(%s_WINDOW_SEQ1[0])},\n",
+           macro, macro, macro, macro);
+    printf("    .window_per_plane = true,  // per-plane windows differ => separate regions\n");
+  } else {
+    printf("    .window_sequence = {%s_WINDOW_SEQ0, nullptr},\n", macro);
+    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");
+  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);
   printf("    .refresh_sequence = %s_REFRESH,\n", macro);
   printf("    .refresh_sequence_len = sizeof(%s_REFRESH) / sizeof(%s_REFRESH[0]),\n", macro, macro);