mirror of
https://github.com/esphome/esphome.git
synced 2026-10-06 19:06:37 +00:00
[ethernet] Add spi_id option to attach to an existing SPI bus (#18530)
This commit is contained in:
@@ -4,6 +4,7 @@ import logging
|
||||
from esphome import automation, pins
|
||||
from esphome.automation import Condition
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import spi
|
||||
from esphome.components.network import (
|
||||
add_use_address,
|
||||
get_network_priority,
|
||||
@@ -39,6 +40,7 @@ from esphome.const import (
|
||||
CONF_POLLING_INTERVAL,
|
||||
CONF_RESET_PIN,
|
||||
CONF_SPI,
|
||||
CONF_SPI_ID,
|
||||
CONF_STATIC_IP,
|
||||
CONF_SUBNET,
|
||||
CONF_TYPE,
|
||||
@@ -263,10 +265,42 @@ def _is_framework_spi_polling_mode_supported() -> bool:
|
||||
return False
|
||||
|
||||
|
||||
# Options that come from the referenced spi bus when spi_id is set
|
||||
_SPI_BUS_PROVIDED_OPTIONS = (
|
||||
CONF_CLK_PIN,
|
||||
CONF_MOSI_PIN,
|
||||
CONF_MISO_PIN,
|
||||
CONF_INTERFACE,
|
||||
)
|
||||
|
||||
|
||||
def _validate_spi_bus(config: ConfigType) -> ConfigType:
|
||||
"""Cross-validate spi_id against the options the referenced bus provides."""
|
||||
if CONF_SPI_ID in config:
|
||||
for key in _SPI_BUS_PROVIDED_OPTIONS:
|
||||
if key in config:
|
||||
raise cv.Invalid(
|
||||
f"'{key}' cannot be used together with '{CONF_SPI_ID}'; "
|
||||
f"it comes from the referenced 'spi:' bus.",
|
||||
path=[key],
|
||||
)
|
||||
else:
|
||||
for key in (CONF_CLK_PIN, CONF_MOSI_PIN, CONF_MISO_PIN):
|
||||
if key not in config:
|
||||
raise cv.Invalid(
|
||||
f"'{key}' is a required option when '{CONF_SPI_ID}' is not set.",
|
||||
path=[key],
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
def _validate_spi_interface(config: ConfigType) -> ConfigType:
|
||||
"""Set default SPI interface or validate user choice against the variant."""
|
||||
if not CORE.is_esp32:
|
||||
return config
|
||||
if CONF_SPI_ID in config:
|
||||
# The interface comes from the referenced spi bus; don't set a default.
|
||||
return config
|
||||
from esphome.components.esp32 import VARIANT_ESP32, get_esp32_variant
|
||||
from esphome.components.spi import get_hw_interface_list
|
||||
|
||||
@@ -451,9 +485,14 @@ def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)) ->
|
||||
BASE_SCHEMA.extend(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Required(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Required(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
|
||||
# clk/mosi/miso are required unless spi_id is set; enforced
|
||||
# by _validate_spi_bus below.
|
||||
cv.Optional(CONF_CLK_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_MISO_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_MOSI_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_SPI_ID): cv.All(
|
||||
cv.only_on_esp32, cv.use_id(spi.SPIComponent)
|
||||
),
|
||||
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(
|
||||
CONF_INTERRUPT_PIN
|
||||
@@ -478,6 +517,7 @@ def _spi_schema(default_clock: str = "26.67MHz", max_clock: int = int(80e6)) ->
|
||||
),
|
||||
),
|
||||
cv.only_on([Platform.ESP32, Platform.RP2]),
|
||||
_validate_spi_bus,
|
||||
_validate_spi_interface,
|
||||
)
|
||||
|
||||
@@ -529,6 +569,30 @@ def _final_validate_spi(config: ConfigType) -> None:
|
||||
return
|
||||
from esphome.components.spi import CONF_INTERFACE_INDEX, get_spi_interface
|
||||
|
||||
if CONF_SPI_ID in config:
|
||||
# Sharing the bus: the standard spi device schema enforces that the
|
||||
# referenced bus declares both data lines. The IDF ethernet drivers
|
||||
# additionally need a hardware host, which shows as an interface index
|
||||
# on the validated bus config.
|
||||
spi.final_validate_device_schema(
|
||||
"ethernet", require_mosi=True, require_miso=True
|
||||
)(config)
|
||||
cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_SPI_ID): fv.id_declaration_match_schema(
|
||||
{
|
||||
cv.Required(
|
||||
CONF_INTERFACE_INDEX,
|
||||
msg="Component ethernet requires this spi bus to use "
|
||||
"a hardware interface",
|
||||
): cv.valid
|
||||
}
|
||||
)
|
||||
},
|
||||
extra=cv.ALLOW_EXTRA,
|
||||
)(config)
|
||||
return
|
||||
|
||||
if spi_configs := fv.full_config.get().get(CONF_SPI):
|
||||
# get_spi_interface() returns strings like "SPI2_HOST"
|
||||
spi_host = f"{config[CONF_INTERFACE].upper()}_HOST"
|
||||
@@ -625,9 +689,15 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None:
|
||||
)
|
||||
|
||||
if config[CONF_TYPE] in SPI_ETHERNET_TYPES:
|
||||
cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
|
||||
cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
|
||||
cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
|
||||
if (spi_id := config.get(CONF_SPI_ID)) is not None:
|
||||
# Pins and host come from the shared spi bus.
|
||||
spi_parent = await cg.get_variable(spi_id)
|
||||
cg.add(var.set_spi_parent(spi_parent))
|
||||
else:
|
||||
cg.add(var.set_clk_pin(config[CONF_CLK_PIN]))
|
||||
cg.add(var.set_miso_pin(config[CONF_MISO_PIN]))
|
||||
cg.add(var.set_mosi_pin(config[CONF_MOSI_PIN]))
|
||||
cg.add(var.set_interface(SPI_INTERFACE_MAP[config[CONF_INTERFACE]]))
|
||||
cg.add(var.set_cs_pin(config[CONF_CS_PIN]))
|
||||
if CONF_INTERRUPT_PIN in config:
|
||||
cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN]))
|
||||
@@ -641,7 +711,6 @@ async def _to_code_esp32(var: cg.MockObj, config: ConfigType) -> None:
|
||||
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
|
||||
cg.add(var.set_interface(SPI_INTERFACE_MAP[config[CONF_INTERFACE]]))
|
||||
add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True)
|
||||
# CONFIG_ETH_SPI_ETHERNET_{TYPE} Kconfig options were removed in IDF 6.0
|
||||
# Types that are never built into IDF ship no Kconfig option at all
|
||||
|
||||
@@ -13,6 +13,9 @@
|
||||
#include "esp_eth.h"
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#include "hal/spi_types.h"
|
||||
#ifdef USE_SPI
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#endif
|
||||
#endif
|
||||
#include "esp_eth_mac.h"
|
||||
#include "esp_eth_mac_esp.h"
|
||||
@@ -176,6 +179,9 @@ class EthernetComponent final : public Component {
|
||||
void set_reset_pin(uint8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
void set_clock_speed(int clock_speed) { this->clock_speed_ = clock_speed; }
|
||||
void set_interface(spi_host_device_t interface) { this->interface_ = interface; }
|
||||
#ifdef USE_SPI
|
||||
void set_spi_parent(spi::SPIComponent *parent) { this->spi_parent_ = parent; }
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
void set_polling_interval(uint32_t polling_interval) { this->polling_interval_ = polling_interval; }
|
||||
#endif
|
||||
@@ -258,6 +264,11 @@ class EthernetComponent final : public Component {
|
||||
int phy_addr_spi_{-1};
|
||||
int clock_speed_;
|
||||
spi_host_device_t interface_{SPI2_HOST};
|
||||
#ifdef USE_SPI
|
||||
// When set, the SPI bus is owned and initialized by this spi component
|
||||
// and the ethernet chip only adds a device to it.
|
||||
spi::SPIComponent *spi_parent_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
uint32_t polling_interval_{0};
|
||||
#endif
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
#include <driver/gpio.h>
|
||||
#include <driver/spi_master.h>
|
||||
#ifdef USE_SPI
|
||||
#include "esphome/components/spi/spi.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
namespace esphome::ethernet {
|
||||
@@ -168,25 +171,34 @@ void EthernetComponent::ethernet_lazy_init_() {
|
||||
// Install GPIO ISR handler to be able to service SPI Eth modules interrupts
|
||||
gpio_install_isr_service(0);
|
||||
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = this->mosi_pin_,
|
||||
.miso_io_num = this->miso_pin_,
|
||||
.sclk_io_num = this->clk_pin_,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.max_transfer_sz = 0,
|
||||
.flags = 0,
|
||||
.intr_flags = 0,
|
||||
};
|
||||
spi_host_device_t host;
|
||||
#ifdef USE_SPI
|
||||
if (this->spi_parent_ != nullptr) {
|
||||
// The bus is owned and already initialized by the spi component; share its host.
|
||||
host = this->spi_parent_->get_interface();
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
spi_bus_config_t buscfg = {
|
||||
.mosi_io_num = this->mosi_pin_,
|
||||
.miso_io_num = this->miso_pin_,
|
||||
.sclk_io_num = this->clk_pin_,
|
||||
.quadwp_io_num = -1,
|
||||
.quadhd_io_num = -1,
|
||||
.data4_io_num = -1,
|
||||
.data5_io_num = -1,
|
||||
.data6_io_num = -1,
|
||||
.data7_io_num = -1,
|
||||
.max_transfer_sz = 0,
|
||||
.flags = 0,
|
||||
.intr_flags = 0,
|
||||
};
|
||||
|
||||
auto host = this->interface_;
|
||||
host = this->interface_;
|
||||
|
||||
err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
|
||||
err = spi_bus_initialize(host, &buscfg, SPI_DMA_CH_AUTO);
|
||||
ESPHL_ERROR_CHECK(err, "SPI bus initialize error");
|
||||
}
|
||||
#endif
|
||||
// Network interface setup handled by network component
|
||||
|
||||
@@ -575,17 +587,25 @@ void EthernetComponent::dump_config() {
|
||||
YESNO(this->is_connected()));
|
||||
this->dump_connect_params_();
|
||||
#ifdef USE_ETHERNET_SPI
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" CLK Pin: %u\n"
|
||||
" MISO Pin: %u\n"
|
||||
" MOSI Pin: %u\n"
|
||||
" CS Pin: %u",
|
||||
this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_);
|
||||
const char *spi_interface = "spi3";
|
||||
if (this->interface_ == SPI2_HOST) {
|
||||
spi_interface = "spi2";
|
||||
#ifdef USE_SPI
|
||||
if (this->spi_parent_ != nullptr) {
|
||||
// Pins and interface come from the shared spi bus; only CS is ours.
|
||||
ESP_LOGCONFIG(TAG, " CS Pin: %u", this->cs_pin_);
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" CLK Pin: %u\n"
|
||||
" MISO Pin: %u\n"
|
||||
" MOSI Pin: %u\n"
|
||||
" CS Pin: %u",
|
||||
this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_);
|
||||
const char *spi_interface = "spi3";
|
||||
if (this->interface_ == SPI2_HOST) {
|
||||
spi_interface = "spi2";
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Interface: %s", spi_interface);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG, " Interface: %s", spi_interface);
|
||||
#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT
|
||||
if (this->polling_interval_ != 0) {
|
||||
ESP_LOGCONFIG(TAG, " Polling Interval: %" PRIu32 " ms", this->polling_interval_);
|
||||
|
||||
@@ -352,6 +352,8 @@ class SPIComponent final : public Component {
|
||||
this->using_hw_ = true;
|
||||
}
|
||||
|
||||
SPIInterface get_interface() const { return this->interface_; }
|
||||
|
||||
void set_interface_name(const char *name) { this->interface_name_ = name; }
|
||||
|
||||
float get_setup_priority() const override { return setup_priority::BUS; }
|
||||
|
||||
Reference in New Issue
Block a user