diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index f519d79aa1..6d72e329ba 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -119,6 +119,7 @@ ETHERNET_TYPES = { "OPENETH": EthernetType.ETHERNET_TYPE_OPENETH, "DM9051": EthernetType.ETHERNET_TYPE_DM9051, "LAN8670": EthernetType.ETHERNET_TYPE_LAN8670, + "ENC28J60": EthernetType.ETHERNET_TYPE_ENC28J60, } # PHY types that need compile-time defines for conditional compilation @@ -134,6 +135,7 @@ _PHY_TYPE_TO_DEFINE = { "W5500": "USE_ETHERNET_W5500", "DM9051": "USE_ETHERNET_DM9051", "LAN8670": "USE_ETHERNET_LAN8670", + "ENC28J60": "USE_ETHERNET_ENC28J60", } @@ -155,11 +157,12 @@ _IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = { "KSZ8081RNA": IDFRegistryComponent("espressif/ksz80xx", "1.0.0"), "W5500": IDFRegistryComponent("espressif/w5500", "1.0.1"), "DM9051": IDFRegistryComponent("espressif/dm9051", "1.0.0"), + "ENC28J60": IDFRegistryComponent("espressif/enc28j60", "1.0.1"), } -SPI_ETHERNET_TYPES = ["W5500", "DM9051"] +SPI_ETHERNET_TYPES = ["W5500", "DM9051", "ENC28J60"] # RP2040-supported SPI ethernet types -RP2040_SPI_ETHERNET_TYPES = ["W5500"] +RP2040_SPI_ETHERNET_TYPES = ["W5500", "ENC28J60"] SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10) emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t") @@ -367,6 +370,7 @@ CONFIG_SCHEMA = cv.All( "W5500": SPI_SCHEMA, "OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])), "DM9051": SPI_SCHEMA, + "ENC28J60": SPI_SCHEMA, "LAN8670": RMII_SCHEMA, }, upper=True, @@ -502,7 +506,8 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None: cg.add_define("USE_ETHERNET_SPI") add_idf_sdkconfig_option("CONFIG_ETH_USE_SPI_ETHERNET", True) # CONFIG_ETH_SPI_ETHERNET_{TYPE} Kconfig options were removed in IDF 6.0 - if idf_version() < cv.Version(6, 0, 0): + # ENC28J60 was never built-in to IDF, so it has no Kconfig option + if idf_version() < cv.Version(6, 0, 0) and config[CONF_TYPE] != "ENC28J60": add_idf_sdkconfig_option( f"CONFIG_ETH_SPI_ETHERNET_{config[CONF_TYPE]}", True ) @@ -536,9 +541,11 @@ async def _to_code_esp32(var: cg.Pvariable, config: ConfigType) -> None: if config[CONF_TYPE] == "LAN8670": # Add LAN867x 10BASE-T1S PHY support component add_idf_component(name="espressif/lan867x", ref="2.0.0") - - # IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry - if idf_version() >= cv.Version(6, 0, 0) and ( + elif config[CONF_TYPE] == "ENC28J60": + # ENC28J60 is always an external component (never built-in to ESP-IDF) + add_idf_component(name="espressif/enc28j60", ref="1.0.1") + elif idf_version() >= cv.Version(6, 0, 0) and ( + # IDF 6.0 moved per-chip PHY/MAC drivers to the Espressif Component Registry component := _IDF6_ETHERNET_COMPONENTS.get(config[CONF_TYPE]) ): add_idf_component(name=component.name, ref=component.version) @@ -555,7 +562,10 @@ 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") - cg.add_library("lwIP_w5500", None) + if config[CONF_TYPE] == "ENC28J60": + cg.add_library("lwIP_enc28j60", None) + else: + cg.add_library("lwIP_w5500", 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 88a86bc043..1112ee3116 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -23,7 +23,11 @@ extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void); #endif // USE_ESP32 #ifdef USE_RP2040 +#if defined(USE_ETHERNET_W5500) #include +#elif defined(USE_ETHERNET_ENC28J60) +#include +#endif #endif namespace esphome::ethernet { @@ -57,6 +61,7 @@ enum EthernetType : uint8_t { ETHERNET_TYPE_OPENETH, ETHERNET_TYPE_DM9051, ETHERNET_TYPE_LAN8670, + ETHERNET_TYPE_ENC28J60, }; struct ManualIP { @@ -215,7 +220,11 @@ 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_W5500) Wiznet5500lwIP *eth_{nullptr}; +#elif defined(USE_ETHERNET_ENC28J60) + ENC28J60lwIP *eth_{nullptr}; +#endif uint32_t last_link_check_{0}; uint8_t clk_pin_; uint8_t miso_pin_; diff --git a/esphome/components/ethernet/ethernet_component_esp32.cpp b/esphome/components/ethernet/ethernet_component_esp32.cpp index fb69a901aa..ff0318622d 100644 --- a/esphome/components/ethernet/ethernet_component_esp32.cpp +++ b/esphome/components/ethernet/ethernet_component_esp32.cpp @@ -44,6 +44,11 @@ #include "esp_eth_phy_lan867x.h" #endif +// ENC28J60 header exists on all IDF versions (always an external component) +#ifdef USE_ETHERNET_ENC28J60 +#include "esp_eth_enc28j60.h" +#endif + #ifdef USE_ETHERNET_SPI #include #include @@ -194,25 +199,29 @@ void EthernetComponent::setup() { .post_cb = nullptr, }; -#ifdef USE_ETHERNET_W5500 +#if defined(USE_ETHERNET_W5500) eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host, &devcfg); -#endif -#ifdef USE_ETHERNET_DM9051 +#elif defined(USE_ETHERNET_DM9051) eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(host, &devcfg); +#elif defined(USE_ETHERNET_ENC28J60) + eth_enc28j60_config_t enc28j60_config = ETH_ENC28J60_DEFAULT_CONFIG(host, &devcfg); #endif -#ifdef USE_ETHERNET_W5500 +#if defined(USE_ETHERNET_W5500) w5500_config.int_gpio_num = this->interrupt_pin_; #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT w5500_config.poll_period_ms = this->polling_interval_; #endif -#endif - -#ifdef USE_ETHERNET_DM9051 +#elif defined(USE_ETHERNET_DM9051) dm9051_config.int_gpio_num = this->interrupt_pin_; #ifdef USE_ETHERNET_SPI_POLLING_SUPPORT dm9051_config.poll_period_ms = this->polling_interval_; #endif +#elif defined(USE_ETHERNET_ENC28J60) + enc28j60_config.int_gpio_num = this->interrupt_pin_; +#ifdef USE_ETHERNET_SPI_POLLING_SUPPORT + enc28j60_config.poll_period_ms = this->polling_interval_; +#endif #endif phy_config.phy_addr = this->phy_addr_spi_; @@ -300,19 +309,24 @@ void EthernetComponent::setup() { #endif #endif #ifdef USE_ETHERNET_SPI -#ifdef USE_ETHERNET_W5500 +#if defined(USE_ETHERNET_W5500) case ETHERNET_TYPE_W5500: { mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config); this->phy_ = esp_eth_phy_new_w5500(&phy_config); break; } -#endif -#ifdef USE_ETHERNET_DM9051 +#elif defined(USE_ETHERNET_DM9051) case ETHERNET_TYPE_DM9051: { mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config); this->phy_ = esp_eth_phy_new_dm9051(&phy_config); break; } +#elif defined(USE_ETHERNET_ENC28J60) + case ETHERNET_TYPE_ENC28J60: { + mac = esp_eth_mac_new_enc28j60(&enc28j60_config, &mac_config); + this->phy_ = esp_eth_phy_new_enc28j60(&phy_config); + break; + } #endif #endif default: { @@ -405,15 +419,18 @@ void EthernetComponent::dump_config() { eth_type = "KSZ8081RNA"; break; #endif -#ifdef USE_ETHERNET_W5500 +#if defined(USE_ETHERNET_W5500) case ETHERNET_TYPE_W5500: eth_type = "W5500"; break; -#endif -#ifdef USE_ETHERNET_DM9051 +#elif defined(USE_ETHERNET_DM9051) case ETHERNET_TYPE_DM9051: eth_type = "DM9051"; break; +#elif defined(USE_ETHERNET_ENC28J60) + case ETHERNET_TYPE_ENC28J60: + eth_type = "ENC28J60"; + break; #endif #ifdef USE_ETHERNET_OPENETH case ETHERNET_TYPE_OPENETH: diff --git a/esphome/components/ethernet/ethernet_component_rp2040.cpp b/esphome/components/ethernet/ethernet_component_rp2040.cpp index 77b1a22d66..1ad61a5e94 100644 --- a/esphome/components/ethernet/ethernet_component_rp2040.cpp +++ b/esphome/components/ethernet/ethernet_component_rp2040.cpp @@ -12,6 +12,10 @@ #include #include +#ifdef USE_ETHERNET_ENC28J60 +#include +#endif + namespace esphome::ethernet { static const char *const TAG = "ethernet"; @@ -31,11 +35,15 @@ void EthernetComponent::setup() { reset_pin.digital_write(false); delay(1); // NOLINT reset_pin.digital_write(true); - delay(10); // NOLINT - wait for W5500 to initialize after reset + delay(10); // NOLINT - wait for chip to initialize after reset } - // Create the W5500 device instance + // 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_ENC28J60) + this->eth_ = new ENC28J60lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT +#endif // Set hostname before begin() so the LWIP netif gets it this->eth_->hostname(App.get_name().c_str()); @@ -61,7 +69,7 @@ void EthernetComponent::setup() { } if (!success) { - ESP_LOGE(TAG, "Failed to initialize W5500 Ethernet"); + ESP_LOGE(TAG, "Failed to initialize Ethernet"); delete this->eth_; // NOLINT(cppcoreguidelines-owning-memory) this->eth_ = nullptr; this->mark_failed(); @@ -164,9 +172,15 @@ void EthernetComponent::loop() { } void EthernetComponent::dump_config() { + const char *type_str = "Unknown"; +#if defined(USE_ETHERNET_W5500) + type_str = "W5500"; +#elif defined(USE_ETHERNET_ENC28J60) + type_str = "ENC28J60"; +#endif ESP_LOGCONFIG(TAG, "Ethernet:\n" - " Type: W5500\n" + " Type: %s\n" " Connected: %s\n" " CLK Pin: %u\n" " MISO Pin: %u\n" @@ -174,7 +188,7 @@ void EthernetComponent::dump_config() { " 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_, + type_str, 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_(); } @@ -216,13 +230,23 @@ const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer( } eth_duplex_t EthernetComponent::get_duplex_mode() { +#ifdef USE_ETHERNET_ENC28J60 + // ENC28J60 supports full-duplex but defaults to half-duplex + return ETH_DUPLEX_FULL; +#else // W5500 is always full duplex return ETH_DUPLEX_FULL; +#endif } eth_speed_t EthernetComponent::get_link_speed() { +#ifdef USE_ETHERNET_ENC28J60 + // ENC28J60 is 10Mbps only + return ETH_SPEED_10M; +#else // W5500 is always 100Mbps return ETH_SPEED_100M; +#endif } bool EthernetComponent::powerdown() { diff --git a/esphome/core/defines.h b/esphome/core/defines.h index 09269ea1b4..10d65d3b48 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -282,6 +282,7 @@ #define USE_ETHERNET_OPENETH #define USE_ETHERNET_W5500 #define USE_ETHERNET_DM9051 +#define USE_ETHERNET_ENC28J60 #define CONFIG_ETH_SPI_ETHERNET_W5500 1 #define CONFIG_ETH_SPI_ETHERNET_DM9051 1 #define CONFIG_ETH_USE_ESP32_EMAC 1 diff --git a/tests/components/ethernet/common-enc28j60-rp2040.yaml b/tests/components/ethernet/common-enc28j60-rp2040.yaml new file mode 100644 index 0000000000..0718723d8a --- /dev/null +++ b/tests/components/ethernet/common-enc28j60-rp2040.yaml @@ -0,0 +1,18 @@ +ethernet: + type: ENC28J60 + 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/common-enc28j60.yaml b/tests/components/ethernet/common-enc28j60.yaml new file mode 100644 index 0000000000..6b288bed19 --- /dev/null +++ b/tests/components/ethernet/common-enc28j60.yaml @@ -0,0 +1,19 @@ +ethernet: + type: ENC28J60 + clk_pin: 19 + mosi_pin: 21 + miso_pin: 23 + cs_pin: 18 + interrupt_pin: 36 + reset_pin: 22 + clock_speed: 10Mhz + 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-enc28j60.esp32-idf.yaml b/tests/components/ethernet/test-enc28j60.esp32-idf.yaml new file mode 100644 index 0000000000..b7c1b7f5aa --- /dev/null +++ b/tests/components/ethernet/test-enc28j60.esp32-idf.yaml @@ -0,0 +1 @@ +<<: !include common-enc28j60.yaml diff --git a/tests/components/ethernet/test-enc28j60.rp2040-ard.yaml b/tests/components/ethernet/test-enc28j60.rp2040-ard.yaml new file mode 100644 index 0000000000..61d1cd86f5 --- /dev/null +++ b/tests/components/ethernet/test-enc28j60.rp2040-ard.yaml @@ -0,0 +1 @@ +<<: !include common-enc28j60-rp2040.yaml