[spi] Enable >6 devices with ESP-IDF (#9128)

This commit is contained in:
Clyde Stubbs 2025-06-21 07:55:08 +10:00 committed by GitHub
parent b693b8ccb1
commit 169db9cc0a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 75 additions and 41 deletions

View File

@ -472,3 +472,4 @@ async def to_code(config):
cg.add(var.set_writer(lambda_)) cg.add(var.set_writer(lambda_))
await display.register_display(var, config) await display.register_display(var, config)
await spi.register_spi_device(var, config) await spi.register_spi_device(var, config)
cg.add(var.set_write_only(True))

View File

@ -79,6 +79,7 @@ CONF_SPI_MODE = "spi_mode"
CONF_FORCE_SW = "force_sw" CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface" CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index" CONF_INTERFACE_INDEX = "interface_index"
CONF_RELEASE_DEVICE = "release_device"
TYPE_SINGLE = "single" TYPE_SINGLE = "single"
TYPE_QUAD = "quad" TYPE_QUAD = "quad"
TYPE_OCTAL = "octal" TYPE_OCTAL = "octal"
@ -378,6 +379,7 @@ def spi_device_schema(
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum( cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
SPI_MODE_OPTIONS, upper=True SPI_MODE_OPTIONS, upper=True
), ),
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_with_esp_idf),
} }
if cs_pin_required: if cs_pin_required:
schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema schema[cv.Required(CONF_CS_PIN)] = pins.gpio_output_pin_schema
@ -389,13 +391,15 @@ def spi_device_schema(
async def register_spi_device(var, config): async def register_spi_device(var, config):
parent = await cg.get_variable(config[CONF_SPI_ID]) parent = await cg.get_variable(config[CONF_SPI_ID])
cg.add(var.set_spi_parent(parent)) cg.add(var.set_spi_parent(parent))
if CONF_CS_PIN in config: if cs_pin := config.get(CONF_CS_PIN):
pin = await cg.gpio_pin_expression(config[CONF_CS_PIN]) pin = await cg.gpio_pin_expression(cs_pin)
cg.add(var.set_cs_pin(pin)) cg.add(var.set_cs_pin(pin))
if CONF_DATA_RATE in config: if data_rate := config.get(CONF_DATA_RATE):
cg.add(var.set_data_rate(config[CONF_DATA_RATE])) cg.add(var.set_data_rate(data_rate))
if CONF_SPI_MODE in config: if spi_mode := config.get(CONF_SPI_MODE):
cg.add(var.set_mode(config[CONF_SPI_MODE])) cg.add(var.set_mode(spi_mode))
if release_device := config.get(CONF_RELEASE_DEVICE):
cg.add(var.set_release_device(release_device))
def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool): def final_validate_device_schema(name: str, *, require_mosi: bool, require_miso: bool):

View File

@ -16,12 +16,13 @@ bool SPIDelegate::is_ready() { return true; }
GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) GPIOPin *const NullPin::NULL_PIN = new NullPin(); // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, SPIDelegate *SPIComponent::register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
GPIOPin *cs_pin) { GPIOPin *cs_pin, bool release_device, bool write_only) {
if (this->devices_.count(device) != 0) { if (this->devices_.count(device) != 0) {
ESP_LOGE(TAG, "Device already registered"); ESP_LOGE(TAG, "Device already registered");
return this->devices_[device]; return this->devices_[device];
} }
SPIDelegate *delegate = this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin); // NOLINT SPIDelegate *delegate =
this->spi_bus_->get_delegate(data_rate, bit_order, mode, cs_pin, release_device, write_only); // NOLINT
this->devices_[device] = delegate; this->devices_[device] = delegate;
return delegate; return delegate;
} }

View File

