[spi] Use PSRAM DMA for external buffers (#18699)

This commit is contained in:
n-IA-hane
2026-08-27 10:55:39 +10:00
committed by GitHub
parent 39177402dd
commit e458a38f89
6 changed files with 351 additions and 1 deletions
+59
View File
@@ -15,6 +15,7 @@ from esphome.components.esp32 import (
VARIANT_ESP32P4,
VARIANT_ESP32S2,
VARIANT_ESP32S3,
VARIANT_ESP32S31,
only_on_variant,
)
from esphome.config_helpers import filter_source_files_from_platform
@@ -126,6 +127,7 @@ CONF_FORCE_SW = "force_sw"
CONF_INTERFACE = "interface"
CONF_INTERFACE_INDEX = "interface_index"
CONF_RELEASE_DEVICE = "release_device"
CONF_PSRAM_DMA = "psram_dma"
TYPE_SINGLE = "single"
TYPE_QUAD = "quad"
TYPE_OCTAL = "octal"
@@ -136,6 +138,29 @@ TYPE_CLASS = {
TYPE_OCTAL: OctalSPIComponent,
}
def _validate_psram_dma(value: Any) -> bool:
value = cv.boolean(value)
if not value:
return value
return cv.All(
cv.only_on_esp32,
cv.only_with_framework("esp-idf"),
only_on_variant(
supported=[
VARIANT_ESP32C5,
VARIANT_ESP32C61,
VARIANT_ESP32P4,
VARIANT_ESP32S31,
VARIANT_ESP32S3,
],
msg_prefix="PSRAM DMA",
),
cv.require_framework_version(esp_idf=cv.Version(5, 5, 3)),
cv.requires_component("psram"),
)(value)
# RP2040 SPI pin assignments are complicated;
# refer to GPIO function select table in https://datasheets.raspberrypi.com/rp2040/rp2040-datasheet.pdf
@@ -450,6 +475,7 @@ def spi_device_schema(
SPI_MODE_OPTIONS, upper=True
),
cv.Optional(CONF_RELEASE_DEVICE): cv.All(cv.boolean, cv.only_on_esp32),
cv.Optional(CONF_PSRAM_DMA): _validate_psram_dma,
cs_pin_option(CONF_CS_PIN): pins.gpio_output_pin_schema,
}
)
@@ -471,6 +497,9 @@ async def register_spi_device(
cg.add(var.set_mode(spi_mode))
if release_device := config.get(CONF_RELEASE_DEVICE):
cg.add(var.set_release_device(release_device))
if psram_dma := config.get(CONF_PSRAM_DMA):
cg.add_define("USE_SPI_PSRAM_DMA")
cg.add(var.set_psram_dma(psram_dma))
def final_validate_device_schema(
@@ -498,6 +527,36 @@ def final_validate_device_schema(
)
def _walk_config(value: Any, path: tuple[Any, ...] = ()):
if isinstance(value, dict):
yield value, path
for key, child in value.items():
yield from _walk_config(child, (*path, key))
elif isinstance(value, list):
for index, child in enumerate(value):
yield from _walk_config(child, (*path, index))
def _final_validate(config: Any) -> Any:
buses = config if isinstance(config, list) else [config]
software_bus_ids = {
bus[CONF_ID] for bus in buses if CONF_INTERFACE_INDEX not in bus
}
if not software_bus_ids:
return config
for candidate, path in _walk_config(fv.full_config.get()):
if (
candidate.get(CONF_PSRAM_DMA)
and candidate.get(CONF_SPI_ID) in software_bus_ids
):
with cv.prepend_path([cv.ROOT_CONFIG_PATH, *path, CONF_PSRAM_DMA]):
raise cv.Invalid("psram_dma requires a hardware SPI interface")
return config
FINAL_VALIDATE_SCHEMA = _final_validate
FILTER_SOURCE_FILES = filter_source_files_from_platform(
{
"spi_arduino.cpp": {
+18
View File
@@ -253,11 +253,18 @@ class SPIDelegate {
// check if device is ready
virtual bool is_ready();
#ifdef USE_SPI_PSRAM_DMA
void set_psram_dma(bool enable) { this->psram_dma_ = enable; }
#endif
protected:
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
uint32_t data_rate_{1000000};
SPIMode mode_{MODE0};
GPIOPin *cs_pin_{NullPin::NULL_PIN};
#ifdef USE_SPI_PSRAM_DMA
bool psram_dma_{false};
#endif
static SPIDelegate *const NULL_DELEGATE; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
};
@@ -397,6 +404,11 @@ class SPIClient {
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->release_device_, this->write_only_);
#ifdef USE_SPI_PSRAM_DMA
this->delegate_->set_psram_dma(this->psram_dma_);
if (this->psram_dma_)
esph_log_config("spi_device", "PSRAM DMA: enabled");
#endif
}
virtual void spi_teardown() {
@@ -407,6 +419,9 @@ 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; }
#ifdef USE_SPI_PSRAM_DMA
void set_psram_dma(bool enable) { this->psram_dma_ = enable; }
#endif
protected:
SPIBitOrder bit_order_{BIT_ORDER_MSB_FIRST};
@@ -416,6 +431,9 @@ class SPIClient {
GPIOPin *cs_{nullptr};
bool release_device_{false};
bool write_only_{false};
#ifdef USE_SPI_PSRAM_DMA
bool psram_dma_{false};
#endif
SPIDelegate *delegate_{SPIDelegate::NULL_DELEGATE};
};
+36 -1
View File
@@ -1,12 +1,24 @@
#include "spi.h"
#include <vector>
#ifdef USE_SPI_PSRAM_DMA
#include <esp_memory_utils.h>
#endif
namespace esphome::spi {
#ifdef USE_ESP32
static const char *const TAG = "spi";
static const size_t MAX_TRANSFER_SIZE = 4092; // dictated by ESP-IDF API.
#ifdef USE_SPI_PSRAM_DMA
static uint32_t get_psram_dma_flags(bool enabled, const void *tx_buffer) {
if (enabled && tx_buffer != nullptr && esp_ptr_dma_ext_capable(tx_buffer))
return SPI_TRANS_DMA_USE_PSRAM;
return 0;
}
#endif
class SPIDelegateHw : public SPIDelegate {
public:
SPIDelegateHw(SPIInterface channel, uint32_t data_rate, SPIBitOrder bit_order, SPIMode mode, GPIOPin *cs_pin,
@@ -65,8 +77,13 @@ class SPIDelegateHw : public SPIDelegate {
return;
}
spi_transaction_t desc = {};
desc.flags = 0;
#ifdef USE_SPI_PSRAM_DMA
const uint32_t psram_flags = rxbuf == nullptr ? get_psram_dma_flags(this->psram_dma_, txbuf) : 0;
#endif
while (length != 0) {
#ifdef USE_SPI_PSRAM_DMA
desc.flags = psram_flags;
#endif
size_t const partial = std::min(length, MAX_TRANSFER_SIZE);
desc.length = partial * 8;
desc.rxlength = this->write_only_ ? 0 : partial * 8;
@@ -81,6 +98,12 @@ class SPIDelegateHw : public SPIDelegate {
ESP_LOGE(TAG, "Transmit failed - err %X", err);
break;
}
#ifdef USE_SPI_PSRAM_DMA
if ((desc.flags & SPI_TRANS_DMA_TX_FAIL) != 0) {
ESP_LOGE(TAG, "PSRAM DMA TX underflow");
break;
}
#endif
length -= partial;
if (txbuf != nullptr)
txbuf += partial;
@@ -133,7 +156,13 @@ class SPIDelegateHw : public SPIDelegate {
desc.base.rxlength = 0;
desc.base.cmd = cmd;
desc.base.addr = address;
#ifdef USE_SPI_PSRAM_DMA
const uint32_t transaction_flags = desc.base.flags | get_psram_dma_flags(this->psram_dma_, data);
#endif
do {
#ifdef USE_SPI_PSRAM_DMA
desc.base.flags = transaction_flags;
#endif
size_t chunk_size = std::min(length, MAX_TRANSFER_SIZE);
if (data != nullptr && chunk_size != 0) {
desc.base.length = chunk_size * 8;
@@ -152,6 +181,12 @@ class SPIDelegateHw : public SPIDelegate {
ESP_LOGE(TAG, "Transmit failed - err %X", err);
return;
}
#ifdef USE_SPI_PSRAM_DMA
if ((desc.base.flags & SPI_TRANS_DMA_TX_FAIL) != 0) {
ESP_LOGE(TAG, "PSRAM DMA TX underflow");
return;
}
#endif
// if more data is to be sent, skip the command and address phases.
desc.command_bits = 0;
desc.address_bits = 0;
+1
View File
@@ -355,6 +355,7 @@
#define USE_SPEAKER
#define USE_SPEAKER_MEDIA_PLAYER_ON_OFF
#define USE_SPI
#define USE_SPI_PSRAM_DMA
#define USE_VOICE_ASSISTANT
#define USE_WEBSERVER
#define USE_WEBSERVER_AUTH