arduino-esp32 以太网实战指南:从 LAN8720/TLK110 RMII 到 W5500 SPI 的完整接入方案
2026/9/13 23:22:10 网站建设 项目流程

arduino-esp32 以太网实战指南:从 LAN8720/TLK110 RMII 到 W5500 SPI 的完整接入方案

【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32

导读

本文围绕 Arduino core for ESP32(arduino-esp32)的Ethernet 库展开,系统讲解如何在 ESP32 系列 SoC 上通过 RMII 或 SPI 接口接入以太网 PHY 芯片(LAN8720、TLK110、W5500 等)。你将掌握官方示例中完整的引脚宏配置、事件驱动编程模型、ETH.begin()的多种调用形式与底层实现原理,并能直接复制运行文中代码完成有线网络接入。文中所有示例与源码证据均来自当前仓库的 libraries/Ethernet 目录及 tests/validation/ethernet 验证测试。

文档现状与本文定位

官方 API 文档 docs/en/api/ethernet.rst 目前标注为work in progressAbout一节明确说明该章节内容仍待补充,并邀请开发者通过 docs/en/contributing.rst 的贡献指南参与完善。文档的Examples一节是现有最核心的内容——它通过literalinclude直接内嵌了 ETH_LAN8720 与 ETH_TLK110 两个可编译示例。

因此,本文以这两个官方示例为骨架,结合仓库中 ETH.h / ETH.cpp 的实现细节、W5500 示例、WiFi 桥接示例 以及 以太网验证测试,将文档中"缺失的 About"补全为一份可查证、可运行的实战资料。

一、Ethernet 库总览:能做什么、支持哪些 PHY

从 library.properties 可确认该库的定位:Enables network connection (local and Internet) using the ESP32 Ethernet——它使 ESP32 能够实例化 Server、Client,并通过以太网收发 UDP 报文,IP 地址可静态指定或通过 DHCP 获取,同时支持 DNS 管理。

结合 ETH.h 中eth_phy_type_t枚举,当前库按编译配置支持以下 PHY 芯片:

接口类型PHY 类型枚举说明
RMII(ESP32 内置 EMAC)ETH_PHY_GENERICESP-IDF 5.4+ 的通用驱动,ETH_PHY_JL1101为其别名
RMIIETH_PHY_LAN8720最常见的低成本 RMII PHY(Olimex 等开发板)
RMIIETH_PHY_TLK110ETH_PHY_IP101(IP101 系列,见 ETH.h 的宏定义)
RMIIETH_PHY_RTL8201/ETH_PHY_DP83848瑞昱 / TI 经典 RMII PHY
RMIIETH_PHY_KSZ8041/ETH_PHY_KSZ8081Microchip KSZ80xx 系列
RMIIETH_PHY_LAN867X需 ESP-IDF 提供esp_eth_phy_lan867x.h时才启用(条件编译)
SPIETH_PHY_W5500WIZnet 硬协议栈芯片,最常用的 SPI 以太网方案
SPIETH_PHY_DM9051/ETH_PHY_KSZ8851其他 SPI 接口 PHY,同样按头文件是否存在条件编译

注意:上表中 RMII 类 PHY 需要CONFIG_ETH_USE_ESP32_EMAC配置开启(即使用芯片内置以太网 MAC);SPI 类则依赖CONFIG_ETH_SPI_ETHERNET_*系列配置。库顶层还受CONFIG_ETH_ENABLED保护(ETH.h),未启用以太网时整个头文件为空。

二、开始之前:理解引脚宏配置约定

两个官方示例(LAN8720 / TLK110)都用同一套"宏 + 默认值"模式组织硬件配置。核心约定是:

  • #include <ETH.h>之前,通过#define定义ETH_PHY_*系列宏;
  • 示例统一用#ifndef ETH_PHY_MDC包裹默认值,这样用户在pins_arduino.h中预先定义好的板级配置不会被示例覆盖;
  • 定义齐全后,setup()里直接调用无参的ETH.begin()即可(ETH.h 中的无参begin()会读取这些宏自动路由到对应重载)。

