#pragma once #include "esphome/core/application.h" #include "esphome/core/component.h" #include "esphome/core/hal.h" #include "esphome/core/log.h" #include #include #include #ifdef USE_ARDUINO #include #ifdef USE_RP2040 using SPIInterface = SPIClassRP2040 *; #else using SPIInterface = SPIClass *; #endif #endif #ifdef USE_ESP_IDF #include "driver/spi_master.h" using SPIInterface = spi_host_device_t; #endif // USE_ESP_IDF /** * Implementation of SPI Controller mode. */ namespace esphome { 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, }; /** * Modes mapping to clock phase and polarity. * */ enum SPIMode { MODE0 = 0, MODE1 = 1, MODE2 = 2, MODE3 = 3, }; /** The SPI clock signal frequency, which determines the transfer bit rate/second. * * 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_75KHZ = 75000, DATA_RATE_200KHZ = 200000, DATA_RATE_1MHZ = 1000000, DATA_RATE_2MHZ = 2000000, DATA_RATE_4MHZ = 4000000, DATA_RATE_5MHZ = 5000000, DATA_RATE_8MHZ = 8000000, DATA_RATE_10MHZ = 10000000, DATA_RATE_20MHZ = 20000000, DATA_RATE_40MHZ = 40000000, DATA_RATE_80MHZ = 80000000, }; /** * A pin to replace those that don't exist. */ class NullPin : public GPIOPin { friend class SPIComponent; friend class SPIDelegate; friend class Utility; public: void setup() override {} void pin_mode(gpio::Flags flags) override {} bool digital_read() override { return false; } void digital_write(bool value) override {} std::string dump_summary() const override { return std::string(); } protected: static GPIOPin *const NULL_PIN; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) // https://bugs.llvm.org/show_bug.cgi?id=48040 }; class Utility { public: static int get_pin_no(GPIOPin *pin) { if (pin == nullptr || !pin->is_internal()) return -1; if (((InternalGPIOPin *) pin)->is_inverted()) return -1; return ((InternalGPIOPin *) pin)->get_pin(); } static SPIMode get_mode(SPIClockPolarity polarity, SPIClockPhase phase) { if (polarity == CLOCK_POLARITY_HIGH) { return phase == CLOCK_PHASE_LEADING ? MODE2 : MODE3; } return phase == CLOCK_PHASE_LEADING ? MODE0 : MODE1; } static SPIClockPhase get_phase(SPIMode mode) { switch (mode) { case MODE0: case MODE2: return CLOCK_PHASE_LEADING; default: return CLOCK_PHASE_TRAILING; } } static SPIClockPolarity get_polarity(SPIMode mode) { switch (mode) { case MODE0: case MODE1: return CLOCK_POLARITY_LOW; default: return CLOCK_POLARITY_HIGH; } } }; class SPIDelegateDummy; // represents a device attached to an SPI bus, with a defined clock rate, mode and bit order. On Arduino this is // a thin wrapper over SPIClass. class SPIDelegate { friend class SPIClient; public: SPIDelegate() = default; SPIDelegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) : bit_order_(bit_order), data_rate_(data_rate), mode_(mode), cs_pin_(cs_pin) { if (this->cs_pin_ == nullptr) this->cs_pin_ = NullPin::NULL_PIN; this->cs_pin_->setup(); this->cs_pin_->digital_write(true); } virtual ~SPIDelegate(){}; // enable CS if configured. virtual void begin_transaction() { this->cs_pin_->digital_write(false); } // end the transaction virtual void end_transaction() { this->cs_pin_->digital_write(true); } // transfer one byte, return the byte that was read. virtual uint8_t transfer(uint8_t data) = 0; // transfer a buffer, replace the contents with read data virtual void transfer(uint8_t *ptr, size_t length) { this->transfer(ptr, ptr, length); } virtual void transfer(const uint8_t *txbuf, uint8_t *rxbuf, size_t length) { for (size_t i = 0; i != length; i++) rxbuf[i] = this->transfer(txbuf[i]); } /** * write a variable length data item, up to 16 bits. * @param data The data to send. Should be LSB-aligned (i.e. top bits will be discarded.) * @param num_bits The number of bits to send */ virtual void write(uint16_t data, size_t num_bits) { esph_log_e("spi_device", "variable length write not implemented"); } virtual void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, size_t length, uint8_t bus_width) { esph_log_e("spi_device", "write_cmd_addr_data not implemented"); } // write 16 bits virtual void write16(uint16_t data) { if (this->bit_order_ == BIT_ORDER_MSB_FIRST) { uint16_t buffer; buffer = (data >> 8) | (data << 8); this->write_array(reinterpret_cast(&buffer), 2); } else { this->write_array(reinterpret_cast(&data), 2); } } virtual void write_array16(const uint16_t *data, size_t length) { for (size_t i = 0; i != length; i++) { this->write16(data[i]); } } // write the contents of a buffer, ignore read data (buffer is unchanged.) virtual void write_array(const uint8_t *ptr, size_t length) { for (size_t i = 0; i != length; i++) this->transfer(ptr[i]); } // read into a buffer, write nulls virtual void read_array(uint8_t *ptr, size_t length) { for (size_t i = 0; i != length; i++) ptr[i] = this->transfer(0); } // check if device is ready virtual bool is_ready(); protected: SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; uint32_t data_rate_{1000000}; SPIMode mode_{MODE0}; GPIOPin *cs_pin_{NullPin::NULL_PIN}; static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) }; /** * A dummy SPIDelegate that complains if it's used. */ class SPIDelegateDummy : public SPIDelegate { public: SPIDelegateDummy() = default; uint8_t transfer(uint8_t data) override { return 0; } void end_transaction() override{}; void begin_transaction() override; }; /** * An implementation of SPI that relies only on software toggling of pins. * */ class SPIDelegateBitBash : public SPIDelegate { public: SPIDelegateBitBash(uint32_t clock, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, GPIOPin *clk_pin, GPIOPin *sdo_pin, GPIOPin *sdi_pin) : SPIDelegate(clock, bit_order, mode, cs_pin), clk_pin_(clk_pin), sdo_pin_(sdo_pin), sdi_pin_(sdi_pin) { // this calculation is pretty meaningless except at very low bit rates. this->wait_cycle_ = uint32_t(arch_get_cpu_freq_hz()) / this->data_rate_ / 2ULL; this->clock_polarity_ = Utility::get_polarity(this->mode_); this->clock_phase_ = Utility::get_phase(this->mode_); } uint8_t transfer(uint8_t data) override; void write(uint16_t data, size_t num_bits) override; void write16(uint16_t data) override { this->write(data, 16); }; protected: GPIOPin *clk_pin_; GPIOPin *sdo_pin_; GPIOPin *sdi_pin_; uint32_t last_transition_{0}; uint32_t wait_cycle_; SPIClockPolarity clock_polarity_; SPIClockPhase clock_phase_; void HOT cycle_clock_() { while (this->last_transition_ - arch_get_cpu_cycle_count() < this->wait_cycle_) continue; this->last_transition_ += this->wait_cycle_; } uint16_t transfer_(uint16_t data, size_t num_bits); }; class SPIBus { public: SPIBus() = default; SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {} virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) { return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_); } virtual bool is_hw() { return false; } protected: GPIOPin *clk_pin_{}; GPIOPin *sdo_pin_{}; GPIOPin *sdi_pin_{}; }; class SPIClient; class SPIComponent : public Component { public: SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, GPIOPin *cs_pin); void unregister_device(SPIClient *device); void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; } void set_miso(GPIOPin *sdi) { this->sdi_pin_ = sdi; } void set_mosi(GPIOPin *sdo) { this->sdo_pin_ = sdo; } void set_data_pins(std::vector pins) { this->data_pins_ = std::move(pins); } void set_interface(SPIInterface interface) { this->interface_ = interface; this->using_hw_ = true; } void set_interface_name(const char *name) { this->interface_name_ = name; } float get_setup_priority() const override { return setup_priority::BUS; } void setup() override; void dump_config() override; protected: GPIOPin *clk_pin_{nullptr}; GPIOPin *sdi_pin_{nullptr}; GPIOPin *sdo_pin_{nullptr}; std::vector data_pins_{}; SPIInterface interface_{}; bool using_hw_{false}; const char *interface_name_{nullptr}; SPIBus *spi_bus_{}; std::map devices_; static SPIBus *get_bus(SPIInterface interface, GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi, const std::vector &data_pins); }; using QuadSPIComponent = SPIComponent; /** * Base class for SPIDevice, un-templated. */ class SPIClient { public: SPIClient(SPIBitOrder bit_order, SPIMode mode, uint32_t data_rate) : bit_order_(bit_order), mode_(mode), data_rate_(data_rate) {} virtual void spi_setup() { esph_log_d("spi_device", "mode %u, data_rate %ukHz", (unsigned) this->mode_, (unsigned) (this->data_rate_ / 1000)); this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_); } virtual void spi_teardown() { this->parent_->unregister_device(this); this->delegate_ = SPIDelegate::NULL_DELEGATE; } bool spi_is_ready() { return this->delegate_->is_ready(); } protected: SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; SPIMode mode_{MODE0}; uint32_t data_rate_{1000000}; SPIComponent *parent_{nullptr}; GPIOPin *cs_{nullptr}; SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; }; /** * The SPIDevice is what components using the SPI will create. * * @tparam BIT_ORDER * @tparam CLOCK_POLARITY * @tparam CLOCK_PHASE * @tparam DATA_RATE */ template class SPIDevice : public SPIClient { public: SPIDevice() : SPIClient(BIT_ORDER, Utility::get_mode(CLOCK_POLARITY, CLOCK_PHASE), DATA_RATE) {} SPIDevice(SPIComponent *parent, GPIOPin *cs_pin) { this->set_spi_parent(parent); this->set_cs_pin(cs_pin); } void spi_setup() override { SPIClient::spi_setup(); } void spi_teardown() override { SPIClient::spi_teardown(); } void set_spi_parent(SPIComponent *parent) { this->parent_ = parent; } void set_cs_pin(GPIOPin *cs) { this->cs_ = cs; } void set_data_rate(uint32_t data_rate) { this->data_rate_ = data_rate; } void set_bit_order(SPIBitOrder order) { this->bit_order_ = order; } void set_mode(SPIMode mode) { this->mode_ = mode; } uint8_t read_byte() { return this->delegate_->transfer(0); } void read_array(uint8_t *data, size_t length) { return this->delegate_->read_array(data, length); } /** * Write a single data item, up to 32 bits. * @param data The data * @param num_bits The number of bits to write. The lower num_bits of data will be sent. */ void write(uint16_t data, size_t num_bits) { this->delegate_->write(data, num_bits); }; /* Write command, address and data. Command and address will be written as single-bit SPI, * data phase can be multiple bit (currently only 1 or 4) * @param cmd_bits Number of bits to write in the command phase * @param cmd The command value to write * @param addr_bits Number of bits to write in addr phase * @param address Address data * @param data Plain data bytes * @param length Number of data bytes * @param bus_width The number of data lines to use for the data phase. */ void write_cmd_addr_data(size_t cmd_bits, uint32_t cmd, size_t addr_bits, uint32_t address, const uint8_t *data, size_t length, uint8_t bus_width = 1) { this->delegate_->write_cmd_addr_data(cmd_bits, cmd, addr_bits, address, data, length, bus_width); } void write_byte(uint8_t data) { this->delegate_->write_array(&data, 1); } /** * Write the array data, replace with received data. * @param data * @param length */ void transfer_array(uint8_t *data, size_t length) { this->delegate_->transfer(data, length); } uint8_t transfer_byte(uint8_t data) { return this->delegate_->transfer(data); } /** Write 16 bit data. The driver will byte-swap if required. */ void write_byte16(uint16_t data) { this->delegate_->write16(data); } /** * Write an array of data as 16 bit values, byte-swapping if required. Use of this should be avoided as * it is horribly slow. * @param data * @param length */ void write_array16(const uint16_t *data, size_t length) { this->delegate_->write_array16(data, length); } void enable() { this->delegate_->begin_transaction(); } void disable() { this->delegate_->end_transaction(); } void write_array(const uint8_t *data, size_t length) { this->delegate_->write_array(data, length); } template void write_array(const std::array &data) { this->write_array(data.data(), N); } void write_array(const std::vector &data) { this->write_array(data.data(), data.size()); } template void transfer_array(std::array &data) { this->transfer_array(data.data(), N); } }; } // namespace spi } // namespace esphome