cycle_engine.h 12 KB

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