ETH.h 头部注释给出了三组可放入pins_arduino.h的典型配置样例:

// Example RMII LAN8720 (Olimex, etc.) #define ETH_PHY_TYPE ETH_PHY_LAN8720 #define ETH_PHY_ADDR 0 #define ETH_PHY_MDC 23 #define ETH_PHY_MDIO 18 #define ETH_PHY_POWER -1 #define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN // Example RMII ESP32_Ethernet_V4 #define ETH_PHY_TYPE ETH_PHY_TLK110 #define ETH_PHY_ADDR 1 #define ETH_PHY_MDC 23 #define ETH_PHY_MDIO 18 #define ETH_PHY_POWER -1 #define ETH_CLK_MODE ETH_CLOCK_GPIO0_OUT // Example SPI using ESP-IDF's driver #define ETH_PHY_TYPE ETH_PHY_W5500 #define ETH_PHY_ADDR 1 #define ETH_PHY_CS 15 #define ETH_PHY_IRQ 4 #define ETH_PHY_RST 5 #define ETH_PHY_SPI_HOST SPI2_HOST #define ETH_PHY_SPI_SCK 14 #define ETH_PHY_SPI_MISO 12 #define ETH_PHY_SPI_MOSI 13

各宏含义与取值要点:

含义备注
ETH_PHY_TYPEPHY 芯片类型取值见上文eth_phy_type_t枚举
ETH_PHY_ADDRPHY 的 MDIO 地址可用ETH_PHY_ADDR_AUTO(即ESP_ETH_PHY_ADDR_AUTO)启用自动探测;地址小于该值会被begin()拒绝(见 ETH.cpp)
ETH_PHY_MDC/ETH_PHY_MDIO管理接口时钟/数据引脚(RMII)digitalPinToGPIONumber()转换后写入 SMI 配置
ETH_PHY_POWERPHY 复位/电源引脚-1表示未使用;非 -1 时该引脚会被纳入 pin 总线管理
ETH_CLK_MODERMII 参考时钟模式ESP32 上为ETH_CLOCK_GPIO0_IN/OUTETH_CLOCK_GPIO16_OUTETH_CLOCK_GPIO17_OUT;ESP32-P4 上为 EMAC 时钟枚举
ETH_PHY_CS/IRQ/RSTSPI 方案片选/中断/复位(SPI)CS 必填;IRQ 可选(见下文无 IRQ 说明)
ETH_PHY_SPI_HOSTETH_PHY_SPI_SCK/MISO/MOSISPI 外设与引脚(IDF SPI 方案)引脚缺省为 -1 时复用begin()传入的值

三、官方示例一:LAN8720(RMII 接入)

ETH_LAN8720.ino 展示的是以太网事件的完整用法,其硬件默认值针对典型 RMII 板卡(如 Olimex):

#include <Arduino.h> #ifndef ETH_PHY_MDC #define ETH_PHY_TYPE ETH_PHY_LAN8720 #if CONFIG_IDF_TARGET_ESP32 #define ETH_PHY_ADDR 0 #define ETH_PHY_MDC 23 #define ETH_PHY_MDIO 18 #define ETH_PHY_POWER -1 #define ETH_CLK_MODE ETH_CLOCK_GPIO0_IN #elif CONFIG_IDF_TARGET_ESP32P4 #define ETH_PHY_ADDR 0 #define ETH_PHY_MDC 31 #define ETH_PHY_MDIO 52 #define ETH_PHY_POWER 51 #define ETH_CLK_MODE EMAC_CLK_EXT_IN #endif #endif #include <ETH.h>

