run_diag_harness.h 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. #pragma once
  2. // Diagnostic-fill harness, Pattern A (byte/row ordering) - see the
  3. // gxepd2-diag-fill-spec memory. Instead of the uniform 0xA5 fill, it fills
  4. // the 1bpp plane buffer with a positional TAG, records the exact wire-byte
  5. // stream (trace.h recording), and inverts the tag to recover the driver's
  6. // buffer->wire row permutation (identity / reversed / line_order /
  7. // unknown). Host-only, no hardware.
  8. //
  9. // Scope: 1bpp byte-preserving writeImage path (mono, and the per-plane
  10. // bursts of a BW+accent panel). Packed panels that re-pack 1bpp planes into
  11. // 2bpp/4bpp codes in writeImage do NOT preserve byte identity, so Pattern A
  12. // there needs the native (writeNative) path or address-sequence analysis -
  13. // a documented follow-on, see the spec memory.
  14. //
  15. // Two fills:
  16. // ROW_TAG: each row r -> byte[0]=(r>>8)&0xFF, byte[1]=r&0xFF, rest 0.
  17. // Group the plane burst into rows of bpr; [hi,lo] of each group
  18. // recovers that wire-row's source row -> the permutation.
  19. // COL_TAG: byte[c]=c&0xFF across every row -> each wire row should read
  20. // 0,1,2,... confirming within-row order is identity (catches a
  21. // byte-stride/offset bug - the shape of the 583 smear).
  22. #include <cstdint>
  23. #include <cstdio>
  24. #include <cstring>
  25. #include <vector>
  26. #include "spy/Arduino.h"
  27. #include "spy/SPI.h"
  28. #include "trace.h"
  29. template<typename Driver> struct DiagPeek : Driver {
  30. using Driver::Driver;
  31. int busy_level() const { return this->_busy_level; }
  32. };
  33. // Pull the plane-sized data bursts out of the recorded (is_cmd,byte) stream:
  34. // maximal runs of consecutive data bytes whose length == plane_bytes.
  35. inline std::vector<std::vector<uint8_t>> extract_plane_bursts(uint32_t plane_bytes) {
  36. std::vector<std::vector<uint8_t>> bursts;
  37. std::vector<uint8_t> run;
  38. auto flush = [&]() {
  39. if (run.size() == plane_bytes) {
  40. // Skip all-uniform bursts (e.g. a writeScreenBuffer prime) - they
  41. // carry no tag, only the tagged writeImage burst is of interest.
  42. bool uniform = true;
  43. for (uint8_t v : run)
  44. if (v != run[0]) {
  45. uniform = false;
  46. break;
  47. }
  48. if (!uniform)
  49. bursts.push_back(run);
  50. }
  51. run.clear();
  52. };
  53. for (const auto &ev : Trace::record) {
  54. if (ev.first) { // command byte -> ends any data run
  55. flush();
  56. } else {
  57. run.push_back(ev.second);
  58. }
  59. }
  60. flush();
  61. return bursts;
  62. }
  63. // true if most data bytes are 0xFF rather than 0x00 - the driver inverted
  64. // the plane (our tag is mostly 0x00 bytes), so decode with ^0xFF.
  65. inline bool burst_is_inverted(const std::vector<uint8_t> &b) {
  66. uint32_t zeros = 0, ones = 0;
  67. for (uint8_t v : b) {
  68. if (v == 0x00)
  69. zeros++;
  70. else if (v == 0xFF)
  71. ones++;
  72. }
  73. return ones > zeros;
  74. }
  75. template<typename Driver> void analyze_row_tag(const char *name) {
  76. const uint16_t W = Driver::WIDTH, H = Driver::HEIGHT;
  77. const uint32_t plane_bytes = (uint32_t) W * H / 8;
  78. const uint32_t bpr = (uint32_t) W / 8;
  79. if (bpr < 2) {
  80. printf(" [row-tag] skipped: bpr<2 (needs 2-byte row tag)\n");
  81. return;
  82. }
  83. auto *black = new uint8_t[plane_bytes]();
  84. auto *color = new uint8_t[plane_bytes]();
  85. for (uint16_t r = 0; r < H; r++) {
  86. black[r * bpr + 0] = color[r * bpr + 0] = (r >> 8) & 0xFF;
  87. black[r * bpr + 1] = color[r * bpr + 1] = r & 0xFF;
  88. }
  89. DiagPeek<Driver> d(10, 11, 12, 13);
  90. Trace::set_dc_pin(11);
  91. Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
  92. d.selectSPI(SPI, SPISettings());
  93. d.init(0);
  94. d.writeImage(black, color, 0, 0, W, H); // prime: clears _initial_write (its screen-buffer prime is untracked)
  95. Trace::start_record();
  96. d.writeImage(black, color, 0, 0, W, H); // tagged write, now with no priming burst
  97. Trace::stop_record();
  98. delete[] black;
  99. delete[] color;
  100. auto bursts = extract_plane_bursts(plane_bytes);
  101. printf(" [row-tag] %s %ux%u: bpr=%u, plane_bytes=%u, plane bursts found=%zu\n", name, W, H, (unsigned) bpr,
  102. (unsigned) plane_bytes, bursts.size());
  103. for (size_t p = 0; p < bursts.size(); p++) {
  104. const auto &data = bursts[p];
  105. bool inv = burst_is_inverted(data);
  106. uint8_t x = inv ? 0xFF : 0x00;
  107. std::vector<uint32_t> recovered(H);
  108. for (uint16_t g = 0; g < H; g++)
  109. recovered[g] = (uint32_t) ((data[g * bpr] ^ x) << 8) | (data[g * bpr + 1] ^ x);
  110. bool identity = true, reversed = true, interleave = true;
  111. for (uint16_t g = 0; g < H; g++) {
  112. if (recovered[g] != g)
  113. identity = false;
  114. if (recovered[g] != (uint32_t) (H - 1 - g))
  115. reversed = false;
  116. uint32_t expect = (g % 2 == 0) ? (g / 2) : (H - 1 - (g / 2));
  117. if (recovered[g] != expect)
  118. interleave = false;
  119. }
  120. const char *verdict = identity ? "SEQUENTIAL (identity)"
  121. : reversed ? "REVERSED rows"
  122. : interleave ? "ROW_INTERLEAVE (top/bottom pairs)"
  123. : "UNKNOWN permutation";
  124. printf(" plane %zu: invert=%s -> %s\n", p, inv ? "yes" : "no", verdict);
  125. printf(" first rows:");
  126. for (uint16_t g = 0; g < 6 && g < H; g++)
  127. printf(" %u", recovered[g]);
  128. printf(" | last rows:");
  129. for (uint16_t g = (H > 6 ? H - 6 : 0); g < H; g++)
  130. printf(" %u", recovered[g]);
  131. printf("\n");
  132. }
  133. }
  134. template<typename Driver> void analyze_col_tag(const char *name) {
  135. const uint16_t W = Driver::WIDTH, H = Driver::HEIGHT;
  136. const uint32_t plane_bytes = (uint32_t) W * H / 8;
  137. const uint32_t bpr = (uint32_t) W / 8;
  138. auto *black = new uint8_t[plane_bytes]();
  139. auto *color = new uint8_t[plane_bytes]();
  140. for (uint16_t r = 0; r < H; r++)
  141. for (uint32_t c = 0; c < bpr; c++)
  142. black[r * bpr + c] = color[r * bpr + c] = c & 0xFF;
  143. DiagPeek<Driver> d(10, 11, 12, 13);
  144. Trace::set_dc_pin(11);
  145. Trace::last_busy_release_value = d.busy_level() == HIGH ? LOW : HIGH;
  146. d.selectSPI(SPI, SPISettings());
  147. d.init(0);
  148. d.writeImage(black, color, 0, 0, W, H); // prime (untracked)
  149. Trace::start_record();
  150. d.writeImage(black, color, 0, 0, W, H); // tagged write
  151. Trace::stop_record();
  152. delete[] black;
  153. delete[] color;
  154. auto bursts = extract_plane_bursts(plane_bytes);
  155. for (size_t p = 0; p < bursts.size(); p++) {
  156. const auto &data = bursts[p];
  157. bool inv = burst_is_inverted(data);
  158. uint8_t x = inv ? 0xFF : 0x00;
  159. // Within-row order is identity iff every row reads 0,1,2,...,bpr-1 (mod 256).
  160. bool ok = true;
  161. uint16_t bad_row = 0, bad_col = 0;
  162. for (uint16_t g = 0; g < H && ok; g++)
  163. for (uint32_t c = 0; c < bpr; c++)
  164. if ((uint8_t) (data[g * bpr + c] ^ x) != (uint8_t) (c & 0xFF)) {
  165. ok = false;
  166. bad_row = g;
  167. bad_col = c;
  168. break;
  169. }
  170. if (ok)
  171. printf(" plane %zu: within-row order OK (0..bpr-1 each row)\n", p);
  172. else
  173. printf(" plane %zu: within-row MISMATCH at row %u col %u\n", p, bad_row, bad_col);
  174. }
  175. }
  176. template<typename Driver> void run_diag_harness(const char *name) {
  177. printf("=== %s: DIAGNOSTIC FILL (Pattern A: row/byte ordering) ===\n", name);
  178. analyze_row_tag<Driver>(name);
  179. analyze_col_tag<Driver>(name);
  180. }