SPI.h 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  1. #pragma once
  2. // Minimal SPIClass/SPISettings substitute so GxEPD2 compiles under ESP-IDF,
  3. // backed by esphome::spi::SPIDevice underneath. GxEPD2 always drives
  4. // chip-select itself around each transfer, so this only needs to own the
  5. // clock/data lines - which is what lets a gxepd2 instance share one spi:
  6. // bus with other devices, each with its own SPIDevice registration.
  7. #include <cstdint>
  8. #include <esp_task_wdt.h>
  9. #include "esphome/core/hal.h"
  10. #include "esphome/components/spi/spi.h"
  11. class SPISettings {
  12. public:
  13. SPISettings() = default;
  14. SPISettings(uint32_t clock, uint8_t bit_order, uint8_t data_mode) {}
  15. };
  16. #define MSBFIRST 0
  17. #define SPI_MODE0 0
  18. class SPIClass : public esphome::spi::SPIDevice<esphome::spi::BIT_ORDER_MSB_FIRST, esphome::spi::CLOCK_POLARITY_LOW,
  19. esphome::spi::CLOCK_PHASE_LEADING, esphome::spi::DATA_RATE_4MHZ> {
  20. public:
  21. void begin() {}
  22. void end() {}
  23. void beginTransaction(SPISettings settings) { this->enable(); }
  24. void endTransaction() { this->disable(); }
  25. // GxEPD2 panels (all but the IT8951-based ones, which aren't supported -
  26. // see display.py) never read anything back over MISO; e-paper wiring
  27. // normally leaves it unconnected, and esphome's spi: bus then registers
  28. // this device as write-only. transfer_byte()/delegate transfer() ask for
  29. // a real bidirectional exchange regardless, which a write-only device
  30. // refuses outright - it doesn't just skip the read, it drops the write
  31. // too (see SPIDelegateHw::transfer in spi_esp_idf.cpp), silently bricking
  32. // every panel command. write_byte() only ever asks to write, so it works
  33. // whether or not MISO is wired.
  34. uint8_t transfer(uint8_t data) {
  35. // A full-frame refresh calls this thousands of times back-to-back (a
  36. // 4-color panel's write loop alone is tens of thousands of bytes), with
  37. // no yield anywhere in GxEPD2's own per-byte loops, and ESP-IDF's
  38. // polling-mode SPI transaction doesn't yield the scheduler while it
  39. // waits either. Left unchecked, that starves this core's idle task
  40. // long enough to trip ITS watchdog - a hard crash, not just a stall.
  41. // Resetting our own task's watchdog subscription doesn't stop the
  42. // idle-task starvation on its own, and a plain yield() just hands the
  43. // CPU right back to us since nothing else made us not-ready - only a
  44. // real blocking delay actually lets a lower-priority task like idle
  45. // run, so force one periodically. esp_task_wdt_reset() (not
  46. // esphome::App.feed_wdt(), whose rate-limiting is global and gets
  47. // satisfied by the main loop task's own frequent calls regardless of
  48. // whether this task's subscription has actually been reset) keeps
  49. // this task itself from tripping the watchdog across a long burst.
  50. if ((this->transfer_count_++ & 0xFF) == 0) {
  51. esp_task_wdt_reset();
  52. esphome::delay(1);
  53. }
  54. this->write_byte(data);
  55. return 0;
  56. }
  57. private:
  58. uint32_t transfer_count_{0};
  59. };
  60. // Unused: every gxepd2 instance overrides this via selectSPI() with its own
  61. // SPIClass member - see gxepd2_display.h.
  62. inline SPIClass SPI; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)