news 2026/9/15 23:57:28

ESP-IDF 以太网驱动开发指南:从 IEEE 802.3 帧格式到 esp_eth 驱动安装与 TCP/IP 接入

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
ESP-IDF 以太网驱动开发指南:从 IEEE 802.3 帧格式到 esp_eth 驱动安装与 TCP/IP 接入

ESP-IDF 以太网驱动开发指南:从 IEEE 802.3 帧格式到 esp_eth 驱动安装与 TCP/IP 接入

【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf

本指南基于 ESP-IDF 官方编程指南 docs/en/api-reference/network/esp_eth.rst 编写,系统讲解 ESP-IDF 以太网(Ethernet)驱动的完整开发流程:从以太网帧格式等基础概念,到内部 EMAC 与 SPI-Ethernet 模块的 MAC/PHY 配置、驱动安装、事件处理、接入 LwIP TCP/IP 协议栈,再到硬件时间戳(PTP)、流量控制等进阶能力。读完本文,你将能够基于 ESP-IDF 从零配置并驱动一块以太网 PHY 芯片,让设备通过有线网络获得可 Ping 通的 IP 地址。

Overview:ESP-IDF 以太网驱动概览

ESP-IDF 提供了一组一致且灵活的 API,用于同时支持内部以太网 MAC 控制器(EMAC)外部 SPI-Ethernet 模块。对于内部集成 EMAC 的芯片,驱动同时涵盖 MAC 与 PHY 两层;对于仅支持外部模块的芯片,驱动则专注于 SPI-Ethernet 模块。

整个编程指南按以下四个部分组织:

  1. Basic Ethernet Concepts —— 以太网基础概念;
  2. Driver Configuration and Installation —— 驱动配置与安装;
  3. Connect Driver to Stack —— 将驱动接入 TCP/IP 协议栈;
  4. Misc Operation of Driver —— 驱动杂项控制。

在 components/esp_eth 组件中,include/目录下的头文件定义了全部公共 API(esp_eth.hesp_eth_driver.hesp_eth_mac.hesp_eth_mac_esp.hesp_eth_phy.hesp_eth_com.hesp_eth_netif_glue.h等),src/目录则包含 MAC(mac/)、PHY(phy/)与驱动核心(esp_eth.c)的实现。

以太网基础概念:理解 IEEE 802.3 帧结构

以太网是一种异步的载波侦听多路访问/冲突检测(CSMA/CD)协议。它虽不适合低功耗应用,但因部署广泛、接入互联网便捷、数据速率高、可无限扩展距离等优势,几乎可以承载所有有线通信需求。

符合 IEEE 802.3 规范的以太网帧长度为64 到 1518 字节,由五或六个字段组成:

字段长度说明
DA(目的 MAC 地址)6 字节帧要送达的节点 MAC 地址
SA(源 MAC 地址)6 字节产生该帧的节点 MAC 地址
Type/Length(类型/长度)2 字节协议类型或数据长度
Payload(数据负载)0~1500 字节客户端数据,如 IP 数据报
Padding(填充)可变使帧达到最小长度要求
FCS(帧校验序列)4 字节32 位 CRC 校验值

此外,帧在物理介质上传输时,其开头还会附加7 字节前导码(Preamble)帧起始定界符(SFD,Start-of-Frame Delimiter)。以太网双绞线上的流量结构如下(原文档配图为 docs/_static/diagrams/ethernet/data_frame_format.diag)。

前导码与帧起始定界符

前导码包含7 个字节的55H,用于让接收端在实际帧到来之前锁定数据流。SFD 是在物理介质上呈现的二进制序列10101011,有时被视为前导码的一部分。收发数据时,前导码与 SFD 会被硬件自动生成或剥离。

目的地址(DA)

目的地址字段包含 6 字节的目标节点 MAC 地址。若 MAC 地址首字节的最低位为 1,则为组播(multicast)地址。例如01-00-00-00-F0-0033-45-67-89-AB-CD是组播地址,而00-00-00-00-F0-0032-45-67-89-AB-CD不是。若目的地址为保留组播地址FF-FF-FF-FF-FF-FF,则该帧是广播(broadcast)帧,会送达网络中所有节点;若首字节最低位为 0,则为单播(unicast)地址,仅由被寻址的节点使用。

EMAC 控制器通常内置接收过滤器,可丢弃或接收具有组播、广播和/或单播目的地址的帧。发送时,主机控制器负责把目的地址写入发送缓冲区。

源地址(SA)

源地址字段包含 6 字节的源节点 MAC 地址。每个控制器都需要唯一的 MAC 地址,它由两部分组成:前 3 字节为组织唯一标识符(OUI,Organizationally Unique Identifier),由 IEEE 分配;后 3 字节为购买 OUI 的公司自行分配的地址字节。关于 ESP-IDF 中的 MAC 地址分配策略,可参考 MAC 地址分配文档。

类型/长度字段

这是 2 字节字段:

  • 若值≤ 1500(十进制),则视为长度字段,表示数据字段中非填充数据的数量;
  • 若值≥ 1536,则表示数据所属的协议类型。

常见类型值:IPv4 =0800HIPv6 =86DDHARP =0806H。实现私有网络的用户可把该字段当作长度字段;而实现 IP、ARP 等协议的应用,发送时应按协议规范写入相应类型值。

