emit_palette_4c.h 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. #pragma once
  2. // 4-colour (BWRY, 2bpp packed) palette/blank-code resolver for the descriptor
  3. // emitter. This is the diag Pattern-B colour->code derivation (run_diagB_
  4. // harness.h) folded into the one-command capture, so a 4-colour single-region
  5. // panel comes out of tools/capture.py complete - bits_per_pixel / palette /
  6. // blank_code filled - instead of carrying TODOs a human pastes in afterwards.
  7. //
  8. // It stays observation-based, not a hardcode: it drives the vendor driver's
  9. // own GxEPD2_4C wrapper (fillScreen of each named colour) and reads the wire
  10. // code the driver emits for each, then CONFIRMS that table matches the engine's
  11. // GXEPD2_PALETTE_4C codes (epd_command.h) before referencing it. A panel whose
  12. // codes don't match - a non-standard 4-colour map, or something that isn't
  13. // really 2bpp - stays unresolved and emits the old TODO plus the observed
  14. // table, so a surprise is visible, never silently mislabelled.
  15. //
  16. // Include order (tools/capture.py's generated 4C main handles this): the driver
  17. // header and GxEPD2_4C.h first (they define GxEPD_* and the WIDTH/HEIGHT the
  18. // wrapper references unqualified), then emit_descriptor.h, then this file.
  19. #include <cstdint>
  20. #include <cstdio>
  21. #include <string>
  22. #include <vector>
  23. #include "GxEPD2_4C.h"
  24. #include "emit_descriptor.h"
  25. #include "trace.h"
  26. namespace emit {
  27. // The engine's canonical 4-colour map (mirrors epd_command.h GXEPD2_PALETTE_4C
  28. // / GxEPD2_4C.h color4()): the wire code each named colour must resolve to for
  29. // the standard palette to be the right answer.
  30. struct Expect4C {
  31. uint16_t color;
  32. uint8_t code;
  33. const char *label;
  34. };
  35. inline const Expect4C EXPECT_4C[] = {
  36. {GxEPD_BLACK, 0x00, "black"}, {GxEPD_WHITE, 0x01, "white"}, {GxEPD_YELLOW, 0x02, "yellow"},
  37. {GxEPD_GREEN, 0x02, "green"}, {GxEPD_ORANGE, 0x02, "orange"}, {GxEPD_RED, 0x03, "red"},
  38. {GxEPD_BLUE, 0x00, "blue"},
  39. };
  40. // First run of exactly plane_bytes data bytes in the current Trace::record
  41. // (one fillScreen flush), or empty. Mirrors run_diagB_harness.h.
  42. inline std::vector<uint8_t> first_plane_burst(uint32_t plane_bytes) {
  43. std::vector<uint8_t> run, best;
  44. auto flush = [&]() {
  45. if (run.size() == plane_bytes && best.empty())
  46. best = run;
  47. run.clear();
  48. };
  49. for (const auto &ev : Trace::record) {
  50. if (ev.first) // command byte: ends the current data run
  51. flush();
  52. else
  53. run.push_back(ev.second);
  54. }
  55. flush();
  56. return best;
  57. }
  58. // Build the 4-colour packed resolver for `Driver`. It instantiates the vendor
  59. // GxEPD2_4C<Peek<Driver>, HEIGHT> wrapper (single full-height page) and reads
  60. // each colour's code off the wire.
  61. template<typename Driver> PackedResolverFn make_resolver_4c() {
  62. return [](uint8_t /*clear_fill*/, const PlaneInvert & /*pv*/) -> PackedResolved {
  63. constexpr int bpp = 2, ppb = 4; // 4-colour: 2 bits/pixel, 4 pixels/byte, MSB-first
  64. const uint16_t W = Driver::WIDTH, H = Driver::HEIGHT;
  65. const uint32_t plane_bytes = (uint32_t) W * H * bpp / 8;
  66. using Wrapper = GxEPD2_4C<Peek<Driver>, Driver::HEIGHT>; // page_height=HEIGHT => one page
  67. Wrapper *dp = new Wrapper(Peek<Driver>(10, 11, 12, 13));
  68. Wrapper &display = *dp;
  69. Trace::set_dc_pin(11);
  70. Trace::last_busy_release_value = display.epd2.busy_level() == HIGH ? LOW : HIGH;
  71. display.epd2.selectSPI(SPI, SPISettings());
  72. display.init(0);
  73. display.setFullWindow();
  74. // Prime (untracked) so the first-page buffer prime isn't read as a fill.
  75. display.firstPage();
  76. do {
  77. display.fillScreen(GxEPD_WHITE);
  78. } while (display.nextPage());
  79. auto code_of = [&](uint16_t color, bool &uniform) -> int {
  80. Trace::start_record();
  81. display.firstPage();
  82. do {
  83. display.fillScreen(color);
  84. } while (display.nextPage());
  85. Trace::stop_record();
  86. auto b = first_plane_burst(plane_bytes);
  87. if (b.empty())
  88. return -1;
  89. uint8_t byte0 = b[0];
  90. uint8_t f0 = (byte0 >> ((ppb - 1) * bpp)) & 0x3; // MSB field
  91. uniform = true;
  92. for (int j = 1; j < ppb; j++)
  93. if (((byte0 >> ((ppb - 1 - j) * bpp)) & 0x3) != f0)
  94. uniform = false;
  95. return f0;
  96. };
  97. const int ncol = (int) (sizeof(EXPECT_4C) / sizeof(EXPECT_4C[0]));
  98. bool matches = true;
  99. int white_code = -1;
  100. std::string observed;
  101. for (int k = 0; k < ncol; k++) {
  102. bool uniform = false;
  103. int code = code_of(EXPECT_4C[k].color, uniform);
  104. char buf[48];
  105. snprintf(buf, sizeof(buf), "%s%s=%d", k ? " " : "", EXPECT_4C[k].label, code);
  106. observed += buf;
  107. if (EXPECT_4C[k].color == GxEPD_WHITE)
  108. white_code = code;
  109. if (!uniform || code != (int) EXPECT_4C[k].code)
  110. matches = false;
  111. }
  112. delete dp;
  113. PackedResolved r;
  114. if (matches) {
  115. r.resolved = true;
  116. r.bits_per_pixel = bpp;
  117. r.palette_expr = "GXEPD2_PALETTE_4C";
  118. r.palette_len_expr = "GXEPD2_PALETTE_4C_LEN";
  119. r.blank_code = white_code; // panel's white/clear code (0x01 for GxEPD2 4C)
  120. } else {
  121. r.resolved = false;
  122. r.note = "4C probe mismatch, observed [" + observed + "] (expected black=0 white=1 yellow/green/orange=2 red=3 blue=0)";
  123. }
  124. return r;
  125. };
  126. }
  127. // 4-colour capture entry: like emit_descriptor(), but resolves the packed
  128. // fields by observing the GxEPD2_4C wrapper. tools/capture.py calls this for an
  129. // epd4c/ driver.
  130. template<typename Driver> void emit_descriptor_4c(const char *class_name, const char *macro) {
  131. emit_descriptor_impl<Driver>(class_name, macro, make_resolver_4c<Driver>());
  132. }
  133. } // namespace emit