epd_command.h 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. #pragma once
  2. #include <cstdint>
  3. namespace esphome::gxepd2 {
  4. // One command byte plus its (fixed, compile-time) data bytes, as sent by
  5. // GxEPD2_EPD::_writeCommand()/_writeData(). delay_before_ms/delay_after_ms
  6. // and wait_busy_after mirror the fixed delay()s and _waitWhileBusy() calls
  7. // bracketing specific commands in the vendor source (which command gets
  8. // which varies per panel - e.g. GxEPD2_213_Z98c delays 10ms on both sides
  9. // of its SWRESET, while GxEPD2_154_Z90c busy-waits after its SWRESET
  10. // instead). At most one of delay_after_ms/wait_busy_after is expected to be
  11. // set on any one command.
  12. struct EpdCommand {
  13. uint8_t cmd;
  14. const uint8_t *data; // nullptr if the command takes no data bytes
  15. uint8_t data_len;
  16. uint16_t delay_before_ms{0};
  17. uint16_t delay_after_ms{0};
  18. bool wait_busy_after{false};
  19. };
  20. // One palette entry for a packed (bits_per_pixel > 1) panel: a reference
  21. // RGB color (ESPHome Color space, 0-255 per channel) and the wire code the
  22. // controller uses for it. Classification at draw time picks the entry
  23. // nearest the drawn color, with exact matches winning - so named colors map
  24. // deterministically, the way GxEPD2's own color4()/color7() switch does.
  25. // Several entries may share a code (GxEPD2 maps green and orange both onto
  26. // the 4-color yellow code, and blue onto black).
  27. struct EpdPaletteEntry {
  28. uint8_t r, g, b;
  29. uint8_t code;
  30. };
  31. // Standard GxEPD2 4-color (BWRY) palette - codes per GxEPD2_4C.h color4():
  32. // black=0x00, white=0x01, yellow/green/orange=0x02, red=0x03, blue->black.
  33. inline const EpdPaletteEntry GXEPD2_PALETTE_4C[] = {
  34. {0x00, 0x00, 0x00, 0x00}, // black
  35. {0xFF, 0xFF, 0xFF, 0x01}, // white
  36. {0xFF, 0xFF, 0x00, 0x02}, // yellow
  37. {0x00, 0xFF, 0x00, 0x02}, // green -> yellow code
  38. {0xFF, 0xA4, 0x00, 0x02}, // orange -> yellow code
  39. {0xFF, 0x00, 0x00, 0x03}, // red
  40. {0x00, 0x00, 0xFF, 0x00}, // blue -> black code
  41. };
  42. inline const uint8_t GXEPD2_PALETTE_4C_LEN = sizeof(GXEPD2_PALETTE_4C) / sizeof(GXEPD2_PALETTE_4C[0]);
  43. // Standard ACeP 7-color palette - codes per GxEPD2_7C.h color7(): black=0x00,
  44. // white=0x01, green=0x02, blue=0x03, red=0x04, yellow=0x05, orange=0x06.
  45. // Ready for the first 4bpp panel (GxEPD2_565c/730c); no shipped descriptor
  46. // consumes it yet - see the gxepd2 panel work.
  47. inline const EpdPaletteEntry GXEPD2_PALETTE_7C[] = {
  48. {0x00, 0x00, 0x00, 0x00}, // black
  49. {0xFF, 0xFF, 0xFF, 0x01}, // white
  50. {0x00, 0xFF, 0x00, 0x02}, // green
  51. {0x00, 0x00, 0xFF, 0x03}, // blue
  52. {0xFF, 0x00, 0x00, 0x04}, // red
  53. {0xFF, 0xFF, 0x00, 0x05}, // yellow
  54. {0xFF, 0xA4, 0x00, 0x06}, // orange
  55. };
  56. inline const uint8_t GXEPD2_PALETTE_7C_LEN = sizeof(GXEPD2_PALETTE_7C) / sizeof(GXEPD2_PALETTE_7C[0]);
  57. // Everything CycleEngine needs to drive one panel, hand-derived from a
  58. // GxEPD2 vendor driver's .cpp/.h (see panel_154_z90c.h). Only what this
  59. // first slice's engine actually uses - the RAM-window-set command layout
  60. // (0x44/0x45/0x4E/0x4F) is common to every panel inspected so far and is
  61. // hardcoded in CycleEngine itself rather than made part of this struct;
  62. // revisit if/when the extraction-tooling phase finds a controller family
  63. // that lays it out differently.
  64. // Order in which the engine emits a plane's wire lines, derived by the diag
  65. // native-path tool (CONTRIBUTING.md). The buffer always holds `height` rows
  66. // of real pixels; this controls only which buffer row each wire line carries
  67. // and how many wire lines there are, so wire length can exceed buffer size.
  68. // SEQUENTIAL - wire line i carries buffer row i; line count == height.
  69. // Every single-region panel captured so far.
  70. // PAIRED_EDGES - the controller fills a 2x-height RAM scanned from both
  71. // edges toward the middle: for i in [0,height) it wants
  72. // buffer row (height-1-i) then row i, so line count ==
  73. // 2*height and each row is sent twice. GDEY0579F51 / HX8717.
  74. // This is exactly what GxEPD2's writeNative emits for that
  75. // panel (99 bytes/line x 544 lines/region), so a faithful
  76. // replay needs it - nothing panel-specific in the engine.
  77. enum class LineOrder : uint8_t { SEQUENTIAL, PAIRED_EDGES };
  78. struct EpdPanelDescriptor {
  79. uint16_t width;
  80. uint16_t height;
  81. bool busy_active_level; // digitalRead() level meaning "still busy"
  82. uint32_t busy_timeout_ms; // ceiling for every busy wait (init and refresh alike)
  83. const EpdCommand *init_sequence; // run once, after the reset pulse
  84. uint8_t init_sequence_len;
  85. // Sets the (always full-frame, since this engine has no partial-window
  86. // support) RAM addressing window. window_sequence[0] runs once after
  87. // init_sequence and before the first plane's byte burst; index 1 is only
  88. // used when window_per_plane is true (see below). Was hardcoded into
  89. // CycleEngine assuming every controller shares SSD1680's
  90. // 0x44/0x45/0x4E/0x4F byte-column layout - GxEPD2_426_GDEQ0426T82
  91. // (SSD1677) turned out to address in raw pixels with reversed Y and
  92. // re-issues its entry-mode command here, and GxEPD2_583_GDEQ0583T31
  93. // (UC8179) uses one 0x90 command bracketed by 0x91/0x92 markers instead.
  94. // Made per-panel data, same as init/refresh, rather than special-cased
  95. // in the engine.
  96. const EpdCommand *window_sequence[2];
  97. uint8_t window_sequence_len[2];
  98. // true if each plane needs window_sequence[plane index] re-run before
  99. // its own byte burst, instead of one window_sequence[0] covering every
  100. // plane's burst (the default, e.g. a BW+red panel's shared full-frame
  101. // window). This is how GxEPD2_579c_GDEY0579F51's real master/slave
  102. // regions are handled: two genuinely separate physical RAM windows, not
  103. // just two color planes sharing one - "plane" here just means "one more
  104. // buffer with its own byte burst," which happens to fit a two-physical-
  105. // region panel as well as a two-color-plane one. See
  106. // panel_579c_gdey0579f51.h.
  107. bool window_per_plane{false};
  108. uint8_t plane_count; // 1 (BW), 2 (BW + accent color), or 2 physical regions
  109. uint8_t plane_write_cmd[2]; // e.g. {0x24, 0x26}
  110. // The extra "previous RAM bank" write GxEPD2's writeImageForFullRefresh()
  111. // makes that plain writeImage() doesn't, on controllers where the two
  112. // diverge (SSD1680/SSD1681/SSD1619-family: 0x26 then 0x24, or 0x10 then
  113. // 0x13 - same bitmap written twice, to two different commands). 0x00 (the
  114. // default, and every panel captured before this field existed) means no
  115. // divergence was observed: writeImageForFullRefresh falls back to
  116. // writeImage for that controller, the common case. NOT read by CycleEngine
  117. // today - this engine always does a plain single write per plane, correct
  118. // for full-refresh-only operation, since the full-refresh waveform doesn't
  119. // depend on the "previous" bank's content. It matters once partial refresh
  120. // reads that bank to compute its diff, so it's captured now rather than
  121. // needing a second capture pass later - see emit_descriptor.h's
  122. // probe_full_refresh_extra_write_cmd.
  123. uint8_t plane_write_cmd_for_full_refresh[2]{0, 0};
  124. bool plane_invert[2]; // true if the byte must be bit-inverted on write
  125. // When window_per_plane is true, each plane's own buffer is sized for
  126. // this width, not the descriptor's overall `width` (which is the full
  127. // logical canvas ESPHome draws into and get_width_internal() reports) -
  128. // e.g. 396 for GDEY0579F51's master/slave halves, each 396x272 on its
  129. // own, not the full 792x272. Unused otherwise.
  130. uint16_t plane_width{0};
  131. // Wire-line emission order for this panel's plane(s) - see LineOrder above.
  132. // Default SEQUENTIAL. GDEY0579F51 uses PAIRED_EDGES to match GxEPD2's own
  133. // doubled, both-edges-to-middle line order byte-for-byte; not assumed for
  134. // any other panel without the diag native-path tool deriving it.
  135. LineOrder line_order{LineOrder::SEQUENTIAL};
  136. // Pixel bit-depth, which also selects the buffer model:
  137. // 1 = one-bit-per-pixel plane model (mono, or BW + accent as two
  138. // separate 1bpp planes). plane_invert applies; palette is unused.
  139. // 2 = packed 4-color: 2 bits/pixel, 4 pixels/byte, MSB-first.
  140. // 4 = packed 7-color (ACeP): 4 bits/pixel, 2 pixels/byte, MSB-first.
  141. // For bits_per_pixel > 1 the buffer is a single packed code-per-pixel
  142. // stream (per region when window_per_plane), sized width*height*bpp/8,
  143. // and each pixel's wire code comes from `palette` (nearest-color
  144. // classification at draw time) rather than the 1bpp plane bitmasks;
  145. // plane_invert is unused. This replaces the old packed_2bpp_plane bool -
  146. // "is it packed" is now bits_per_pixel > 1, "which codes" is data, so a
  147. // 4bpp 7-color panel is a palette + bits_per_pixel=4, no new engine flag.
  148. uint8_t bits_per_pixel{1};
  149. // Packed-mode (bits_per_pixel > 1) palette and the wire code the buffer is
  150. // cleared to (the panel's white/blank). Unused when bits_per_pixel == 1.
  151. // See GXEPD2_PALETTE_4C / GXEPD2_PALETTE_7C.
  152. const EpdPaletteEntry *palette{nullptr};
  153. uint8_t palette_len{0};
  154. uint8_t blank_code{0};
  155. const EpdCommand *refresh_sequence; // e.g. 0x22/0xF7 then trigger 0x20
  156. uint8_t refresh_sequence_len;
  157. // powerOff()/hibernate(), captured the same way as refresh_sequence - see
  158. // make_power_sequence_resolvers in emit_descriptor.h. Exposed for a user's
  159. // own lambda (CycleEngine::request_power_off/request_hibernate), not run
  160. // automatically by update(): this engine does a full hardware reset on
  161. // every cycle regardless of prior power state, so correctness never
  162. // depends on calling either. hibernate_sequence is the deep-sleep trigger
  163. // ONLY (every GxEPD2 driver's hibernate() is powerOff() + one more
  164. // command when rst is wired) - CycleEngine runs power_off_sequence first,
  165. // then hibernate_sequence, to replay the same two-step effect. Both
  166. // nullptr/0 (not yet captured) is a safe no-op: CycleEngine treats a
  167. // zero-length sequence as already done.
  168. const EpdCommand *power_off_sequence{nullptr};
  169. uint8_t power_off_sequence_len{0};
  170. const EpdCommand *hibernate_sequence{nullptr}; // deep-sleep trigger only, after power_off_sequence
  171. uint8_t hibernate_sequence_len{0};
  172. };
  173. } // namespace esphome::gxepd2