engine.h 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. #pragma once
  2. #include <cstdint>
  3. #include <cstdio>
  4. #include "esphome/core/gpio.h"
  5. #include "esphome/core/hal.h"
  6. #include "esphome/core/log.h"
  7. #include "esphome/components/spi/spi.h"
  8. #include "engine_model.h"
  9. namespace esphome::zepd {
  10. // Stock GxEPD2 vendor value (20MHz).
  11. using EpdSPIDevice = spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
  12. spi::DATA_RATE_20MHZ>;
  13. // Replays one GxEPD2 driver's refresh cycle - reset pulse, init command table,
  14. // RAM-window set, per-plane byte burst, refresh trigger, busy-pin wait - as a
  15. // state machine stepped from loop(), instead of GxEPD2's own synchronous
  16. // top-to-bottom call stack. step() never blocks: every state does a small
  17. // bounded unit of work and returns, so nothing here can starve another task or
  18. // need a watchdog subscription.
  19. //
  20. // request_power_off()/request_hibernate() are a separate, standalone entry
  21. // point (no init/window/plane-write) replaying just
  22. // power_off_sequence/hibernate_sequence - for a user's own lambda, not run
  23. // automatically by request_full_update(). Called while a refresh is still
  24. // in flight (e.g. from an adapter's teardown path), they force a reset
  25. // pulse first to abort it rather than writing over a busy controller - see
  26. // begin_power_down_().
  27. class Engine {
  28. public:
  29. void begin(const EngineModel *desc, GPIOPin *cs, GPIOPin *dc, GPIOPin *rst, GPIOPin *busy, EpdSPIDevice *spi) {
  30. this->desc_ = desc;
  31. this->cs_ = cs;
  32. this->dc_ = dc;
  33. this->rst_ = rst;
  34. this->busy_ = busy;
  35. this->spi_ = spi;
  36. this->cs_->setup();
  37. this->dc_->setup();
  38. if (this->rst_ != nullptr)
  39. this->rst_->setup();
  40. if (this->busy_ != nullptr)
  41. this->busy_->setup();
  42. this->cs_->digital_write(true);
  43. this->dc_->digital_write(true);
  44. }
  45. // Arms the state machine; returns immediately. planes[1] is ignored when
  46. // desc->plane_count == 1. Undefined to call while !idle().
  47. void request_full_update(const uint8_t *plane0, const uint8_t *plane1) {
  48. this->arm_full_update_(plane0, plane1, this->rst_ != nullptr ? State::RESET_LOW : State::INIT_CMD);
  49. }
  50. #ifdef USE_ZEPD_DIAGNOSTICS
  51. // Dev-time diagnostic, compiled out unless the adapter's `diagnostics:
  52. // true` is set: arms a full update starting at INIT_CMD, skipping the
  53. // reset pulse entirely - the same path a panel with no reset pin wired
  54. // already takes every cycle. Lets a bench test use the screen as the
  55. // oracle for "is the panel actually non-responsive right now" (e.g. right
  56. // after hibernate()): if a distinct pattern drawn this way does NOT
  57. // appear, the prior power call really did take the panel off-line; a
  58. // normal request_full_update() right after (reset pulse included) should
  59. // then wake it and render correctly.
  60. void diag_request_update_no_reset(const uint8_t *plane0, const uint8_t *plane1) {
  61. this->arm_full_update_(plane0, plane1, State::INIT_CMD);
  62. }
  63. #endif
  64. // Arms a standalone powerOff() replay - no init/window/plane write, just
  65. // the captured power_off_sequence (if any) then DONE. Safe to call any
  66. // time, including mid-refresh: see begin_power_down_() below for why that
  67. // matters. Safe to call repeatedly: GxEPD2's own powerOff() is itself
  68. // guarded on "already off," which the capture already baked in.
  69. void request_power_off() { this->begin_power_down_(false); }
  70. // Arms a standalone hibernate() replay: power_off_sequence then
  71. // hibernate_sequence (the deep-sleep trigger only - see engine_model.h).
  72. // See begin_power_down_() below for the mid-refresh case. No paired "wake"
  73. // call exists or is needed: request_full_update()'s RESET_LOW is an
  74. // unconditional hardware reset pulse every cycle, which is exactly how
  75. // GxEPD2 wakes a hibernating (RST-wakeable) panel.
  76. void request_hibernate() { this->begin_power_down_(true); }
  77. bool idle() const { return this->state_ == State::IDLE; }
  78. // Called once per loop() tick. Does one bounded unit of work.
  79. void step() {
  80. switch (this->state_) {
  81. case State::IDLE:
  82. return;
  83. case State::RESET_LOW:
  84. this->rst_->digital_write(true); // preset high, then pulse low - see GxEPD2_EPD::_reset()
  85. this->deadline_ = millis() + 10;
  86. this->set_state_(State::RESET_LOW_WAIT);
  87. return;
  88. case State::RESET_LOW_WAIT:
  89. if (millis() < this->deadline_)
  90. return;
  91. this->rst_->digital_write(false);
  92. this->deadline_ = millis() + 10;
  93. this->set_state_(State::RESET_HIGH_WAIT);
  94. return;
  95. case State::RESET_HIGH_WAIT:
  96. if (millis() < this->deadline_)
  97. return;
  98. this->rst_->digital_write(true);
  99. this->deadline_ = millis() + 10;
  100. this->set_state_(State::RESET_SETTLE);
  101. return;
  102. case State::RESET_SETTLE:
  103. if (millis() < this->deadline_)
  104. return;
  105. this->cursor_ = 0;
  106. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  107. // Post-reset busy wait, matching every vendor _InitDisplay()'s
  108. // _waitWhileBusy() right after the reset pulse. Some controllers hold
  109. // BUSY while they load settings/waveforms from OTP after a hardware
  110. // reset (notably JD79676 / GDEY0213F52, whose init is otherwise just
  111. // 0xE9 + power-on) - issuing any command before that clears gets it
  112. // silently dropped, so the panel never refreshes. Panels that don't
  113. // assert BUSY post-reset read "not busy" on the first poll and fall
  114. // straight through to INIT_CMD, so this is safe for all of them.
  115. // begin_power_down_()'s abort path sets reset_then_wait_busy_ to
  116. // false instead - see there for why.
  117. if (this->reset_then_wait_busy_) {
  118. this->start_busy_wait_(this->reset_done_next_);
  119. } else {
  120. this->set_state_(this->reset_done_next_);
  121. }
  122. return;
  123. case State::INIT_CMD:
  124. this->step_sequence_(this->desc_->init_sequence, this->desc_->init_sequence_len, State::INIT_CMD,
  125. State::SET_WINDOW);
  126. return;
  127. case State::SET_WINDOW:
  128. // window_sequence[this->plane_]: index 0 on the first entry
  129. // (plane_ reset to 0 in request_full_update()); when
  130. // window_per_plane is true, step_write_plane_() re-enters this
  131. // state with plane_ already advanced to 1 before the next plane's
  132. // byte burst, picking window_sequence[1] instead.
  133. this->step_sequence_(this->desc_->window_sequence[this->plane_], this->desc_->window_sequence_len[this->plane_],
  134. State::SET_WINDOW, State::WRITE_PLANE);
  135. return;
  136. case State::WRITE_PLANE:
  137. this->step_write_plane_();
  138. return;
  139. case State::REFRESH_CMD:
  140. this->step_sequence_(this->desc_->refresh_sequence, this->desc_->refresh_sequence_len, State::REFRESH_CMD,
  141. State::DONE);
  142. return;
  143. case State::POWER_OFF_CMD:
  144. // request_hibernate() routes through here first (hibernate() is
  145. // itself powerOff() + one more command on every GxEPD2 driver) -
  146. // done_state branches to HIBERNATE_CMD only then.
  147. this->step_sequence_(this->desc_->power_off_sequence, this->desc_->power_off_sequence_len,
  148. State::POWER_OFF_CMD, this->hibernating_ ? State::HIBERNATE_CMD : State::DONE);
  149. return;
  150. case State::HIBERNATE_CMD:
  151. this->step_sequence_(this->desc_->hibernate_sequence, this->desc_->hibernate_sequence_len,
  152. State::HIBERNATE_CMD, State::DONE);
  153. return;
  154. case State::POST_DELAY:
  155. if (millis() < this->deadline_)
  156. return;
  157. this->set_state_(this->delay_return_state_);
  158. return;
  159. case State::WAIT_BUSY:
  160. this->step_wait_busy_();
  161. return;
  162. case State::DONE:
  163. ESP_LOGD("zepd", "Display update finished");
  164. this->set_state_(State::IDLE);
  165. return;
  166. case State::ABORTED:
  167. // A busy timeout means the panel isn't behaving as wired/expected
  168. // (unplugged, dead, wrong busy polarity, ...) - pressing ahead
  169. // with the rest of the sequence (more command bytes, another
  170. // busy-wait) is very unlikely to "just work" and only obscures
  171. // which step actually failed. Bail the whole cycle here instead of
  172. // continuing to whatever state the timed-out wait was headed to.
  173. ESP_LOGE("zepd", "Aborting display update: busy timeout");
  174. this->cursor_ = 0;
  175. this->plane_ = 0;
  176. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  177. this->set_state_(State::IDLE);
  178. return;
  179. }
  180. }
  181. private:
  182. // State/CmdPhase must come before arm_full_update_() below: a member
  183. // function's PARAMETER types (unlike its body) are resolved at the point
  184. // of declaration, not deferred to complete-class context, so `State` has
  185. // to already be a known type by the time arm_full_update_()'s signature is
  186. // parsed.
  187. enum class State {
  188. IDLE,
  189. RESET_LOW,
  190. RESET_LOW_WAIT,
  191. RESET_HIGH_WAIT,
  192. RESET_SETTLE,
  193. INIT_CMD,
  194. SET_WINDOW,
  195. WRITE_PLANE,
  196. REFRESH_CMD,
  197. POWER_OFF_CMD,
  198. HIBERNATE_CMD,
  199. POST_DELAY,
  200. WAIT_BUSY,
  201. DONE,
  202. ABORTED,
  203. };
  204. enum class CmdPhase { BEFORE_DELAY, ISSUE };
  205. // Shared by request_full_update() and (dev builds only) the no-reset
  206. // diagnostic path above - identical setup, different starting state.
  207. void arm_full_update_(const uint8_t *plane0, const uint8_t *plane1, State start_state) {
  208. this->planes_[0] = plane0;
  209. this->planes_[1] = plane1;
  210. uint32_t w = this->desc_->window_per_plane ? this->desc_->plane_width : this->desc_->width;
  211. // width*height*bpp/8: /8 at 1bpp, /4 at 2bpp, /2 at 4bpp.
  212. this->plane_bytes_ = (uint32_t) w * this->desc_->height * this->desc_->bits_per_pixel / 8;
  213. this->bytes_per_row_ = this->plane_bytes_ / this->desc_->height;
  214. // Wire length can exceed the buffer: PAIRED_EDGES sends each row twice
  215. // (2x-height RAM). plane_bytes_ stays the real buffer size; wire_bytes_
  216. // is what actually goes out on 0x10/0x24/... per plane.
  217. uint32_t wire_lines = this->desc_->height;
  218. if (this->desc_->line_order == LineOrder::PAIRED_EDGES)
  219. wire_lines *= 2;
  220. this->wire_bytes_ = wire_lines * this->bytes_per_row_;
  221. this->cursor_ = 0;
  222. this->plane_ = 0;
  223. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  224. // Normal reset path: post-reset busy wait (see RESET_SETTLE below), then
  225. // the real init sequence - this->reset_done_next_/reset_then_wait_busy_
  226. // are only ever overridden by begin_power_down_()'s abort path.
  227. this->reset_done_next_ = State::INIT_CMD;
  228. this->reset_then_wait_busy_ = true;
  229. this->set_state_(start_state);
  230. }
  231. // Shared by request_power_off()/request_hibernate(). Most controllers
  232. // ignore new commands outright while BUSY is asserted, so writing
  233. // power_off_sequence/hibernate_sequence straight over a cycle that's still
  234. // mid-refresh would silently do nothing - the panel never actually powers
  235. // down, it just keeps running the update it was already doing. If a reset
  236. // pin is wired, force a hardware reset pulse first (the same one
  237. // request_full_update() issues every normal cycle) to abort whatever's in
  238. // flight and bring the controller back to a known, command-accepting
  239. // state before sending the power-down sequence - skipping the post-reset
  240. // busy wait that a real update takes, since this is an emergency path
  241. // that can't afford to stall on busy_timeout_ms. Without a reset pin
  242. // there's no way to interrupt a busy controller, so this is best-effort
  243. // in that case: jamming the state machine straight to POWER_OFF_CMD, same
  244. // as calling this while idle.
  245. void begin_power_down_(bool hibernating) {
  246. this->cursor_ = 0;
  247. this->plane_ = 0;
  248. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  249. this->hibernating_ = hibernating;
  250. if (this->state_ != State::IDLE && this->rst_ != nullptr) {
  251. this->reset_done_next_ = State::POWER_OFF_CMD;
  252. this->reset_then_wait_busy_ = false;
  253. this->set_state_(State::RESET_LOW);
  254. } else {
  255. this->set_state_(State::POWER_OFF_CMD);
  256. }
  257. }
  258. // Every command byte and every data byte is its own SPI transaction, CS
  259. // toggled around each one - this looks wasteful, but it's mechanically
  260. // what GxEPD2_EPD::_writeCommand()/_writeData(uint8_t) do (see
  261. // third_party/GxEPD2/src/GxEPD2_EPD.cpp), and matching the vendor cycle
  262. // byte-for-byte is the point of this engine.
  263. void write_byte_(uint8_t b, bool dc_high) {
  264. this->dc_->digital_write(dc_high);
  265. this->spi_->enable();
  266. this->cs_->digital_write(false);
  267. this->spi_->write_byte(b);
  268. this->cs_->digital_write(true);
  269. this->spi_->disable();
  270. }
  271. void write_command_(uint8_t cmd, const uint8_t *data, uint8_t data_len) {
  272. #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
  273. // Command-level skeleton trace - deliberately left in (not gated to
  274. // VERY_VERBOSE): the exact byte-for-byte counterpart to compare against
  275. // a captured reference trace on the same panel. Bulk plane data isn't
  276. // logged here (see step_write_plane_) - only the command skeleton
  277. // matters for that comparison.
  278. char buf[32] = {0};
  279. int n = 0;
  280. for (uint8_t i = 0; i < data_len && n < (int) sizeof(buf) - 3; i++)
  281. n += snprintf(buf + n, sizeof(buf) - n, "%02X ", data[i]);
  282. ESP_LOGD("zepd", "cmd 0x%02X data[%u]: %s", cmd, data_len, buf);
  283. #endif
  284. this->write_byte_(cmd, false);
  285. for (uint8_t i = 0; i < data_len; i++)
  286. this->write_byte_(data[i], true);
  287. }
  288. // Steps through one EpdCommand table one entry (and one delay/busy-wait,
  289. // if that entry has one) at a time, resuming in self_state after any
  290. // wait so the same call re-enters here for the next entry. Shared by
  291. // INIT_CMD and REFRESH_CMD - which table is being stepped is entirely
  292. // determined by which state called this.
  293. void step_sequence_(const EpdCommand *seq, uint8_t len, State self_state, State done_state) {
  294. if (this->cursor_ >= len) {
  295. this->cursor_ = 0;
  296. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  297. this->set_state_(done_state);
  298. return;
  299. }
  300. const EpdCommand &c = seq[this->cursor_];
  301. if (this->cmd_phase_ == CmdPhase::BEFORE_DELAY) {
  302. this->cmd_phase_ = CmdPhase::ISSUE;
  303. if (c.delay_before_ms > 0) {
  304. this->start_delay_(c.delay_before_ms, self_state);
  305. return;
  306. }
  307. }
  308. this->write_command_(c.cmd, c.data, c.data_len);
  309. this->cursor_++;
  310. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  311. if (c.wait_busy_after) {
  312. this->start_busy_wait_(self_state);
  313. } else if (c.delay_after_ms > 0) {
  314. this->start_delay_(c.delay_after_ms, self_state);
  315. }
  316. // else: stays in self_state; the next step() call processes the next entry.
  317. }
  318. // Writes up to BYTE_BUDGET bytes of the current plane per call, then
  319. // returns - this bound is what keeps a full-frame write (thousands of
  320. // bytes) from ever making one loop() tick run long.
  321. //
  322. // 16384 allows up to ~25ms of blocking per step; the dominant cost is
  323. // write_byte_()'s per-byte SPI enable()/disable() (ESP-IDF transaction
  324. // setup/teardown), intentionally NOT batched away: it lets other devices
  325. // on a shared spi: bus interleave between our bytes instead of being
  326. // locked out for the whole burst.
  327. static constexpr uint32_t BYTE_BUDGET = 16384;
  328. void step_write_plane_() {
  329. if (this->cursor_ == 0) {
  330. const uint8_t *buf = this->planes_[this->plane_];
  331. uint32_t histo[3] = {0, 0, 0}; // [0]=0x00, [1]=0xFF, [2]=other
  332. if (buf != nullptr) {
  333. for (uint32_t i = 0; i < this->plane_bytes_; i++) {
  334. if (buf[i] == 0x00)
  335. histo[0]++;
  336. else if (buf[i] == 0xFF)
  337. histo[1]++;
  338. else
  339. histo[2]++;
  340. }
  341. }
  342. ESP_LOGD("zepd", "plane %u: cmd 0x%02X, %u wire bytes (buf %u), invert=%d, buf[0]=0x%02X buf[last]=0x%02X, histo(00/FF/other)=%u/%u/%u",
  343. this->plane_, this->desc_->plane_write_cmd[this->plane_], (unsigned) this->wire_bytes_,
  344. (unsigned) this->plane_bytes_, this->desc_->plane_invert[this->plane_], buf != nullptr ? buf[0] : 0,
  345. buf != nullptr ? buf[this->plane_bytes_ - 1] : 0, (unsigned) histo[0], (unsigned) histo[1],
  346. (unsigned) histo[2]);
  347. this->write_byte_(this->desc_->plane_write_cmd[this->plane_], false);
  348. }
  349. const uint8_t *buf = this->planes_[this->plane_];
  350. bool invert = this->desc_->plane_invert[this->plane_];
  351. uint32_t end = this->cursor_ + BYTE_BUDGET;
  352. if (end > this->wire_bytes_)
  353. end = this->wire_bytes_;
  354. for (; this->cursor_ < end; this->cursor_++) {
  355. uint32_t src = this->line_src_index_(this->cursor_);
  356. uint8_t b = buf != nullptr ? buf[src] : 0xFF;
  357. this->write_byte_(invert ? static_cast<uint8_t>(~b) : b, true);
  358. }
  359. if (this->cursor_ >= this->wire_bytes_) {
  360. this->cursor_ = 0;
  361. this->plane_++;
  362. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  363. if (this->plane_ >= this->desc_->plane_count) {
  364. this->plane_ = 0;
  365. this->set_state_(State::REFRESH_CMD);
  366. } else if (this->desc_->window_per_plane) {
  367. // Another plane to go and it needs its own window-set first (a
  368. // genuinely separate physical RAM region, not just another color
  369. // plane sharing the window already set) - re-enter SET_WINDOW,
  370. // which will pick window_sequence[this->plane_] now that plane_
  371. // has already advanced.
  372. this->set_state_(State::SET_WINDOW);
  373. }
  374. // else: more planes, sharing one window already set - stay in
  375. // WRITE_PLANE, next step() call starts the next plane's byte burst.
  376. }
  377. }
  378. // Maps a wire-transmission-order byte position (0..wire_bytes_-1) to the
  379. // buffer byte it reads. The wire is a sequence of lines, each bytes_per_row_
  380. // bytes from one buffer row; line_order picks which buffer row each wire
  381. // line carries (and, for PAIRED_EDGES, there are 2x as many wire lines as
  382. // buffer rows). SEQUENTIAL is the identity (wire line i -> row i).
  383. uint32_t line_src_index_(uint32_t wire_pos) const {
  384. uint32_t wire_line = wire_pos / this->bytes_per_row_;
  385. uint32_t byte_in_line = wire_pos % this->bytes_per_row_;
  386. uint32_t buf_row;
  387. switch (this->desc_->line_order) {
  388. case LineOrder::PAIRED_EDGES: {
  389. // wire lines come in pairs: for i in [0,H), line 2i = row (H-1-i),
  390. // line 2i+1 = row i. Matches GxEPD2 writeNative's emission exactly.
  391. uint32_t i = wire_line / 2;
  392. buf_row = (wire_line % 2 == 0) ? (this->desc_->height - 1 - i) : i;
  393. break;
  394. }
  395. case LineOrder::SEQUENTIAL:
  396. default:
  397. buf_row = wire_line;
  398. break;
  399. }
  400. return buf_row * this->bytes_per_row_ + byte_in_line;
  401. }
  402. void start_delay_(uint16_t delay_ms, State return_to) {
  403. this->deadline_ = millis() + delay_ms;
  404. this->delay_return_state_ = return_to;
  405. this->set_state_(State::POST_DELAY);
  406. }
  407. void start_busy_wait_(State next) {
  408. this->busy_wait_next_ = next;
  409. this->busy_wait_deadline_ = millis() + this->desc_->busy_timeout_ms;
  410. this->set_state_(State::WAIT_BUSY);
  411. }
  412. void step_wait_busy_() {
  413. if (this->busy_ == nullptr) {
  414. this->set_state_(this->busy_wait_next_);
  415. return;
  416. }
  417. bool busy = this->busy_->digital_read() == this->desc_->busy_active_level;
  418. if (!busy) {
  419. this->set_state_(this->busy_wait_next_);
  420. return;
  421. }
  422. if (millis() >= this->busy_wait_deadline_) {
  423. ESP_LOGW("zepd", "Busy timeout waiting for panel");
  424. this->set_state_(State::ABORTED);
  425. }
  426. }
  427. // Logs every state transition (only when the state actually changes -
  428. // WAIT_BUSY and POST_DELAY re-enter their own case on every step() call
  429. // while polling, which would otherwise spam this at loop() rate) so a
  430. // first hardware run can be checked against the vendor cycle it's meant
  431. // to replicate before this gets dialed back to WARN-only.
  432. void set_state_(State s) {
  433. if (s != this->state_)
  434. ESP_LOGD("zepd", "state: %s -> %s", state_name_(this->state_), state_name_(s));
  435. this->state_ = s;
  436. }
  437. static const char *state_name_(State s) {
  438. switch (s) {
  439. case State::IDLE:
  440. return "IDLE";
  441. case State::RESET_LOW:
  442. return "RESET_LOW";
  443. case State::RESET_LOW_WAIT:
  444. return "RESET_LOW_WAIT";
  445. case State::RESET_HIGH_WAIT:
  446. return "RESET_HIGH_WAIT";
  447. case State::RESET_SETTLE:
  448. return "RESET_SETTLE";
  449. case State::INIT_CMD:
  450. return "INIT_CMD";
  451. case State::SET_WINDOW:
  452. return "SET_WINDOW";
  453. case State::WRITE_PLANE:
  454. return "WRITE_PLANE";
  455. case State::REFRESH_CMD:
  456. return "REFRESH_CMD";
  457. case State::POWER_OFF_CMD:
  458. return "POWER_OFF_CMD";
  459. case State::HIBERNATE_CMD:
  460. return "HIBERNATE_CMD";
  461. case State::POST_DELAY:
  462. return "POST_DELAY";
  463. case State::WAIT_BUSY:
  464. return "WAIT_BUSY";
  465. case State::DONE:
  466. return "DONE";
  467. case State::ABORTED:
  468. return "ABORTED";
  469. }
  470. return "?";
  471. }
  472. const EngineModel *desc_{nullptr};
  473. GPIOPin *cs_{nullptr};
  474. GPIOPin *dc_{nullptr};
  475. GPIOPin *rst_{nullptr};
  476. GPIOPin *busy_{nullptr};
  477. EpdSPIDevice *spi_{nullptr};
  478. State state_{State::IDLE};
  479. uint32_t cursor_{0};
  480. CmdPhase cmd_phase_{CmdPhase::BEFORE_DELAY};
  481. uint8_t plane_{0};
  482. const uint8_t *planes_[2]{nullptr, nullptr};
  483. uint32_t plane_bytes_{0}; // real buffer size per plane
  484. uint32_t wire_bytes_{0}; // bytes actually sent per plane (>= plane_bytes_)
  485. uint32_t bytes_per_row_{0};
  486. uint32_t deadline_{0};
  487. State reset_done_next_{State::INIT_CMD}; // RESET_SETTLE's continuation - see arm_full_update_()/begin_power_down_()
  488. bool reset_then_wait_busy_{true}; // false only for begin_power_down_()'s abort path
  489. State delay_return_state_{State::DONE};
  490. uint32_t busy_wait_deadline_{0};
  491. State busy_wait_next_{State::DONE};
  492. bool hibernating_{false}; // POWER_OFF_CMD's done_state: HIBERNATE_CMD if set, else DONE
  493. };
  494. } // namespace esphome::zepd