mirror of
https://github.com/esphome/esphome.git
synced 2024-11-26 08:55:22 +01: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:
parent
88ccd60a08
commit
726b0e73d9
14 changed files with 300 additions and 111 deletions
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue