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 模块。
整个编程指南按以下四个部分组织:
- Basic Ethernet Concepts —— 以太网基础概念;
- Driver Configuration and Installation —— 驱动配置与安装;
- Connect Driver to Stack —— 将驱动接入 TCP/IP 协议栈;
- Misc Operation of Driver —— 驱动杂项控制。
在 components/esp_eth 组件中,include/目录下的头文件定义了全部公共 API(esp_eth.h、esp_eth_driver.h、esp_eth_mac.h、esp_eth_mac_esp.h、esp_eth_phy.h、esp_eth_com.h、esp_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-00和33-45-67-89-AB-CD是组播地址,而00-00-00-00-F0-00和32-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 =0800H、IPv6 =86DDH、ARP =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:驱动安装前的关键参数
以太网驱动由MAC与PHY两部分组成。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配置 |
|---|---|---|
| a | PHY 芯片从其外部 25 MHz 晶振衍生REF_CLK | EMAC_CLK_EXT_IN |
| b | PHY 使用外部 50 MHz 晶振或其他时钟源作为 MAC 侧的REF_CLK | EMAC_CLK_EXT_IN |
| c | EMAC 控制器使用内部高精度 PLL 生成REF_CLK | EMAC_CLK_OUT |
无论选择哪种模式,硬件设计上都必须重视REF_CLK 的信号完整性:走线尽量短,远离射频器件与电感元件。
ESP32 上的特殊注意事项
- 若配置为
EMAC_CLK_OUT,内部Audio PLL(APLL)被用作 50 MHz 时钟源,需确保不与 I2S 总线配置冲突;此时GPIO0可直接输出REF_CLK(该时钟直接输出到 GPIO,与 EMAC 外设无直接关系,某些 PHY 下可能工作不佳);若设计中未使用 PSRAM,GPIO16和GPIO17也可输出参考时钟(来源同为 APLL,但从 EMAC 外设路由)。 - 若配置为
EMAC_CLK_EXT_IN,GPIO0是输入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_OUT,REF_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 中的默认配置印证):
| 引脚名称 | ESP32 | ESP32-P4 |
|---|---|---|
| TX_EN | GPIO21 | GPIO33、GPIO40、GPIO49 |
| TXD0 | GPIO19 | GPIO34、GPIO41 |
| TXD1 | GPIO22 | GPIO35、GPIO42 |
| CRS_DV | GPIO27 | GPIO28、GPIO45、GPIO51 |
| RXD0 | GPIO25 | GPIO29、GPIO46、GPIO52 |
| RXD1 | GPIO26 | GPIO30、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_size | MAC 驱动会创建专用任务处理入包,此参数设置该任务的栈大小 |
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 号 |
interface | MAC 到 PHY 的数据接口(MII/RMII),必须为 RMII |
clock_config | EMAC 接口时钟配置(RMII 下的REF_CLK模式与 GPIO 号) |
intr_priority | MAC 中断优先级。设为 0 或负值则由驱动按默认优先级分配;Low/Medium 优先级(1~3)可在 C 中处理 |
emac_dataif_gpio | EMAC MII/RMII 数据面 GPIO 号配置(多 IO_MUX 芯片) |
clock_config_out_in | 内部生成REF_CLK并外部回环时,EMAC 输入接口时钟的配置;模式必须始终为EMAC_CLK_EXT_IN,仅当clock_config为EMAC_CLK_OUT时有效 |
dma_burst_len | EMAC DMA 突发长度(Tx/Rx) |
mdc_freq_hz | EMAC 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_NUM或CONFIG_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_addr | PHY 地址,范围 0~15。多个 PHY 可共享同一 SMI 总线,故每个 PHY 需唯一地址(通常由硬件 strapping 引脚决定)。若 SMI 总线仅挂一个 PHY,设为-1(ESP_ETH_PHY_ADDR_AUTO)可让驱动自动探测地址 |
reset_timeout_ms | PHY 复位超时值(毫秒),通常在 100 ms 内完成 |
autonego_timeout_ms | 自动协商超时值(毫秒)。驱动会自动与对端协商双工与速率模式,具体取决于板载 PHY 能力 |
reset_gpio_num | PHY 复位引脚 GPIO,未连接则设为-1 |
hw_reset_assert_time_us | PHY 复位引脚断言时间(微秒),设为 0 使用芯片默认时序 |
post_hw_reset_delay_ms | 硬件复位完成后的等待时间(毫秒),0 使用芯片默认,-1(ESP_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 = 100、autonego_timeout_ms = 4000、reset_gpio_num = 5。
创建 MAC 与 PHY 实例
以太网驱动采用面向对象风格实现,对 MAC 与 PHY 的任何操作都基于二者的实例。MAC 与 PHY 的构造函数成对使用,例如esp_eth_mac_new_esp32与esp_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_dm9051与esp_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_reg | PHY 寄存器读写函数,通常由 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, ð_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, ð_event_handler, NULL); // 注册以太网事件处理器事件类型定义于 esp_eth_com.h 的eth_event_t:ETHERNET_EVENT_START、ETHERNET_EVENT_STOP、ETHERNET_EVENT_CONNECTED、ETHERNET_EVENT_DISCONNECTED,事件基为ETH_EVENT。驱动内部通过esp_eth_mediator_t的on_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 协议栈需三步:
- 为以太网驱动创建网络接口(esp_netif);
- 将网络接口附加到以太网驱动;
- 注册 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_stop、esp_eth_del_netif_glue、esp_netif_destroy、esp_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_SPEED、ETH_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_MULTICAST、ETH_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_src | PTP 时钟源,从soc_periph_emac_ptp_clk_src_t枚举中选择 |
clk_src_period_ns | PTP 时钟源周期(纳秒)。例如 40 MHz 时钟源周期为 25 ns |
required_accuracy_ns | PTP 所需精度(纳秒),必须劣于 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_vargs的ctrl参数(发送路径)获取;更友好的用户态获取方式是使用 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 驱动的步骤:
- 基于 PHY 数据手册定义厂商专属寄存器布局;
- 准备派生的 PHY 管理对象信息结构,其必须包含父级 IEEE 802.3 对象(
phy_802_3_t),并可选包含支持非 IEEE 802.3 或定制功能所需的附加变量; - 定义芯片专属管理回调函数;
- 初始化父级 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),仅供参考