这段配置的关键点:

  • 通过CONFIG_IDF_TARGET_ESP32/CONFIG_IDF_TARGET_ESP32P4区分芯片平台,为不同 SoC 提供不同的默认引脚与时钟模式,说明该库同时支持经典 ESP32 与新一代 ESP32-P4的内置 EMAC;
  • 经典 ESP32 的 RMII 数据引脚是固定的ETH_RMII_TX_EN=21ETH_RMII_TX0=19ETH_RMII_TX1=22ETH_RMII_RX0=25ETH_RMII_RX1_EN=26ETH_RMII_CRS_DV=27(见 ETH.h),因此只需配置 MDC/MDIO 与时钟;
  • ESP32-P4 的 RMII 引脚通过pins_arduino.h可覆盖,默认值为 TX_EN=49、TX0=34、TX1=35、RX0=29、RX1_EN=30、CRS_DV=28、RMII_CLK=50(ETH.h)。

启动流程本身极简:

static bool eth_connected = false; void setup() { Serial.begin(115200); Network.onEvent(onEvent); ETH.begin(); } void loop() { if (eth_connected) { testClient("google.com", 80); } delay(10000); }

示例随后用testClient()发起一个原始 HTTP GET 请求来验证连通性。需要特别注意的是它使用NetworkClient(而非旧版WiFiClient),这正是当前 Arduino-ESP32 网络抽象层的统一客户端类型,与Network.onEvent()配套使用。

四、官方示例二:TLK110(显式传参的 RMII 接入)

ETH_TLK110.ino 与 LAN8720 示例的差异在于两点:一是 PHY 类型与引脚默认值不同(ETH_PHY_TLK110ETH_PHY_ADDR 31ETH_PHY_POWER 17,ESP32-P4 下地址为 1);二是它在setup()显式传参调用begin()

ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_MDC, ETH_PHY_MDIO, ETH_PHY_POWER, ETH_CLK_MODE);

对应的重载原型为(ETH.h):

bool begin(eth_phy_type_t type, int32_t phy_addr, int mdc, int mdio, int power, eth_clock_mode_t clk_mode);

该重载仅在CONFIG_ETH_USE_ESP32_EMAC下可用,是 RMII 方案的标准入口。从 ETH.cpp 可以梳理出它在底层依次完成的核心调用链:

  1. Network.begin()初始化网络抽象层,并注册 ETH 事件处理器;
  2. 通过ETH_EMAC_DEFAULT_CONFIG()取得默认 EMAC 配置,按clock_mode映射 GPIO0/GPIO16/GPIO17 或外部输入时钟(经典 ESP32 分支见 ETH.cpp);
  3. esp_eth_mac_new_esp32()创建 MAC 对象,esp_eth_phy_new_lan87xx()/esp_eth_phy_new_ip101()等按type创建对应 PHY 对象(ETH.cpp);
  4. esp_eth_driver_install()安装驱动、esp_netif_new()创建网络接口、esp_eth_new_netif_glue()+esp_netif_attach()将驱动挂接到 TCP/IP 协议栈;
  5. esp_eth_start()启动以太网状态机,最后delay(50)等待 DHCP 进入良好状态(注释中注明这是为了规避历史 issue #5733);
  6. 所有占用的 GPIO 通过perimanSetPinBus()登记到 pin 总线管理器,end()或总线释放时会自动回收。

整个流程就是"MAC + PHY + netif glue + 协议栈"的 ESP-IDF 标准装配,Arduino 层把它封装成了一个布尔返回的begin()

五、事件驱动模型:Arduino 网络事件机制

两个官方示例的核心都放在onEvent(arduino_event_id_t event)回调里,setup()中通过Network.onEvent(onEvent)注册。示例注释明确警告:该回调运行在独立的 FreeRTOS 任务(线程)中,因此回调内应避免耗时阻塞操作。

回调覆盖的以太网事件生命周期如下:

事件含义示例中的处理
ARDUINO_EVENT_ETH_START以太网接口启动打印日志,并在此设置主机名ETH.setHostname("esp32-ethernet")——注释说明主机名须在接口启动后、DHCP 之前设置,因此放在事件线程里最合适
ARDUINO_EVENT_ETH_CONNECTED物理链路已连接(网线插入、协商完成)打印日志
ARDUINO_EVENT_ETH_GOT_IP通过 DHCP 获取到 IP打印ETH对象(会输出 IP/网关/掩码等),置eth_connected = true
ARDUINO_EVENT_ETH_LOST_IP丢失 IPeth_connected = false
ARDUINO_EVENT_ETH_DISCONNECTED链路断开eth_connected = false
ARDUINO_EVENT_ETH_STOP接口停止打印日志,置eth_connected = false

从源码看,这一事件流是由 ETH.cpp 的_onEthEvent()完成的:它接收 ESP-IDF 的ETHERNET_EVENT_*原始事件,映射为ARDUINO_EVENT_ETH_*枚举,并通过Network.postEvent()派发到用户的onEvent。同时,onEthConnected()(ETH.cpp)会在链路连通后按需为接口创建 IPv6 链路本地地址(esp_netif_create_ip6_linklocal()),这说明IPv6 支持默认是开启的CONFIG_LWIP_IPV6条件下)。

