run_diagB_harness.h 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. #pragma once
  2. // Diagnostic-fill harness, Pattern B (pixel packing + codes) - see the
  3. // gxepd2-diag-fill-spec memory. Packing geometry and the colour->code table
  4. // live in the vendor wrapper's draw path (color4()/color7() and _pixel_buffer
  5. // are private), so we derive them by observation: drive the wrapper's
  6. // fillScreen/drawPixel and read the emitted wire bytes (writeNative output).
  7. // Host-only, no hardware.
  8. //
  9. // B2 colour->code: fillScreen(colour) -> every byte is that colour's code
  10. // replicated across its pixel fields; read byte0, all fields equal = the
  11. // code. The set of codes across the named colours IS the palette.
  12. // B1 intra-byte order: fill background=black, set pixels 0..ppb-1 of row 0
  13. // to distinct-code colours; byte0's fields (read MSB-first) reveal which
  14. // pixel lands in which field -> MSB-first vs LSB-first, and confirms ppb.
  15. #include <cstdint>
  16. #include <cstdio>
  17. #include <vector>
  18. #include "spy/Arduino.h"
  19. #include "spy/SPI.h"
  20. #include "trace.h"
  21. template<typename Driver> struct DiagBPeek : Driver {
  22. using Driver::Driver;
  23. int busy_level() const { return this->_busy_level; }
  24. };
  25. // First plane-sized burst from the record (any content - unlike Pattern A we
  26. // do NOT skip uniform bursts, since a fillScreen burst is what B2 reads).
  27. inline std::vector<uint8_t> diagB_first_burst(uint32_t plane_bytes) {
  28. std::vector<uint8_t> run, best;
  29. auto flush = [&]() {
  30. if (run.size() == plane_bytes && best.empty())
  31. best = run;
  32. run.clear();
  33. };
  34. for (const auto &ev : Trace::record) {
  35. if (ev.first)
  36. flush();
  37. else
  38. run.push_back(ev.second);
  39. }
  40. flush();
  41. return best;
  42. }
  43. // Decode a byte's pixel fields, MSB field first (field 0 = highest bits).
  44. inline void diagB_fields(uint8_t byte, int bpp, int ppb, uint8_t out[]) {
  45. uint8_t mask = (uint8_t) ((1u << bpp) - 1);
  46. for (int j = 0; j < ppb; j++)
  47. out[j] = (byte >> ((ppb - 1 - j) * bpp)) & mask;
  48. }
  49. template<typename WrapperT, typename DriverT> void run_diagB(const char *name, uint16_t W, uint16_t H, int bpp) {
  50. const int ppb = 8 / bpp;
  51. const uint32_t plane_bytes = (uint32_t) W * H * bpp / 8; // per region/page
  52. printf("=== %s: DIAGNOSTIC FILL (Pattern B: packing + codes) bpp=%d ppb=%d ===\n", name, bpp, ppb);
  53. // Heap-allocate: the wrapper embeds a full-page _pixel_buffer, which for a
  54. // 7-colour panel (4bpp, e.g. 600x448 -> ~134KB) is too big for the stack.
  55. WrapperT *dp = new WrapperT(DriverT(10, 11, 12, 13));
  56. WrapperT &display = *dp;
  57. Trace::set_dc_pin(11);
  58. Trace::last_busy_release_value = display.epd2.busy_level() == HIGH ? LOW : HIGH;
  59. display.epd2.selectSPI(SPI, SPISettings());
  60. display.init(0);
  61. display.setFullWindow();
  62. // Prime (untracked) so the first-page screen-buffer prime doesn't land in a
  63. // recorded burst.
  64. display.firstPage();
  65. do {
  66. display.fillScreen(GxEPD_WHITE);
  67. } while (display.nextPage());
  68. struct NC {
  69. uint16_t color;
  70. const char *label;
  71. };
  72. const NC colors[] = {{GxEPD_BLACK, "black"}, {GxEPD_WHITE, "white"}, {GxEPD_RED, "red"}, {GxEPD_YELLOW, "yellow"},
  73. {GxEPD_GREEN, "green"}, {GxEPD_BLUE, "blue"}, {GxEPD_ORANGE, "orange"}};
  74. const int NCOL = (int) (sizeof(colors) / sizeof(colors[0]));
  75. uint8_t code_of[16] = {0};
  76. printf(" [B2] colour -> code (fillScreen, byte0 replicated field):\n");
  77. for (int k = 0; k < NCOL; k++) {
  78. Trace::start_record();
  79. display.firstPage();
  80. do {
  81. display.fillScreen(colors[k].color);
  82. } while (display.nextPage());
  83. Trace::stop_record();
  84. auto b = diagB_first_burst(plane_bytes);
  85. uint8_t byte0 = b.empty() ? 0 : b[0];
  86. uint8_t f[8];
  87. diagB_fields(byte0, bpp, ppb, f);
  88. bool uniform = true;
  89. for (int j = 1; j < ppb; j++)
  90. if (f[j] != f[0])
  91. uniform = false;
  92. code_of[k] = f[0];
  93. printf(" %-7s byte0=0x%02X code=%u%s\n", colors[k].label, byte0, f[0],
  94. uniform ? "" : " (WARN fields not uniform: bpp/ppb mismatch)");
  95. }
  96. auto code_for = [&](uint16_t col) -> int {
  97. for (int k = 0; k < NCOL; k++)
  98. if (colors[k].color == col)
  99. return code_of[k];
  100. return -1;
  101. };
  102. // [B1] packing order across bytes. Place one pixel per DISTINCT code along
  103. // row 0 (so every code the panel has is exercised - 4 on a 4-colour panel,
  104. // all 7 on ACeP), spanning ceil(ndist*bpp/8) bytes. Verify each pixel i
  105. // lands in byte i/ppb, field i%ppb (MSB-first), which also confirms the
  106. // byte-to-byte stride, not just intra-byte order.
  107. struct PC {
  108. uint16_t color;
  109. uint8_t code;
  110. const char *label;
  111. };
  112. PC distinct[16];
  113. int nd = 0;
  114. for (int k = 0; k < NCOL; k++) {
  115. bool seen = false;
  116. for (int j = 0; j < nd; j++)
  117. if (distinct[j].code == code_of[k])
  118. seen = true;
  119. if (!seen)
  120. distinct[nd++] = {colors[k].color, code_of[k], colors[k].label};
  121. }
  122. (void) code_for;
  123. int nbytes = (nd * bpp + 7) / 8;
  124. printf(" [B1] packing order: %d distinct-code pixels across %d byte(s):\n", nd, nbytes);
  125. Trace::start_record();
  126. display.firstPage();
  127. do {
  128. display.fillScreen(GxEPD_BLACK);
  129. for (int i = 0; i < nd; i++)
  130. display.drawPixel(i, 0, distinct[i].color);
  131. } while (display.nextPage());
  132. Trace::stop_record();
  133. auto b = diagB_first_burst(plane_bytes);
  134. bool msb_first = true, lsb_first = true;
  135. for (int i = 0; i < nd; i++) {
  136. int bi = i / ppb, fi = i % ppb;
  137. uint8_t f[8];
  138. diagB_fields(bi < (int) b.size() ? b[bi] : 0, bpp, ppb, f);
  139. if ((int) f[fi] != distinct[i].code)
  140. msb_first = false;
  141. if ((int) f[ppb - 1 - fi] != distinct[i].code)
  142. lsb_first = false;
  143. }
  144. printf(" bytes:");
  145. for (int t = 0; t < nbytes && t < (int) b.size(); t++)
  146. printf(" 0x%02X", b[t]);
  147. printf("\n pixels (label=code):");
  148. for (int i = 0; i < nd; i++)
  149. printf(" %s=%u", distinct[i].label, distinct[i].code);
  150. printf("\n => %s\n", msb_first ? "MSB-first: pixel i -> byte i/ppb, field i%ppb (shift=(ppb-1-f)*bpp)"
  151. : lsb_first ? "LSB-first within byte"
  152. : "UNKNOWN packing order");
  153. delete dp;
  154. }