From 41f34e99a7b9973f10ec6a3592b1037559c75f85 Mon Sep 17 00:00:00 2001 From: Guo Date: Fri, 24 Jul 2026 11:56:09 -0500 Subject: [PATCH 1/6] add ethernet kit component --- components/esp32-ethernet-kit/CMakeLists.txt | 10 + components/esp32-ethernet-kit/README.md | 67 +++++ .../esp32-ethernet-kit/example/CMakeLists.txt | 22 ++ .../esp32-ethernet-kit/example/README.md | 15 ++ .../example/main/CMakeLists.txt | 5 + .../example/main/Kconfig.projbuild | 45 ++++ .../main/esp32_ethernet_kit_example.cpp | 85 ++++++ .../example/sdkconfig.defaults | 13 + .../esp32-ethernet-kit/idf_component.yml | 22 ++ .../include/esp32-ethernet-kit.hpp | 147 +++++++++++ .../src/esp32-ethernet-kit.cpp | 248 ++++++++++++++++++ 11 files changed, 679 insertions(+) create mode 100644 components/esp32-ethernet-kit/CMakeLists.txt create mode 100644 components/esp32-ethernet-kit/README.md create mode 100644 components/esp32-ethernet-kit/example/CMakeLists.txt create mode 100644 components/esp32-ethernet-kit/example/README.md create mode 100644 components/esp32-ethernet-kit/example/main/CMakeLists.txt create mode 100644 components/esp32-ethernet-kit/example/main/Kconfig.projbuild create mode 100644 components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp create mode 100644 components/esp32-ethernet-kit/example/sdkconfig.defaults create mode 100644 components/esp32-ethernet-kit/idf_component.yml create mode 100644 components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp create mode 100644 components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp diff --git a/components/esp32-ethernet-kit/CMakeLists.txt b/components/esp32-ethernet-kit/CMakeLists.txt new file mode 100644 index 000000000..f3befb4dc --- /dev/null +++ b/components/esp32-ethernet-kit/CMakeLists.txt @@ -0,0 +1,10 @@ +idf_component_register( + INCLUDE_DIRS "include" + SRC_DIRS "src" + REQUIRES + "base_component" + "esp_eth" + "esp_netif" + "esp_event" + REQUIRED_IDF_TARGETS "esp32" + ) diff --git a/components/esp32-ethernet-kit/README.md b/components/esp32-ethernet-kit/README.md new file mode 100644 index 000000000..cb4170778 --- /dev/null +++ b/components/esp32-ethernet-kit/README.md @@ -0,0 +1,67 @@ +# ESP32-Ethernet-Kit A V1.2 + +[![Badge](https://components.espressif.com/components/espp/esp32-ethernet-kit/badge.svg)](https://components.espressif.com/components/espp/esp32-ethernet-kit) + +Board Support Package (BSP) for the Espressif **ESP32-Ethernet-Kit A V1.2**. + +## Official board documentation + +- [ESP32-Ethernet-Kit overview](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-ethernet-kit/index.html) +- [ESP32-Ethernet-Kit V1.2 User Guide](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-ethernet-kit/user_guide_v1.2.html) +- [Board schematic V1.2](https://dl.espressif.com/dl/schematics/SCH_ESP32-ETHERNET-KIT_A_V1.2_20200528.pdf) + +## What this BSP provides + +The `espp::Esp32EthernetKit` class is a singleton hardware abstraction for: + +- **10/100 Ethernet** — internal ESP32 EMAC + IP101GRI RMII PHY with DHCP +- **BOOT button** — GPIO0 (see pin conflict note below) + +## RMII pin mapping + +The ESP32 RMII data-plane signals are fixed to specific GPIOs via IO_MUX and +**cannot be changed**. The control-plane signals (MDC/MDIO/PHY_RST) can be +routed via the GPIO matrix. + +| Signal | GPIO | Notes | +|--------------|------|--------------------------------------------| +| REF_CLK (in) | 0 | External 50 MHz oscillator on V1.2 | +| TX_EN | 21 | IO_MUX — fixed | +| TXD0 | 19 | IO_MUX — fixed | +| TXD1 | 22 | IO_MUX — fixed | +| CRS_DV | 27 | IO_MUX — fixed | +| RXD0 | 25 | IO_MUX — fixed | +| RXD1 | 26 | IO_MUX — fixed | +| MDC | 23 | GPIO matrix — reconfigurable | +| MDIO | 18 | GPIO matrix — reconfigurable | +| PHY_RST | 5 | Active-low; set `eth_phy_reset_gpio = -1` to skip | + +> [!WARNING] +> **GPIO0 conflict.** GPIO0 is both the RMII REF_CLK input (driven by the +> on-board 50 MHz oscillator) and the BOOT strapping pin / BOOT button. +> Pressing BOOT while Ethernet is running briefly pulls the clock line to GND, +> disrupting the 50 MHz clock and corrupting active traffic. This also puts the +> ESP32 into ROM bootloader mode. Do **not** use the BOOT button as a runtime +> input while Ethernet is active. + +## Usage + +```cpp +#include "esp32-ethernet-kit.hpp" + +auto &board = espp::Esp32EthernetKit::get(); + +board.initialize_ethernet([](esp_ip4_addr_t ip) { + printf("Got IP: %d.%d.%d.%d\n", + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); +}); +``` + +## sdkconfig requirements + +``` +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_ESP32_EMAC=y +CONFIG_ETH_PHY_ENABLE_IP101=y +``` diff --git a/components/esp32-ethernet-kit/example/CMakeLists.txt b/components/esp32-ethernet-kit/example/CMakeLists.txt new file mode 100644 index 000000000..67f753320 --- /dev/null +++ b/components/esp32-ethernet-kit/example/CMakeLists.txt @@ -0,0 +1,22 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.20) + +set(ENV{IDF_COMPONENT_MANAGER} "0") +include($ENV{IDF_PATH}/tools/cmake/project.cmake) + +# add the component directories that we want to use +set(EXTRA_COMPONENT_DIRS + "../../../components/" +) + +set( + COMPONENTS + "main esptool_py esp32-ethernet-kit" + CACHE STRING + "List of components to include" + ) + +project(esp32_ethernet_kit_example) + +set(CMAKE_CXX_STANDARD 20) diff --git a/components/esp32-ethernet-kit/example/README.md b/components/esp32-ethernet-kit/example/README.md new file mode 100644 index 000000000..6e300fa4d --- /dev/null +++ b/components/esp32-ethernet-kit/example/README.md @@ -0,0 +1,15 @@ +# ESP32-Ethernet-Kit Example + +This example shows how to use the `espp::Esp32EthernetKit` BSP to bring up the +Ethernet interface and print the assigned IP address. + +## Hardware + +[ESP32-Ethernet-Kit A V1.2](https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32/esp32-ethernet-kit/index.html) + +## How to build and flash + +```bash +idf.py set-target esp32 +idf.py -p PORT flash monitor +``` diff --git a/components/esp32-ethernet-kit/example/main/CMakeLists.txt b/components/esp32-ethernet-kit/example/main/CMakeLists.txt new file mode 100644 index 000000000..c3bbd1284 --- /dev/null +++ b/components/esp32-ethernet-kit/example/main/CMakeLists.txt @@ -0,0 +1,5 @@ +idf_component_register( + SRC_DIRS "." + INCLUDE_DIRS "." + REQUIRES "esp32-ethernet-kit" + ) diff --git a/components/esp32-ethernet-kit/example/main/Kconfig.projbuild b/components/esp32-ethernet-kit/example/main/Kconfig.projbuild new file mode 100644 index 000000000..74d44ef43 --- /dev/null +++ b/components/esp32-ethernet-kit/example/main/Kconfig.projbuild @@ -0,0 +1,45 @@ +menu "ESP32-Ethernet-Kit Example Configuration" + + choice EXAMPLE_ETH_DHCP_MODE + prompt "DHCP mode" + default EXAMPLE_ETH_DHCP_CLIENT + help + Select whether the board acts as a DHCP client (acquires an IP from + an upstream router) or as a DHCP server (assigns IPs to connected hosts). + + config EXAMPLE_ETH_DHCP_CLIENT + bool "DHCP client" + help + The ESP32 requests an IP address from the network's DHCP server. + + config EXAMPLE_ETH_DHCP_SERVER + bool "DHCP server" + help + The ESP32 acts as a DHCP server. It gets a static IP and hands out + addresses to connected hosts. Configure the static IP below. + endchoice + + if EXAMPLE_ETH_DHCP_SERVER + + config EXAMPLE_ETH_SERVER_IP + string "Server static IP address" + default "192.168.4.1" + help + Static IPv4 address assigned to the ESP32's Ethernet interface when + operating as a DHCP server. + + config EXAMPLE_ETH_SERVER_NETMASK + string "Server netmask" + default "255.255.255.0" + help + Netmask for the DHCP server subnet. + + config EXAMPLE_ETH_SERVER_GW + string "Server gateway" + default "192.168.4.1" + help + Gateway address advertised to DHCP clients (usually the server's own IP). + + endif # EXAMPLE_ETH_DHCP_SERVER + +endmenu diff --git a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp new file mode 100644 index 000000000..d3e12d6d2 --- /dev/null +++ b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp @@ -0,0 +1,85 @@ +// [esp32 ethernet kit example] +#include +#include + +#include + +#include "esp32-ethernet-kit.hpp" +#include "logger.hpp" + +#if CONFIG_EXAMPLE_ETH_DHCP_SERVER +#include +#endif + +using namespace std::chrono_literals; +using DhcpMode = espp::Esp32EthernetKit::DhcpMode; + +extern "C" void app_main(void) { + espp::Logger logger({.tag = "EthKitExample", .level = espp::Logger::Verbosity::INFO}); + logger.info("ESP32-Ethernet-Kit A V1.2 example starting"); + + auto &board = espp::Esp32EthernetKit::get(); + +#if CONFIG_EXAMPLE_ETH_DHCP_SERVER + // DHCP server mode: ESP32 assigns IPs to connected hosts using a static IP. + espp::Esp32EthernetKit::ServerConfig srv_cfg; + ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_IP, + reinterpret_cast(&srv_cfg.ip_info.ip)); + ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_NETMASK, + reinterpret_cast(&srv_cfg.ip_info.netmask)); + ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_GW, + reinterpret_cast(&srv_cfg.ip_info.gw)); + + bool eth_ok = board.initialize_ethernet( + [&](esp_ip4_addr_t ip) { + logger.info("DHCP server up at {}.{}.{}.{}", + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); + }, + DhcpMode::SERVER, srv_cfg); +#else + // DHCP client mode (default): ESP32 acquires an IP from the network. + bool eth_ok = board.initialize_ethernet( + [&](esp_ip4_addr_t ip) { + logger.info("DHCP lease acquired: {}.{}.{}.{}", + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); + }, + DhcpMode::CLIENT); +#endif + + if (!eth_ok) { + logger.error("Ethernet initialization failed"); + return; + } + + // SERVER mode: connected as soon as the cable is plugged in (static IP). + // CLIENT mode: connected once the DHCP lease is granted (up to ~30 s). + logger.info("Waiting for Ethernet..."); + for (int i = 0; i < 60 && !board.is_ethernet_connected(); ++i) { + std::this_thread::sleep_for(500ms); + } + + if (board.is_ethernet_connected()) { + auto ip = board.ethernet_ip(); + logger.info("Connected. IP: {}.{}.{}.{}", + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); + } else { + logger.warn("Ethernet not ready within 30 s"); + } + + while (true) { + std::this_thread::sleep_for(5s); + if (board.is_ethernet_connected()) { + auto ip = board.ethernet_ip(); + logger.info("Ethernet up, IP: {}.{}.{}.{}", + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); + } else { + logger.warn("Ethernet not connected"); + } + } +} +// [esp32 ethernet kit example] + diff --git a/components/esp32-ethernet-kit/example/sdkconfig.defaults b/components/esp32-ethernet-kit/example/sdkconfig.defaults new file mode 100644 index 000000000..35b87569c --- /dev/null +++ b/components/esp32-ethernet-kit/example/sdkconfig.defaults @@ -0,0 +1,13 @@ +CONFIG_IDF_TARGET="esp32" + +# Ethernet +CONFIG_ETH_ENABLED=y +CONFIG_ETH_USE_ESP32_EMAC=y +CONFIG_ETH_PHY_ENABLE_IP101=y + +# FreeRTOS tick rate +CONFIG_FREERTOS_HZ=1000 + +# Allow RMII clock on GPIO0 (strapping pin warning suppressed by IDF when used +# as EMAC_CLK_EXT_IN input — GPIO0 is configured as input by the EMAC driver). +CONFIG_ESP_SYSTEM_GPIO_MATRIX_BYPASS=n diff --git a/components/esp32-ethernet-kit/idf_component.yml b/components/esp32-ethernet-kit/idf_component.yml new file mode 100644 index 000000000..8ee569f00 --- /dev/null +++ b/components/esp32-ethernet-kit/idf_component.yml @@ -0,0 +1,22 @@ +## IDF Component Manager Manifest File +license: "MIT" +description: "ESP32-Ethernet-Kit A V1.2 Board Support Package (BSP) component in C++" +url: "https://github.com/esp-cpp/espp/tree/main/components/esp32-ethernet-kit" +repository: "git://github.com/esp-cpp/espp.git" +maintainers: + - William Emfinger +documentation: "https://esp-cpp.github.io/espp/dev_boards/espressif/esp32_ethernet_kit.html" +examples: + - path: example +tags: + - cpp + - Component + - BSP + - ESP32 + - Ethernet + - EthernetKit +dependencies: + idf: ">=5.0" + espp/base_component: ">=1.0" +targets: + - esp32 diff --git a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp new file mode 100644 index 000000000..7e96b205e --- /dev/null +++ b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp @@ -0,0 +1,147 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +#include "base_component.hpp" + +namespace espp { +/// @brief Board Support Package (BSP) for the Espressif ESP32-Ethernet-Kit A V1.2. +/// +/// This class provides a singleton interface to the board's Ethernet peripheral: +/// - 10/100 Ethernet via the internal ESP32 EMAC and an IP101GRI RMII PHY +/// +/// RMII pin mapping (fixed in the ESP32 IO_MUX; cannot be changed): +/// | Signal | GPIO | +/// |----------|------| +/// | REF_CLK | 0 | ← external 50 MHz oscillator (EMAC_CLK_EXT_IN) +/// | TX_EN | 21 | +/// | TXD0 | 19 | +/// | TXD1 | 22 | +/// | CRS_DV | 27 | +/// | RXD0 | 25 | +/// | RXD1 | 26 | +/// | MDC | 23 | +/// | MDIO | 18 | +/// | PHY_RST | 5 | +/// +/// The class is a singleton and can be accessed via get(). +/// +/// \section esp32_ethernet_kit_example Example +/// \snippet esp32_ethernet_kit_example.cpp esp32 ethernet kit example +class Esp32EthernetKit : public BaseComponent { +public: + /// Callback invoked when the Ethernet link comes up and an IP is assigned + using ethernet_link_callback_t = std::function; + + /// Callback invoked (SERVER mode only) each time the DHCP server assigns an + /// IP address to a connected client. + /// \param ip IPv4 address that was assigned. + /// \param mac MAC address of the client. + using client_ip_callback_t = + std::function mac)>; + + /// DHCP operating mode for the Ethernet interface + enum class DhcpMode { + CLIENT, ///< DHCP client — acquire an IP from an upstream server (default) + SERVER, ///< DHCP server — assign IPs to hosts connected to this interface + }; + + /// Static IP configuration used when operating as a DHCP server. + /// Leave \c ip_info zero-initialised to use the built-in defaults + /// (192.168.4.1 / 255.255.255.0 / gw 192.168.4.1). + struct ServerConfig { + esp_netif_ip_info_t ip_info; ///< IP / netmask / gateway; zero → 192.168.4.1/24 + client_ip_callback_t on_client_assigned; ///< Called each time a client is assigned an IP + }; + + /// @brief Access the singleton instance + /// @return Reference to the singleton instance + static Esp32EthernetKit &get() { + static Esp32EthernetKit instance; + return instance; + } + + Esp32EthernetKit(const Esp32EthernetKit &) = delete; + Esp32EthernetKit &operator=(const Esp32EthernetKit &) = delete; + Esp32EthernetKit(Esp32EthernetKit &&) = delete; + Esp32EthernetKit &operator=(Esp32EthernetKit &&) = delete; + + ///////////////////////////////////////////////////////////////////////////// + // Ethernet (EMAC + IP101GRI RMII PHY) + ///////////////////////////////////////////////////////////////////////////// + + /// Initialize the Ethernet interface (EMAC + IP101GRI RMII PHY) + /// \param on_link_up Optional callback invoked once a usable IP is available. + /// CLIENT mode: called when DHCP assigns an IP. + /// SERVER mode: called immediately when the link comes up + /// (the static IP is already known). + /// \param mode DHCP operating mode (CLIENT or SERVER, default CLIENT). + /// \param server_config Static IP configuration for SERVER mode. + /// Ignored in CLIENT mode. + /// \return True if Ethernet was successfully initialized and started. + /// \note Requires the ESP-IDF default event loop. The BSP creates it if needed. + bool initialize_ethernet(const ethernet_link_callback_t &on_link_up = nullptr, + DhcpMode mode = DhcpMode::CLIENT, + const ServerConfig &server_config = ServerConfig{}); + + /// Check whether the Ethernet link is up (cable connected + negotiated) + /// \return True if the link is up + bool is_ethernet_connected() const { return ethernet_connected_; } + + /// Get the most recently acquired IPv4 address (0 if none) + /// \return The IPv4 address + esp_ip4_addr_t ethernet_ip() const { return ethernet_ip_; } + +protected: + Esp32EthernetKit(); + + ///////////////////////////////////////////////////////////////////////////// + // RMII / EMAC pin constants + ///////////////////////////////////////////////////////////////////////////// + // The ESP32 RMII data-plane signals are fixed via IO_MUX and cannot be + // reassigned (TX_EN=21, TXD0=19, TXD1=22, CRS_DV=27, RXD0=25, RXD1=26). + // They are listed here for documentation only; the EMAC driver selects them + // automatically when EMAC_DATA_INTERFACE_RMII is chosen. + + // RMII REF_CLK input: external 50 MHz oscillator on V1.2 drives GPIO0 (EMAC_CLK_IN_GPIO) + static constexpr int rmii_clk_gpio = 0; // EMAC_CLK_IN_GPIO — fixed by ESP32 IO_MUX + + // SMI (management interface) — routable via GPIO matrix + static constexpr int eth_mdc_io = 23; + static constexpr int eth_mdio_io = 18; + + // IP101GRI PHY + static constexpr int eth_phy_reset_gpio = 5; ///< Active-low; set to -1 to disable + static constexpr int eth_phy_addr = 1; + + ///////////////////////////////////////////////////////////////////////////// + // Member variables + ///////////////////////////////////////////////////////////////////////////// + + // Ethernet + DhcpMode dhcp_mode_{DhcpMode::CLIENT}; + esp_netif_ip_info_t server_ip_info_{}; ///< Resolved static IP (server mode only) + client_ip_callback_t client_ip_callback_{nullptr}; ///< Per-client lease callback (server mode) + std::atomic ethernet_initialized_{false}; + std::atomic ethernet_connected_{false}; + esp_ip4_addr_t ethernet_ip_{}; + ethernet_link_callback_t ethernet_link_callback_{nullptr}; + esp_eth_handle_t eth_handle_{nullptr}; + void *eth_glue_{nullptr}; // esp_eth_netif_glue_handle_t + esp_netif_t *eth_netif_{nullptr}; + + static void ethernet_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, + void *event_data); + static void ethernet_got_ip_handler(void *arg, esp_event_base_t event_base, int32_t event_id, + void *event_data); + static void ethernet_client_ip_handler(void *arg, esp_event_base_t event_base, int32_t event_id, + void *event_data); +}; // class Esp32EthernetKit +} // namespace espp diff --git a/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp b/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp new file mode 100644 index 000000000..0785fcab0 --- /dev/null +++ b/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp @@ -0,0 +1,248 @@ +#include "esp32-ethernet-kit.hpp" + +#include "esp_idf_version.h" +#ifndef ESP_IDF_VERSION_VAL +#define ESP_IDF_VERSION_VAL(major, minor, patch) (((major) << 16) | ((minor) << 8) | (patch)) +#endif +#ifndef ESP_IDF_VERSION +#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(0, 0, 0) +#endif + +#include +#include +#include +#include +#include +#include + +#include + +namespace espp { + +Esp32EthernetKit::Esp32EthernetKit() + : BaseComponent("Esp32EthernetKit") {} + +void Esp32EthernetKit::ethernet_event_handler(void *arg, esp_event_base_t /*event_base*/, + int32_t event_id, void * /*event_data*/) { + auto *self = static_cast(arg); + switch (event_id) { + case ETHERNET_EVENT_CONNECTED: { + eth_speed_t speed = ETH_SPEED_10M; + eth_duplex_t duplex = ETH_DUPLEX_HALF; + if (self->eth_handle_) { + esp_eth_ioctl(self->eth_handle_, ETH_CMD_G_SPEED, &speed); + esp_eth_ioctl(self->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex); + } + self->logger_.info("Ethernet link up: {} Mbps, {} duplex", + speed == ETH_SPEED_100M ? 100 : 10, + duplex == ETH_DUPLEX_FULL ? "full" : "half"); + // In server mode the IP is static — fire the callback immediately + if (self->dhcp_mode_ == DhcpMode::SERVER) { + self->ethernet_ip_ = self->server_ip_info_.ip; + self->ethernet_connected_ = true; + if (self->ethernet_link_callback_) { + self->ethernet_link_callback_(self->server_ip_info_.ip); + } + } + break; + } + case ETHERNET_EVENT_DISCONNECTED: + self->logger_.info("Ethernet link down"); + self->ethernet_connected_ = false; + self->ethernet_ip_ = {}; + break; + case ETHERNET_EVENT_START: + self->logger_.info("Ethernet started"); + break; + case ETHERNET_EVENT_STOP: + self->logger_.info("Ethernet stopped"); + break; + default: + break; + } +} + +void Esp32EthernetKit::ethernet_got_ip_handler(void *arg, esp_event_base_t /*event_base*/, + int32_t /*event_id*/, void *event_data) { + auto *self = static_cast(arg); + auto *event = static_cast(event_data); + self->ethernet_ip_ = event->ip_info.ip; + self->ethernet_connected_ = true; + self->logger_.info("Ethernet got IP: {}.{}.{}.{}", + esp_ip4_addr1_16(&event->ip_info.ip), + esp_ip4_addr2_16(&event->ip_info.ip), + esp_ip4_addr3_16(&event->ip_info.ip), + esp_ip4_addr4_16(&event->ip_info.ip)); + if (self->ethernet_link_callback_) { + self->ethernet_link_callback_(event->ip_info.ip); + } +} + +void Esp32EthernetKit::ethernet_client_ip_handler(void *arg, esp_event_base_t /*event_base*/, + int32_t /*event_id*/, void *event_data) { + auto *self = static_cast(arg); + auto *event = static_cast(event_data); + // Filter: only handle leases from our Ethernet DHCP server netif + if (event->esp_netif != self->eth_netif_) { + return; + } + std::array mac; + std::copy(std::begin(event->mac), std::end(event->mac), mac.begin()); + self->logger_.info("DHCP server assigned {}.{}.{}.{} to {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + esp_ip4_addr1_16(&event->ip), esp_ip4_addr2_16(&event->ip), + esp_ip4_addr3_16(&event->ip), esp_ip4_addr4_16(&event->ip), + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + if (self->client_ip_callback_) { + self->client_ip_callback_(event->ip, mac); + } +} + +bool Esp32EthernetKit::initialize_ethernet(const ethernet_link_callback_t &on_link_up, + DhcpMode mode, + const ServerConfig &server_config) { + if (ethernet_initialized_) { + logger_.warn("Ethernet already initialized"); + return true; + } + + logger_.info("Initializing Ethernet (EMAC + IP101GRI RMII, DHCP {})", + mode == DhcpMode::SERVER ? "server" : "client"); + ethernet_link_callback_ = on_link_up; + dhcp_mode_ = mode; + client_ip_callback_ = server_config.on_client_assigned; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); + eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); + + // ESP32-Ethernet-Kit A V1.2 uses an external 50 MHz oscillator connected to + // GPIO0 as the RMII reference clock (EMAC_CLK_EXT_IN). GPIO0 is also the BOOT + // strapping pin; see the class-level warning about the conflict. + // + // Note: on ESP32 the RMII data-plane pins are fixed via IO_MUX (TX_EN=21, + // TXD0=19, TXD1=22, CRS_DV=27, RXD0=25, RXD1=26) so SOC_EMAC_USE_MULTI_IO_MUX + // is not defined and emac_dataif_gpio does not exist in the struct. + // SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=1 on ESP32, so clock_config_out_in + // is also not present in the struct. + eth_esp32_emac_config_t esp32_emac_config = { + .smi_gpio = {.mdc_num = eth_mdc_io, + .mdio_num = eth_mdio_io}, + .interface = EMAC_DATA_INTERFACE_RMII, + .clock_config = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, + .clock_gpio = static_cast(rmii_clk_gpio)}}, + .dma_burst_len = ETH_DMA_BURST_LEN_32, + .intr_priority = 0, +#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) + .mdc_freq_hz = 0, +#endif + }; +#pragma GCC diagnostic pop + + phy_config.phy_addr = eth_phy_addr; + phy_config.reset_gpio_num = eth_phy_reset_gpio; + + logger_.info("Creating ESP32 EMAC"); + esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); + if (!mac) { + logger_.error("Failed to create EMAC"); + return false; + } + + logger_.info("Creating IP101GRI PHY"); + esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config); + if (!phy) { + logger_.error("Failed to create PHY"); + mac->del(mac); + return false; + } + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmissing-field-initializers" + esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); +#pragma GCC diagnostic pop + + eth_handle_ = nullptr; + logger_.info("Installing Ethernet driver"); + esp_err_t ret = esp_eth_driver_install(&config, ð_handle_); + if (ret != ESP_OK) { + logger_.error("esp_eth_driver_install failed: {}", esp_err_to_name(ret)); + mac->del(mac); + phy->del(phy); + return false; + } + + ret = esp_netif_init(); + if (ret != ESP_OK) { + logger_.error("esp_netif_init failed: {}", esp_err_to_name(ret)); + return false; + } + + ret = esp_event_loop_create_default(); + if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { + logger_.error("esp_event_loop_create_default failed: {}", esp_err_to_name(ret)); + return false; + } + + // Create Ethernet netif — server mode uses a proper DHCP-server-flagged netif so that + // esp_netif allocates dhcps_t and wires up dhcps_set_new_lease_cb, which then fires + // IP_EVENT_AP_STAIPASSIGNED for every lease. Client mode uses the standard ETH default. + if (mode == DhcpMode::SERVER) { + esp_netif_ip_info_t ip_info = server_config.ip_info; + if (ip_info.ip.addr == 0) { + IP4_ADDR(&ip_info.ip, 192, 168, 4, 1); + IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); + IP4_ADDR(&ip_info.gw, 192, 168, 4, 1); + } + server_ip_info_ = ip_info; + + // ESP_NETIF_FLAG_AUTOUP causes the DHCP server to start at ETHERNET_EVENT_START + // (before the cable is connected), so dhcps_set_new_lease_cb is properly registered + // and will fire IP_EVENT_AP_STAIPASSIGNED on each lease. + esp_netif_inherent_config_t dhcps_cfg = {}; + dhcps_cfg.flags = (esp_netif_flags_t)(ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP); + dhcps_cfg.ip_info = &ip_info; + dhcps_cfg.if_key = "ETH_DHCPS"; + dhcps_cfg.if_desc = "eth"; + dhcps_cfg.route_prio = 50; + esp_netif_config_t netif_cfg = {}; + netif_cfg.base = &dhcps_cfg; + netif_cfg.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH; + eth_netif_ = esp_netif_new(&netif_cfg); + } else { + esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); + eth_netif_ = esp_netif_new(&netif_cfg); + } + if (!eth_netif_) { + logger_.error("Failed to create Ethernet netif"); + return false; + } + + eth_glue_ = esp_eth_new_netif_glue(eth_handle_); + ret = esp_netif_attach(eth_netif_, static_cast(eth_glue_)); + if (ret != ESP_OK) { + logger_.error("esp_netif_attach failed: {}", esp_err_to_name(ret)); + return false; + } + + esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ðernet_event_handler, this); + esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, ðernet_got_ip_handler, this); + if (mode == DhcpMode::SERVER) { + // IP_EVENT_AP_STAIPASSIGNED fires for every client the DHCP server serves + esp_event_handler_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, + ðernet_client_ip_handler, this); + } + + ret = esp_eth_start(eth_handle_); + if (ret != ESP_OK) { + logger_.error("esp_eth_start failed: {}", esp_err_to_name(ret)); + return false; + } + + ethernet_initialized_ = true; + logger_.info("Ethernet initialized (DHCP {})", + mode == DhcpMode::SERVER ? "server" : "client — waiting for link/DHCP"); + return true; +} + +} // namespace espp From 5f675a4beac58ff1a5d8841c7946fd661c751215 Mon Sep 17 00:00:00 2001 From: Guo Date: Fri, 24 Jul 2026 14:06:43 -0500 Subject: [PATCH 2/6] update readme --- components/esp32-ethernet-kit/README.md | 80 ++++++++++++++++++------- 1 file changed, 58 insertions(+), 22 deletions(-) diff --git a/components/esp32-ethernet-kit/README.md b/components/esp32-ethernet-kit/README.md index cb4170778..950ed867c 100644 --- a/components/esp32-ethernet-kit/README.md +++ b/components/esp32-ethernet-kit/README.md @@ -14,8 +14,59 @@ Board Support Package (BSP) for the Espressif **ESP32-Ethernet-Kit A V1.2**. The `espp::Esp32EthernetKit` class is a singleton hardware abstraction for: -- **10/100 Ethernet** — internal ESP32 EMAC + IP101GRI RMII PHY with DHCP -- **BOOT button** — GPIO0 (see pin conflict note below) +- **10/100 Ethernet** — internal ESP32 EMAC + IP101GRI RMII PHY, selectable + DHCP client **or** DHCP server mode + +## DHCP modes + +`initialize_ethernet` accepts a `DhcpMode` parameter (default `CLIENT`). + +### DHCP client (default) + +The ESP32 requests an IP address from an upstream router/switch. +The `on_link_up` callback fires once the DHCP lease is granted. + +```cpp +auto &board = espp::Esp32EthernetKit::get(); + +board.initialize_ethernet( + [](esp_ip4_addr_t ip) { + // called when lease is acquired + }, + espp::Esp32EthernetKit::DhcpMode::CLIENT); // default — can be omitted +``` + +### DHCP server + +The ESP32 assigns IP addresses to connected hosts using a static IP. +The `on_link_up` callback fires when the cable is connected (IP is static, so +it is known immediately). An optional `on_client_assigned` callback fires each +time a client receives a lease. + +```cpp +using DhcpMode = espp::Esp32EthernetKit::DhcpMode; +using ServerConfig = espp::Esp32EthernetKit::ServerConfig; + +auto &board = espp::Esp32EthernetKit::get(); + +ServerConfig cfg; +// Leave cfg.ip_info zero to use the built-in default: 192.168.4.1 / 255.255.255.0 +// Or set a custom address: +// IP4_ADDR(&cfg.ip_info.ip, 10, 0, 0, 1); +// IP4_ADDR(&cfg.ip_info.netmask, 255, 255, 255, 0); +// IP4_ADDR(&cfg.ip_info.gw, 10, 0, 0, 1); + +cfg.on_client_assigned = [](esp_ip4_addr_t ip, std::array mac) { + // fired each time a client gets a DHCP lease +}; + +board.initialize_ethernet( + [](esp_ip4_addr_t ip) { + // called when link is up (static IP is already known) + }, + DhcpMode::SERVER, + cfg); +``` ## RMII pin mapping @@ -37,26 +88,11 @@ routed via the GPIO matrix. | PHY_RST | 5 | Active-low; set `eth_phy_reset_gpio = -1` to skip | > [!WARNING] -> **GPIO0 conflict.** GPIO0 is both the RMII REF_CLK input (driven by the -> on-board 50 MHz oscillator) and the BOOT strapping pin / BOOT button. -> Pressing BOOT while Ethernet is running briefly pulls the clock line to GND, -> disrupting the 50 MHz clock and corrupting active traffic. This also puts the -> ESP32 into ROM bootloader mode. Do **not** use the BOOT button as a runtime -> input while Ethernet is active. - -## Usage - -```cpp -#include "esp32-ethernet-kit.hpp" - -auto &board = espp::Esp32EthernetKit::get(); - -board.initialize_ethernet([](esp_ip4_addr_t ip) { - printf("Got IP: %d.%d.%d.%d\n", - esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), - esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); -}); -``` +> **GPIO0 / REF_CLK conflict.** GPIO0 is both the RMII REF_CLK input (driven by +> the on-board 50 MHz oscillator) and the BOOT strapping pin. Pressing BOOT while +> Ethernet is running briefly pulls the clock line to GND, disrupting the 50 MHz +> clock and corrupting active traffic. Do **not** use GPIO0 as a runtime input +> while Ethernet is active. ## sdkconfig requirements From f5ffb12f0da06dede6f01f7d2d1958c9d4e9bd47 Mon Sep 17 00:00:00 2001 From: Guo Date: Fri, 24 Jul 2026 14:53:28 -0500 Subject: [PATCH 3/6] update doc --- .../main/esp32_ethernet_kit_example.cpp | 4 +- doc/Doxyfile | 2 + .../espressif/esp32_ethernet_kit.rst | 77 +++++++++++++++++++ .../espressif/esp32_ethernet_kit_example.md | 2 + doc/en/dev_boards/espressif/index.rst | 1 + 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 doc/en/dev_boards/espressif/esp32_ethernet_kit.rst create mode 100644 doc/en/dev_boards/espressif/esp32_ethernet_kit_example.md diff --git a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp index d3e12d6d2..e9fdc6dd4 100644 --- a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp +++ b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp @@ -1,4 +1,3 @@ -// [esp32 ethernet kit example] #include #include @@ -15,6 +14,7 @@ using namespace std::chrono_literals; using DhcpMode = espp::Esp32EthernetKit::DhcpMode; extern "C" void app_main(void) { + //! [esp32 ethernet kit example] espp::Logger logger({.tag = "EthKitExample", .level = espp::Logger::Verbosity::INFO}); logger.info("ESP32-Ethernet-Kit A V1.2 example starting"); @@ -80,6 +80,6 @@ extern "C" void app_main(void) { logger.warn("Ethernet not connected"); } } + //! [esp32 ethernet kit example] } -// [esp32 ethernet kit example] diff --git a/doc/Doxyfile b/doc/Doxyfile index 63fd3069a..931f9fde6 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -104,6 +104,7 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/drv2605/example/main/drv2605_example.cpp \ $(PROJECT_PATH)/components/encoder/example/main/encoder_example.cpp \ $(PROJECT_PATH)/components/esp32-timer-cam/example/main/esp_timer_cam_example.cpp \ + $(PROJECT_PATH)/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp \ $(PROJECT_PATH)/components/esp32-p4-function-ev-board/example/main/esp32_p4_function_ev_board_example.cpp \ $(PROJECT_PATH)/components/esp-box/example/main/esp_box_example.cpp \ $(PROJECT_PATH)/components/event_manager/example/main/event_manager_example.cpp \ @@ -254,6 +255,7 @@ INPUT = \ $(PROJECT_PATH)/components/encoder/include/abi_encoder.hpp \ $(PROJECT_PATH)/components/encoder/include/encoder_types.hpp \ $(PROJECT_PATH)/components/esp32-timer-cam/include/esp32-timer-cam.hpp \ + $(PROJECT_PATH)/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp \ $(PROJECT_PATH)/components/esp32-p4-function-ev-board/include/esp32-p4-function-ev-board.hpp \ $(PROJECT_PATH)/components/esp-box/include/esp-box.hpp \ $(PROJECT_PATH)/components/event_manager/include/event_manager.hpp \ diff --git a/doc/en/dev_boards/espressif/esp32_ethernet_kit.rst b/doc/en/dev_boards/espressif/esp32_ethernet_kit.rst new file mode 100644 index 000000000..2785c5b0a --- /dev/null +++ b/doc/en/dev_boards/espressif/esp32_ethernet_kit.rst @@ -0,0 +1,77 @@ +ESP32-Ethernet-Kit A V1.2 +************************* + +Esp32-Ethernet-Kit +------------------ + +The ESP32-Ethernet-Kit A V1.2 is an Espressif development board built around +the ESP32, featuring a wired 10/100 Ethernet port via the on-board IP101GRI +PHY connected to the ESP32's internal EMAC over RMII. + +The `espp::Esp32EthernetKit` component provides a singleton hardware abstraction +for initializing the Ethernet interface in either **DHCP client** or **DHCP +server** mode. + +**DHCP client** (default): the ESP32 requests an IP address from an upstream +router or switch. The ``on_link_up`` callback fires once the DHCP lease is +granted. + +**DHCP server**: the ESP32 assigns IP addresses to connected hosts. The interface +uses a static IP (default ``192.168.4.1/24``, configurable via +:cpp:class:`espp::Esp32EthernetKit::ServerConfig`). The ``on_link_up`` callback +fires immediately when the cable is connected. An optional +``on_client_assigned`` callback fires each time a DHCP lease is issued to a +client. + +.. warning:: + + **GPIO0 / REF_CLK conflict.** GPIO0 is both the RMII REF_CLK input (driven + by the on-board 50 MHz oscillator) and the ESP32 BOOT strapping pin. + Pressing BOOT while Ethernet is active briefly pulls the clock line to GND, + disrupting the 50 MHz clock and corrupting active traffic. Do **not** use + GPIO0 as a runtime input while Ethernet is active. + +RMII pin mapping (fixed via ESP32 IO_MUX; cannot be reassigned): + ++-------------+------+------------------------------------------+ +| Signal | GPIO | Notes | ++=============+======+==========================================+ +| REF_CLK in | 0 | External 50 MHz oscillator (V1.2) | ++-------------+------+------------------------------------------+ +| TX_EN | 21 | IO_MUX — fixed | ++-------------+------+------------------------------------------+ +| TXD0 | 19 | IO_MUX — fixed | ++-------------+------+------------------------------------------+ +| TXD1 | 22 | IO_MUX — fixed | ++-------------+------+------------------------------------------+ +| CRS_DV | 27 | IO_MUX — fixed | ++-------------+------+------------------------------------------+ +| RXD0 | 25 | IO_MUX — fixed | ++-------------+------+------------------------------------------+ +| RXD1 | 26 | IO_MUX — fixed | ++-------------+------+------------------------------------------+ +| MDC | 23 | GPIO matrix — reconfigurable | ++-------------+------+------------------------------------------+ +| MDIO | 18 | GPIO matrix — reconfigurable | ++-------------+------+------------------------------------------+ +| PHY_RST | 5 | Active-low | ++-------------+------+------------------------------------------+ + +Official board documentation: + +- `ESP32-Ethernet-Kit overview `_ +- `ESP32-Ethernet-Kit V1.2 User Guide `_ +- `Board schematic V1.2 (PDF) `_ + +.. ------------------------------- Example ------------------------------------- + +.. toctree:: + + esp32_ethernet_kit_example + +.. ---------------------------- API Reference ---------------------------------- + +API Reference +------------- + +.. include-build-file:: inc/esp32-ethernet-kit.inc diff --git a/doc/en/dev_boards/espressif/esp32_ethernet_kit_example.md b/doc/en/dev_boards/espressif/esp32_ethernet_kit_example.md new file mode 100644 index 000000000..5abc0790f --- /dev/null +++ b/doc/en/dev_boards/espressif/esp32_ethernet_kit_example.md @@ -0,0 +1,2 @@ +```{include} ../../components/esp32-ethernet-kit/example/README.md +``` diff --git a/doc/en/dev_boards/espressif/index.rst b/doc/en/dev_boards/espressif/index.rst index 443e55843..91f7b0b87 100644 --- a/doc/en/dev_boards/espressif/index.rst +++ b/doc/en/dev_boards/espressif/index.rst @@ -5,6 +5,7 @@ Espressif Boards :maxdepth: 1 esp_box + esp32_ethernet_kit esp32_p4_function_ev_board esp32_timer_cam wrover_kit From efc37bd59f66c0c85872296148176e46d742b489 Mon Sep 17 00:00:00 2001 From: Guo Date: Fri, 24 Jul 2026 15:05:35 -0500 Subject: [PATCH 4/6] update example code link --- .../main/esp32_ethernet_kit_example.cpp | 22 ++++++++++++++----- .../include/esp32-ethernet-kit.hpp | 7 +++++- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp index e9fdc6dd4..0be3536d1 100644 --- a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp +++ b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp @@ -14,14 +14,19 @@ using namespace std::chrono_literals; using DhcpMode = espp::Esp32EthernetKit::DhcpMode; extern "C" void app_main(void) { - //! [esp32 ethernet kit example] espp::Logger logger({.tag = "EthKitExample", .level = espp::Logger::Verbosity::INFO}); logger.info("ESP32-Ethernet-Kit A V1.2 example starting"); + // Example 1: get the singleton board instance + //! [esp32 ethernet kit get instance] auto &board = espp::Esp32EthernetKit::get(); + //! [esp32 ethernet kit get instance] #if CONFIG_EXAMPLE_ETH_DHCP_SERVER - // DHCP server mode: ESP32 assigns IPs to connected hosts using a static IP. + // Example 2: init as DHCP server — ESP32 assigns IPs to connected hosts. + // ServerConfig::ip_info zero → default 192.168.4.1/24. Supply Kconfig values + // (EXAMPLE_ETH_SERVER_IP / _NETMASK / _GW) for a custom static IP. + //! [esp32 ethernet kit dhcp server] espp::Esp32EthernetKit::ServerConfig srv_cfg; ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_IP, reinterpret_cast(&srv_cfg.ip_info.ip)); @@ -29,7 +34,12 @@ extern "C" void app_main(void) { reinterpret_cast(&srv_cfg.ip_info.netmask)); ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_GW, reinterpret_cast(&srv_cfg.ip_info.gw)); - + srv_cfg.on_client_assigned = [&](esp_ip4_addr_t ip, std::array mac) { + logger.info("Client assigned {}.{}.{}.{} (mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})", + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip), + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + }; bool eth_ok = board.initialize_ethernet( [&](esp_ip4_addr_t ip) { logger.info("DHCP server up at {}.{}.{}.{}", @@ -37,8 +47,10 @@ extern "C" void app_main(void) { esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); }, DhcpMode::SERVER, srv_cfg); + //! [esp32 ethernet kit dhcp server] #else - // DHCP client mode (default): ESP32 acquires an IP from the network. + // Example 3: init as DHCP client — ESP32 acquires an IP from the network. + //! [esp32 ethernet kit dhcp client] bool eth_ok = board.initialize_ethernet( [&](esp_ip4_addr_t ip) { logger.info("DHCP lease acquired: {}.{}.{}.{}", @@ -46,6 +58,7 @@ extern "C" void app_main(void) { esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); }, DhcpMode::CLIENT); + //! [esp32 ethernet kit dhcp client] #endif if (!eth_ok) { @@ -80,6 +93,5 @@ extern "C" void app_main(void) { logger.warn("Ethernet not connected"); } } - //! [esp32 ethernet kit example] } diff --git a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp index 7e96b205e..b14f9b292 100644 --- a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp +++ b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp @@ -34,7 +34,12 @@ namespace espp { /// The class is a singleton and can be accessed via get(). /// /// \section esp32_ethernet_kit_example Example -/// \snippet esp32_ethernet_kit_example.cpp esp32 ethernet kit example +/// \subsection esp32_ethernet_kit_get_instance Get Instance +/// \snippet esp32_ethernet_kit_example.cpp esp32 ethernet kit get instance +/// \subsection esp32_ethernet_kit_dhcp_server DHCP Server +/// \snippet esp32_ethernet_kit_example.cpp esp32 ethernet kit dhcp server +/// \subsection esp32_ethernet_kit_dhcp_client DHCP Client +/// \snippet esp32_ethernet_kit_example.cpp esp32 ethernet kit dhcp client class Esp32EthernetKit : public BaseComponent { public: /// Callback invoked when the Ethernet link comes up and an IP is assigned From e24b54db2a9ee0c528cd1051492559bfa4709a3f Mon Sep 17 00:00:00 2001 From: Guo Date: Tue, 28 Jul 2026 09:54:01 -0500 Subject: [PATCH 5/6] add more callback; tear down when init error --- .../main/esp32_ethernet_kit_example.cpp | 66 ++--- .../include/esp32-ethernet-kit.hpp | 88 ++++-- .../src/esp32-ethernet-kit.cpp | 252 +++++++++++++----- 3 files changed, 279 insertions(+), 127 deletions(-) diff --git a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp index 0be3536d1..79472af10 100644 --- a/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp +++ b/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp @@ -12,6 +12,7 @@ using namespace std::chrono_literals; using DhcpMode = espp::Esp32EthernetKit::DhcpMode; +using EthConfig = espp::Esp32EthernetKit::EthernetConfig; extern "C" void app_main(void) { espp::Logger logger({.tag = "EthKitExample", .level = espp::Logger::Verbosity::INFO}); @@ -23,41 +24,47 @@ extern "C" void app_main(void) { //! [esp32 ethernet kit get instance] #if CONFIG_EXAMPLE_ETH_DHCP_SERVER - // Example 2: init as DHCP server — ESP32 assigns IPs to connected hosts. - // ServerConfig::ip_info zero → default 192.168.4.1/24. Supply Kconfig values - // (EXAMPLE_ETH_SERVER_IP / _NETMASK / _GW) for a custom static IP. + // Example 2: init as DHCP server -- ESP32 assigns IPs to connected hosts. + // ServerConfig::ip_info zero -> default 192.168.4.1/24. Supply Kconfig + // values (EXAMPLE_ETH_SERVER_IP / _NETMASK / _GW) for a custom static IP. //! [esp32 ethernet kit dhcp server] espp::Esp32EthernetKit::ServerConfig srv_cfg; - ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_IP, - reinterpret_cast(&srv_cfg.ip_info.ip)); + ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_IP, reinterpret_cast(&srv_cfg.ip_info.ip)); ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_NETMASK, reinterpret_cast(&srv_cfg.ip_info.netmask)); - ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_GW, - reinterpret_cast(&srv_cfg.ip_info.gw)); + ip4addr_aton(CONFIG_EXAMPLE_ETH_SERVER_GW, reinterpret_cast(&srv_cfg.ip_info.gw)); srv_cfg.on_client_assigned = [&](esp_ip4_addr_t ip, std::array mac) { logger.info("Client assigned {}.{}.{}.{} (mac {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x})", - esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), - esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip), - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), esp_ip4_addr3_16(&ip), + esp_ip4_addr4_16(&ip), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); }; - bool eth_ok = board.initialize_ethernet( - [&](esp_ip4_addr_t ip) { - logger.info("DHCP server up at {}.{}.{}.{}", - esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), - esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); - }, - DhcpMode::SERVER, srv_cfg); + bool eth_ok = board.initialize_ethernet({ + .mode = DhcpMode::SERVER, + .server_config = srv_cfg, + .on_link_up = [&]() { logger.info("Ethernet link up"); }, + .on_link_down = [&]() { logger.warn("Ethernet link down"); }, + .on_got_ip = + [&](esp_ip4_addr_t ip) { + logger.info("DHCP server up at {}.{}.{}.{}", esp_ip4_addr1_16(&ip), + esp_ip4_addr2_16(&ip), esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); + }, + .on_lost_ip = [&]() { logger.warn("Ethernet lost IP"); }, + }); //! [esp32 ethernet kit dhcp server] #else - // Example 3: init as DHCP client — ESP32 acquires an IP from the network. + // Example 3: init as DHCP client -- ESP32 acquires an IP from the network. //! [esp32 ethernet kit dhcp client] - bool eth_ok = board.initialize_ethernet( - [&](esp_ip4_addr_t ip) { - logger.info("DHCP lease acquired: {}.{}.{}.{}", - esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), - esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); - }, - DhcpMode::CLIENT); + bool eth_ok = board.initialize_ethernet({ + .mode = DhcpMode::CLIENT, + .on_link_up = [&]() { logger.info("Ethernet link up"); }, + .on_link_down = [&]() { logger.warn("Ethernet link down"); }, + .on_got_ip = + [&](esp_ip4_addr_t ip) { + logger.info("DHCP lease acquired: {}.{}.{}.{}", esp_ip4_addr1_16(&ip), + esp_ip4_addr2_16(&ip), esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); + }, + .on_lost_ip = [&]() { logger.warn("Ethernet lost IP"); }, + }); //! [esp32 ethernet kit dhcp client] #endif @@ -75,8 +82,7 @@ extern "C" void app_main(void) { if (board.is_ethernet_connected()) { auto ip = board.ethernet_ip(); - logger.info("Connected. IP: {}.{}.{}.{}", - esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + logger.info("Connected. IP: {}.{}.{}.{}", esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); } else { logger.warn("Ethernet not ready within 30 s"); @@ -86,12 +92,10 @@ extern "C" void app_main(void) { std::this_thread::sleep_for(5s); if (board.is_ethernet_connected()) { auto ip = board.ethernet_ip(); - logger.info("Ethernet up, IP: {}.{}.{}.{}", - esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), + logger.info("Ethernet up, IP: {}.{}.{}.{}", esp_ip4_addr1_16(&ip), esp_ip4_addr2_16(&ip), esp_ip4_addr3_16(&ip), esp_ip4_addr4_16(&ip)); } else { logger.warn("Ethernet not connected"); } } -} - +} \ No newline at end of file diff --git a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp index b14f9b292..4dd949a9f 100644 --- a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp +++ b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp @@ -42,15 +42,19 @@ namespace espp { /// \snippet esp32_ethernet_kit_example.cpp esp32 ethernet kit dhcp client class Esp32EthernetKit : public BaseComponent { public: - /// Callback invoked when the Ethernet link comes up and an IP is assigned - using ethernet_link_callback_t = std::function; - /// Callback invoked (SERVER mode only) each time the DHCP server assigns an /// IP address to a connected client. - /// \param ip IPv4 address that was assigned. - /// \param mac MAC address of the client. - using client_ip_callback_t = - std::function mac)>; + using client_ip_callback_t = std::function mac)>; + + /// Callback invoked when the Ethernet link state changes (comes up or goes + /// down) or when the IP address is lost. + /// \note Runs in the ESP-IDF event-loop task context — return quickly, do not block. + using EthernetLinkCallback = std::function; + + /// Callback invoked when the interface obtains an IPv4 address. + /// \param ip The assigned IPv4 address. + /// \note Runs in the ESP-IDF event-loop task context — return quickly, do not block. + using EthernetIpCallback = std::function; /// DHCP operating mode for the Ethernet interface enum class DhcpMode { @@ -62,10 +66,34 @@ class Esp32EthernetKit : public BaseComponent { /// Leave \c ip_info zero-initialised to use the built-in defaults /// (192.168.4.1 / 255.255.255.0 / gw 192.168.4.1). struct ServerConfig { - esp_netif_ip_info_t ip_info; ///< IP / netmask / gateway; zero → 192.168.4.1/24 + esp_netif_ip_info_t ip_info; ///< IP / netmask / gateway; zero → 192.168.4.1/24 client_ip_callback_t on_client_assigned; ///< Called each time a client is assigned an IP }; + /// Configuration for the Ethernet interface. + struct EthernetConfig { + /// DHCP operating mode (CLIENT or SERVER). + DhcpMode mode{DhcpMode::CLIENT}; + + /// Static IP / DHCP server settings — only used when mode == SERVER. + ServerConfig server_config{}; + + /// Called when the physical link comes up (cable connected + negotiated). + EthernetLinkCallback on_link_up{nullptr}; + + /// Called when the physical link goes down (cable disconnected). + EthernetLinkCallback on_link_down{nullptr}; + + /// Called when the interface is assigned an IPv4 address. + /// CLIENT mode: fired by the DHCP lease. + /// SERVER mode: fired immediately when the link comes up (static IP). + EthernetIpCallback on_got_ip{nullptr}; + + /// Called when the interface loses its IPv4 address + /// (DHCP lease loss in CLIENT mode, or link-down in SERVER mode). + EthernetLinkCallback on_lost_ip{nullptr}; + }; + /// @brief Access the singleton instance /// @return Reference to the singleton instance static Esp32EthernetKit &get() { @@ -82,26 +110,25 @@ class Esp32EthernetKit : public BaseComponent { // Ethernet (EMAC + IP101GRI RMII PHY) ///////////////////////////////////////////////////////////////////////////// - /// Initialize the Ethernet interface (EMAC + IP101GRI RMII PHY) - /// \param on_link_up Optional callback invoked once a usable IP is available. - /// CLIENT mode: called when DHCP assigns an IP. - /// SERVER mode: called immediately when the link comes up - /// (the static IP is already known). - /// \param mode DHCP operating mode (CLIENT or SERVER, default CLIENT). - /// \param server_config Static IP configuration for SERVER mode. - /// Ignored in CLIENT mode. + /// Initialize the Ethernet interface (EMAC + IP101GRI RMII PHY). + /// \param config Ethernet configuration (DHCP mode, callbacks). + /// All fields have defaults so \c EthernetConfig{} gives a + /// plain DHCP-client interface with no callbacks. /// \return True if Ethernet was successfully initialized and started. /// \note Requires the ESP-IDF default event loop. The BSP creates it if needed. - bool initialize_ethernet(const ethernet_link_callback_t &on_link_up = nullptr, - DhcpMode mode = DhcpMode::CLIENT, - const ServerConfig &server_config = ServerConfig{}); + bool initialize_ethernet(const EthernetConfig &config); + + /// Initialize Ethernet with default configuration (DHCP client mode). + /// \return True if Ethernet was successfully initialized and started. + bool initialize_ethernet(); - /// Check whether the Ethernet link is up (cable connected + negotiated) - /// \return True if the link is up + /// Check whether the interface has a usable IP address + /// (DHCP lease granted in CLIENT mode, or link is up in SERVER mode). + /// \return True if the interface is connected with a valid IP. bool is_ethernet_connected() const { return ethernet_connected_; } - /// Get the most recently acquired IPv4 address (0 if none) - /// \return The IPv4 address + /// Get the most recently acquired IPv4 address (0 if none). + /// \return The IPv4 address. esp_ip4_addr_t ethernet_ip() const { return ethernet_ip_; } protected: @@ -119,12 +146,12 @@ class Esp32EthernetKit : public BaseComponent { static constexpr int rmii_clk_gpio = 0; // EMAC_CLK_IN_GPIO — fixed by ESP32 IO_MUX // SMI (management interface) — routable via GPIO matrix - static constexpr int eth_mdc_io = 23; + static constexpr int eth_mdc_io = 23; static constexpr int eth_mdio_io = 18; // IP101GRI PHY static constexpr int eth_phy_reset_gpio = 5; ///< Active-low; set to -1 to disable - static constexpr int eth_phy_addr = 1; + static constexpr int eth_phy_addr = 1; ///////////////////////////////////////////////////////////////////////////// // Member variables @@ -133,19 +160,24 @@ class Esp32EthernetKit : public BaseComponent { // Ethernet DhcpMode dhcp_mode_{DhcpMode::CLIENT}; esp_netif_ip_info_t server_ip_info_{}; ///< Resolved static IP (server mode only) - client_ip_callback_t client_ip_callback_{nullptr}; ///< Per-client lease callback (server mode) + client_ip_callback_t client_ip_callback_{nullptr}; + EthernetLinkCallback on_link_up_{}; + EthernetLinkCallback on_link_down_{}; + EthernetIpCallback on_got_ip_{}; + EthernetLinkCallback on_lost_ip_{}; std::atomic ethernet_initialized_{false}; std::atomic ethernet_connected_{false}; esp_ip4_addr_t ethernet_ip_{}; - ethernet_link_callback_t ethernet_link_callback_{nullptr}; esp_eth_handle_t eth_handle_{nullptr}; - void *eth_glue_{nullptr}; // esp_eth_netif_glue_handle_t + esp_eth_netif_glue_handle_t eth_glue_{nullptr}; // esp_eth_netif_glue_handle_t esp_netif_t *eth_netif_{nullptr}; static void ethernet_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void ethernet_got_ip_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); + static void ethernet_lost_ip_handler(void *arg, esp_event_base_t event_base, int32_t event_id, + void *event_data); static void ethernet_client_ip_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); }; // class Esp32EthernetKit diff --git a/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp b/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp index 0785fcab0..6456a0aa3 100644 --- a/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp +++ b/components/esp32-ethernet-kit/src/esp32-ethernet-kit.cpp @@ -23,8 +23,11 @@ Esp32EthernetKit::Esp32EthernetKit() : BaseComponent("Esp32EthernetKit") {} void Esp32EthernetKit::ethernet_event_handler(void *arg, esp_event_base_t /*event_base*/, - int32_t event_id, void * /*event_data*/) { + int32_t event_id, void * /*event_data*/) { auto *self = static_cast(arg); + if (!self) { + return; + } switch (event_id) { case ETHERNET_EVENT_CONNECTED: { eth_speed_t speed = ETH_SPEED_10M; @@ -33,23 +36,33 @@ void Esp32EthernetKit::ethernet_event_handler(void *arg, esp_event_base_t /*even esp_eth_ioctl(self->eth_handle_, ETH_CMD_G_SPEED, &speed); esp_eth_ioctl(self->eth_handle_, ETH_CMD_G_DUPLEX_MODE, &duplex); } - self->logger_.info("Ethernet link up: {} Mbps, {} duplex", - speed == ETH_SPEED_100M ? 100 : 10, + self->logger_.info("Ethernet link up: {} Mbps, {} duplex", speed == ETH_SPEED_100M ? 100 : 10, duplex == ETH_DUPLEX_FULL ? "full" : "half"); - // In server mode the IP is static — fire the callback immediately + if (self->on_link_up_) { + self->on_link_up_(); + } + // In server mode the IP is static — fire got_ip immediately if (self->dhcp_mode_ == DhcpMode::SERVER) { - self->ethernet_ip_ = self->server_ip_info_.ip; + self->ethernet_ip_ = self->server_ip_info_.ip; self->ethernet_connected_ = true; - if (self->ethernet_link_callback_) { - self->ethernet_link_callback_(self->server_ip_info_.ip); + if (self->on_got_ip_) { + self->on_got_ip_(self->server_ip_info_.ip); } } break; } case ETHERNET_EVENT_DISCONNECTED: self->logger_.info("Ethernet link down"); - self->ethernet_connected_ = false; + if (self->ethernet_connected_) { + self->ethernet_connected_ = false; + if (self->on_lost_ip_) { + self->on_lost_ip_(); + } + } self->ethernet_ip_ = {}; + if (self->on_link_down_) { + self->on_link_down_(); + } break; case ETHERNET_EVENT_START: self->logger_.info("Ethernet started"); @@ -63,24 +76,42 @@ void Esp32EthernetKit::ethernet_event_handler(void *arg, esp_event_base_t /*even } void Esp32EthernetKit::ethernet_got_ip_handler(void *arg, esp_event_base_t /*event_base*/, - int32_t /*event_id*/, void *event_data) { + int32_t /*event_id*/, void *event_data) { auto *self = static_cast(arg); + if (!self) { + return; + } auto *event = static_cast(event_data); self->ethernet_ip_ = event->ip_info.ip; self->ethernet_connected_ = true; - self->logger_.info("Ethernet got IP: {}.{}.{}.{}", - esp_ip4_addr1_16(&event->ip_info.ip), - esp_ip4_addr2_16(&event->ip_info.ip), - esp_ip4_addr3_16(&event->ip_info.ip), + self->logger_.info("Ethernet got IP: {}.{}.{}.{}", esp_ip4_addr1_16(&event->ip_info.ip), + esp_ip4_addr2_16(&event->ip_info.ip), esp_ip4_addr3_16(&event->ip_info.ip), esp_ip4_addr4_16(&event->ip_info.ip)); - if (self->ethernet_link_callback_) { - self->ethernet_link_callback_(event->ip_info.ip); + if (self->on_got_ip_) { + self->on_got_ip_(event->ip_info.ip); + } +} + +void Esp32EthernetKit::ethernet_lost_ip_handler(void *arg, esp_event_base_t /*event_base*/, + int32_t /*event_id*/, void * /*event_data*/) { + auto *self = static_cast(arg); + if (!self) { + return; + } + self->logger_.info("Ethernet lost IP"); + self->ethernet_connected_ = false; + self->ethernet_ip_ = {}; + if (self->on_lost_ip_) { + self->on_lost_ip_(); } } void Esp32EthernetKit::ethernet_client_ip_handler(void *arg, esp_event_base_t /*event_base*/, - int32_t /*event_id*/, void *event_data) { + int32_t /*event_id*/, void *event_data) { auto *self = static_cast(arg); + if (!self) { + return; + } auto *event = static_cast(event_data); // Filter: only handle leases from our Ethernet DHCP server netif if (event->esp_netif != self->eth_netif_) { @@ -88,28 +119,99 @@ void Esp32EthernetKit::ethernet_client_ip_handler(void *arg, esp_event_base_t /* } std::array mac; std::copy(std::begin(event->mac), std::end(event->mac), mac.begin()); - self->logger_.info("DHCP server assigned {}.{}.{}.{} to {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", - esp_ip4_addr1_16(&event->ip), esp_ip4_addr2_16(&event->ip), - esp_ip4_addr3_16(&event->ip), esp_ip4_addr4_16(&event->ip), - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + self->logger_.info( + "DHCP server assigned {}.{}.{}.{} to {:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}", + esp_ip4_addr1_16(&event->ip), esp_ip4_addr2_16(&event->ip), esp_ip4_addr3_16(&event->ip), + esp_ip4_addr4_16(&event->ip), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); if (self->client_ip_callback_) { self->client_ip_callback_(event->ip, mac); } } -bool Esp32EthernetKit::initialize_ethernet(const ethernet_link_callback_t &on_link_up, - DhcpMode mode, - const ServerConfig &server_config) { +bool Esp32EthernetKit::initialize_ethernet() { return initialize_ethernet(EthernetConfig{}); } + +bool Esp32EthernetKit::initialize_ethernet(const EthernetConfig &config) { if (ethernet_initialized_) { logger_.warn("Ethernet already initialized"); return true; } + const DhcpMode mode = config.mode; logger_.info("Initializing Ethernet (EMAC + IP101GRI RMII, DHCP {})", mode == DhcpMode::SERVER ? "server" : "client"); - ethernet_link_callback_ = on_link_up; dhcp_mode_ = mode; - client_ip_callback_ = server_config.on_client_assigned; + on_link_up_ = config.on_link_up; + on_link_down_ = config.on_link_down; + on_got_ip_ = config.on_got_ip; + on_lost_ip_ = config.on_lost_ip; + client_ip_callback_ = config.server_config.on_client_assigned; + + esp_eth_mac_t *mac = nullptr; + esp_eth_phy_t *phy = nullptr; + bool eth_handler_registered = false; + bool got_ip_handler_registered = false; + bool lost_ip_handler_registered = false; + bool client_ip_handler_registered = false; + + auto fail = [&](const char *message, esp_err_t err) { + logger_.error("{}: {}", message, esp_err_to_name(err)); + + if (client_ip_handler_registered) { + esp_event_handler_unregister(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, + &Esp32EthernetKit::ethernet_client_ip_handler); + } + if (lost_ip_handler_registered) { + esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_LOST_IP, + &Esp32EthernetKit::ethernet_lost_ip_handler); + } + if (got_ip_handler_registered) { + esp_event_handler_unregister(IP_EVENT, IP_EVENT_ETH_GOT_IP, + &Esp32EthernetKit::ethernet_got_ip_handler); + } + if (eth_handler_registered) { + esp_event_handler_unregister(ETH_EVENT, ESP_EVENT_ANY_ID, + &Esp32EthernetKit::ethernet_event_handler); + } + + if (eth_handle_) { + // Best-effort stop in case the driver was already started. + esp_eth_stop(eth_handle_); + } + if (eth_glue_) { + esp_eth_del_netif_glue(eth_glue_); + eth_glue_ = nullptr; + } + if (eth_handle_) { + esp_eth_driver_uninstall(eth_handle_); + eth_handle_ = nullptr; + } + if (phy) { + phy->del(phy); + phy = nullptr; + } + if (mac) { + mac->del(mac); + mac = nullptr; + } + if (eth_netif_) { + esp_netif_destroy(eth_netif_); + eth_netif_ = nullptr; + } + + // Reset observable runtime state so retries start from a clean slate. + ethernet_initialized_ = false; + ethernet_connected_ = false; + ethernet_ip_ = {}; + server_ip_info_ = {}; + dhcp_mode_ = DhcpMode::CLIENT; + on_link_up_ = nullptr; + on_link_down_ = nullptr; + on_got_ip_ = nullptr; + on_lost_ip_ = nullptr; + client_ip_callback_ = nullptr; + + return false; + }; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" @@ -126,73 +228,67 @@ bool Esp32EthernetKit::initialize_ethernet(const ethernet_link_callback_t &on_li // SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK=1 on ESP32, so clock_config_out_in // is also not present in the struct. eth_esp32_emac_config_t esp32_emac_config = { - .smi_gpio = {.mdc_num = eth_mdc_io, - .mdio_num = eth_mdio_io}, - .interface = EMAC_DATA_INTERFACE_RMII, - .clock_config = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, - .clock_gpio = static_cast(rmii_clk_gpio)}}, + .smi_gpio = {.mdc_num = eth_mdc_io, .mdio_num = eth_mdio_io}, + .interface = EMAC_DATA_INTERFACE_RMII, + .clock_config = {.rmii = {.clock_mode = EMAC_CLK_EXT_IN, + .clock_gpio = static_cast(rmii_clk_gpio)}}, .dma_burst_len = ETH_DMA_BURST_LEN_32, .intr_priority = 0, #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(6, 0, 0) - .mdc_freq_hz = 0, + .mdc_freq_hz = 0, #endif }; #pragma GCC diagnostic pop - phy_config.phy_addr = eth_phy_addr; + phy_config.phy_addr = eth_phy_addr; phy_config.reset_gpio_num = eth_phy_reset_gpio; logger_.info("Creating ESP32 EMAC"); - esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); + mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); if (!mac) { - logger_.error("Failed to create EMAC"); - return false; + return fail("Failed to create EMAC", ESP_FAIL); } logger_.info("Creating IP101GRI PHY"); - esp_eth_phy_t *phy = esp_eth_phy_new_ip101(&phy_config); + phy = esp_eth_phy_new_ip101(&phy_config); if (!phy) { - logger_.error("Failed to create PHY"); - mac->del(mac); - return false; + return fail("Failed to create PHY", ESP_FAIL); } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmissing-field-initializers" - esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); + esp_eth_config_t eth_config = ETH_DEFAULT_CONFIG(mac, phy); #pragma GCC diagnostic pop eth_handle_ = nullptr; logger_.info("Installing Ethernet driver"); - esp_err_t ret = esp_eth_driver_install(&config, ð_handle_); + esp_err_t ret = esp_eth_driver_install(ð_config, ð_handle_); if (ret != ESP_OK) { - logger_.error("esp_eth_driver_install failed: {}", esp_err_to_name(ret)); - mac->del(mac); - phy->del(phy); - return false; + return fail("esp_eth_driver_install failed", ret); } + // Ownership of MAC/PHY objects transfers to the driver after install. + mac = nullptr; + phy = nullptr; ret = esp_netif_init(); if (ret != ESP_OK) { - logger_.error("esp_netif_init failed: {}", esp_err_to_name(ret)); - return false; + return fail("esp_netif_init failed", ret); } ret = esp_event_loop_create_default(); if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE) { - logger_.error("esp_event_loop_create_default failed: {}", esp_err_to_name(ret)); - return false; + return fail("esp_event_loop_create_default failed", ret); } // Create Ethernet netif — server mode uses a proper DHCP-server-flagged netif so that // esp_netif allocates dhcps_t and wires up dhcps_set_new_lease_cb, which then fires // IP_EVENT_AP_STAIPASSIGNED for every lease. Client mode uses the standard ETH default. if (mode == DhcpMode::SERVER) { - esp_netif_ip_info_t ip_info = server_config.ip_info; + esp_netif_ip_info_t ip_info = config.server_config.ip_info; if (ip_info.ip.addr == 0) { - IP4_ADDR(&ip_info.ip, 192, 168, 4, 1); + IP4_ADDR(&ip_info.ip, 192, 168, 4, 1); IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0); - IP4_ADDR(&ip_info.gw, 192, 168, 4, 1); + IP4_ADDR(&ip_info.gw, 192, 168, 4, 1); } server_ip_info_ = ip_info; @@ -200,13 +296,13 @@ bool Esp32EthernetKit::initialize_ethernet(const ethernet_link_callback_t &on_li // (before the cable is connected), so dhcps_set_new_lease_cb is properly registered // and will fire IP_EVENT_AP_STAIPASSIGNED on each lease. esp_netif_inherent_config_t dhcps_cfg = {}; - dhcps_cfg.flags = (esp_netif_flags_t)(ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP); - dhcps_cfg.ip_info = &ip_info; - dhcps_cfg.if_key = "ETH_DHCPS"; - dhcps_cfg.if_desc = "eth"; + dhcps_cfg.flags = (esp_netif_flags_t)(ESP_NETIF_DHCP_SERVER | ESP_NETIF_FLAG_AUTOUP); + dhcps_cfg.ip_info = &ip_info; + dhcps_cfg.if_key = "ETH_DHCPS"; + dhcps_cfg.if_desc = "eth"; dhcps_cfg.route_prio = 50; esp_netif_config_t netif_cfg = {}; - netif_cfg.base = &dhcps_cfg; + netif_cfg.base = &dhcps_cfg; netif_cfg.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH; eth_netif_ = esp_netif_new(&netif_cfg); } else { @@ -214,29 +310,49 @@ bool Esp32EthernetKit::initialize_ethernet(const ethernet_link_callback_t &on_li eth_netif_ = esp_netif_new(&netif_cfg); } if (!eth_netif_) { - logger_.error("Failed to create Ethernet netif"); - return false; + return fail("Failed to create Ethernet netif", ESP_FAIL); } eth_glue_ = esp_eth_new_netif_glue(eth_handle_); - ret = esp_netif_attach(eth_netif_, static_cast(eth_glue_)); + if (!eth_glue_) { + return fail("Failed to create Ethernet netif glue", ESP_FAIL); + } + ret = esp_netif_attach(eth_netif_, eth_glue_); if (ret != ESP_OK) { - logger_.error("esp_netif_attach failed: {}", esp_err_to_name(ret)); - return false; + return fail("esp_netif_attach failed", ret); } - esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ðernet_event_handler, this); - esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, ðernet_got_ip_handler, this); + ret = esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ðernet_event_handler, this); + if (ret != ESP_OK) { + return fail("Failed to register Ethernet event handler", ret); + } + eth_handler_registered = true; + + ret = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, ðernet_got_ip_handler, this); + if (ret != ESP_OK) { + return fail("Failed to register Ethernet got-IP handler", ret); + } + got_ip_handler_registered = true; + + ret = esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_LOST_IP, ðernet_lost_ip_handler, this); + if (ret != ESP_OK) { + return fail("Failed to register Ethernet lost-IP handler", ret); + } + lost_ip_handler_registered = true; + if (mode == DhcpMode::SERVER) { // IP_EVENT_AP_STAIPASSIGNED fires for every client the DHCP server serves - esp_event_handler_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, - ðernet_client_ip_handler, this); + ret = esp_event_handler_register(IP_EVENT, IP_EVENT_AP_STAIPASSIGNED, + ðernet_client_ip_handler, this); + if (ret != ESP_OK) { + return fail("Failed to register DHCP-server client-IP handler", ret); + } + client_ip_handler_registered = true; } ret = esp_eth_start(eth_handle_); if (ret != ESP_OK) { - logger_.error("esp_eth_start failed: {}", esp_err_to_name(ret)); - return false; + return fail("esp_eth_start failed", ret); } ethernet_initialized_ = true; From 569db8b8a5751972d72f83a1345f3dbfad0fb1a1 Mon Sep 17 00:00:00 2001 From: Guo Date: Tue, 28 Jul 2026 10:31:47 -0500 Subject: [PATCH 6/6] update dox; --- components/esp32-ethernet-kit/README.md | 91 ++++++++++++------- .../example/main/CMakeLists.txt | 1 - .../include/esp32-ethernet-kit.hpp | 6 +- doc/Doxyfile | 4 +- 4 files changed, 65 insertions(+), 37 deletions(-) diff --git a/components/esp32-ethernet-kit/README.md b/components/esp32-ethernet-kit/README.md index 950ed867c..a662a5f48 100644 --- a/components/esp32-ethernet-kit/README.md +++ b/components/esp32-ethernet-kit/README.md @@ -17,55 +17,82 @@ The `espp::Esp32EthernetKit` class is a singleton hardware abstraction for: - **10/100 Ethernet** — internal ESP32 EMAC + IP101GRI RMII PHY, selectable DHCP client **or** DHCP server mode -## DHCP modes +## Initialization API -`initialize_ethernet` accepts a `DhcpMode` parameter (default `CLIENT`). +The BSP exposes two initialization entry points: -### DHCP client (default) +- `initialize_ethernet()` +- `initialize_ethernet(const EthernetConfig &config)` -The ESP32 requests an IP address from an upstream router/switch. -The `on_link_up` callback fires once the DHCP lease is granted. +The no-argument overload is equivalent to `EthernetConfig{}` (DHCP client mode, +no callbacks). ```cpp auto &board = espp::Esp32EthernetKit::get(); +bool ok = board.initialize_ethernet(); +``` + +## DHCP modes and callbacks + +Mode selection is done through `EthernetConfig::mode`: + +- `DhcpMode::CLIENT` (default): obtains an address from an upstream DHCP server. +- `DhcpMode::SERVER`: serves leases to connected hosts from a static interface IP. + +Callback behavior: + +- `on_link_up`: cable/link negotiated. +- `on_link_down`: link lost. +- `on_got_ip`: IPv4 becomes usable. + - Client mode: after DHCP lease is obtained. + - Server mode: when link comes up (static IP is already known). +- `on_lost_ip`: IPv4 no longer usable. +- `server_config.on_client_assigned` (server mode only): fires for each lease assignment. + +### DHCP client (default) -board.initialize_ethernet( - [](esp_ip4_addr_t ip) { - // called when lease is acquired - }, - espp::Esp32EthernetKit::DhcpMode::CLIENT); // default — can be omitted +```cpp +using Kit = espp::Esp32EthernetKit; + +Kit::EthernetConfig cfg; +cfg.mode = Kit::DhcpMode::CLIENT; +cfg.on_link_up = []() { + // physical link is up +}; +cfg.on_got_ip = [](esp_ip4_addr_t ip) { + // DHCP lease acquired +}; +cfg.on_lost_ip = []() { + // lease lost or interface disconnected +}; + +auto &board = Kit::get(); +bool ok = board.initialize_ethernet(cfg); ``` ### DHCP server -The ESP32 assigns IP addresses to connected hosts using a static IP. -The `on_link_up` callback fires when the cable is connected (IP is static, so -it is known immediately). An optional `on_client_assigned` callback fires each -time a client receives a lease. - ```cpp -using DhcpMode = espp::Esp32EthernetKit::DhcpMode; -using ServerConfig = espp::Esp32EthernetKit::ServerConfig; +using Kit = espp::Esp32EthernetKit; -auto &board = espp::Esp32EthernetKit::get(); +Kit::EthernetConfig cfg; +cfg.mode = Kit::DhcpMode::SERVER; -ServerConfig cfg; -// Leave cfg.ip_info zero to use the built-in default: 192.168.4.1 / 255.255.255.0 -// Or set a custom address: -// IP4_ADDR(&cfg.ip_info.ip, 10, 0, 0, 1); -// IP4_ADDR(&cfg.ip_info.netmask, 255, 255, 255, 0); -// IP4_ADDR(&cfg.ip_info.gw, 10, 0, 0, 1); +// Leave ip_info all-zero to use default 192.168.4.1/24. +// Or set custom static address information: +// IP4_ADDR(&cfg.server_config.ip_info.ip, 10, 0, 0, 1); +// IP4_ADDR(&cfg.server_config.ip_info.netmask, 255, 255, 255, 0); +// IP4_ADDR(&cfg.server_config.ip_info.gw, 10, 0, 0, 1); -cfg.on_client_assigned = [](esp_ip4_addr_t ip, std::array mac) { - // fired each time a client gets a DHCP lease +cfg.on_got_ip = [](esp_ip4_addr_t ip) { + // server interface IP became active +}; +cfg.server_config.on_client_assigned = [](esp_ip4_addr_t ip, std::array mac) { + // a client received a lease }; -board.initialize_ethernet( - [](esp_ip4_addr_t ip) { - // called when link is up (static IP is already known) - }, - DhcpMode::SERVER, - cfg); +auto &board = Kit::get(); +bool ok = board.initialize_ethernet(cfg); ``` ## RMII pin mapping diff --git a/components/esp32-ethernet-kit/example/main/CMakeLists.txt b/components/esp32-ethernet-kit/example/main/CMakeLists.txt index c3bbd1284..7840fb139 100644 --- a/components/esp32-ethernet-kit/example/main/CMakeLists.txt +++ b/components/esp32-ethernet-kit/example/main/CMakeLists.txt @@ -1,5 +1,4 @@ idf_component_register( SRC_DIRS "." INCLUDE_DIRS "." - REQUIRES "esp32-ethernet-kit" ) diff --git a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp index 4dd949a9f..3f8a50f81 100644 --- a/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp +++ b/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp @@ -66,8 +66,10 @@ class Esp32EthernetKit : public BaseComponent { /// Leave \c ip_info zero-initialised to use the built-in defaults /// (192.168.4.1 / 255.255.255.0 / gw 192.168.4.1). struct ServerConfig { - esp_netif_ip_info_t ip_info; ///< IP / netmask / gateway; zero → 192.168.4.1/24 - client_ip_callback_t on_client_assigned; ///< Called each time a client is assigned an IP + esp_netif_ip_info_t ip_info{ + .ip = 0, .netmask = 0, .gw = 0}; ///< IP / netmask / gateway; zero → 192.168.4.1/24 + client_ip_callback_t on_client_assigned{ + nullptr}; ///< Called each time a client is assigned an IP }; /// Configuration for the Ethernet interface. diff --git a/doc/Doxyfile b/doc/Doxyfile index f0baad9b1..990e7b412 100755 --- a/doc/Doxyfile +++ b/doc/Doxyfile @@ -103,9 +103,9 @@ EXAMPLE_PATH = \ $(PROJECT_PATH)/components/dns_server/example/main/dns_server_example.cpp \ $(PROJECT_PATH)/components/drv2605/example/main/drv2605_example.cpp \ $(PROJECT_PATH)/components/encoder/example/main/encoder_example.cpp \ - $(PROJECT_PATH)/components/esp32-timer-cam/example/main/esp_timer_cam_example.cpp \ $(PROJECT_PATH)/components/esp32-ethernet-kit/example/main/esp32_ethernet_kit_example.cpp \ $(PROJECT_PATH)/components/esp32-p4-function-ev-board/example/main/esp32_p4_function_ev_board_example.cpp \ + $(PROJECT_PATH)/components/esp32-timer-cam/example/main/esp_timer_cam_example.cpp \ $(PROJECT_PATH)/components/esp-box/example/main/esp_box_example.cpp \ $(PROJECT_PATH)/components/event_manager/example/main/event_manager_example.cpp \ $(PROJECT_PATH)/components/expressive_eyes/example/main/expressive_eyes_example.cpp \ @@ -255,9 +255,9 @@ INPUT = \ $(PROJECT_PATH)/components/drv2605/include/drv2605_menu.hpp \ $(PROJECT_PATH)/components/encoder/include/abi_encoder.hpp \ $(PROJECT_PATH)/components/encoder/include/encoder_types.hpp \ - $(PROJECT_PATH)/components/esp32-timer-cam/include/esp32-timer-cam.hpp \ $(PROJECT_PATH)/components/esp32-ethernet-kit/include/esp32-ethernet-kit.hpp \ $(PROJECT_PATH)/components/esp32-p4-function-ev-board/include/esp32-p4-function-ev-board.hpp \ + $(PROJECT_PATH)/components/esp32-timer-cam/include/esp32-timer-cam.hpp \ $(PROJECT_PATH)/components/esp-box/include/esp-box.hpp \ $(PROJECT_PATH)/components/event_manager/include/event_manager.hpp \ $(PROJECT_PATH)/components/expressive_eyes/include/expressive_eyes.hpp \