cycle_engine.h 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421
  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 "epd_command.h"
  9. namespace esphome::gxepd2 {
  10. // Stock GxEPD2 vendor value (20MHz) for this panel. Was dropped to 10MHz
  11. // (matching epaper_spi) while chasing the solid-black/solid-red symptom,
  12. // which turned out to be unrelated - display.py never wired the configured
  13. // lambda to set_writer() at all, so nothing but auto_clear's default fill
  14. // ever rendered, regardless of SPI clock. Rolled back to test the stock
  15. // GxEPD2 clock now that the real bug is fixed.
  16. using EpdSPIDevice = spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING,
  17. spi::DATA_RATE_20MHZ>;
  18. // Replays one GxEPD2 driver's refresh cycle - reset pulse, init command table,
  19. // RAM-window set, per-plane byte burst, refresh trigger, busy-pin wait - as a
  20. // state machine stepped from loop(), instead of GxEPD2's own synchronous
  21. // top-to-bottom call stack. step() never blocks: every state does a small
  22. // bounded unit of work and returns, so nothing here can starve another task or
  23. // need a watchdog subscription.
  24. class CycleEngine {
  25. public:
  26. void begin(const EpdPanelDescriptor *desc, GPIOPin *cs, GPIOPin *dc, GPIOPin *rst, GPIOPin *busy,
  27. EpdSPIDevice *spi) {
  28. this->desc_ = desc;
  29. this->cs_ = cs;
  30. this->dc_ = dc;
  31. this->rst_ = rst;
  32. this->busy_ = busy;
  33. this->spi_ = spi;
  34. this->cs_->setup();
  35. this->dc_->setup();
  36. if (this->rst_ != nullptr)
  37. this->rst_->setup();
  38. if (this->busy_ != nullptr)
  39. this->busy_->setup();
  40. this->cs_->digital_write(true);
  41. this->dc_->digital_write(true);
  42. }
  43. // Arms the state machine; returns immediately. planes[1] is ignored when
  44. // desc->plane_count == 1. Undefined to call while !idle().
  45. void request_full_update(const uint8_t *plane0, const uint8_t *plane1) {
  46. this->planes_[0] = plane0;
  47. this->planes_[1] = plane1;
  48. uint32_t w = this->desc_->window_per_plane ? this->desc_->plane_width : this->desc_->width;
  49. // width*height*bpp/8: /8 at 1bpp, /4 at 2bpp, /2 at 4bpp.
  50. this->plane_bytes_ = (uint32_t) w * this->desc_->height * this->desc_->bits_per_pixel / 8;
  51. this->bytes_per_row_ = this->plane_bytes_ / this->desc_->height;
  52. this->cursor_ = 0;
  53. this->plane_ = 0;
  54. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  55. this->set_state_(this->rst_ != nullptr ? State::RESET_LOW : State::INIT_CMD);
  56. }
  57. bool idle() const { return this->state_ == State::IDLE; }
  58. // Called once per loop() tick. Does one bounded unit of work.
  59. void step() {
  60. switch (this->state_) {
  61. case State::IDLE:
  62. return;
  63. case State::RESET_LOW:
  64. this->rst_->digital_write(true); // preset high, then pulse low - see GxEPD2_EPD::_reset()
  65. this->deadline_ = millis() + 10;
  66. this->set_state_(State::RESET_LOW_WAIT);
  67. return;
  68. case State::RESET_LOW_WAIT:
  69. if (millis() < this->deadline_)
  70. return;
  71. this->rst_->digital_write(false);
  72. this->deadline_ = millis() + 10;
  73. this->set_state_(State::RESET_HIGH_WAIT);
  74. return;
  75. case State::RESET_HIGH_WAIT:
  76. if (millis() < this->deadline_)
  77. return;
  78. this->rst_->digital_write(true);
  79. this->deadline_ = millis() + 10;
  80. this->set_state_(State::RESET_SETTLE);
  81. return;
  82. case State::RESET_SETTLE:
  83. if (millis() < this->deadline_)
  84. return;
  85. this->cursor_ = 0;
  86. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  87. // Post-reset busy wait, matching every vendor _InitDisplay()'s
  88. // _waitWhileBusy() right after the reset pulse. Some controllers hold
  89. // BUSY while they load settings/waveforms from OTP after a hardware
  90. // reset (notably JD79676 / GDEY0213F52, whose init is otherwise just
  91. // 0xE9 + power-on) - issuing any command before that clears gets it
  92. // silently dropped, so the panel never refreshes. Panels that don't
  93. // assert BUSY post-reset read "not busy" on the first poll and fall
  94. // straight through to INIT_CMD, so this is safe for all of them.
  95. this->start_busy_wait_(State::INIT_CMD);
  96. return;
  97. case State::INIT_CMD:
  98. this->step_sequence_(this->desc_->init_sequence, this->desc_->init_sequence_len, State::INIT_CMD,
  99. State::SET_WINDOW);
  100. return;
  101. case State::SET_WINDOW:
  102. // window_sequence[this->plane_]: index 0 on the first entry
  103. // (plane_ reset to 0 in request_full_update()); when
  104. // window_per_plane is true, step_write_plane_() re-enters this
  105. // state with plane_ already advanced to 1 before the next plane's
  106. // byte burst, picking window_sequence[1] instead - see
  107. // panel_579c_gdey0579f51.h for why a panel needs that.
  108. this->step_sequence_(this->desc_->window_sequence[this->plane_], this->desc_->window_sequence_len[this->plane_],
  109. State::SET_WINDOW, State::WRITE_PLANE);
  110. return;
  111. case State::WRITE_PLANE:
  112. this->step_write_plane_();
  113. return;
  114. case State::REFRESH_CMD:
  115. this->step_sequence_(this->desc_->refresh_sequence, this->desc_->refresh_sequence_len, State::REFRESH_CMD,
  116. State::DONE);
  117. return;
  118. case State::POST_DELAY:
  119. if (millis() < this->deadline_)
  120. return;
  121. this->set_state_(this->delay_return_state_);
  122. return;
  123. case State::WAIT_BUSY:
  124. this->step_wait_busy_();
  125. return;
  126. case State::DONE:
  127. ESP_LOGD("gxepd2", "Display update finished");
  128. this->set_state_(State::IDLE);
  129. return;
  130. case State::ABORTED:
  131. // A busy timeout means the panel isn't behaving as wired/expected
  132. // (unplugged, dead, wrong busy polarity, ...) - pressing ahead
  133. // with the rest of the sequence (more command bytes, another
  134. // busy-wait) is very unlikely to "just work" and only obscures
  135. // which step actually failed. Bail the whole cycle here instead of
  136. // continuing to whatever state the timed-out wait was headed to.
  137. ESP_LOGE("gxepd2", "Aborting display update: busy timeout");
  138. this->cursor_ = 0;
  139. this->plane_ = 0;
  140. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  141. this->set_state_(State::IDLE);
  142. return;
  143. }
  144. }
  145. private:
  146. enum class State {
  147. IDLE,
  148. RESET_LOW,
  149. RESET_LOW_WAIT,
  150. RESET_HIGH_WAIT,
  151. RESET_SETTLE,
  152. INIT_CMD,
  153. SET_WINDOW,
  154. WRITE_PLANE,
  155. REFRESH_CMD,
  156. POST_DELAY,
  157. WAIT_BUSY,
  158. DONE,
  159. ABORTED,
  160. };
  161. enum class CmdPhase { BEFORE_DELAY, ISSUE };
  162. // Every command byte and every data byte is its own SPI transaction, CS
  163. // toggled around each one - this looks wasteful, but it's mechanically
  164. // what GxEPD2_EPD::_writeCommand()/_writeData(uint8_t) do (see
  165. // third_party/GxEPD2/src/GxEPD2_EPD.cpp), and matching the vendor cycle byte-for-byte
  166. // is the point of this engine.
  167. void write_byte_(uint8_t b, bool dc_high) {
  168. this->dc_->digital_write(dc_high);
  169. this->spi_->enable();
  170. this->cs_->digital_write(false);
  171. this->spi_->write_byte(b);
  172. this->cs_->digital_write(true);
  173. this->spi_->disable();
  174. }
  175. void write_command_(uint8_t cmd, const uint8_t *data, uint8_t data_len) {
  176. #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
  177. // Command-level skeleton trace - deliberately left in (not gated to
  178. // VERY_VERBOSE) while diagnosing a real-hardware content bug: this is
  179. // the exact byte-for-byte counterpart to compare against a captured
  180. // epaper_spi trace on the same panel. Bulk plane data isn't logged
  181. // here (see step_write_plane_) - only the command skeleton matters for
  182. // that comparison.
  183. char buf[32] = {0};
  184. int n = 0;
  185. for (uint8_t i = 0; i < data_len && n < (int) sizeof(buf) - 3; i++)
  186. n += snprintf(buf + n, sizeof(buf) - n, "%02X ", data[i]);
  187. ESP_LOGD("gxepd2", "cmd 0x%02X data[%u]: %s", cmd, data_len, buf);
  188. #endif
  189. this->write_byte_(cmd, false);
  190. for (uint8_t i = 0; i < data_len; i++)
  191. this->write_byte_(data[i], true);
  192. }
  193. // Steps through one EpdCommand table one entry (and one delay/busy-wait,
  194. // if that entry has one) at a time, resuming in self_state after any
  195. // wait so the same call re-enters here for the next entry. Shared by
  196. // INIT_CMD and REFRESH_CMD - which table is being stepped is entirely
  197. // determined by which state called this.
  198. void step_sequence_(const EpdCommand *seq, uint8_t len, State self_state, State done_state) {
  199. if (this->cursor_ >= len) {
  200. this->cursor_ = 0;
  201. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  202. this->set_state_(done_state);
  203. return;
  204. }
  205. const EpdCommand &c = seq[this->cursor_];
  206. if (this->cmd_phase_ == CmdPhase::BEFORE_DELAY) {
  207. this->cmd_phase_ = CmdPhase::ISSUE;
  208. if (c.delay_before_ms > 0) {
  209. this->start_delay_(c.delay_before_ms, self_state);
  210. return;
  211. }
  212. }
  213. this->write_command_(c.cmd, c.data, c.data_len);
  214. this->cursor_++;
  215. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  216. if (c.wait_busy_after) {
  217. this->start_busy_wait_(self_state);
  218. } else if (c.delay_after_ms > 0) {
  219. this->start_delay_(c.delay_after_ms, self_state);
  220. }
  221. // else: stays in self_state; the next step() call processes the next entry.
  222. }
  223. // Writes up to BYTE_BUDGET bytes of the current plane per call, then
  224. // returns - this bound is what keeps a full-frame write (thousands of
  225. // bytes) from ever making one loop() tick run long, replacing the old
  226. // per-256-byte esp_task_wdt_reset()+delay(1) throttle with simply not
  227. // doing enough work in one call to need it.
  228. //
  229. // 64 was overly conservative: at 20MHz SPI that's ~25us of actual bus
  230. // time, so a 426T82 full-plane write (48000 bytes) was bottlenecked
  231. // almost entirely on loop() scheduling overhead (~17ms/step observed),
  232. // not SPI - ~12.7s for one plane. Raised to 16384 (up to ~25ms of
  233. // blocking per step is fine here), cutting that to 3 steps instead of
  234. // 750 - measured ~1.07s for the plane write on hardware. The remaining
  235. // time is dominated by write_byte_()'s per-byte SPI enable()/disable()
  236. // (ESP-IDF transaction setup/teardown, ~21.7us/byte vs ~0.4us raw
  237. // transfer), which is intentionally NOT batched away: it lets other
  238. // devices on a shared spi: bus interleave between our bytes instead of
  239. // being locked out for the whole burst. 1s is well under half this
  240. // panel's own multi-second refresh time anyway, so it wasn't worth
  241. // trading that fairness for more throughput.
  242. static constexpr uint32_t BYTE_BUDGET = 16384;
  243. void step_write_plane_() {
  244. if (this->cursor_ == 0) {
  245. const uint8_t *buf = this->planes_[this->plane_];
  246. uint32_t histo[3] = {0, 0, 0}; // [0]=0x00, [1]=0xFF, [2]=other
  247. if (buf != nullptr) {
  248. for (uint32_t i = 0; i < this->plane_bytes_; i++) {
  249. if (buf[i] == 0x00)
  250. histo[0]++;
  251. else if (buf[i] == 0xFF)
  252. histo[1]++;
  253. else
  254. histo[2]++;
  255. }
  256. }
  257. ESP_LOGD("gxepd2", "plane %u: cmd 0x%02X, %u bytes, invert=%d, buf[0]=0x%02X buf[last]=0x%02X, histo(00/FF/other)=%u/%u/%u",
  258. this->plane_, this->desc_->plane_write_cmd[this->plane_], (unsigned) this->plane_bytes_,
  259. this->desc_->plane_invert[this->plane_], buf != nullptr ? buf[0] : 0,
  260. buf != nullptr ? buf[this->plane_bytes_ - 1] : 0, (unsigned) histo[0], (unsigned) histo[1],
  261. (unsigned) histo[2]);
  262. this->write_byte_(this->desc_->plane_write_cmd[this->plane_], false);
  263. }
  264. const uint8_t *buf = this->planes_[this->plane_];
  265. bool invert = this->desc_->plane_invert[this->plane_];
  266. uint32_t end = this->cursor_ + BYTE_BUDGET;
  267. if (end > this->plane_bytes_)
  268. end = this->plane_bytes_;
  269. for (; this->cursor_ < end; this->cursor_++) {
  270. uint32_t src = this->desc_->row_interleave ? this->interleaved_index_(this->cursor_) : this->cursor_;
  271. uint8_t b = buf != nullptr ? buf[src] : 0xFF;
  272. this->write_byte_(invert ? static_cast<uint8_t>(~b) : b, true);
  273. }
  274. if (this->cursor_ >= this->plane_bytes_) {
  275. this->cursor_ = 0;
  276. this->plane_++;
  277. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  278. if (this->plane_ >= this->desc_->plane_count) {
  279. this->plane_ = 0;
  280. this->set_state_(State::REFRESH_CMD);
  281. } else if (this->desc_->window_per_plane) {
  282. // Another plane to go and it needs its own window-set first (a
  283. // genuinely separate physical RAM region, not just another color
  284. // plane sharing the window already set) - re-enter SET_WINDOW,
  285. // which will pick window_sequence[this->plane_] now that plane_
  286. // has already advanced.
  287. this->set_state_(State::SET_WINDOW);
  288. }
  289. // else: more planes, sharing one window already set - stay in
  290. // WRITE_PLANE, next step() call starts the next plane's byte burst.
  291. }
  292. }
  293. // Maps a wire-transmission-order byte position to the buffer's actual
  294. // (plain top-to-bottom) byte index, for panels whose controller expects
  295. // rows in interleaved top/bottom pairs (row 0, row H-1, row 1, row H-2,
  296. // ...) instead of sequential order - see EpdPanelDescriptor::row_interleave.
  297. // wire_pos counts up linearly (0..plane_bytes_-1) the same way it always
  298. // has; only which buffer byte it reads changes.
  299. uint32_t interleaved_index_(uint32_t wire_pos) const {
  300. uint32_t wire_row = wire_pos / this->bytes_per_row_;
  301. uint32_t byte_in_row = wire_pos % this->bytes_per_row_;
  302. uint32_t pair = wire_row / 2;
  303. uint32_t buf_row = (wire_row % 2 == 0) ? pair : (this->desc_->height - 1 - pair);
  304. return buf_row * this->bytes_per_row_ + byte_in_row;
  305. }
  306. void start_delay_(uint16_t delay_ms, State return_to) {
  307. this->deadline_ = millis() + delay_ms;
  308. this->delay_return_state_ = return_to;
  309. this->set_state_(State::POST_DELAY);
  310. }
  311. void start_busy_wait_(State next) {
  312. this->busy_wait_next_ = next;
  313. this->busy_wait_deadline_ = millis() + this->desc_->busy_timeout_ms;
  314. this->set_state_(State::WAIT_BUSY);
  315. }
  316. void step_wait_busy_() {
  317. if (this->busy_ == nullptr) {
  318. this->set_state_(this->busy_wait_next_);
  319. return;
  320. }
  321. bool busy = this->busy_->digital_read() == this->desc_->busy_active_level;
  322. if (!busy) {
  323. this->set_state_(this->busy_wait_next_);
  324. return;
  325. }
  326. if (millis() >= this->busy_wait_deadline_) {
  327. ESP_LOGW("gxepd2", "Busy timeout waiting for panel");
  328. this->set_state_(State::ABORTED);
  329. }
  330. }
  331. // Logs every state transition (only when the state actually changes -
  332. // WAIT_BUSY and POST_DELAY re-enter their own case on every step() call
  333. // while polling, which would otherwise spam this at loop() rate) so a
  334. // first hardware run can be checked against the vendor cycle it's meant
  335. // to replicate before this gets dialed back to WARN-only.
  336. void set_state_(State s) {
  337. if (s != this->state_)
  338. ESP_LOGD("gxepd2", "state: %s -> %s", state_name_(this->state_), state_name_(s));
  339. this->state_ = s;
  340. }
  341. static const char *state_name_(State s) {
  342. switch (s) {
  343. case State::IDLE:
  344. return "IDLE";
  345. case State::RESET_LOW:
  346. return "RESET_LOW";
  347. case State::RESET_LOW_WAIT:
  348. return "RESET_LOW_WAIT";
  349. case State::RESET_HIGH_WAIT:
  350. return "RESET_HIGH_WAIT";
  351. case State::RESET_SETTLE:
  352. return "RESET_SETTLE";
  353. case State::INIT_CMD:
  354. return "INIT_CMD";
  355. case State::SET_WINDOW:
  356. return "SET_WINDOW";
  357. case State::WRITE_PLANE:
  358. return "WRITE_PLANE";
  359. case State::REFRESH_CMD:
  360. return "REFRESH_CMD";
  361. case State::POST_DELAY:
  362. return "POST_DELAY";
  363. case State::WAIT_BUSY:
  364. return "WAIT_BUSY";
  365. case State::DONE:
  366. return "DONE";
  367. case State::ABORTED:
  368. return "ABORTED";
  369. }
  370. return "?";
  371. }
  372. const EpdPanelDescriptor *desc_{nullptr};
  373. GPIOPin *cs_{nullptr};
  374. GPIOPin *dc_{nullptr};
  375. GPIOPin *rst_{nullptr};
  376. GPIOPin *busy_{nullptr};
  377. EpdSPIDevice *spi_{nullptr};
  378. State state_{State::IDLE};
  379. uint32_t cursor_{0};
  380. CmdPhase cmd_phase_{CmdPhase::BEFORE_DELAY};
  381. uint8_t plane_{0};
  382. const uint8_t *planes_[2]{nullptr, nullptr};
  383. uint32_t plane_bytes_{0};
  384. uint32_t bytes_per_row_{0};
  385. uint32_t deadline_{0};
  386. State delay_return_state_{State::DONE};
  387. uint32_t busy_wait_deadline_{0};
  388. State busy_wait_next_{State::DONE};
  389. };
  390. } // namespace esphome::gxepd2