六、扩展到 SPI 以太网:W5500 的两种接线方式

文档示例之外,仓库还提供了两个 W5500 示例,分别对应两种 SPI 接入路径,正好补全"About 缺失"的 SPI 部分。

方式一:使用 Arduino SPI 驱动(ETH_W5500_Arduino_SPI.ino)

#include <SPI.h> #define ETH_PHY_TYPE ETH_PHY_W5500 #define ETH_PHY_ADDR 1 #define ETH_PHY_CS 15 #define ETH_PHY_IRQ 4 #define ETH_PHY_RST 5 #define ETH_SPI_SCK 14 #define ETH_SPI_MISO 12 #define ETH_SPI_MOSI 13 void setup() { SPI.begin(ETH_SPI_SCK, ETH_SPI_MISO, ETH_SPI_MOSI); ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, SPI); }

对应重载(ETH.h):

bool begin(eth_phy_type_t type, int32_t phy_addr, int cs, int irq, int rst, SPIClass &spi, uint8_t spi_freq_mhz = ETH_PHY_SPI_FREQ_MHZ);

该路径由 Arduino 的SPIClass接管 SPI 总线,库通过自定义 SPI 驱动回调(_eth_spi_read/_eth_spi_write,见 ETH.cpp)与 W5500 通信。从实现看,W5500 的 SPI 帧格式是"16 位命令 + 8 位地址",DM9051 与 KSZ8851 则使用各自的命令编码——这些细节对用户透明。

方式二:使用 ESP-IDF SPI 驱动(ETH_W5500_IDF_SPI.ino)

#define ETH_PHY_TYPE ETH_PHY_W5500 #define ETH_PHY_ADDR 1 #define ETH_PHY_CS 15 #define ETH_PHY_IRQ 4 #define ETH_PHY_RST 5 #define ETH_PHY_SPI_HOST SPI2_HOST #define ETH_PHY_SPI_SCK 14 #define ETH_PHY_SPI_MISO 12 #define ETH_PHY_SPI_MOSI 13 ETH.begin(ETH_PHY_TYPE, ETH_PHY_ADDR, ETH_PHY_CS, ETH_PHY_IRQ, ETH_PHY_RST, ETH_PHY_SPI_HOST, ETH_PHY_SPI_SCK, ETH_PHY_SPI_MISO, ETH_PHY_SPI_MOSI);

对应重载(ETH.h)允许指定spi_host_device_t与 SCK/MISO/MOSI 引脚,库内部调用spi_bus_initialize()初始化总线(ETH.cpp),SPI 频率默认ETH_PHY_SPI_FREQ_MHZ = 20MHz(ETH.h),可通过最后一个参数覆盖。

双以太网与无 IRQ 轮询模式

两个 W5500 示例均内置USE_TWO_ETH_PORTS开关,展示单 SPI 总线挂接两个 W5500 端口的能力:第二个端口用ETHClass ETH1(1)实例化(索引 1),共享 SPI 时ETH1.begin()不再传 SPI 引脚:

