[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_))
await display.register_display(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_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index"
CONF_RELEASE_DEVICE = "release_device"
TYPE_SINGLE = "single"
TYPE_QUAD = "quad"
TYPE_OCTAL = "octal"
@ -378,6 +379,7 @@ def spi_device_schema(
cv.Optional(CONF_SPI_MODE, default=default_mode): cv.enum(
SPI_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_with_esp_idf),
}
if cs_pin_required:
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):
parent = await cg.get_variable(config[CONF_SPI_ID])
cg.add(var.set_spi_parent(parent))
if CONF_CS_PIN in config:
pin = await cg.gpio_pin_expression(config[CONF_CS_PIN])
if cs_pin := config.get(CONF_CS_PIN):
pin = await cg.gpio_pin_expression(cs_pin)
cg.add(var.set_cs_pin(pin))
if CONF_DATA_RATE in config:
cg.add(var.set_data_rate(config[CONF_DATA_RATE]))
if CONF_SPI_MODE in config:
cg.add(var.set_mode(config[CONF_SPI_MODE]))
if data_rate := config.get(CONF_DATA_RATE):
cg.add(var.set_data_rate(data_rate))
if spi_mode := config.get(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):

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)
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) {
ESP_LOGE(TAG, "Device already registered");
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;
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) {}
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_);
}
@ -334,7 +335,7 @@ class SPIClient;
class SPIComponent : public Component {
public:
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 set_clk(GPIOPin *clk) { this->clk_pin_ = clk; }
@ -390,7 +391,8 @@ class SPIClient {
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_);
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() {
@ -399,6 +401,8 @@ class SPIClient {
}
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:
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
@ -406,6 +410,8 @@ class SPIClient {
uint32_t data_rate_{1000000};
SPIComponent *parent_{nullptr};
GPIOPin *cs_{nullptr};
bool release_device_{false};
bool write_only_{false};
SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
};

View File

@ -43,10 +43,7 @@ class SPIDelegateHw : public SPIDelegate {
return;
}
#ifdef USE_RP2040
// avoid overwriting the supplied buffer. Use vector for automatic deallocation
auto rxbuf = std::vector<uint8_t>(length);
memcpy(rxbuf.data(), ptr, length);
this->channel_->transfer((void *) rxbuf.data(), length);
this->channel_->transfer(ptr, nullptr, length);
#elif defined(USE_ESP8266)
// 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
@ -89,7 +86,8 @@ class SPIBusHw : public SPIBus {
#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);
}

View File

@ -11,34 +11,26 @@ static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API.
class SPIDelegateHw : public SPIDelegate {
public:
SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
bool write_only)
: SPIDelegate(data_rate, bit_order, mode, cs_pin), channel_(channel), write_only_(write_only) {
spi_device_interface_config_t config = {};
config.mode = static_cast<uint8_t>(mode);
config.clock_speed_hz = static_cast<int>(data_rate);
config.spics_io_num = -1;
config.flags = 0;
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 release_device, bool write_only)
: SPIDelegate(data_rate, bit_order, mode, cs_pin),
channel_(channel),
release_device_(release_device),
write_only_(write_only) {
if (!this->release_device_)
add_device_();
}
bool is_ready() override { return this->handle_ != nullptr; }
void begin_transaction() override {
if (this->release_device_)
this->add_device_();
if (this->is_ready()) {
if (spi_device_acquire_bus(this->handle_, portMAX_DELAY) != ESP_OK)
ESP_LOGE(TAG, "Failed to acquire SPI bus");
SPIDelegate::begin_transaction();
} 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()) {
SPIDelegate::end_transaction();
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); }
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_{};
spi_device_handle_t handle_{};
bool release_device_{false};
bool write_only_{false};
};
@ -231,9 +249,10 @@ class SPIBusHw : public SPIBus {
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 {
return new SPIDelegateHw(this->channel_, data_rate, bit_order, mode, cs_pin,
Utility::get_pin_no(this->sdi_pin_) == -1);
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, release_device,
write_only || Utility::get_pin_no(this->sdi_pin_) == -1);
}
protected:

View File

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

View File

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