@ -317,7 +317,8 @@ class SPIBus {
SPIBus(GPIOPin *clk, GPIOPin *sdo, GPIOPin *sdi) : clk_pin_(clk), sdo_pin_(sdo), sdi_pin_(sdi) {} 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) { virtual SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
bool release_device, bool write_only) {
return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_); return new SPIDelegateBitBash(data_rate, bit_order, mode, cs_pin, this->clk_pin_, this->sdo_pin_, this->sdi_pin_);
} }
@ -334,7 +335,7 @@ class SPIClient;
class SPIComponent : public Component { class SPIComponent : public Component {
public: public:
SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate, SPIDelegate *register_device(SPIClient *device, SPIMode mode, SPIBitOrder bit_order, uint32_t data_rate,
GPIOPin *cs_pin); GPIOPin *cs_pin, bool release_device, bool write_only);
void unregister_device(SPIClient *device); void unregister_device(SPIClient *device);
void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; } void set_clk(GPIOPin *clk) { this->clk_pin_ = clk; }
@ -390,7 +391,8 @@ class SPIClient {
virtual void spi_setup() { virtual void spi_setup() {
esph_log_d("spi_device", "mode %u, data_rate %ukHz", (unsigned) this->mode_, (unsigned) (this->data_rate_ / 1000)); 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_); this->delegate_ = this->parent_->register_device(this, this->mode_, this->bit_order_, this->data_rate_, this->cs_,
this->release_device_, this->write_only_);
} }
virtual void spi_teardown() { virtual void spi_teardown() {
@ -399,6 +401,8 @@ class SPIClient {
} }
bool spi_is_ready() { return this->delegate_->is_ready(); } bool spi_is_ready() { return this->delegate_->is_ready(); }
void set_release_device(bool release) { this->release_device_ = release; }
void set_write_only(bool write_only) { this->write_only_ = write_only; }
protected: protected:
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST}; SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
@ -406,6 +410,8 @@ class SPIClient {
uint32_t data_rate_{1000000}; uint32_t data_rate_{1000000};
SPIComponent *parent_{nullptr}; SPIComponent *parent_{nullptr};
GPIOPin *cs_{nullptr}; GPIOPin *cs_{nullptr};
bool release_device_{false};
bool write_only_{false};
SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE}; SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
}; };

View File

@ -43,10 +43,7 @@ class SPIDelegateHw : public SPIDelegate {
return; return;
} }
#ifdef USE_RP2040 #ifdef USE_RP2040
// avoid overwriting the supplied buffer. Use vector for automatic deallocation this->channel_->transfer(ptr, nullptr, length);
auto rxbuf = std::vector<uint8_t>(length);
memcpy(rxbuf.data(), ptr, length);
this->channel_->transfer((void *) rxbuf.data(), length);
#elif defined(USE_ESP8266) #elif defined(USE_ESP8266)
// ESP8266 SPI library requires the pointer to be word aligned, but the data may not be // ESP8266 SPI library requires the pointer to be word aligned, but the data may not be
// so we need to copy the data to a temporary buffer // so we need to copy the data to a temporary buffer
@ -89,7 +86,8 @@ class SPIBusHw : public SPIBus {
#endif #endif
} }
SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override { SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
bool release_device, bool write_only) override {
return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin); return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin);
} }

View File

