mirror of
https://github.com/esphome/esphome.git
synced 2026-03-23 21:13:45 +08:00
[ethernet] Add RP2040 W5500 Ethernet support (#14820)
This commit is contained in:
@@ -158,6 +158,8 @@ _IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = {
|
||||
}
|
||||
|
||||
SPI_ETHERNET_TYPES = ["W5500", "DM9051"]
|
||||
# RP2040-supported SPI ethernet types
|
||||
RP2040_SPI_ETHERNET_TYPES = ["W5500"]
|
||||
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
|
||||
|
||||
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||
@@ -273,6 +275,11 @@ def _validate(config):
|
||||
f"{config[CONF_TYPE]} PHY requires RMII interface and is only supported "
|
||||
f"on ESP32 classic and ESP32-P4, not {variant}"
|
||||
)
|
||||
elif CORE.is_rp2040 and config[CONF_TYPE] not in RP2040_SPI_ETHERNET_TYPES:
|
||||
raise cv.Invalid(
|
||||
f"Only {', '.join(RP2040_SPI_ETHERNET_TYPES)} are supported on RP2040, "
|
||||
f"not {config[CONF_TYPE]}"
|
||||
)
|
||||
return config
|
||||
|
||||
|
||||
@@ -330,18 +337,21 @@ SPI_SCHEMA = cv.All(
|
||||
cv.Required(CONF_CS_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_INTERRUPT_PIN): pins.internal_gpio_input_pin_number,
|
||||
cv.Optional(CONF_RESET_PIN): pins.internal_gpio_output_pin_number,
|
||||
cv.Optional(CONF_CLOCK_SPEED, default="26.67MHz"): cv.All(
|
||||
cv.frequency, cv.int_range(int(8e6), int(80e6))
|
||||
cv.SplitDefault(CONF_CLOCK_SPEED, esp32="26.67MHz"): cv.All(
|
||||
cv.only_on_esp32,
|
||||
cv.frequency,
|
||||
cv.int_range(int(8e6), int(80e6)),
|
||||
),
|
||||
# Set default value (SPI_ETHERNET_DEFAULT_POLLING_INTERVAL) at _validate()
|
||||
cv.Optional(CONF_POLLING_INTERVAL): cv.All(
|
||||
cv.only_on_esp32,
|
||||
cv.positive_time_period_milliseconds,
|
||||
cv.Range(min=TimePeriodMilliseconds(milliseconds=1)),
|
||||
),
|
||||
}
|
||||
),
|
||||
),
|
||||
cv.only_on([Platform.ESP32]),
|
||||
cv.only_on([Platform.ESP32, Platform.RP2040]),
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
@@ -431,6 +441,8 @@ async def to_code(config):
|
||||
|
||||
if CORE.is_esp32:
|
||||
await _to_code_esp32(var, config)
|
||||
elif CORE.is_rp2040:
|
||||
await _to_code_rp2040(var, config)
|
||||
|
||||
cg.add(var.set_type(ETHERNET_TYPES[config[CONF_TYPE]]))
|
||||
cg.add(var.set_use_address(config[CONF_USE_ADDRESS]))
|
||||
@@ -464,7 +476,7 @@ async def to_code(config):
|
||||
CORE.add_job(final_step)
|
||||
|
||||
|
||||
async def _to_code_esp32(var, config):
|
||||
async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
from esphome.components.esp32 import (
|
||||
add_idf_component,
|
||||
add_idf_sdkconfig_option,
|
||||
@@ -532,6 +544,20 @@ async def _to_code_esp32(var, config):
|
||||
add_idf_component(name=component.name, ref=component.version)
|
||||
|
||||
|
||||
async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
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_cs_pin(config[CONF_CS_PIN]))
|
||||
if CONF_INTERRUPT_PIN in config:
|
||||
cg.add(var.set_interrupt_pin(config[CONF_INTERRUPT_PIN]))
|
||||
if CONF_RESET_PIN in config:
|
||||
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
|
||||
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
cg.add_library("lwIP_w5500", None)
|
||||
|
||||
|
||||
def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
"""Validate that RMII pins are not used by other components."""
|
||||
if not CORE.is_esp32:
|
||||
@@ -611,6 +637,7 @@ _platform_filter = filter_source_files_from_platform(
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
},
|
||||
"ethernet_component_rp2040.cpp": {PlatformFramework.RP2040_ARDUINO},
|
||||
"esp_eth_phy_jl1101.c": {
|
||||
PlatformFramework.ESP32_IDF,
|
||||
PlatformFramework.ESP32_ARDUINO,
|
||||
|
||||
@@ -22,6 +22,10 @@ extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void);
|
||||
#endif
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
#include <W5500lwIP.h>
|
||||
#endif
|
||||
|
||||
namespace esphome::ethernet {
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
@@ -135,6 +139,15 @@ class EthernetComponent : public Component {
|
||||
#endif // USE_ETHERNET_SPI
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
void set_clk_pin(uint8_t clk_pin);
|
||||
void set_miso_pin(uint8_t miso_pin);
|
||||
void set_mosi_pin(uint8_t mosi_pin);
|
||||
void set_cs_pin(uint8_t cs_pin);
|
||||
void set_interrupt_pin(int8_t interrupt_pin);
|
||||
void set_reset_pin(int8_t reset_pin);
|
||||
#endif // USE_RP2040
|
||||
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
|
||||
#endif
|
||||
@@ -200,6 +213,18 @@ class EthernetComponent : public Component {
|
||||
esp_eth_phy_t *phy_{nullptr};
|
||||
#endif // USE_ESP32
|
||||
|
||||
#ifdef USE_RP2040
|
||||
static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls
|
||||
Wiznet5500lwIP *eth_{nullptr};
|
||||
uint32_t last_link_check_{0};
|
||||
uint8_t clk_pin_;
|
||||
uint8_t miso_pin_;
|
||||
uint8_t mosi_pin_;
|
||||
uint8_t cs_pin_;
|
||||
int8_t interrupt_pin_{-1};
|
||||
int8_t reset_pin_{-1};
|
||||
#endif // USE_RP2040
|
||||
|
||||
// Common members
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
optional<ManualIP> manual_ip_{};
|
||||
|
||||
315
esphome/components/ethernet/ethernet_component_rp2040.cpp
Normal file
315
esphome/components/ethernet/ethernet_component_rp2040.cpp
Normal file
@@ -0,0 +1,315 @@
|
||||
#include "ethernet_component.h"
|
||||
|
||||
#if defined(USE_ETHERNET) && defined(USE_RP2040)
|
||||
|
||||
#include "esphome/core/application.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include "esphome/components/rp2040/gpio.h"
|
||||
|
||||
#include <SPI.h>
|
||||
#include <lwip/dns.h>
|
||||
#include <lwip/netif.h>
|
||||
|
||||
namespace esphome::ethernet {
|
||||
|
||||
static const char *const TAG = "ethernet";
|
||||
|
||||
void EthernetComponent::setup() {
|
||||
// Configure SPI pins
|
||||
SPI.setRX(this->miso_pin_);
|
||||
SPI.setTX(this->mosi_pin_);
|
||||
SPI.setSCK(this->clk_pin_);
|
||||
|
||||
// Toggle reset pin if configured
|
||||
if (this->reset_pin_ >= 0) {
|
||||
rp2040::RP2040GPIOPin reset_pin;
|
||||
reset_pin.set_pin(this->reset_pin_);
|
||||
reset_pin.set_flags(gpio::FLAG_OUTPUT);
|
||||
reset_pin.setup();
|
||||
reset_pin.digital_write(false);
|
||||
delay(1); // NOLINT
|
||||
reset_pin.digital_write(true);
|
||||
delay(10); // NOLINT - wait for W5500 to initialize after reset
|
||||
}
|
||||
|
||||
// Create the W5500 device instance
|
||||
this->eth_ = new Wiznet5500lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
|
||||
|
||||
// Set hostname before begin() so the LWIP netif gets it
|
||||
this->eth_->hostname(App.get_name().c_str());
|
||||
|
||||
// Configure static IP if set (must be done before begin())
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
IPAddress ip(this->manual_ip_->static_ip);
|
||||
IPAddress gateway(this->manual_ip_->gateway);
|
||||
IPAddress subnet(this->manual_ip_->subnet);
|
||||
IPAddress dns1(this->manual_ip_->dns1);
|
||||
IPAddress dns2(this->manual_ip_->dns2);
|
||||
this->eth_->config(ip, gateway, subnet, dns1, dns2);
|
||||
}
|
||||
#endif
|
||||
|
||||
// Begin with fixed MAC or auto-generated
|
||||
bool success;
|
||||
if (this->fixed_mac_.has_value()) {
|
||||
success = this->eth_->begin(this->fixed_mac_->data());
|
||||
} else {
|
||||
success = this->eth_->begin();
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
ESP_LOGE(TAG, "Failed to initialize W5500 Ethernet");
|
||||
delete this->eth_; // NOLINT(cppcoreguidelines-owning-memory)
|
||||
this->eth_ = nullptr;
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
|
||||
// Make this the default interface for routing
|
||||
this->eth_->setDefault(true);
|
||||
|
||||
// The arduino-pico LwipIntfDev automatically handles packet processing
|
||||
// via __addEthernetPacketHandler when no interrupt pin is used,
|
||||
// or via GPIO interrupt when one is provided.
|
||||
|
||||
// Don't set started_ here — let the link polling in loop() set it
|
||||
// when the W5500 link is actually up. Setting it prematurely causes
|
||||
// a "Starting → Stopped → Starting" log sequence because the W5500
|
||||
// needs time after begin() before the PHY link is ready.
|
||||
}
|
||||
|
||||
void EthernetComponent::loop() {
|
||||
// On RP2040, we need to poll connection state since there are no events.
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Throttle link/IP polling to avoid excessive SPI transactions from linkStatus()
|
||||
// which reads the W5500 PHY register via SPI on every call.
|
||||
// connected() reads netif->ip_addr without LwIPLock, but this is a single
|
||||
// 32-bit aligned read (atomic on ARM) — worst case is a one-iteration-stale
|
||||
// value, which is benign for polling.
|
||||
if (this->eth_ != nullptr && now - this->last_link_check_ >= LINK_CHECK_INTERVAL) {
|
||||
this->last_link_check_ = now;
|
||||
bool link_up = this->eth_->linkStatus() == LinkON;
|
||||
bool has_ip = this->eth_->connected();
|
||||
|
||||
if (!link_up) {
|
||||
if (this->started_) {
|
||||
this->started_ = false;
|
||||
this->connected_ = false;
|
||||
}
|
||||
} else {
|
||||
if (!this->started_) {
|
||||
this->started_ = true;
|
||||
}
|
||||
bool was_connected = this->connected_;
|
||||
this->connected_ = has_ip;
|
||||
if (this->connected_ && !was_connected) {
|
||||
#ifdef USE_ETHERNET_IP_STATE_LISTENERS
|
||||
this->notify_ip_state_listeners_();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// State machine
|
||||
switch (this->state_) {
|
||||
case EthernetComponentState::STOPPED:
|
||||
if (this->started_) {
|
||||
ESP_LOGI(TAG, "Starting connection");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTING:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
} else if (this->connected_) {
|
||||
// connection established
|
||||
ESP_LOGI(TAG, "Connected");
|
||||
this->state_ = EthernetComponentState::CONNECTED;
|
||||
|
||||
this->dump_connect_params_();
|
||||
this->status_clear_warning();
|
||||
#ifdef USE_ETHERNET_CONNECT_TRIGGER
|
||||
this->connect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (now - this->connect_begin_ > 15000) {
|
||||
ESP_LOGW(TAG, "Connecting failed; reconnecting");
|
||||
this->start_connect_();
|
||||
}
|
||||
break;
|
||||
case EthernetComponentState::CONNECTED:
|
||||
if (!this->started_) {
|
||||
ESP_LOGI(TAG, "Stopped connection");
|
||||
this->state_ = EthernetComponentState::STOPPED;
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else if (!this->connected_) {
|
||||
ESP_LOGW(TAG, "Connection lost; reconnecting");
|
||||
this->state_ = EthernetComponentState::CONNECTING;
|
||||
this->start_connect_();
|
||||
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
|
||||
this->disconnect_trigger_.trigger();
|
||||
#endif
|
||||
} else {
|
||||
this->finish_connect_();
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG,
|
||||
"Ethernet:\n"
|
||||
" Type: W5500\n"
|
||||
" Connected: %s\n"
|
||||
" CLK Pin: %u\n"
|
||||
" MISO Pin: %u\n"
|
||||
" MOSI Pin: %u\n"
|
||||
" CS Pin: %u\n"
|
||||
" IRQ Pin: %d\n"
|
||||
" Reset Pin: %d",
|
||||
YESNO(this->is_connected()), this->clk_pin_, this->miso_pin_, this->mosi_pin_, this->cs_pin_,
|
||||
this->interrupt_pin_, this->reset_pin_);
|
||||
this->dump_connect_params_();
|
||||
}
|
||||
|
||||
network::IPAddresses EthernetComponent::get_ip_addresses() {
|
||||
network::IPAddresses addresses;
|
||||
if (this->eth_ != nullptr) {
|
||||
LwIPLock lock;
|
||||
addresses[0] = network::IPAddress(this->eth_->localIP());
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
network::IPAddress EthernetComponent::get_dns_address(uint8_t num) {
|
||||
LwIPLock lock;
|
||||
const ip_addr_t *dns_ip = dns_getserver(num);
|
||||
return dns_ip;
|
||||
}
|
||||
|
||||
void EthernetComponent::get_eth_mac_address_raw(uint8_t *mac) {
|
||||
if (this->eth_ != nullptr) {
|
||||
this->eth_->macAddress(mac);
|
||||
} else {
|
||||
memset(mac, 0, 6);
|
||||
}
|
||||
}
|
||||
|
||||
std::string EthernetComponent::get_eth_mac_address_pretty() {
|
||||
char buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
return std::string(this->get_eth_mac_address_pretty_into_buffer(buf));
|
||||
}
|
||||
|
||||
const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
std::span<char, MAC_ADDRESS_PRETTY_BUFFER_SIZE> buf) {
|
||||
uint8_t mac[6];
|
||||
get_eth_mac_address_raw(mac);
|
||||
format_mac_addr_upper(mac, buf.data());
|
||||
return buf.data();
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
// W5500 is always full duplex
|
||||
return ETH_DUPLEX_FULL;
|
||||
}
|
||||
|
||||
eth_speed_t EthernetComponent::get_link_speed() {
|
||||
// W5500 is always 100Mbps
|
||||
return ETH_SPEED_100M;
|
||||
}
|
||||
|
||||
bool EthernetComponent::powerdown() {
|
||||
ESP_LOGI(TAG, "Powering down ethernet");
|
||||
if (this->eth_ != nullptr) {
|
||||
this->eth_->end();
|
||||
}
|
||||
this->connected_ = false;
|
||||
this->started_ = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void EthernetComponent::start_connect_() {
|
||||
this->got_ipv4_address_ = false;
|
||||
this->connect_begin_ = millis();
|
||||
this->status_set_warning(LOG_STR("waiting for IP configuration"));
|
||||
|
||||
// Hostname is already set in setup() via LwipIntf::setHostname()
|
||||
|
||||
#ifdef USE_ETHERNET_MANUAL_IP
|
||||
if (this->manual_ip_.has_value()) {
|
||||
// Static IP was already configured before begin() in setup()
|
||||
// Set DNS servers
|
||||
LwIPLock lock;
|
||||
if (this->manual_ip_->dns1.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns1;
|
||||
dns_setserver(0, &d);
|
||||
}
|
||||
if (this->manual_ip_->dns2.is_set()) {
|
||||
ip_addr_t d;
|
||||
d = this->manual_ip_->dns2;
|
||||
dns_setserver(1, &d);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void EthernetComponent::finish_connect_() {
|
||||
// No additional work needed on RP2040 for now
|
||||
// IPv6 link-local could be added here in the future
|
||||
}
|
||||
|
||||
void EthernetComponent::dump_connect_params_() {
|
||||
if (this->eth_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char subnet_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char gateway_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns1_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char dns2_buf[network::IP_ADDRESS_BUFFER_SIZE];
|
||||
char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
|
||||
// Copy all lwIP state under the lock to avoid races with IRQ callbacks
|
||||
ip_addr_t ip_addr, netmask, gw, dns1_addr, dns2_addr;
|
||||
{
|
||||
LwIPLock lock;
|
||||
auto *netif = this->eth_->getNetIf();
|
||||
ip_addr = netif->ip_addr;
|
||||
netmask = netif->netmask;
|
||||
gw = netif->gw;
|
||||
dns1_addr = *dns_getserver(0);
|
||||
dns2_addr = *dns_getserver(1);
|
||||
}
|
||||
ESP_LOGCONFIG(TAG,
|
||||
" IP Address: %s\n"
|
||||
" Hostname: '%s'\n"
|
||||
" Subnet: %s\n"
|
||||
" Gateway: %s\n"
|
||||
" DNS1: %s\n"
|
||||
" DNS2: %s\n"
|
||||
" MAC Address: %s",
|
||||
network::IPAddress(&ip_addr).str_to(ip_buf), App.get_name().c_str(),
|
||||
network::IPAddress(&netmask).str_to(subnet_buf), network::IPAddress(&gw).str_to(gateway_buf),
|
||||
network::IPAddress(&dns1_addr).str_to(dns1_buf), network::IPAddress(&dns2_addr).str_to(dns2_buf),
|
||||
this->get_eth_mac_address_pretty_into_buffer(mac_buf));
|
||||
}
|
||||
|
||||
void EthernetComponent::set_clk_pin(uint8_t clk_pin) { this->clk_pin_ = clk_pin; }
|
||||
void EthernetComponent::set_miso_pin(uint8_t miso_pin) { this->miso_pin_ = miso_pin; }
|
||||
void EthernetComponent::set_mosi_pin(uint8_t mosi_pin) { this->mosi_pin_ = mosi_pin; }
|
||||
void EthernetComponent::set_cs_pin(uint8_t cs_pin) { this->cs_pin_ = cs_pin; }
|
||||
void EthernetComponent::set_interrupt_pin(int8_t interrupt_pin) { this->interrupt_pin_ = interrupt_pin; }
|
||||
void EthernetComponent::set_reset_pin(int8_t reset_pin) { this->reset_pin_ = reset_pin; }
|
||||
|
||||
} // namespace esphome::ethernet
|
||||
|
||||
#endif // USE_ETHERNET && USE_RP2040
|
||||
@@ -8,6 +8,8 @@
|
||||
#if defined(USE_WIFI)
|
||||
#include <WiFi.h>
|
||||
#include <pico/cyw43_arch.h> // For cyw43_arch_lwip_begin/end (LwIPLock)
|
||||
#elif defined(USE_ETHERNET)
|
||||
#include <LwipEthernet.h> // For ethernet_arch_lwip_begin/end (LwIPLock)
|
||||
#endif
|
||||
#include <hardware/structs/rosc.h>
|
||||
#include <hardware/sync.h>
|
||||
@@ -40,18 +42,27 @@ bool random_bytes(uint8_t *data, size_t len) {
|
||||
IRAM_ATTR InterruptLock::InterruptLock() { state_ = save_and_disable_interrupts(); }
|
||||
IRAM_ATTR InterruptLock::~InterruptLock() { restore_interrupts(state_); }
|
||||
|
||||
// On RP2040 (Pico W), arduino-pico sets PICO_CYW43_ARCH_THREADSAFE_BACKGROUND=1.
|
||||
// This means lwip callbacks run from a low-priority user IRQ context, not the
|
||||
// On RP2040, lwip callbacks run from a low-priority user IRQ context, not the
|
||||
// main loop (see low_priority_irq_handler() in pico-sdk
|
||||
// async_context_threadsafe_background.c). cyw43_arch_lwip_begin/end acquires the
|
||||
// async_context recursive mutex to prevent IRQ callbacks from firing during
|
||||
// critical sections. See esphome#10681.
|
||||
// async_context_threadsafe_background.c). This applies to both WiFi (CYW43) and
|
||||
// Ethernet (W5500) — both use async_context_threadsafe_background.
|
||||
//
|
||||
// When CYW43 is not available (non-WiFi RP2040 boards), this is a no-op since
|
||||
// Without locking, recv_fn() from IRQ context races with read_locked_() on the
|
||||
// main loop, corrupting the shared rx_buf_ pbuf chain (use-after-free, pbuf_cat
|
||||
// assertion failures). See esphome#10681.
|
||||
//
|
||||
// WiFi uses cyw43_arch_lwip_begin/end; Ethernet uses ethernet_arch_lwip_begin/end.
|
||||
// Both acquire the async_context recursive mutex to prevent IRQ callbacks from
|
||||
// firing during critical sections.
|
||||
//
|
||||
// When neither WiFi nor Ethernet is configured, this is a no-op since
|
||||
// there's no network stack and no lwip callbacks to race with.
|
||||
#if defined(USE_WIFI)
|
||||
LwIPLock::LwIPLock() { cyw43_arch_lwip_begin(); }
|
||||
LwIPLock::~LwIPLock() { cyw43_arch_lwip_end(); }
|
||||
#elif defined(USE_ETHERNET)
|
||||
LwIPLock::LwIPLock() { ethernet_arch_lwip_begin(); }
|
||||
LwIPLock::~LwIPLock() { ethernet_arch_lwip_end(); }
|
||||
#else
|
||||
LwIPLock::LwIPLock() {}
|
||||
LwIPLock::~LwIPLock() {}
|
||||
|
||||
@@ -130,7 +130,8 @@ void socket_wake() {
|
||||
// code (CONT context) — they never preempt each other, so no locking is needed.
|
||||
//
|
||||
// esphome::LwIPLock is the platform-provided RAII guard (see helpers.h/helpers.cpp).
|
||||
// On RP2040, it acquires cyw43_arch_lwip_begin/end. On ESP8266, it's a no-op.
|
||||
// On RP2040, it acquires cyw43_arch_lwip_begin/end (WiFi) or ethernet_arch_lwip_begin/end
|
||||
// (Ethernet). On ESP8266, it's a no-op.
|
||||
#define LWIP_LOCK() esphome::LwIPLock lwip_lock_guard // NOLINT
|
||||
|
||||
static const char *const TAG = "socket.lwip";
|
||||
|
||||
@@ -353,6 +353,12 @@
|
||||
#define USE_SOCKET_IMPL_LWIP_TCP
|
||||
#define USE_RP2040_BLE
|
||||
#define USE_SPI
|
||||
#ifndef USE_ETHERNET
|
||||
#define USE_ETHERNET
|
||||
#endif
|
||||
#ifndef USE_ETHERNET_SPI
|
||||
#define USE_ETHERNET_SPI
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef USE_LIBRETINY
|
||||
|
||||
18
tests/components/ethernet/common-w5500-rp2040.yaml
Normal file
18
tests/components/ethernet/common-w5500-rp2040.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
ethernet:
|
||||
type: W5500
|
||||
clk_pin: 18
|
||||
mosi_pin: 19
|
||||
miso_pin: 16
|
||||
cs_pin: 17
|
||||
interrupt_pin: 21
|
||||
reset_pin: 20
|
||||
manual_ip:
|
||||
static_ip: 192.168.178.56
|
||||
gateway: 192.168.178.1
|
||||
subnet: 255.255.255.0
|
||||
domain: .local
|
||||
mac_address: "02:AA:BB:CC:DD:01"
|
||||
on_connect:
|
||||
- logger.log: "Ethernet connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Ethernet disconnected!"
|
||||
1
tests/components/ethernet/test-w5500.rp2040-ard.yaml
Normal file
1
tests/components/ethernet/test-w5500.rp2040-ard.yaml
Normal file
@@ -0,0 +1 @@
|
||||
<<: !include common-w5500-rp2040.yaml
|
||||
Reference in New Issue
Block a user