From b6aec4fa25bb9f7fdb09e9738385f5b8fed45267 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Tue, 24 Mar 2026 14:03:30 -1000 Subject: [PATCH] [ethernet] Add W5100 support for RP2040 (#15131) --- esphome/components/ethernet/__init__.py | 20 +++++++++----- .../components/ethernet/ethernet_component.h | 10 +++++++ .../ethernet/ethernet_component_rp2040.cpp | 26 ++++++++++++++----- esphome/core/defines.h | 1 + .../ethernet/common-w5100-rp2040.yaml | 18 +++++++++++++ .../ethernet/test-w5100.rp2040-ard.yaml | 1 + 6 files changed, 62 insertions(+), 14 deletions(-) create mode 100644 tests/components/ethernet/common-w5100-rp2040.yaml create mode 100644 tests/components/ethernet/test-w5100.rp2040-ard.yaml diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index e17abfcc93..17459cabb6 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -115,6 +115,7 @@ ETHERNET_TYPES = { "JL1101": EthernetType.ETHERNET_TYPE_JL1101, "KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081, "KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA, + "W5100": EthernetType.ETHERNET_TYPE_W5100, "W5500": EthernetType.ETHERNET_TYPE_W5500, "OPENETH": EthernetType.ETHERNET_TYPE_OPENETH, "DM9051": EthernetType.ETHERNET_TYPE_DM9051, @@ -132,6 +133,7 @@ _PHY_TYPE_TO_DEFINE = { "JL1101": "USE_ETHERNET_JL1101", "KSZ8081": "USE_ETHERNET_KSZ8081", "KSZ8081RNA": "USE_ETHERNET_KSZ8081", + "W5100": "USE_ETHERNET_W5100", "W5500": "USE_ETHERNET_W5500", "DM9051": "USE_ETHERNET_DM9051", "LAN8670": "USE_ETHERNET_LAN8670", @@ -164,9 +166,15 @@ _IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = { # These types are always external IDF components (never built-in to ESP-IDF) _ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60"} -SPI_ETHERNET_TYPES = ["W5500", "DM9051", "ENC28J60"] +# ESP32-only SPI ethernet types (W5100 is RP2040-only, no ESP-IDF driver) +SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60"} # RP2040-supported SPI ethernet types -RP2040_SPI_ETHERNET_TYPES = ["W5500", "ENC28J60"] +RP2040_SPI_ETHERNET_TYPES = {"W5100", "W5500", "ENC28J60"} +_RP2040_SPI_LIBRARIES = { + "W5100": "lwIP_w5100", + "W5500": "lwIP_w5500", + "ENC28J60": "lwIP_enc28j60", +} SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10) emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") @@ -295,7 +303,7 @@ def _validate(config): ) 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"Only {', '.join(sorted(RP2040_SPI_ETHERNET_TYPES))} are supported on RP2040, " f"not {config[CONF_TYPE]}" ) return config @@ -382,6 +390,7 @@ CONFIG_SCHEMA = cv.All( "JL1101": RMII_SCHEMA, "KSZ8081": RMII_SCHEMA, "KSZ8081RNA": RMII_SCHEMA, + "W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])), "W5500": SPI_SCHEMA, "OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])), "DM9051": SPI_SCHEMA, @@ -574,10 +583,7 @@ async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None: cg.add(var.set_reset_pin(config[CONF_RESET_PIN])) cg.add_define("USE_ETHERNET_SPI") - if config[CONF_TYPE] == "ENC28J60": - cg.add_library("lwIP_enc28j60", None) - else: - cg.add_library("lwIP_w5500", None) + cg.add_library(_RP2040_SPI_LIBRARIES[config[CONF_TYPE]], None) def _final_validate_rmii_pins(config: ConfigType) -> None: diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 4c85c39eb8..c6e37d01ea 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -25,6 +25,8 @@ extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void); #ifdef USE_RP2040 #if defined(USE_ETHERNET_W5500) #include +#elif defined(USE_ETHERNET_W5100) +#include #elif defined(USE_ETHERNET_ENC28J60) #include #else @@ -59,6 +61,7 @@ enum EthernetType : uint8_t { ETHERNET_TYPE_JL1101, ETHERNET_TYPE_KSZ8081, ETHERNET_TYPE_KSZ8081RNA, + ETHERNET_TYPE_W5100, ETHERNET_TYPE_W5500, ETHERNET_TYPE_OPENETH, ETHERNET_TYPE_DM9051, @@ -222,8 +225,15 @@ class EthernetComponent final : public Component { #ifdef USE_RP2040 static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls +#if defined(USE_ETHERNET_W5100) + static constexpr uint32_t RESET_DELAY_MS = 150; // W5100S PLL lock time +#else + static constexpr uint32_t RESET_DELAY_MS = 10; +#endif #if defined(USE_ETHERNET_W5500) Wiznet5500lwIP *eth_{nullptr}; +#elif defined(USE_ETHERNET_W5100) + Wiznet5100lwIP *eth_{nullptr}; #elif defined(USE_ETHERNET_ENC28J60) ENC28J60lwIP *eth_{nullptr}; #else diff --git a/esphome/components/ethernet/ethernet_component_rp2040.cpp b/esphome/components/ethernet/ethernet_component_rp2040.cpp index bd8c458985..9771bc59d5 100644 --- a/esphome/components/ethernet/ethernet_component_rp2040.cpp +++ b/esphome/components/ethernet/ethernet_component_rp2040.cpp @@ -31,12 +31,15 @@ void EthernetComponent::setup() { reset_pin.digital_write(false); delay(1); // NOLINT reset_pin.digital_write(true); - delay(10); // NOLINT - wait for chip to initialize after reset + // W5100S needs 150ms for PLL lock; W5500/ENC28J60 need ~10ms + delay(RESET_DELAY_MS); // NOLINT } // Create the SPI Ethernet device instance #if defined(USE_ETHERNET_W5500) this->eth_ = new Wiznet5500lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#elif defined(USE_ETHERNET_W5100) + this->eth_ = new Wiznet5100lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT #elif defined(USE_ETHERNET_ENC28J60) this->eth_ = new ENC28J60lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT #endif @@ -80,8 +83,8 @@ void EthernetComponent::setup() { // 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 + // when the link is actually up. Setting it prematurely causes + // a "Starting → Stopped → Starting" log sequence because the chip // needs time after begin() before the PHY link is ready. } @@ -89,14 +92,21 @@ 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. + // Throttle link/IP polling to avoid excessive SPI transactions. + // W5500/ENC28J60 read PHY register via SPI on every linkStatus() call. + // W5100 can't detect link state, so we skip the SPI read and assume link-up. // 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; +#if defined(USE_ETHERNET_W5100) + // W5100 can't detect link (isLinkDetectable() returns false), so linkStatus() + // returns Unknown — assume link is up after successful begin() + bool link_up = true; +#else bool link_up = this->eth_->linkStatus() == LinkON; +#endif bool has_ip = this->eth_->connected(); if (!link_up) { @@ -171,6 +181,8 @@ void EthernetComponent::dump_config() { const char *type_str = "Unknown"; #if defined(USE_ETHERNET_W5500) type_str = "W5500"; +#elif defined(USE_ETHERNET_W5100) + type_str = "W5100"; #elif defined(USE_ETHERNET_ENC28J60) type_str = "ENC28J60"; #endif @@ -226,7 +238,7 @@ const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( } eth_duplex_t EthernetComponent::get_duplex_mode() { - // Both W5500 and ENC28J60 are full-duplex on RP2040 + // W5100, W5500, and ENC28J60 are full-duplex on RP2040 return ETH_DUPLEX_FULL; } @@ -235,7 +247,7 @@ eth_speed_t EthernetComponent::get_link_speed() { // ENC28J60 is 10Mbps only return ETH_SPEED_10M; #else - // W5500 is always 100Mbps + // W5100 and W5500 are 100Mbps return ETH_SPEED_100M; #endif } diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 996818c2e6..676ad3024f 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -284,6 +284,7 @@ #define USE_ETHERNET_SPI #define USE_ETHERNET_SPI_POLLING_SUPPORT #define USE_ETHERNET_OPENETH +#define USE_ETHERNET_W5100 #define USE_ETHERNET_W5500 #define USE_ETHERNET_DM9051 #define CONFIG_ETH_SPI_ETHERNET_W5500 1 diff --git a/tests/components/ethernet/common-w5100-rp2040.yaml b/tests/components/ethernet/common-w5100-rp2040.yaml new file mode 100644 index 0000000000..4c6d0313df --- /dev/null +++ b/tests/components/ethernet/common-w5100-rp2040.yaml @@ -0,0 +1,18 @@ +ethernet: + type: W5100 + 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!" diff --git a/tests/components/ethernet/test-w5100.rp2040-ard.yaml b/tests/components/ethernet/test-w5100.rp2040-ard.yaml new file mode 100644 index 0000000000..e101f3112a --- /dev/null +++ b/tests/components/ethernet/test-w5100.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common-w5100-rp2040.yaml