@ -11,34 +11,26 @@ static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API.
class SPIDelegateHw : public SPIDelegate { class SPIDelegateHw : public SPIDelegate {
public: public:
SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin, SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
bool write_only) bool release_device, bool write_only)
: SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel), write_only_(write_only) { : SPIDelegate(data_rate, bit_order, mode, cs_pin),
spi_device_interface_config_t config = {}; channel_(channel),
config.mode = static_cast<uint8_t>(mode); release_device_(release_device),
config.clock_speed_hz = static_cast<int>(data_rate); write_only_(write_only) {
config.spics_io_num = -1; if (!this->release_device_)
config.flags = 0; add_device_();
config.queue_size = 1;
config.pre_cb = nullptr;
config.post_cb = nullptr;
if (bit_order == BIT_ORDER_LSB_FIRST)
config.flags |= SPI_DEVICE_BIT_LSBFIRST;
if (write_only)
config.flags |= SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY;
esp_err_t const err = spi_bus_add_device(channel, &config, &this->handle_);
if (err != ESP_OK)
ESP_LOGE(TAG, "Add device failed - err %X", err);
} }
bool is_ready() override { return this->handle_ != nullptr; } bool is_ready() override { return this->handle_ != nullptr; }
void begin_transaction() override { void begin_transaction() override {
if (this->release_device_)
this->add_device_();
if (this->is_ready()) { if (this->is_ready()) {
if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK) if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK)
ESP_LOGE(TAG, "Failed to acquire SPI bus"); ESP_LOGE(TAG, "Failed to acquire SPI bus");
SPIDelegate::begin_transaction(); SPIDelegate::begin_transaction();
} else { } else {
ESP_LOGW(TAG, "spi_setup called before initialisation"); ESP_LOGW(TAG, "SPI device not ready, cannot begin transaction");
} }
} }
@ -46,6 +38,10 @@ class SPIDelegateHw : public SPIDelegate {
if (this->is_ready()) { if (this->is_ready()) {
SPIDelegate::end_transaction(); SPIDelegate::end_transaction();
spi_device_release_bus(this->handle_); spi_device_release_bus(this->handle_);
if (this->release_device_) {
spi_bus_remove_device(this->handle_);
this->handle_ = nullptr; // reset handle to indicate no device is registered
}
} }
} }
@ -189,8 +185,30 @@ class SPIDelegateHw : public SPIDelegate {
void read_array(uint8_t *ptr, size_t length) override { this->transfer(nullptr, ptr, length); } void read_array(uint8_t *ptr, size_t length) override { this->transfer(nullptr, ptr, length); }
protected: protected:
bool add_device_() {
spi_device_interface_config_t config = {};
config.mode = static_cast<uint8_t>(this->mode_);
config.clock_speed_hz = static_cast<int>(this->data_rate_);
config.spics_io_num = -1;
config.flags = 0;
config.queue_size = 1;
config.pre_cb = nullptr;
config.post_cb = nullptr;
if (this->bit_order_ == BIT_ORDER_LSB_FIRST)
config.flags |= SPI_DEVICE_BIT_LSBFIRST;
if (this->write_only_)
config.flags |= SPI_DEVICE_HALFDUPLEX | SPI_DEVICE_NO_DUMMY;
esp_err_t const err = spi_bus_add_device(this->channel_, &config, &this->handle_);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Add device failed - err %X", err);
return false;
}
return true;
}
SPIInterface channel_{}; SPIInterface channel_{};
spi_device_handle_t handle_{}; spi_device_handle_t handle_{};
bool release_device_{false};
bool write_only_{false}; bool write_only_{false};
}; };
@ -231,9 +249,10 @@ class SPIBusHw : public SPIBus {
ESP_LOGE(TAG, "Bus init failed - err %X", err); ESP_LOGE(TAG, "Bus init failed - err %X", err);
} }
SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin) override { SPIDelegate *get_delegate(uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin, bool release_device, bool write_only) override {
Utility::get_pin_no(this->sdi_pin_) == -1); return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin, release_device,
write_only || Utility::get_pin_no(this->sdi_pin_) == -1);
} }
protected: protected:

View File

@ -5,7 +5,7 @@ spi:
miso_pin: ${miso_pin} miso_pin: ${miso_pin}
spi_device: spi_device:
id: spi_device_test - id: spi_device_test
data_rate: 2MHz data_rate: 2MHz
spi_mode: 3 spi_mode: 3
bit_order: lsb_first bit_order: lsb_first

View File

@ -4,3 +4,8 @@ substitutions:
miso_pin: GPIO15 miso_pin: GPIO15
<<: !include common.yaml <<: !include common.yaml
spi_device:
- id: spi_device_test
release_device: true
data_rate: 1MHz
spi_mode: 0