负载(Payload)

负载字段长度可变,为0 到 1500 字节。更大的数据包违反以太网标准,会被大多数以太网节点丢弃。该字段包含客户端数据,例如 IP 数据报。

填充与 FCS

  • 填充字段长度可变,用于在数据负载较小时满足 IEEE 802.3 规范要求;
  • DA、SA、Type、Payload 与 Padding 合计不得小于 60 字节;加上 4 字节 FCS 后,帧不得小于 64 字节;当 Payload 小于 46 字节时必须填充;
  • FCS 是 4 字节字段,包含基于 DA、SA、Type、Payload、Padding 计算的业界标准 32 位 CRC。鉴于 CRC 计算的复杂度,硬件通常会自动生成并发送有效 CRC;否则主机控制器必须自行计算并写入发送缓冲区。

通常主机控制器无需关心填充与 CRC——硬件 EMAC 在发送时自动生成、接收时自动校验。但帧到达时填充与 CRC 字段会写入接收缓冲区,因此主机控制器需要时也可读取评估。

注意:除了上述基本数据帧,10/100 Mbps 以太网还有另外两种常见帧类型:控制帧VLAN 标记帧,这两种类型在 ESP-IDF 中不支持。

配置 MAC 与 PHY:驱动安装前的关键参数

以太网驱动由MACPHY两部分组成。MAC 与 PHY 之间的通信有多种接口选择:MII(Media Independent Interface,介质无关接口)、RMII(Reduced Media Independent Interface,精简介质无关接口)等。

MII 与 RMII 最明显的差异是信号数量:MII 通常最多占用18 根信号线,而 RMII 可将消耗降至9 根。ESP-IDF 仅支持 RMII 接口,因此eth_esp32_emac_config_t::interface必须始终设置为EMAC_DATA_INTERFACE_RMII

RMII 参考时钟(REF_CLK)的三种生成方式

在 RMII 模式下,收发信号都以REF_CLK为基准。对 PHY 与 MAC 的任何访问期间,REF_CLK必须保持稳定。根据电路设计,通常有三种生成REF_CLK的方式:

方式时钟来源clock_config.rmii.clock_mode配置
aPHY 芯片从其外部 25 MHz 晶振衍生REF_CLKEMAC_CLK_EXT_IN
bPHY 使用外部 50 MHz 晶振或其他时钟源作为 MAC 侧的REF_CLKEMAC_CLK_EXT_IN
cEMAC 控制器使用内部高精度 PLL 生成REF_CLKEMAC_CLK_OUT

无论选择哪种模式,硬件设计上都必须重视REF_CLK 的信号完整性:走线尽量短,远离射频器件与电感元件。

ESP32 上的特殊注意事项
  • 若配置为EMAC_CLK_OUT,内部Audio PLL(APLL)被用作 50 MHz 时钟源,需确保不与 I2S 总线配置冲突;此时GPIO0可直接输出REF_CLK(该时钟直接输出到 GPIO,与 EMAC 外设无直接关系,某些 PHY 下可能工作不佳);若设计中未使用 PSRAM,GPIO16GPIO17也可输出参考时钟(来源同为 APLL,但从 EMAC 外设路由)。
  • 若配置为EMAC_CLK_EXT_INGPIO0是输入REF_CLK的唯一选择。注意GPIO0 是 ESP32 的重要 strapping 引脚:上电时若采样到低电平,ESP32 会进入下载模式,系统将停滞直至手动复位。规避方法是默认在硬件上禁用REF_CLK(不干扰 boot 阶段 strapping),然后在以太网驱动安装阶段重新使能。禁用方式包括:禁用或断电晶振(方式 b)、强制 PHY 复位(方式 a,但部分 PHY 即使处于复位状态仍会向 GPIO0 输出信号,此方法可能失效)。
  • 若希望以太网与 Wi-Fi 或蓝牙共存,不要选择 ESP32 作为REF_CLK来源(会导致REF_CLK不稳定)。应禁用 Wi-Fi,或使用 PHY / 外部晶振作为REF_CLK源。
ESP32-P4 的 MPLL 限制

ESP32-P4 上若配置为EMAC_CLK_OUT,EMAC 通过整数分频器从MPLL衍生 50 MHz RMII 参考时钟。当同时启用 PSRAM 时,两者共享 MPLL,而 PSRAM 会将其锁定到由其速度配置决定的频率。若 PSRAM 速度配置为 80 MHz(CONFIG_SPIRAM_SPEED),MPLL 运行在 320 MHz,而 320 MHz 不存在能在 ±50 ppm 容差内产生 50 MHz 的整数分频(最接近的是 320/6 ≈ 53.33 MHz),此时EMAC 初始化将失败。若必须使用 80 MHz PSRAM 速度,应从外部(PHY 或晶振)提供REF_CLK并配置为EMAC_CLK_EXT_IN

内部时钟输出时的外部回环要求

对于不支持内部回环的芯片(not SOC_EMAC_RMII_CLK_OUT_INTERNAL_LOOPBACK),若配置为EMAC_CLK_OUTREF_CLK输出信号必须在外部回环到 EMAC:需要将clock_config_out_in.clock_mode配置为EMAC_CLK_EXT_IN,并选择与REF_CLK输入 GPIO 对应的 GPIO 号。

