mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	Add more efficient SPI implementation (#622)
* Add more efficient SPI implementation * Lint * Add 200KHZ * Updates * Fix write_byte * Update from datasheet * Shift clock * Fix calculation
This commit is contained in:
		| @@ -82,7 +82,5 @@ void MAX31855Sensor::read_data_() { | |||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MAX31855Sensor::is_device_msb_first() { return true; } |  | ||||||
|  |  | ||||||
| }  // namespace max31855 | }  // namespace max31855 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,7 +7,10 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace max31855 { | namespace max31855 { | ||||||
|  |  | ||||||
| class MAX31855Sensor : public sensor::Sensor, public PollingComponent, public spi::SPIDevice { | class MAX31855Sensor : public sensor::Sensor, | ||||||
|  |                        public PollingComponent, | ||||||
|  |                        public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||||
|  |                                              spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_4MHZ> { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -16,8 +19,6 @@ class MAX31855Sensor : public sensor::Sensor, public PollingComponent, public sp | |||||||
|   void update() override; |   void update() override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool is_device_msb_first() override; |  | ||||||
|  |  | ||||||
|   void read_data_(); |   void read_data_(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -48,7 +48,5 @@ void MAX6675Sensor::read_data_() { | |||||||
|   this->status_clear_warning(); |   this->status_clear_warning(); | ||||||
| } | } | ||||||
|  |  | ||||||
| bool MAX6675Sensor::is_device_msb_first() { return true; } |  | ||||||
|  |  | ||||||
| }  // namespace max6675 | }  // namespace max6675 | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,7 +7,10 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace max6675 { | namespace max6675 { | ||||||
|  |  | ||||||
| class MAX6675Sensor : public sensor::Sensor, public PollingComponent, public spi::SPIDevice { | class MAX6675Sensor : public sensor::Sensor, | ||||||
|  |                       public PollingComponent, | ||||||
|  |                       public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING, | ||||||
|  |                                             spi::DATA_RATE_1KHZ> { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
| @@ -16,8 +19,6 @@ class MAX6675Sensor : public sensor::Sensor, public PollingComponent, public spi | |||||||
|   void update() override; |   void update() override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool is_device_msb_first() override; |  | ||||||
|  |  | ||||||
|   void read_data_(); |   void read_data_(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|   | |||||||
| @@ -155,7 +155,6 @@ void MAX7219Component::send_to_all_(uint8_t a_register, uint8_t data) { | |||||||
|     this->send_byte_(a_register, data); |     this->send_byte_(a_register, data); | ||||||
|   this->disable(); |   this->disable(); | ||||||
| } | } | ||||||
| bool MAX7219Component::is_device_msb_first() { return true; } |  | ||||||
| void MAX7219Component::update() { | void MAX7219Component::update() { | ||||||
|   for (uint8_t i = 0; i < this->num_chips_ * 8; i++) |   for (uint8_t i = 0; i < this->num_chips_ * 8; i++) | ||||||
|     this->buffer_[i] = 0; |     this->buffer_[i] = 0; | ||||||
|   | |||||||
| @@ -16,7 +16,9 @@ class MAX7219Component; | |||||||
|  |  | ||||||
| using max7219_writer_t = std::function<void(MAX7219Component &)>; | using max7219_writer_t = std::function<void(MAX7219Component &)>; | ||||||
|  |  | ||||||
| class MAX7219Component : public PollingComponent, public spi::SPIDevice { | class MAX7219Component : public PollingComponent, | ||||||
|  |                          public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||||
|  |                                                spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_1MHZ> { | ||||||
|  public: |  public: | ||||||
|   void set_writer(max7219_writer_t &&writer); |   void set_writer(max7219_writer_t &&writer); | ||||||
|  |  | ||||||
| @@ -54,7 +56,6 @@ class MAX7219Component : public PollingComponent, public spi::SPIDevice { | |||||||
|  protected: |  protected: | ||||||
|   void send_byte_(uint8_t a_register, uint8_t data); |   void send_byte_(uint8_t a_register, uint8_t data); | ||||||
|   void send_to_all_(uint8_t a_register, uint8_t data); |   void send_to_all_(uint8_t a_register, uint8_t data); | ||||||
|   bool is_device_msb_first() override; |  | ||||||
|  |  | ||||||
|   uint8_t intensity_{15};  /// Intensity of the display from 0 to 15 (most) |   uint8_t intensity_{15};  /// Intensity of the display from 0 to 15 (most) | ||||||
|   uint8_t num_chips_{1}; |   uint8_t num_chips_{1}; | ||||||
|   | |||||||
| @@ -335,7 +335,6 @@ bool PN532::wait_ready_() { | |||||||
|   return true; |   return true; | ||||||
| } | } | ||||||
|  |  | ||||||
| bool PN532::is_device_msb_first() { return false; } |  | ||||||
| void PN532::dump_config() { | void PN532::dump_config() { | ||||||
|   ESP_LOGCONFIG(TAG, "PN532:"); |   ESP_LOGCONFIG(TAG, "PN532:"); | ||||||
|   switch (this->error_code_) { |   switch (this->error_code_) { | ||||||
|   | |||||||
| @@ -11,7 +11,9 @@ namespace pn532 { | |||||||
| class PN532BinarySensor; | class PN532BinarySensor; | ||||||
| class PN532Trigger; | class PN532Trigger; | ||||||
|  |  | ||||||
| class PN532 : public PollingComponent, public spi::SPIDevice { | class PN532 : public PollingComponent, | ||||||
|  |               public spi::SPIDevice<spi::BIT_ORDER_LSB_FIRST, spi::CLOCK_POLARITY_LOW, spi::CLOCK_PHASE_LEADING, | ||||||
|  |                                     spi::DATA_RATE_1MHZ> { | ||||||
|  public: |  public: | ||||||
|   void setup() override; |   void setup() override; | ||||||
|  |  | ||||||
| @@ -26,8 +28,6 @@ class PN532 : public PollingComponent, public spi::SPIDevice { | |||||||
|   void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } |   void register_trigger(PN532Trigger *trig) { this->triggers_.push_back(trig); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   bool is_device_msb_first() override; |  | ||||||
|  |  | ||||||
|   /// Write the full command given in data to the PN532 |   /// Write the full command given in data to the PN532 | ||||||
|   void pn532_write_command_(const std::vector<uint8_t> &data); |   void pn532_write_command_(const std::vector<uint8_t> &data); | ||||||
|   bool pn532_write_command_check_ack_(const std::vector<uint8_t> &data); |   bool pn532_write_command_check_ack_(const std::vector<uint8_t> &data); | ||||||
|   | |||||||
| @@ -8,73 +8,19 @@ namespace spi { | |||||||
|  |  | ||||||
| static const char *TAG = "spi"; | static const char *TAG = "spi"; | ||||||
|  |  | ||||||
| void ICACHE_RAM_ATTR HOT SPIComponent::write_byte(uint8_t data) { | template<SPIClockPolarity CLOCK_POLARITY> void SPIComponent::enable(GPIOPin *cs, uint32_t wait_cycle) { | ||||||
|   uint8_t send_bits = data; |   this->debug_enable(cs->get_pin()); | ||||||
|   if (this->msb_first_) |   this->wait_cycle_ = wait_cycle; | ||||||
|     send_bits = reverse_bits_8(data); |  | ||||||
|  |  | ||||||
|   this->clk_->digital_write(true); |   this->clk_->digital_write(CLOCK_POLARITY); | ||||||
|   if (!this->high_speed_) |  | ||||||
|     delayMicroseconds(5); |  | ||||||
|  |  | ||||||
|   for (size_t i = 0; i < 8; i++) { |  | ||||||
|     if (!this->high_speed_) |  | ||||||
|       delayMicroseconds(5); |  | ||||||
|     this->clk_->digital_write(false); |  | ||||||
|  |  | ||||||
|     // sampling on leading edge |  | ||||||
|     this->mosi_->digital_write(send_bits & (1 << i)); |  | ||||||
|     if (!this->high_speed_) |  | ||||||
|       delayMicroseconds(5); |  | ||||||
|     this->clk_->digital_write(true); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "    Wrote 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| uint8_t ICACHE_RAM_ATTR HOT SPIComponent::read_byte() { |  | ||||||
|   this->clk_->digital_write(true); |  | ||||||
|  |  | ||||||
|   uint8_t data = 0; |  | ||||||
|   for (size_t i = 0; i < 8; i++) { |  | ||||||
|     if (!this->high_speed_) |  | ||||||
|       delayMicroseconds(5); |  | ||||||
|     data |= uint8_t(this->miso_->digital_read()) << i; |  | ||||||
|     this->clk_->digital_write(false); |  | ||||||
|     if (!this->high_speed_) |  | ||||||
|       delayMicroseconds(5); |  | ||||||
|     this->clk_->digital_write(true); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   if (this->msb_first_) { |  | ||||||
|     data = reverse_bits_8(data); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   ESP_LOGVV(TAG, "    Received 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(data), data); |  | ||||||
|  |  | ||||||
|   return data; |  | ||||||
| } |  | ||||||
| void ICACHE_RAM_ATTR HOT SPIComponent::read_array(uint8_t *data, size_t length) { |  | ||||||
|   for (size_t i = 0; i < length; i++) |  | ||||||
|     data[i] = this->read_byte(); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ICACHE_RAM_ATTR HOT SPIComponent::write_array(uint8_t *data, size_t length) { |  | ||||||
|   for (size_t i = 0; i < length; i++) { |  | ||||||
|     App.feed_wdt(); |  | ||||||
|     this->write_byte(data[i]); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| void ICACHE_RAM_ATTR HOT SPIComponent::enable(GPIOPin *cs, bool msb_first, bool high_speed) { |  | ||||||
|   ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", cs->get_pin()); |  | ||||||
|   cs->digital_write(false); |  | ||||||
|  |  | ||||||
|   this->active_cs_ = cs; |   this->active_cs_ = cs; | ||||||
|   this->msb_first_ = msb_first; |   this->active_cs_->digital_write(false); | ||||||
|   this->high_speed_ = high_speed; |  | ||||||
| } | } | ||||||
|  |  | ||||||
|  | template void SPIComponent::enable<CLOCK_POLARITY_LOW>(GPIOPin *cs, uint32_t wait_cycle); | ||||||
|  | template void SPIComponent::enable<CLOCK_POLARITY_HIGH>(GPIOPin *cs, uint32_t wait_cycle); | ||||||
|  |  | ||||||
| void ICACHE_RAM_ATTR HOT SPIComponent::disable() { | void ICACHE_RAM_ATTR HOT SPIComponent::disable() { | ||||||
|   ESP_LOGVV(TAG, "Disabling SPI Chip on pin %u...", this->active_cs_->get_pin()); |   ESP_LOGVV(TAG, "Disabling SPI Chip on pin %u...", this->active_cs_->get_pin()); | ||||||
|   this->active_cs_->digital_write(true); |   this->active_cs_->digital_write(true); | ||||||
| @@ -100,5 +46,150 @@ void SPIComponent::dump_config() { | |||||||
| } | } | ||||||
| float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } | float SPIComponent::get_setup_priority() const { return setup_priority::BUS; } | ||||||
|  |  | ||||||
|  | void SPIComponent::debug_tx(uint8_t value) { | ||||||
|  |   ESP_LOGVV(TAG, "    TX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); | ||||||
|  | } | ||||||
|  | void SPIComponent::debug_rx(uint8_t value) { | ||||||
|  |   ESP_LOGVV(TAG, "    RX 0b" BYTE_TO_BINARY_PATTERN " (0x%02X)", BYTE_TO_BINARY(value), value); | ||||||
|  | } | ||||||
|  | void SPIComponent::debug_enable(uint8_t pin) { ESP_LOGVV(TAG, "Enabling SPI Chip on pin %u...", pin); } | ||||||
|  |  | ||||||
|  | void SPIComponent::cycle_clock_(bool value) { | ||||||
|  |   uint32_t start = ESP.getCycleCount(); | ||||||
|  |   while (start - ESP.getCycleCount() < this->wait_cycle_) | ||||||
|  |     ; | ||||||
|  |   this->clk_->digital_write(value); | ||||||
|  |   start += this->wait_cycle_; | ||||||
|  |   while (start - ESP.getCycleCount() < this->wait_cycle_) | ||||||
|  |     ; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // NOLINTNEXTLINE | ||||||
|  | #pragma GCC optimize("unroll-loops") | ||||||
|  | // NOLINTNEXTLINE | ||||||
|  | #pragma GCC optimize("O2") | ||||||
|  |  | ||||||
|  | template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE> | ||||||
|  | uint8_t HOT SPIComponent::transfer_(uint8_t data) { | ||||||
|  |   // Clock starts out at idle level | ||||||
|  |   this->clk_->digital_write(CLOCK_POLARITY); | ||||||
|  |   uint8_t out_data = 0; | ||||||
|  |  | ||||||
|  |   for (uint8_t i = 0; i < 8; i++) { | ||||||
|  |     uint8_t shift; | ||||||
|  |     if (BIT_ORDER == BIT_ORDER_MSB_FIRST) | ||||||
|  |       shift = 7 - i; | ||||||
|  |     else | ||||||
|  |       shift = i; | ||||||
|  |  | ||||||
|  |     if (CLOCK_PHASE == CLOCK_PHASE_LEADING) { | ||||||
|  |       // sampling on leading edge | ||||||
|  |       if (WRITE) { | ||||||
|  |         this->mosi_->digital_write(data & (1 << shift)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // SAMPLE! | ||||||
|  |       this->cycle_clock_(!CLOCK_POLARITY); | ||||||
|  |  | ||||||
|  |       if (READ) { | ||||||
|  |         out_data |= uint8_t(this->miso_->digital_read()) << shift; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       this->cycle_clock_(CLOCK_POLARITY); | ||||||
|  |     } else { | ||||||
|  |       // sampling on trailing edge | ||||||
|  |       this->cycle_clock_(!CLOCK_POLARITY); | ||||||
|  |  | ||||||
|  |       if (WRITE) { | ||||||
|  |         this->mosi_->digital_write(data & (1 << shift)); | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       // SAMPLE! | ||||||
|  |       this->cycle_clock_(CLOCK_POLARITY); | ||||||
|  |  | ||||||
|  |       if (READ) { | ||||||
|  |         out_data |= uint8_t(this->miso_->digital_read()) << shift; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | #ifdef ESPHOME_LOG_HAS_VERY_VERBOSE | ||||||
|  |   if (WRITE) { | ||||||
|  |     SPIComponent::debug_tx(data); | ||||||
|  |   } | ||||||
|  |   if (READ) { | ||||||
|  |     SPIComponent::debug_rx(out_data); | ||||||
|  |   } | ||||||
|  | #endif | ||||||
|  |  | ||||||
|  |   App.feed_wdt(); | ||||||
|  |  | ||||||
|  |   return out_data; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | // Generate with (py3): | ||||||
|  | // | ||||||
|  | // from itertools import product | ||||||
|  | // bit_orders = ['BIT_ORDER_LSB_FIRST', 'BIT_ORDER_MSB_FIRST'] | ||||||
|  | // clock_pols = ['CLOCK_POLARITY_LOW', 'CLOCK_POLARITY_HIGH'] | ||||||
|  | // clock_phases = ['CLOCK_PHASE_LEADING', 'CLOCK_PHASE_TRAILING'] | ||||||
|  | // reads = [False, True] | ||||||
|  | // writes = [False, True] | ||||||
|  | // cpp_bool = {False: 'false', True: 'true'} | ||||||
|  | // for b, cpol, cph, r, w in product(bit_orders, clock_pols, clock_phases, reads, writes): | ||||||
|  | //     if not r and not w: | ||||||
|  | //         continue | ||||||
|  | //     print(f"template uint8_t SPIComponent::transfer_<{b}, {cpol}, {cph}, {cpp_bool[r]}, {cpp_bool[w]}>(uint8_t | ||||||
|  | //     data);") | ||||||
|  |  | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_LSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_LEADING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_LOW, CLOCK_PHASE_TRAILING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_LEADING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, false, true>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, false>( | ||||||
|  |     uint8_t data); | ||||||
|  | template uint8_t SPIComponent::transfer_<BIT_ORDER_MSB_FIRST, CLOCK_POLARITY_HIGH, CLOCK_PHASE_TRAILING, true, true>( | ||||||
|  |     uint8_t data); | ||||||
|  |  | ||||||
| }  // namespace spi | }  // namespace spi | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -6,6 +6,56 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace spi { | namespace spi { | ||||||
|  |  | ||||||
|  | /// The bit-order for SPI devices. This defines how the data read from and written to the device is interpreted. | ||||||
|  | enum SPIBitOrder { | ||||||
|  |   /// The least significant bit is transmitted/received first. | ||||||
|  |   BIT_ORDER_LSB_FIRST, | ||||||
|  |   /// The most significant bit is transmitted/received first. | ||||||
|  |   BIT_ORDER_MSB_FIRST, | ||||||
|  | }; | ||||||
|  | /** The SPI clock signal polarity, | ||||||
|  |  * | ||||||
|  |  * This defines how the clock signal is used. Flipping this effectively inverts the clock signal. | ||||||
|  |  */ | ||||||
|  | enum SPIClockPolarity { | ||||||
|  |   /** The clock signal idles on LOW. (CPOL=0) | ||||||
|  |    * | ||||||
|  |    * A rising edge means a leading edge for the clock. | ||||||
|  |    */ | ||||||
|  |   CLOCK_POLARITY_LOW = false, | ||||||
|  |   /** The clock signal idles on HIGH. (CPOL=1) | ||||||
|  |    * | ||||||
|  |    * A falling edge means a trailing edge for the clock. | ||||||
|  |    */ | ||||||
|  |   CLOCK_POLARITY_HIGH = true, | ||||||
|  | }; | ||||||
|  | /** The SPI clock signal phase. | ||||||
|  |  * | ||||||
|  |  * This defines when the data signals are sampled. Most SPI devices use the LEADING clock phase. | ||||||
|  |  */ | ||||||
|  | enum SPIClockPhase { | ||||||
|  |   /// The data is sampled on a leading clock edge. (CPHA=0) | ||||||
|  |   CLOCK_PHASE_LEADING, | ||||||
|  |   /// The data is sampled on a trailing clock edge. (CPHA=1) | ||||||
|  |   CLOCK_PHASE_TRAILING, | ||||||
|  | }; | ||||||
|  | /** The SPI clock signal data rate. This defines for what duration the clock signal is HIGH/LOW. | ||||||
|  |  * So effectively the rate of bytes can be calculated using | ||||||
|  |  * | ||||||
|  |  * effective_byte_rate = spi_data_rate / 16 | ||||||
|  |  * | ||||||
|  |  * Implementations can use the pre-defined constants here, or use an integer in the template definition | ||||||
|  |  * to manually use a specific data rate. | ||||||
|  |  */ | ||||||
|  | enum SPIDataRate : uint32_t { | ||||||
|  |   DATA_RATE_1KHZ = 1000, | ||||||
|  |   DATA_RATE_200KHZ = 200000, | ||||||
|  |   DATA_RATE_1MHZ = 1000000, | ||||||
|  |   DATA_RATE_2MHZ = 2000000, | ||||||
|  |   DATA_RATE_4MHZ = 4000000, | ||||||
|  |   DATA_RATE_8MHZ = 8000000, | ||||||
|  | }; | ||||||
|  |  | ||||||
| class SPIComponent : public Component { | class SPIComponent : public Component { | ||||||
|  public: |  public: | ||||||
|   void set_clk(GPIOPin *clk) { clk_ = clk; } |   void set_clk(GPIOPin *clk) { clk_ = clk; } | ||||||
| @@ -16,59 +66,117 @@ class SPIComponent : public Component { | |||||||
|  |  | ||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|  |  | ||||||
|   uint8_t read_byte(); |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> uint8_t read_byte() { | ||||||
|  |     return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, false>(0x00); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void read_array(uint8_t *data, size_t length); |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> | ||||||
|  |   void read_array(uint8_t *data, size_t length) { | ||||||
|  |     for (size_t i = 0; i < length; i++) { | ||||||
|  |       data[i] = this->read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void write_byte(uint8_t data); |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> | ||||||
|  |   void write_byte(uint8_t data) { | ||||||
|  |     this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, false, true>(data); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void write_array(uint8_t *data, size_t length); |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> | ||||||
|  |   void write_array(const uint8_t *data, size_t length) { | ||||||
|  |     for (size_t i = 0; i < length; i++) { | ||||||
|  |       this->write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void enable(GPIOPin *cs, bool msb_first, bool high_speed); |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> | ||||||
|  |   uint8_t transfer_byte(uint8_t data) { | ||||||
|  |     return this->transfer_<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE, true, true>(data); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE> | ||||||
|  |   void transfer_array(uint8_t *data, size_t length) { | ||||||
|  |     for (size_t i = 0; i < length; i++) { | ||||||
|  |       data[i] = this->transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data[i]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<SPIClockPolarity CLOCK_POLARITY> void enable(GPIOPin *cs, uint32_t wait_cycle); | ||||||
|  |  | ||||||
|   void disable(); |   void disable(); | ||||||
|  |  | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|  |   inline void cycle_clock_(bool value); | ||||||
|  |  | ||||||
|  |   static void debug_enable(uint8_t pin); | ||||||
|  |   static void debug_tx(uint8_t value); | ||||||
|  |   static void debug_rx(uint8_t value); | ||||||
|  |  | ||||||
|  |   template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, bool READ, bool WRITE> | ||||||
|  |   uint8_t transfer_(uint8_t data); | ||||||
|  |  | ||||||
|   GPIOPin *clk_; |   GPIOPin *clk_; | ||||||
|   GPIOPin *miso_{nullptr}; |   GPIOPin *miso_{nullptr}; | ||||||
|   GPIOPin *mosi_{nullptr}; |   GPIOPin *mosi_{nullptr}; | ||||||
|   GPIOPin *active_cs_{nullptr}; |   GPIOPin *active_cs_{nullptr}; | ||||||
|   bool msb_first_{true}; |   uint32_t wait_cycle_; | ||||||
|   bool high_speed_{false}; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | template<SPIBitOrder BIT_ORDER, SPIClockPolarity CLOCK_POLARITY, SPIClockPhase CLOCK_PHASE, SPIDataRate DATA_RATE> | ||||||
| class SPIDevice { | class SPIDevice { | ||||||
|  public: |  public: | ||||||
|   SPIDevice() = default; |   SPIDevice() = default; | ||||||
|   SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {} |   SPIDevice(SPIComponent *parent, GPIOPin *cs) : parent_(parent), cs_(cs) {} | ||||||
|  |  | ||||||
|   void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; } |   void set_spi_parent(SPIComponent *parent) { parent_ = parent; } | ||||||
|   void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } |   void set_cs_pin(GPIOPin *cs) { cs_ = cs; } | ||||||
|  |  | ||||||
|   void spi_setup() { |   void spi_setup() { | ||||||
|     this->cs_->setup(); |     this->cs_->setup(); | ||||||
|     this->cs_->digital_write(true); |     this->cs_->digital_write(true); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void enable() { this->parent_->enable(this->cs_, this->is_device_msb_first(), this->is_device_high_speed()); } |   void enable() { this->parent_->template enable<CLOCK_POLARITY>(this->cs_, uint32_t(F_CPU) / DATA_RATE / 2ULL); } | ||||||
|  |  | ||||||
|   void disable() { this->parent_->disable(); } |   void disable() { this->parent_->disable(); } | ||||||
|  |  | ||||||
|   uint8_t read_byte() { return this->parent_->read_byte(); } |   uint8_t read_byte() { return this->parent_->template read_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(); } | ||||||
|  |  | ||||||
|   void read_array(uint8_t *data, size_t length) { return this->parent_->read_array(data, length); } |   void read_array(uint8_t *data, size_t length) { | ||||||
|  |     return this->parent_->template read_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void write_byte(uint8_t data) { return this->parent_->write_byte(data); } |   template<size_t N> std::array<uint8_t, N> read_array() { | ||||||
|  |     std::array<uint8_t, N> data; | ||||||
|  |     this->read_array(data.data(), N); | ||||||
|  |     return data; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   void write_array(uint8_t *data, size_t length) { this->parent_->write_array(data, length); } |   void write_byte(uint8_t data) { | ||||||
|  |     return this->parent_->template write_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void write_array(const uint8_t *data, size_t length) { | ||||||
|  |     this->parent_->template write_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<size_t N> void write_array(const std::array<uint8_t, N> &data) { this->write_array(data.data(), N); } | ||||||
|  |  | ||||||
|  |   void write_array(const std::vector<uint8_t> &data) { this->write_array(data.data(), data.size()); } | ||||||
|  |  | ||||||
|  |   uint8_t transfer_byte(uint8_t data) { | ||||||
|  |     return this->parent_->template transfer_byte<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   void transfer_array(uint8_t *data, size_t length) { | ||||||
|  |     this->parent_->template transfer_array<BIT_ORDER, CLOCK_POLARITY, CLOCK_PHASE>(data, length); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   template<size_t N> void transfer_array(std::array<uint8_t, N> &data) { this->transfer_array(data.data(), N); } | ||||||
|  |  | ||||||
|  protected: |  protected: | ||||||
|   virtual bool is_device_msb_first() = 0; |  | ||||||
|  |  | ||||||
|   virtual bool is_device_high_speed() { return false; } |  | ||||||
|  |  | ||||||
|   SPIComponent *parent_{nullptr}; |   SPIComponent *parent_{nullptr}; | ||||||
|   GPIOPin *cs_{nullptr}; |   GPIOPin *cs_{nullptr}; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -7,7 +7,6 @@ namespace ssd1306_spi { | |||||||
|  |  | ||||||
| static const char *TAG = "ssd1306_spi"; | static const char *TAG = "ssd1306_spi"; | ||||||
|  |  | ||||||
| bool SPISSD1306::is_device_msb_first() { return true; } |  | ||||||
| void SPISSD1306::setup() { | void SPISSD1306::setup() { | ||||||
|   ESP_LOGCONFIG(TAG, "Setting up SPI SSD1306..."); |   ESP_LOGCONFIG(TAG, "Setting up SPI SSD1306..."); | ||||||
|   this->spi_setup(); |   this->spi_setup(); | ||||||
| @@ -52,7 +51,6 @@ void HOT SPISSD1306::write_display_data() { | |||||||
|     this->disable(); |     this->disable(); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| bool SPISSD1306::is_device_high_speed() { return true; } |  | ||||||
|  |  | ||||||
| }  // namespace ssd1306_spi | }  // namespace ssd1306_spi | ||||||
| }  // namespace esphome | }  // namespace esphome | ||||||
|   | |||||||
| @@ -7,7 +7,9 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace ssd1306_spi { | namespace ssd1306_spi { | ||||||
|  |  | ||||||
| class SPISSD1306 : public ssd1306_base::SSD1306, public spi::SPIDevice { | class SPISSD1306 : public ssd1306_base::SSD1306, | ||||||
|  |                    public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH, spi::CLOCK_PHASE_TRAILING, | ||||||
|  |                                          spi::DATA_RATE_8MHZ> { | ||||||
|  public: |  public: | ||||||
|   void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } |   void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } | ||||||
|  |  | ||||||
| @@ -19,8 +21,6 @@ class SPISSD1306 : public ssd1306_base::SSD1306, public spi::SPIDevice { | |||||||
|   void command(uint8_t value) override; |   void command(uint8_t value) override; | ||||||
|  |  | ||||||
|   void write_display_data() override; |   void write_display_data() override; | ||||||
|   bool is_device_msb_first() override; |  | ||||||
|   bool is_device_high_speed() override; |  | ||||||
|  |  | ||||||
|   GPIOPin *dc_pin_; |   GPIOPin *dc_pin_; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -42,7 +42,6 @@ void WaveshareEPaper::data(uint8_t value) { | |||||||
|   this->write_byte(value); |   this->write_byte(value); | ||||||
|   this->end_data_(); |   this->end_data_(); | ||||||
| } | } | ||||||
| bool WaveshareEPaper::is_device_msb_first() { return true; } |  | ||||||
| bool WaveshareEPaper::wait_until_idle_() { | bool WaveshareEPaper::wait_until_idle_() { | ||||||
|   if (this->busy_pin_ == nullptr) { |   if (this->busy_pin_ == nullptr) { | ||||||
|     return true; |     return true; | ||||||
| @@ -81,7 +80,6 @@ void HOT WaveshareEPaper::draw_absolute_pixel_internal(int x, int y, int color) | |||||||
|     this->buffer_[pos] &= ~(0x80 >> subpos); |     this->buffer_[pos] &= ~(0x80 >> subpos); | ||||||
| } | } | ||||||
| uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; } | uint32_t WaveshareEPaper::get_buffer_length_() { return this->get_width_internal() * this->get_height_internal() / 8u; } | ||||||
| bool WaveshareEPaper::is_device_high_speed() { return true; } |  | ||||||
| void WaveshareEPaper::start_command_() { | void WaveshareEPaper::start_command_() { | ||||||
|   this->dc_pin_->digital_write(false); |   this->dc_pin_->digital_write(false); | ||||||
|   this->enable(); |   this->enable(); | ||||||
| @@ -495,7 +493,6 @@ void HOT WaveshareEPaper4P2In::display() { | |||||||
| } | } | ||||||
| int WaveshareEPaper4P2In::get_width_internal() { return 400; } | int WaveshareEPaper4P2In::get_width_internal() { return 400; } | ||||||
| int WaveshareEPaper4P2In::get_height_internal() { return 300; } | int WaveshareEPaper4P2In::get_height_internal() { return 300; } | ||||||
| bool WaveshareEPaper4P2In::is_device_high_speed() { return false; } |  | ||||||
| void WaveshareEPaper4P2In::dump_config() { | void WaveshareEPaper4P2In::dump_config() { | ||||||
|   LOG_DISPLAY("", "Waveshare E-Paper", this); |   LOG_DISPLAY("", "Waveshare E-Paper", this); | ||||||
|   ESP_LOGCONFIG(TAG, "  Model: 4.2in"); |   ESP_LOGCONFIG(TAG, "  Model: 4.2in"); | ||||||
|   | |||||||
| @@ -7,14 +7,16 @@ | |||||||
| namespace esphome { | namespace esphome { | ||||||
| namespace waveshare_epaper { | namespace waveshare_epaper { | ||||||
|  |  | ||||||
| class WaveshareEPaper : public PollingComponent, public spi::SPIDevice, public display::DisplayBuffer { | class WaveshareEPaper : public PollingComponent, | ||||||
|  |                         public display::DisplayBuffer, | ||||||
|  |                         public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_LOW, | ||||||
|  |                                               spi::CLOCK_PHASE_LEADING, spi::DATA_RATE_2MHZ> { | ||||||
|  public: |  public: | ||||||
|   void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } |   void set_dc_pin(GPIOPin *dc_pin) { dc_pin_ = dc_pin; } | ||||||
|   float get_setup_priority() const override; |   float get_setup_priority() const override; | ||||||
|   void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } |   void set_reset_pin(GPIOPin *reset) { this->reset_pin_ = reset; } | ||||||
|   void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } |   void set_busy_pin(GPIOPin *busy) { this->busy_pin_ = busy; } | ||||||
|  |  | ||||||
|   bool is_device_msb_first() override; |  | ||||||
|   void command(uint8_t value); |   void command(uint8_t value); | ||||||
|   void data(uint8_t value); |   void data(uint8_t value); | ||||||
|  |  | ||||||
| @@ -51,8 +53,6 @@ class WaveshareEPaper : public PollingComponent, public spi::SPIDevice, public d | |||||||
|  |  | ||||||
|   uint32_t get_buffer_length_(); |   uint32_t get_buffer_length_(); | ||||||
|  |  | ||||||
|   bool is_device_high_speed() override; |  | ||||||
|  |  | ||||||
|   void start_command_(); |   void start_command_(); | ||||||
|   void end_command_(); |   void end_command_(); | ||||||
|   void start_data_(); |   void start_data_(); | ||||||
| @@ -166,8 +166,6 @@ class WaveshareEPaper4P2In : public WaveshareEPaper { | |||||||
|   int get_width_internal() override; |   int get_width_internal() override; | ||||||
|  |  | ||||||
|   int get_height_internal() override; |   int get_height_internal() override; | ||||||
|  |  | ||||||
|   bool is_device_high_speed() override; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| class WaveshareEPaper7P5In : public WaveshareEPaper { | class WaveshareEPaper7P5In : public WaveshareEPaper { | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user