cycle_engine.h 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  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. // Mechanically replicates one GxEPD2 driver's refresh cycle - reset pulse,
  19. // init command table, RAM-window set, per-plane byte burst, refresh
  20. // trigger, busy-pin wait - as a state machine stepped from loop(), instead
  21. // of GxEPD2's own synchronous top-to-bottom call stack. See the plan this
  22. // was built from (and gxepd2_task_watchdog_status in project memory) for
  23. // why: that call stack has no yield points, so making it non-blocking used
  24. // to require running the whole thing on a separate FreeRTOS task, which
  25. // intermittently tripped the ESP-IDF watchdog anyway. step() never blocks -
  26. // every state does a small bounded unit of work and returns - so there is
  27. // nothing here that can starve another task or need a watchdog subscription
  28. // at all.
  29. class CycleEngine {
  30. public:
  31. void begin(const EpdPanelDescriptor *desc, GPIOPin *cs, GPIOPin *dc, GPIOPin *rst, GPIOPin *busy,
  32. EpdSPIDevice *spi) {
  33. this->desc_ = desc;
  34. this->cs_ = cs;
  35. this->dc_ = dc;
  36. this->rst_ = rst;
  37. this->busy_ = busy;
  38. this->spi_ = spi;
  39. this->cs_->setup();
  40. this->dc_->setup();
  41. if (this->rst_ != nullptr)
  42. this->rst_->setup();
  43. if (this->busy_ != nullptr)
  44. this->busy_->setup();
  45. this->cs_->digital_write(true);
  46. this->dc_->digital_write(true);
  47. }
  48. // Arms the state machine; returns immediately. planes[1] is ignored when
  49. // desc->plane_count == 1. Undefined to call while !idle().
  50. void request_full_update(const uint8_t *plane0, const uint8_t *plane1) {
  51. this->planes_[0] = plane0;
  52. this->planes_[1] = plane1;
  53. this->plane_bytes_ = (static_cast<uint32_t>(this->desc_->width) * this->desc_->height) / 8;
  54. this->cursor_ = 0;
  55. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  56. this->set_state_(this->rst_ != nullptr ? State::RESET_LOW : State::INIT_CMD);
  57. }
  58. bool idle() const { return this->state_ == State::IDLE; }
  59. // Called once per loop() tick. Does one bounded unit of work.
  60. void step() {
  61. switch (this->state_) {
  62. case State::IDLE:
  63. return;
  64. case State::RESET_LOW:
  65. this->rst_->digital_write(true); // preset high, then pulse low - see GxEPD2_EPD::_reset()
  66. this->deadline_ = millis() + 10;
  67. this->set_state_(State::RESET_LOW_WAIT);
  68. return;
  69. case State::RESET_LOW_WAIT:
  70. if (millis() < this->deadline_)
  71. return;
  72. this->rst_->digital_write(false);
  73. this->deadline_ = millis() + 10;
  74. this->set_state_(State::RESET_HIGH_WAIT);
  75. return;
  76. case State::RESET_HIGH_WAIT:
  77. if (millis() < this->deadline_)
  78. return;
  79. this->rst_->digital_write(true);
  80. this->deadline_ = millis() + 10;
  81. this->set_state_(State::RESET_SETTLE);
  82. return;
  83. case State::RESET_SETTLE:
  84. if (millis() < this->deadline_)
  85. return;
  86. this->cursor_ = 0;
  87. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  88. this->set_state_(State::INIT_CMD);
  89. return;
  90. case State::INIT_CMD:
  91. this->step_sequence_(this->desc_->init_sequence, this->desc_->init_sequence_len, State::INIT_CMD,
  92. State::SET_WINDOW);
  93. return;
  94. case State::SET_WINDOW:
  95. this->step_sequence_(this->desc_->window_sequence, this->desc_->window_sequence_len, State::SET_WINDOW,
  96. State::WRITE_PLANE_START);
  97. return;
  98. case State::WRITE_PLANE_START:
  99. this->cursor_ = 0;
  100. this->plane_ = 0;
  101. this->set_state_(State::WRITE_PLANE);
  102. return;
  103. case State::WRITE_PLANE:
  104. this->step_write_plane_();
  105. return;
  106. case State::REFRESH_CMD:
  107. this->step_sequence_(this->desc_->refresh_sequence, this->desc_->refresh_sequence_len, State::REFRESH_CMD,
  108. State::DONE);
  109. return;
  110. case State::POST_DELAY:
  111. if (millis() < this->deadline_)
  112. return;
  113. this->set_state_(this->delay_return_state_);
  114. return;
  115. case State::WAIT_BUSY:
  116. this->step_wait_busy_();
  117. return;
  118. case State::DONE:
  119. ESP_LOGD("gxepd2", "Display update finished");
  120. this->set_state_(State::IDLE);
  121. return;
  122. }
  123. }
  124. private:
  125. enum class State {
  126. IDLE,
  127. RESET_LOW,
  128. RESET_LOW_WAIT,
  129. RESET_HIGH_WAIT,
  130. RESET_SETTLE,
  131. INIT_CMD,
  132. SET_WINDOW,
  133. WRITE_PLANE_START,
  134. WRITE_PLANE,
  135. REFRESH_CMD,
  136. POST_DELAY,
  137. WAIT_BUSY,
  138. DONE,
  139. };
  140. enum class CmdPhase { BEFORE_DELAY, ISSUE };
  141. // Every command byte and every data byte is its own SPI transaction, CS
  142. // toggled around each one - this looks wasteful, but it's mechanically
  143. // what GxEPD2_EPD::_writeCommand()/_writeData(uint8_t) do (see
  144. // vendor/src/GxEPD2_EPD.cpp), and matching the vendor cycle byte-for-byte
  145. // is the point of this engine.
  146. void write_byte_(uint8_t b, bool dc_high) {
  147. this->dc_->digital_write(dc_high);
  148. this->spi_->enable();
  149. this->cs_->digital_write(false);
  150. this->spi_->write_byte(b);
  151. this->cs_->digital_write(true);
  152. this->spi_->disable();
  153. }
  154. void write_command_(uint8_t cmd, const uint8_t *data, uint8_t data_len) {
  155. #if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
  156. // Command-level skeleton trace - deliberately left in (not gated to
  157. // VERY_VERBOSE) while diagnosing a real-hardware content bug: this is
  158. // the exact byte-for-byte counterpart to compare against a captured
  159. // epaper_spi trace on the same panel. Bulk plane data isn't logged
  160. // here (see step_write_plane_) - only the command skeleton matters for
  161. // that comparison.
  162. char buf[32] = {0};
  163. int n = 0;
  164. for (uint8_t i = 0; i < data_len && n < (int) sizeof(buf) - 3; i++)
  165. n += snprintf(buf + n, sizeof(buf) - n, "%02X ", data[i]);
  166. ESP_LOGD("gxepd2", "cmd 0x%02X data[%u]: %s", cmd, data_len, buf);
  167. #endif
  168. this->write_byte_(cmd, false);
  169. for (uint8_t i = 0; i < data_len; i++)
  170. this->write_byte_(data[i], true);
  171. }
  172. // Steps through one EpdCommand table one entry (and one delay/busy-wait,
  173. // if that entry has one) at a time, resuming in self_state after any
  174. // wait so the same call re-enters here for the next entry. Shared by
  175. // INIT_CMD and REFRESH_CMD - which table is being stepped is entirely
  176. // determined by which state called this.
  177. void step_sequence_(const EpdCommand *seq, uint8_t len, State self_state, State done_state) {
  178. if (this->cursor_ >= len) {
  179. this->cursor_ = 0;
  180. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  181. this->set_state_(done_state);
  182. return;
  183. }
  184. const EpdCommand &c = seq[this->cursor_];
  185. if (this->cmd_phase_ == CmdPhase::BEFORE_DELAY) {
  186. this->cmd_phase_ = CmdPhase::ISSUE;
  187. if (c.delay_before_ms > 0) {
  188. this->start_delay_(c.delay_before_ms, self_state);
  189. return;
  190. }
  191. }
  192. this->write_command_(c.cmd, c.data, c.data_len);
  193. this->cursor_++;
  194. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  195. if (c.wait_busy_after) {
  196. this->start_busy_wait_(self_state);
  197. } else if (c.delay_after_ms > 0) {
  198. this->start_delay_(c.delay_after_ms, self_state);
  199. }
  200. // else: stays in self_state; the next step() call processes the next entry.
  201. }
  202. // Writes up to BYTE_BUDGET bytes of the current plane per call, then
  203. // returns - this bound is what keeps a full-frame write (thousands of
  204. // bytes) from ever making one loop() tick run long, replacing the old
  205. // per-256-byte esp_task_wdt_reset()+delay(1) throttle with simply not
  206. // doing enough work in one call to need it.
  207. static constexpr uint32_t BYTE_BUDGET = 64;
  208. void step_write_plane_() {
  209. if (this->cursor_ == 0) {
  210. const uint8_t *buf = this->planes_[this->plane_];
  211. uint32_t histo[3] = {0, 0, 0}; // [0]=0x00, [1]=0xFF, [2]=other
  212. if (buf != nullptr) {
  213. for (uint32_t i = 0; i < this->plane_bytes_; i++) {
  214. if (buf[i] == 0x00)
  215. histo[0]++;
  216. else if (buf[i] == 0xFF)
  217. histo[1]++;
  218. else
  219. histo[2]++;
  220. }
  221. }
  222. 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",
  223. this->plane_, this->desc_->plane_write_cmd[this->plane_], (unsigned) this->plane_bytes_,
  224. this->desc_->plane_invert[this->plane_], buf != nullptr ? buf[0] : 0,
  225. buf != nullptr ? buf[this->plane_bytes_ - 1] : 0, (unsigned) histo[0], (unsigned) histo[1],
  226. (unsigned) histo[2]);
  227. this->write_byte_(this->desc_->plane_write_cmd[this->plane_], false);
  228. }
  229. const uint8_t *buf = this->planes_[this->plane_];
  230. bool invert = this->desc_->plane_invert[this->plane_];
  231. uint32_t end = this->cursor_ + BYTE_BUDGET;
  232. if (end > this->plane_bytes_)
  233. end = this->plane_bytes_;
  234. for (; this->cursor_ < end; this->cursor_++) {
  235. uint8_t b = buf != nullptr ? buf[this->cursor_] : 0xFF;
  236. this->write_byte_(invert ? static_cast<uint8_t>(~b) : b, true);
  237. }
  238. if (this->cursor_ >= this->plane_bytes_) {
  239. this->cursor_ = 0;
  240. this->plane_++;
  241. if (this->plane_ >= this->desc_->plane_count) {
  242. this->plane_ = 0;
  243. this->cursor_ = 0;
  244. this->cmd_phase_ = CmdPhase::BEFORE_DELAY;
  245. this->set_state_(State::REFRESH_CMD);
  246. }
  247. }
  248. }
  249. void start_delay_(uint16_t delay_ms, State return_to) {
  250. this->deadline_ = millis() + delay_ms;
  251. this->delay_return_state_ = return_to;
  252. this->set_state_(State::POST_DELAY);
  253. }
  254. void start_busy_wait_(State next) {
  255. this->busy_wait_next_ = next;
  256. this->busy_wait_deadline_ = millis() + this->desc_->busy_timeout_ms;
  257. this->set_state_(State::WAIT_BUSY);
  258. }
  259. void step_wait_busy_() {
  260. if (this->busy_ == nullptr) {
  261. this->set_state_(this->busy_wait_next_);
  262. return;
  263. }
  264. bool busy = this->busy_->digital_read() == this->desc_->busy_active_level;
  265. if (!busy) {
  266. this->set_state_(this->busy_wait_next_);
  267. return;
  268. }
  269. if (millis() >= this->busy_wait_deadline_) {
  270. ESP_LOGW("gxepd2", "Busy timeout waiting for panel");
  271. this->set_state_(this->busy_wait_next_);
  272. }
  273. }
  274. // Logs every state transition (only when the state actually changes -
  275. // WAIT_BUSY and POST_DELAY re-enter their own case on every step() call
  276. // while polling, which would otherwise spam this at loop() rate) so a
  277. // first hardware run can be checked against the vendor cycle it's meant
  278. // to replicate before this gets dialed back to WARN-only.
  279. void set_state_(State s) {
  280. if (s != this->state_)
  281. ESP_LOGD("gxepd2", "state: %s -> %s", state_name_(this->state_), state_name_(s));
  282. this->state_ = s;
  283. }
  284. static const char *state_name_(State s) {
  285. switch (s) {
  286. case State::IDLE:
  287. return "IDLE";
  288. case State::RESET_LOW:
  289. return "RESET_LOW";
  290. case State::RESET_LOW_WAIT:
  291. return "RESET_LOW_WAIT";
  292. case State::RESET_HIGH_WAIT:
  293. return "RESET_HIGH_WAIT";
  294. case State::RESET_SETTLE:
  295. return "RESET_SETTLE";
  296. case State::INIT_CMD:
  297. return "INIT_CMD";
  298. case State::SET_WINDOW:
  299. return "SET_WINDOW";
  300. case State::WRITE_PLANE_START:
  301. return "WRITE_PLANE_START";
  302. case State::WRITE_PLANE:
  303. return "WRITE_PLANE";
  304. case State::REFRESH_CMD:
  305. return "REFRESH_CMD";
  306. case State::POST_DELAY:
  307. return "POST_DELAY";
  308. case State::WAIT_BUSY:
  309. return "WAIT_BUSY";
  310. case State::DONE:
  311. return "DONE";
  312. }
  313. return "?";
  314. }
  315. const EpdPanelDescriptor *desc_{nullptr};
  316. GPIOPin *cs_{nullptr};
  317. GPIOPin *dc_{nullptr};
  318. GPIOPin *rst_{nullptr};
  319. GPIOPin *busy_{nullptr};
  320. EpdSPIDevice *spi_{nullptr};
  321. State state_{State::IDLE};
  322. uint32_t cursor_{0};
  323. CmdPhase cmd_phase_{CmdPhase::BEFORE_DELAY};
  324. uint8_t plane_{0};
  325. const uint8_t *planes_[2]{nullptr, nullptr};
  326. uint32_t plane_bytes_{0};
  327. uint32_t deadline_{0};
  328. State delay_return_state_{State::DONE};
  329. uint32_t busy_wait_deadline_{0};
  330. State busy_wait_next_{State::DONE};
  331. };
  332. } // namespace esphome::gxepd2