ETH1.begin(ETH1_PHY_TYPE, ETH1_PHY_ADDR, ETH1_PHY_CS, ETH1_PHY_IRQ, ETH1_PHY_RST, ETH_PHY_SPI_HOST);

从源码看,库支持最多 3 个以太网端口(NUM_SUPPORTED_ETH_PORTS 3),多端口时各接口的 netif 路由优先级按索引递减(route_prio -= _eth_index * 5),MAC 地址会在基址上按索引递增派生(ETH.cpp)。

另外,W5500 支持不接 IRQ 引脚的轮询模式:CS 为必填,IRQ 可传-1,此时需在begin()前用setPollPeriod(poll_period_ms)设置轮询周期(默认 10 ms,见 ETH.cpp 的构造函数初始化)。相关条件编译开关ETH_SPI_SUPPORTS_NO_IRQ定义于 ETH.h。

七、进阶示例:有线 + Wi-Fi 双通道桥接

ETH_WIFI_BRIDGE.ino 演示了以太网与 Wi-Fi 软 AP 的协同:W5500 接有线网络,同时开启 Wi-Fi AP,通过NAPT(网络地址端口转换)让无线客户端共享有线出口:

WiFi.AP.begin(); WiFi.AP.config(ap_ip, ap_ip, ap_mask, ap_leaseStart, ap_dns); WiFi.AP.create(AP_SSID, AP_PASS); ... ETH.begin(ETH_TYPE, ETH_ADDR, ETH_CS, ETH_IRQ, ETH_RST, SPI);

事件回调在ARDUINO_EVENT_ETH_GOT_IP时调用WiFi.AP.enableNAPT(true)开启转发,在LOST_IP/DISCONNECTED时关闭。这展示了该网络抽象层对多网络接口统一管理的能力——同一套onEvent机制同时处理ARDUINO_EVENT_ETH_*ARDUINO_EVENT_WIFI_AP_*事件,无线客户端STAIPASSIGNED等事件也一并在回调中可观测。

八、ETH 类 API 深度解析

结合 ETH.h 与 ETH.cpp,ETHClass继承自NetworkInterface,除网络接口通用能力外还提供以下专有 API:

运行期查询接口

bool fullDuplex() const; // 当前是否全双工(ETH_CMD_G_DUPLEX_MODE) uint16_t linkSpeed() const; // 返回 10 或 100(Mbps) bool autoNegotiation() const; // 当前是否开启自动协商 uint32_t phyAddr() const; // 实际 PHY 地址 esp_eth_handle_t handle() const; // 底层 ESP-IDF 驱动句柄

这些方法通过esp_eth_ioctl()读取,未初始化时返回 0/false(ETH.cpp)。

参数设置接口(须在 begin() 之前调用)

bool setFullDuplex(bool on); // 设置全/半双工 bool setLinkSpeed(uint16_t speed); // 仅支持 10 或 100,其他值直接报错 bool setAutoNegotiation(bool on); // 开关自动协商 void setTaskStackSize(size_t size); // 接收任务栈大小,默认 4096 void setPollPeriod(uint32_t poll_period_ms); // W5500 无 IRQ 时轮询周期,默认 10ms

实现上的关键约束:三个set*方法在_eth_started之后调用会打印"This method must be called before ETH.begin()"并返回 false;且当自动协商开启时,这些手动设置不会生效——ETH.cpp 中只有在!_auto_negotiation时才会在begin()流程内真正下发自动协商关闭、双工模式和链路速率。因此固定速率的正确写法是:

ETH.setAutoNegotiation(false); ETH.setFullDuplex(true); ETH.setLinkSpeed(100); ETH.begin();

组播与 MAC 过滤(ESP-IDF ≥ 5.5)