数据面与控制面 GPIO 的差异

  • 对于不支持多 IO_MUX 的芯片(not SOC_EMAC_USE_MULTI_IO_MUX):数据面信号通过IO_MUX固定到特定 GPIO,无法修改;控制面信号可通过GPIO Matrix路由到任意空闲 GPIO。
  • 对于支持多 IO_MUX 的芯片(SOC_EMAC_USE_MULTI_IO_MUX):数据面 GPIO 可通过 IO_MUX 配置为预定义集合,由驱动根据eth_esp32_emac_config_t::emac_dataif_gpio的内容完成配置;控制面信号仍可通过 GPIO Matrix 自由路由。以下为不同芯片的 RMII 数据面 GPIO 预定义表(由 esp_eth_mac_esp.h 中的默认配置印证):
引脚名称ESP32ESP32-P4
TX_ENGPIO21GPIO33、GPIO40、GPIO49
TXD0GPIO19GPIO34、GPIO41
TXD1GPIO22GPIO35、GPIO42
CRS_DVGPIO27GPIO28、GPIO45、GPIO51
RXD0GPIO25GPIO29、GPIO46、GPIO52
RXD1GPIO26GPIO30、GPIO47、GPIO53

REF_CLK输入 GPIO:ESP32 为 GPIO0,ESP32-P4 为 GPIO32、GPIO44、GPIO50;REF_CLK输出 GPIO:ESP32 为 GPIO0/GPIO16/GPIO17,ESP32-P4 为 GPIO23、GPIO39。)

MAC 层通用配置:eth_mac_config_t

MAC 层通用配置在eth_mac_config_t中描述(定义见 esp_eth_mac.h):

配置项说明
sw_reset_timeout_ms软件复位超时值(毫秒)。MAC 复位通常在 100 ms 内完成
rx_task_stack_sizeMAC 驱动会创建专用任务处理入包,此参数设置该任务的栈大小
rx_task_prio上述接收任务的优先级
flags指定 MAC 驱动的额外特性,可与ETH_MAC_FLAG_前缀的宏按位或。例如需要 MAC 驱动在缓存禁用时也能工作,可配置ETH_MAC_FLAG_WORK_WITH_CACHE_DISABLE

内部 MAC 专用配置:eth_esp32_emac_config_t

针对内部 MAC 模块的配置在eth_esp32_emac_config_t中描述(定义见 esp_eth_mac_esp.h):

配置项说明
smi_gpio.mdc_num/smi_gpio.mdio_num连接 SMI 信号(MDC/MDIO)的 GPIO 号
interfaceMAC 到 PHY 的数据接口(MII/RMII),必须为 RMII
clock_configEMAC 接口时钟配置(RMII 下的REF_CLK模式与 GPIO 号)
intr_priorityMAC 中断优先级。设为 0 或负值则由驱动按默认优先级分配;Low/Medium 优先级(1~3)可在 C 中处理
emac_dataif_gpioEMAC MII/RMII 数据面 GPIO 号配置(多 IO_MUX 芯片)
clock_config_out_in内部生成REF_CLK并外部回环时,EMAC 输入接口时钟的配置;模式必须始终为EMAC_CLK_EXT_IN,仅当clock_configEMAC_CLK_OUT时有效
dma_burst_lenEMAC DMA 突发长度(Tx/Rx)
mdc_freq_hzEMAC MDC 频率上限,0 或负值时驱动将 CSR 时钟范围设为最高 2.5 MHz

以 ESP32 为例,ETH_ESP32_EMAC_DEFAULT_CONFIG()默认配置为:MDC=GPIO23、MDIO=GPIO18、接口 RMII、EMAC_CLK_EXT_IN且 clock_gpio=0、DMA 突发长度 32、中断优先级 0。

内部 MAC 的 DMA 内存考量

内部 MAC 子系统通过 DMA 以描述符链表方式与 CPU 域交换数据,描述符分发送(Transmit)与接收(Receive)两类,每类描述符保存帧的状态信息或提供传输控制,并包含指向当前数据缓冲区与下一描述符的指针。单个 EMAC DMA 描述符在 DMA 内存中占32 字节(ESP32-P4 因内存对齐需要实际占 64 字节)。

默认配置可覆盖大多数场景,但以下情况可能需要调整:

  • 短而频繁的帧占主导的网络流量:尽管额定 100 Mbps,仍可能出现吞吐量低于预期、接收丢帧;发送时 socket API 返回errno = ENOMEM并伴随insufficient TX buffer size日志(调试级别)。原因是默认内存配置面向较大帧优化——CONFIG_ETH_DMA_BUFFER_SIZE默认为 512 字节以取得更好的"数据缓冲区/描述符"开销比。解决办法是增大CONFIG_ETH_DMA_RX_BUFFER_NUMCONFIG_ETH_DMA_TX_BUFFER_NUM,并可考虑减小CONFIG_ETH_DMA_BUFFER_SIZE以匹配网络中的典型帧大小。

  • 高吞吐导致缓冲区耗尽:若发送间歇性返回ENOMEM且吞吐接近额定 100 Mbps,说明接近硬件极限,硬件无法跟上发送请求。应增大CONFIG_ETH_DMA_TX_BUFFER_NUM以缓冲更多帧、缓解瞬时发送峰值。但这无法解决持续超额定吞吐的场景,此时只能在应用层通过软件限制带宽。

相关配置项在 components/esp_eth/Kconfig 中定义:ETH_DMA_BUFFER_SIZE范围 256~1600(默认 512,ESP32-P4/S31 需 64 字节对齐)、ETH_DMA_RX_BUFFER_NUM范围 3~30(ESP32 默认 10,其余默认 20)、ETH_DMA_TX_BUFFER_NUM范围 3~30(默认 10);当 RX 缓冲区数大于 15 时还可启用ETH_SOFT_FLOW_CONTROL软件流控。

PHY 层配置:eth_phy_config_t

PHY 配置在eth_phy_config_t中描述(定义见 esp_eth_phy.h):

配置项说明
phy_addrPHY 地址,范围 0~15。多个 PHY 可共享同一 SMI 总线,故每个 PHY 需唯一地址(通常由硬件 strapping 引脚决定)。若 SMI 总线仅挂一个 PHY,设为-1ESP_ETH_PHY_ADDR_AUTO)可让驱动自动探测地址
reset_timeout_msPHY 复位超时值(毫秒),通常在 100 ms 内完成
autonego_timeout_ms自动协商超时值(毫秒)。驱动会自动与对端协商双工与速率模式,具体取决于板载 PHY 能力
reset_gpio_numPHY 复位引脚 GPIO,未连接则设为-1
hw_reset_assert_time_usPHY 复位引脚断言时间(微秒),设为 0 使用芯片默认时序
post_hw_reset_delay_ms硬件复位完成后的等待时间(毫秒),0 使用芯片默认,-1ESP_ETH_NO_POST_HW_RESET_DELAY)不等待

ESP-IDF 通过宏ETH_MAC_DEFAULT_CONFIG()ETH_PHY_DEFAULT_CONFIG()提供 MAC 与 PHY 的默认配置。ETH_PHY_DEFAULT_CONFIG()默认:phy_addr = -1(自动探测)、reset_timeout_ms = 100autonego_timeout_ms = 4000reset_gpio_num = 5

创建 MAC 与 PHY 实例

以太网驱动采用面向对象风格实现,对 MAC 与 PHY 的任何操作都基于二者的实例。MAC 与 PHY 的构造函数成对使用,例如esp_eth_mac_new_esp32esp_eth_phy_new_generic

内部 EMAC + 外部 PHY

eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); // 应用默认通用 MAC 配置 eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); // 应用默认厂商专属 MAC 配置 esp32_emac_config.smi_gpio.mdc_num = CONFIG_ETHERNET_MDC_GPIO; // 修改 MDC 信号 GPIO esp32_emac_config.smi_gpio.mdio_num = CONFIG_ETHERNET_MDIO_GPIO; // 修改 MDIO 信号 GPIO esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); // 创建 MAC 实例 eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); // 应用默认 PHY 配置 phy_config.phy_addr = CONFIG_ETHERNET_PHY_ADDR; // 按板卡设计修改 PHY 地址 phy_config.reset_gpio_num = CONFIG_ETHERNET_PHY_RST_GPIO; // 修改 PHY 复位 GPIO esp_eth_phy_t *phy = esp_eth_phy_new_generic(&phy_config); // 创建通用 PHY 实例

注意:使用esp_eth_phy_new_generic创建实例时,任何符合 IEEE 802.3 的 PHY 芯片均可使用。但基础功能虽总是可用,部分特定特性(如回环功能,某些 PHY 需设置特定速率模式才能正确工作)可能受限。若需要针对芯片定制的驱动,可使用 ESP-IDF 官方支持的 PHY 驱动,或参考下文"自定义 PHY 驱动"章节自行开发。

可选:运行时 MAC 时钟配置。EMAC 的REF_CLK也可在用户应用代码中配置:

eth_esp32_emac_config_t esp32_emac_config = ETH_ESP32_EMAC_DEFAULT_CONFIG(); // 应用默认厂商专属 MAC 配置 // ... esp32_emac_config.interface = EMAC_DATA_INTERFACE_RMII; // 修改 EMAC 数据接口 esp32_emac_config.clock_config.rmii.clock_mode = EMAC_CLK_OUT; // 选择 EMAC REF_CLK 模式 esp32_emac_config.clock_config.rmii.clock_gpio = 17; // 选择 EMAC REF_CLK 输入/输出 GPIO esp_eth_mac_t *mac = esp_eth_mac_new_esp32(&esp32_emac_config, &mac_config); // 创建 MAC 实例

SPI-Ethernet 模块

eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG(); // 应用默认通用 MAC 配置 eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG(); // 应用默认 PHY 配置 phy_config.phy_addr = CONFIG_ETHERNET_PHY_ADDR; // 按板卡设计修改 PHY 地址 phy_config.reset_gpio_num = CONFIG_ETHERNET_PHY_RST_GPIO; // 修改 PHY 复位 GPIO // 安装 GPIO 中断服务(SPI-Ethernet 模块为中断驱动) gpio_install_isr_service(0); // SPI 总线配置 spi_device_handle_t spi_handle = NULL; spi_bus_config_t buscfg = { .miso_io_num = CONFIG_ETHERNET_SPI_MISO_GPIO, .mosi_io_num = CONFIG_ETHERNET_SPI_MOSI_GPIO, .sclk_io_num = CONFIG_ETHERNET_SPI_SCLK_GPIO, .quadwp_io_num = -1, .quadhd_io_num = -1, }; ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_ETHERNET_SPI_HOST, &buscfg, 1)); // 配置 SPI 设备 spi_device_interface_config_t spi_devcfg = { .mode = 0, .clock_speed_hz = CONFIG_ETHERNET_SPI_CLOCK_MHZ * 1000 * 1000, .spics_io_num = CONFIG_ETHERNET_SPI_CS_GPIO, .queue_size = 20 }; /* dm9051 以太网驱动基于 SPI 驱动 */ eth_dm9051_config_t dm9051_config = ETH_DM9051_DEFAULT_CONFIG(CONFIG_ETHERNET_SPI_HOST, &spi_devcfg); dm9051_config.int_gpio_num = CONFIG_ETHERNET_SPI_INT_GPIO; esp_eth_mac_t *mac = esp_eth_mac_new_dm9051(&dm9051_config, &mac_config); esp_eth_phy_t *phy = esp_eth_phy_new_dm9051(&phy_config);

注意

  • 为 SPI-Ethernet 模块(如 DM9051)创建 MAC 与 PHY 实例时,构造函数必须具有相同后缀(如esp_eth_mac_new_dm9051esp_eth_phy_new_dm9051),因为模块集成了 PHY,别无选择;
  • SPI 设备配置(spi_device_interface_config_t)可能因模块或 PCB 的 SPI 时序要求而异,请查阅模块规格书与 ESP-IDF 示例。

安装驱动:组合 MAC 与 PHY

安装以太网驱动需要将 MAC 与 PHY 实例组合,并在esp_eth_config_t中设置额外的高层配置(定义见 esp_eth_driver.h):