bool addMacFilter(uint8_t *mac_addr); // 添加单播 MAC 过滤 bool removeMacFilter(uint8_t *mac_addr); bool addMulticastFilter(IPAddress address); // 按组播 IP 推导 MAC(01:00:5E:xx:xx:xx) bool removeMulticastFilter(IPAddress address); bool receiveAllMulticast(bool on); // 默认开启(begin() 成功后自动调用)

addMulticastFilter()会校验地址首字节须在 224~239 范围内,并按 IPv4 组播 MAC 映射规则计算目的地址(ETH.cpp)。

九、验证测试:这些能力如何被自动化检验

仓库的 tests/validation/ethernet 目录提供了覆盖上述功能的硬件验证测试,其 README.md 列出的测试矩阵包括:

测试函数验证点
test_eth_link_up30 秒内链路连接并拿到 DHCP IP
test_eth_ip_valid分配的 IP 非零
test_eth_mac_addressMAC 字符串为 17 字符、含 5 个冒号
test_eth_link_speed链路速率必须是 10 或 100 Mbps,并 smoke-checkfullDuplex()
test_eth_gateway/test_eth_subnet网关非零、子网首字节为 255
test_eth_dns_server/test_eth_dns_resolutionDNS 已分配且能解析pool.ntp.org
test_eth_tcp_gateway向网关:80 或 DNS:53 发起 TCP 连接
test_eth_static_ip配置静态 IP192.168.200.250验证后恢复 DHCP

测试代码在 ethernet.ino 中通过事件驱动方式等待ARDUINO_EVENT_ETH_CONNECTED/ARDUINO_EVENT_ETH_GOT_IP,与官方示例的回调模型完全一致。测试要求真实硬件(RMII 或 SPI PHY),不支持 Wokwi/QEMU 模拟。这份测试清单相当于一份"验收标准",可作为判断自己的以太网接线与配置是否正确的参照。

十、快速排错与最佳实践小结

  1. 先确认编译配置:RMII 方案必须启用CONFIG_ETH_USE_ESP32_EMAC,SPI 方案须启用对应的CONFIG_ETH_SPI_ETHERNET_*,整体须CONFIG_ETH_ENABLED——否则ETH.h内容为空,编译会直接失败。
  2. PHY 地址要正确:地址写错时begin()虽可能成功但链路不通,可改用ETH_PHY_ADDR_AUTO自动探测(库对非法负地址会拒绝启动并打印提示)。
  3. 时钟模式按板卡接线选择:LAN8720 通常用ETH_CLOCK_GPIO0_IN(外部晶振)或ETH_CLOCK_GPIO0_OUT;时钟接错是 RMII 最常见的"插线无反应"原因。
  4. RMII 数据引脚不可更改:经典 ESP32 的 6 根 RMII 数据线是固定 GPIO(21/19/22/25/26/27),只能调整 MDC/MDIO 和时钟。
  5. 配置宏尽量下沉到pins_arduino.h:官方示例的#ifndef保护正是为了兼容板级预定义,见 ETH.h 注释。
  6. 回调线程安全onEvent在 FreeRTOS 任务中执行,只做标志位设置与日志输出,网络 I/O 放到loop()
  7. 多接口场景:第二个以太网口用ETHClass ETH1(1),共享 SPI 时省略 SPI 引脚;接口路由优先级会自动按索引调整。

结语

尽管官方文档 docs/en/api/ethernet.rst 的 About 部分仍在建设中,但仓库中的 Ethernet 库、5 个官方示例与 验证测试 已经构成了一套完整可用的有线网络方案:从 RMII 的 LAN8720/TLK110 到 SPI 的 W5500,从单端口到三端口,从纯以太网到与 Wi-Fi 的 NAPT 桥接。本文所述的所有配置宏、API 行为与调用链均可直接在对应源码文件中查证,你可以据此在自己的板卡上完成以太网接入,并进一步为官方文档的 About 章节贡献内容。

【免费下载链接】arduino-esp32Arduino core for the ESP32 family of SoCs项目地址: https://gitcode.com/GitHub_Trending/ar/arduino-esp32

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询