配置项说明
mac由 MAC 构造器创建的实例(如esp_eth_mac_new_esp32
phy由 PHY 构造器创建的实例(如esp_eth_phy_new_generic
check_link_period_ms驱动启动 OS 定时器周期性检查链路状态,此字段设置间隔(毫秒)
stack_input/stack_input_info大多数 IoT 应用中,驱动接收到的帧应上交上层(如 TCP/IP 协议栈)。此字段设置为处理入帧的函数。驱动安装后还可通过esp_eth_update_input_path在运行时更新。stack_input_info变体可额外携带帧信息(时间戳、CRC 校验结果等)
on_lowlevel_init_done/on_lowlevel_deinit_done底层硬件初始化/去初始化完成时调用的钩子
read_phy_reg/write_phy_regPHY 寄存器读写函数,通常由 MAC(SMI 接口)提供;若 PHY 由其他接口(如 I2C)管理则需用户实现,置 NULL 表示由 MAC 的 SMI 接口管理

ETH_DEFAULT_CONFIG(emac, ephy)提供默认配置(check_link_period_ms = 2000),安装代码如下:

esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); // 应用默认驱动配置 esp_eth_handle_t eth_handle = NULL; // 驱动安装后获得驱动句柄 esp_eth_driver_install(&config, &eth_handle); // 安装驱动

以太网驱动还包含事件驱动模型,会向用户空间发送有用事件。需要在安装驱动前初始化事件循环(详见 ESP Event 文档):

/** 以太网事件处理器 */ static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { uint8_t mac_addr[6] = {0}; /* 可从事件数据中获取以太网驱动句柄 */ esp_eth_handle_t eth_handle = *(esp_eth_handle_t *)event_data; switch (event_id) { case ETHERNET_EVENT_CONNECTED: esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); ESP_LOGI(TAG, "Ethernet Link Up"); ESP_LOGI(TAG, "Ethernet HW Addr %02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); break; case ETHERNET_EVENT_DISCONNECTED: ESP_LOGI(TAG, "Ethernet Link Down"); break; case ETHERNET_EVENT_START: ESP_LOGI(TAG, "Ethernet Started"); break; case ETHERNET_EVENT_STOP: ESP_LOGI(TAG, "Ethernet Stopped"); break; default: break; } } esp_event_loop_create_default(); // 创建后台运行的默认事件循环 esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, &eth_event_handler, NULL); // 注册以太网事件处理器

事件类型定义于 esp_eth_com.h 的eth_event_tETHERNET_EVENT_STARTETHERNET_EVENT_STOPETHERNET_EVENT_CONNECTEDETHERNET_EVENT_DISCONNECTED,事件基为ETH_EVENT。驱动内部通过esp_eth_mediator_ton_state_changed回调感知链路、速率、双工等状态变化,再转换为上层事件。

启动以太网驱动

驱动安装完成后,可立即启动:

esp_eth_start(eth_handle); // 启动以太网驱动状态机

esp_eth_start独立模式(即不接 TCP/IP 协议栈)下启动驱动状态机与内部软件定时器(用于周期性检查链路状态)。

将驱动接入 TCP/IP 协议栈

至此我们仍处于 OSI 第二层(数据链路层):虽可在用户空间检测链路上下事件、获取 MAC 地址,但无法获得 IP 地址,更不用说发送 HTTP 请求。ESP-IDF 使用的 TCP/IP 协议栈是LwIP(详见 LwIP 文档)。

将以太网驱动接入 TCP/IP 协议栈需三步:

  1. 为以太网驱动创建网络接口(esp_netif);
  2. 将网络接口附加到以太网驱动;
  3. 注册 IP 事件处理器。

(网络接口相关请参考 esp_netif 文档。)

/** IP_EVENT_ETH_GOT_IP 事件处理器 */ static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { ip_event_got_ip_t *event = (ip_event_got_ip_t *) event_data; const esp_netif_ip_info_t *ip_info = &event->ip_info; ESP_LOGI(TAG, "Ethernet Got IP Address"); ESP_LOGI(TAG, "~~~~~~~~~~~"); ESP_LOGI(TAG, "ETHIP:" IPSTR, IP2STR(&ip_info->ip)); ESP_LOGI(TAG, "ETHMASK:" IPSTR, IP2STR(&ip_info->netmask)); ESP_LOGI(TAG, "ETHGW:" IPSTR, IP2STR(&ip_info->gw)); ESP_LOGI(TAG, "~~~~~~~~~~~"); } esp_netif_init()); // 初始化 TCP/IP 网络接口(应用中只应调用一次) esp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); // 应用以太网默认网络接口配置 esp_netif_t *eth_netif = esp_netif_new(&cfg); // 为以太网驱动创建网络接口 esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handle)); // 将以太网驱动附加到 TCP/IP 协议栈 esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL); // 注册用户 IP 事件处理器 esp_eth_start(eth_handle); // 启动以太网驱动状态机

警告:建议在注册用户的以太网/IP 事件处理器之前,完整初始化以太网驱动与网络接口——即把事件处理器的注册作为启动以太网驱动前的最后一步。这样可确保以太网/IP 事件先由驱动或网络接口处理,用户处理器执行时系统处于预期状态。

完整的应用实现可参考 examples/ethernet/basic/main/ethernet_example_main.c,其中eth_init()完整展示了默认配置初始化、PHY 地址/复位引脚覆盖、RMII 时钟模式与数据面 GPIO 配置(含SOC_EMAC_USE_MULTI_IO_MUX分支),app_main()则展示了 netif 创建、esp_eth_new_netif_glue粘合、事件注册与esp_eth_start的完整调用序列,并支持在运行指定秒数后停止并反初始化整个网络(演示esp_eth_stopesp_eth_del_netif_glueesp_netif_destroyesp_eth_driver_uninstall等清理流程)。

以太网驱动的杂项控制

以下函数应在以太网驱动安装后调用:

  • 停止驱动:esp_eth_stop
  • 更新以太网数据输入路径:esp_eth_update_input_path
  • 驱动属性杂项读写:esp_eth_ioctl
/* 获取 MAC 地址 */ uint8_t mac_addr[6]; memset(mac_addr, 0, sizeof(mac_addr)); esp_eth_ioctl(eth_handle, ETH_CMD_G_MAC_ADDR, mac_addr); ESP_LOGI(TAG, "Ethernet MAC Address: %02x:%02x:%02x:%02x:%02x:%02x", mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]); /* 获取 PHY 地址 */ int phy_addr = -1; esp_eth_ioctl(eth_handle, ETH_CMD_G_PHY_ADDR, &phy_addr); ESP_LOGI(TAG, "Ethernet PHY Address: %d", phy_addr);

esp_eth_ioctl支持的常用命令(完整列表见 esp_eth_driver.h 中的esp_eth_io_cmd_t):

  • ETH_CMD_G/S_MAC_ADDR:获取/设置 MAC 地址(6 字节缓冲区);
  • ETH_CMD_G/S_PHY_ADDR:获取/设置 PHY 地址(0~31,uint32_t);
  • ETH_CMD_G/S_AUTONEGO:获取/启用/禁用自动协商(bool,设置前需停止驱动);
  • ETH_CMD_G/S_SPEEDETH_CMD_G/S_DUPLEX_MODE:获取/设置速率与双工模式(设置前需停止驱动并禁用自动协商);
  • ETH_CMD_S_PROMISCUOUS:设置混杂模式(bool);
  • ETH_CMD_S_FLOW_CTRL:设置流控(bool);
  • ETH_CMD_S_PHY_LOOPBACK:PHY 回环(bool);
  • ETH_CMD_READ/WRITE_PHY_REG:读写 PHY 寄存器(esp_eth_phy_reg_rw_data_t);
  • ETH_CMD_S_ALL_MULTICASTETH_CMD_ADD/DEL_MAC_FILTER:组播接收与 MAC 过滤;
  • ETH_CMD_CUSTOM_MAC_CMDS/ETH_CMD_CUSTOM_PHY_CMDS:MAC/PHY 自定义命令起始偏移(0x0FFF/0x1FFF,见 esp_eth_com.h)。

此外,esp_eth_transmit/esp_eth_transmit_ctrl_vargs提供通用发送能力(后者支持从多个缓冲区零拷贝组帧,并通过ctrl参数传递帧附加控制信息);esp_eth_increase_reference/esp_eth_decrease_reference提供引用计数保护,防止多任务场景下驱动被意外卸载。

EMAC 硬件时间戳(PTP)

时间戳相关 API 目前处于**"实验特性"**状态,可能随未来版本变化。

EMAC 硬件时间戳可精确跟踪以太网帧的收发时刻,对PTP(Precision Time Protocol,精确时间协议)等应用至关重要——硬件内嵌时间戳可避免软件层延迟与处理开销带来的抖动,实现纳秒级精度。

基本使能与时间读写:

esp_eth_mac_t *mac; esp_eth_get_mac_instance(eth_hndl, &mac); // 使能硬件时间戳 eth_mac_ptp_config_t ptp_cfg = ETH_MAC_ESP_PTP_DEFAULT_CONFIG(); esp_eth_mac_ptp_enable(mac, &ptp_cfg); // 获取当前 EMAC 时间 eth_mac_time_t ptp_time; esp_eth_mac_get_ptp_time(mac, &ptp_time); // 设置 EMAC 时间 ptp_time = { .seconds = 42, .nanoseconds = 0 }; esp_eth_mac_set_ptp_time(mac, &ptp_time);

PTP 模块配置项(eth_mac_ptp_config_t,见 esp_eth_mac_esp.h):

配置项说明
clk_srcPTP 时钟源,从soc_periph_emac_ptp_clk_src_t枚举中选择
clk_src_period_nsPTP 时钟源周期(纳秒)。例如 40 MHz 时钟源周期为 25 ns
required_accuracy_nsPTP 所需精度(纳秒),必须劣于 PTP 时钟源(如 40 MHz/25 ns 时钟对应 40 ns)
roll_type亚秒寄存器的翻转模式(数字或二进制)。推荐二进制翻转模式,可提供更精确的时间同步

ETH_MAC_ESP_PTP_DEFAULT_CONFIG()默认:EMAC_PTP_CLK_SRC_XTAL、周期 25 ns、精度 40 ns、ETH_PTP_BINARY_ROLLOVER

收发帧的时间戳可通过esp_eth_config_t::stack_input_info回调的最后一个参数(接收路径)与esp_eth_transmit_ctrl_vargsctrl参数(发送路径)获取;更友好的用户态获取方式是使用 L2 TAP 的扩展缓冲区机制。

还可通过注册回调并配置目标时间,在精确时刻触发事件(回调在 ISR 上下文执行,应尽量简短):

// 注册回调函数 esp_eth_mac_set_target_time_cb(mac, ts_callback); // 设置事件触发时间 eth_mac_time_t mac_target_time = { .seconds = 42, .nanoseconds = 0 }; esp_eth_mac_set_target_time(mac, &mac_target_time);

PTP 同步时间还可通过 GPIO 上的PPS(每秒脉冲,Pulse-Per-Second)信号对外输出,为外部设备提供精确硬件时间基准。PPS 默认每秒一个脉冲,可通过esp_eth_mac_set_pps_out_freq调整输出频率(取值 0~16384:0 = 1PPS 窄脉冲,其他值产生方波时钟信号,频率必须为 2 的幂且 ≤ 16384 Hz);注意数字翻转模式下因位非线性翻转,实际频率为平均值(一秒周期内占空比偏离 50%),二进制翻转模式不受此影响,因此推荐使用。PPS 输出引脚用esp_eth_mac_set_pps_out_gpio配置(ESP32-P4 从硅片修订版本 3 起支持 GPIO 输出 PPS)。相关应用可参考 examples/ethernet/ptp。

流量控制(Flow Control)

MCU 上的以太网受 RAM 容量限制,在网络拥塞时可处理的帧数有限。以太网流控机制允许接收节点向发送方发出暂停请求,直至接收方跟上,其核心是IEEE 802.3x 定义的暂停帧(Pause Frame)

暂停帧是承载暂停命令的特殊以太网帧:EtherType 为0x8808,控制操作码为0x0001。仅全双工节点可发送暂停帧;需要暂停链路对端时,向保留组播地址01-80-C2-00-00-01发送暂停帧,帧中包含 0~65535 的暂停时间(2 字节整数)。

驱动安装后,流控特性默认禁用,可通过以下方式启用:

bool flow_ctrl_enable = true; esp_eth_ioctl(eth_handle, ETH_CMD_S_FLOW_CTRL, &flow_ctrl_enable);

需要留意:暂停帧能力由 PHY 在自动协商阶段向对端通告,因此驱动仅在链路两端都支持时才发送暂停帧。另外在 components/esp_eth/Kconfig 中,当ETH_DMA_RX_BUFFER_NUM > 15时可启用ETH_SOFT_FLOW_CONTROL——ESP32 的 MAC 引擎本身不具备流控逻辑,MAC 驱动可执行软件流控(注意 RX 缓冲区数量较小时启用会带来明显性能损失)。

应用示例

ESP-IDF 提供以下以太网相关示例:

  • examples/ethernet/basic:演示驱动安装、附加到 esp_netif、发送 DHCP 请求并获取可 Ping 通的 IP 地址;
  • examples/ethernet/iperf:使用 iPerf 测量以太网吞吐量/带宽;
  • examples/ethernet/ptp:演示通过以太网进行 PTP 时间同步;
  • examples/network/vlan_support:演示在以太网上创建虚拟网络接口(含 VLAN 与非 VLAN);
  • examples/network/sta2eth:演示使用 Wi-Fi 站点与有线接口(以太网或 USB)建立 1 对 1 桥接;
  • examples/network/simple_sniffer:演示使用 Wi-Fi 与以太网混杂模式抓包并保存为 PCAP 格式;
  • examples/network/eth2ap:演示以太网端口与 Wi-Fi AP 接口间的报文转发桥(1 对多连接,不初始化 TCP/IP 协议栈);
  • examples/network/bridge:演示使用 LwIP IEEE 802.1D 桥基于 MAC 地址在多网段间转发以太网帧;
  • 大多数examples/protocols协议示例同样适用于以太网。

进阶主题:自定义 PHY 驱动

市场上 PHY 厂商众多、芯片型号繁杂。ESP-IDF 支持 Generic PHY 及若干特定 PHY 芯片,但可能因价格、特性、供货等原因都不满足实际需求。

幸运的是,IEEE 802.3 第 22.2.4 节"管理功能"标准化了 EMAC 与 PHY 之间的管理接口(MII Management Interface),定义了一组管理寄存器用于控制芯片行为、链路属性与自动协商配置等。这套基础管理功能在 ESP-IDF 中由 components/esp_eth/src/phy/esp_eth_phy_802_3.c 实现,使得创建新的自定义 PHY 驱动相当简单。

注意:部分 PHY 芯片可能不完全符合 IEEE 802.3 第 22.2.4 节,请始终查阅 PHY 数据手册。这不代表无法创建自定义 PHY 驱动,只是需要更多工作(必须自行定义全部 PHY 管理函数)。

ESP-IDF 以太网驱动所需的大部分 PHY 管理功能由esp_eth_phy_802_3.c覆盖,但以下内容可能需要开发芯片专属的管理函数:

  • 链路状态——几乎总是芯片相关的;
  • 芯片初始化——虽非严格必需,但至少应自定义以确认使用的是预期芯片;
  • 芯片专属特性配置

创建自定义 PHY 驱动的步骤:

  1. 基于 PHY 数据手册定义厂商专属寄存器布局;
  2. 准备派生的 PHY 管理对象信息结构,其必须包含父级 IEEE 802.3 对象(phy_802_3_t),并可选包含支持非 IEEE 802.3 或定制功能所需的附加变量;
  3. 定义芯片专属管理回调函数;
  4. 初始化父级 IEEE 802.3 对象,并重新指派芯片专属管理回调函数。

在 components/esp_eth/src/phy/esp_eth_phy_generic.c 中可以看到esp_eth_phy_new_generic如何基于esp_eth_phy_802_3.c的基础实现构建通用 PHY 实例。完成新驱动后,可考虑通过 ESP Component Registry 与其他用户共享。

总结:驱动生命周期全景

以太网驱动的完整生命周期可归纳为:配置 MAC/PHY → 创建实例 → 安装驱动(组合 MAC+PHY)→ 注册事件 → 创建并附加 esp_netif → 启动 → 运行期 ioctl 控制 → 停止/卸载。整个流程涉及的关键头文件均在 components/esp_eth/include 下,Kconfig 配置项在 components/esp_eth/Kconfig,而 examples/ethernet/basic/main/ethernet_example_main.c 提供了开箱即用的完整参考实现。

【免费下载链接】esp-idfEspressif IoT Development Framework. Official development framework for Espressif SoCs.项目地址: https://gitcode.com/GitHub_Trending/es/esp-idf

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/15 23:54:49

Spring Boot+Vue高校学生实习平台开发实战:从设计到部署全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 23:53:19

WordPress用户分组管理多少钱:揭秘5大方案与真实落地成本

WordPress用户分组管理多少钱:揭秘5大方案与真实落地成本 很多老板找过来,第一句话就是:“我想搞个会员分级,让VIP客户看不同内容,这功能到底多少钱?” 其实,你问的是功能,但真正卡住你的,往往是背后那套看不见的基建。 域名服务器搞不懂,是很多非技术背景创业者的死穴。你以为买个…

作者头像 李华
网站建设 2026/9/15 23:52:51

2026这6款王炸AI智能降重工具全网首测,一键实现AI检测丝滑过审!

步入2026年,学术圈的风向早已悄然改变。曾经只需盯着查重率就能安心的年代一去不复返,如今的论文审核标准如同层层加码的紧箍咒,让每个写作者都如履薄冰。AI检测技术不断进化,算法愈发精准,高校的审查红线也越拉越紧。…

作者头像 李华
网站建设 2026/9/15 23:52:43

SpringBoot中SLF4J日志框架的深度解析与最佳实践

1. SpringBoot中SLF4J的深度解析与实践指南在Java开发领域,日志记录是每个项目不可或缺的基础设施。作为SpringBoot项目的标配日志门面,SLF4J(Simple Logging Facade for Java)的灵活性和扩展性使其成为开发者首选。但很多人在实际…

作者头像 李华
网站建设 2026/9/15 23:51:11

Spring全家桶高效学习路线:从IoC/DI到微服务实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/15 23:49:35

UART实战全链路:从电平匹配到Linux驱动调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华