mirror of
https://github.com/espressif/esp-idf.git
synced 2026-09-22 13:01:16 +03:00
feat(examples): add Ethernet sublayer example and update PTP
Add a dedicated sublayer example under examples/ethernet and enable sublayer usage in the PTP example.
This commit is contained in:
@@ -683,6 +683,20 @@ The majority of PHY management functionality required by the ESP-IDF Ethernet dr
|
||||
|
||||
Once you finish the new custom PHY driver implementation, consider sharing it among other users via `ESP Component Registry <https://components.espressif.com/>`_.
|
||||
|
||||
.. _ethernet-sublayer:
|
||||
|
||||
Ethernet Sublayer
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The Ethernet sublayer is an optional layer between one physical Ethernet driver (``esp_eth_handle_t``) and one or more ``esp_netif`` instances. It owns the driver RX input path, distributes Ethernet events to attached interfaces, and can perform mid-path frame processing such as 802.1Q VLAN multiplexing and demultiplexing.
|
||||
|
||||
.. note::
|
||||
The Ethernet sublayer is an **experimental** feature. Enable :menuitem:`CONFIG_IDF_EXPERIMENTAL_FEATURES` and :menuitem:`CONFIG_ETH_SUBLAYER_SUPPORT` to use it. The API is under active development and may change in future ESP-IDF releases without a deprecation period.
|
||||
|
||||
For architecture details, configuration, and usage guidance, see :component_file:`esp_eth/src/sublayer/README.md`.
|
||||
|
||||
A working demonstration is available in :example:`ethernet/sublayer`, which shares one Ethernet driver between an untagged ``esp_netif`` and a tagged VLAN ``esp_netif``.
|
||||
|
||||
.. ---------------------------- API Reference ----------------------------------
|
||||
|
||||
API Reference
|
||||
|
||||
@@ -683,6 +683,20 @@ ESP-IDF 以太网驱动程序所需的大部分 PHY 管理功能都已涵盖在
|
||||
|
||||
实现新的自定义 PHY 驱动程序后,你可以通过 `乐鑫组件注册表 <https://components.espressif.com/>`_ 将驱动分享给其他用户。
|
||||
|
||||
.. _ethernet-sublayer:
|
||||
|
||||
以太网子层
|
||||
^^^^^^^^^^
|
||||
|
||||
以太网子层是位于一个物理以太网驱动程序(``esp_eth_handle_t``)与一个或多个 ``esp_netif`` 实例之间的可选层。它负责拥有驱动程序的 RX 输入路径,将以太网事件分发给所连接的接口,并可执行路径中的帧处理,例如 802.1Q VLAN 复用与解复用。
|
||||
|
||||
.. note::
|
||||
以太网子层是一项**实验性**功能。需启用 :menuitem:`CONFIG_IDF_EXPERIMENTAL_FEATURES` 和 :menuitem:`CONFIG_ETH_SUBLAYER_SUPPORT` 后方可使用。该 API 仍在积极开发中,未来 ESP-IDF 版本中可能会在不经过弃用周期的情况下发生变更。
|
||||
|
||||
有关架构细节、配置和使用指南,请参阅 :component_file:`esp_eth/src/sublayer/README.md`。
|
||||
|
||||
可参考 :example:`ethernet/sublayer` 中的示例,该示例演示了如何在一个未打标签的 ``esp_netif`` 和一个带标签的 VLAN ``esp_netif`` 之间共享同一个以太网驱动程序。
|
||||
|
||||
.. ---------------------------- API Reference ----------------------------------
|
||||
|
||||
API 参考
|
||||
|
||||
@@ -36,3 +36,14 @@ examples/ethernet/ptp:
|
||||
depends_components:
|
||||
- esp_eth
|
||||
- esp_netif
|
||||
|
||||
examples/ethernet/sublayer:
|
||||
enable:
|
||||
- if: INCLUDE_DEFAULT == 1
|
||||
disable:
|
||||
- if: IDF_TARGET not in ["esp32", "esp32p4", "esp32s31"]
|
||||
depends_components:
|
||||
- esp_eth
|
||||
- esp_netif
|
||||
- lwip
|
||||
- esp_driver_gpio
|
||||
|
||||
@@ -9,6 +9,8 @@ Ethernet examples demonstrate basic features related to Ethernet layer, such as:
|
||||
|
||||
* basic Ethernet initialization and binding to IP stack via ESP-NETIF
|
||||
|
||||
* experimental Ethernet netif sublayer (untagged + VLAN interfaces on one driver)
|
||||
|
||||
* tools for performance measurement, i.e. `iperf`
|
||||
|
||||
* hardware time synchronization
|
||||
|
||||
@@ -59,8 +59,21 @@ void init_ethernet_and_netif(void)
|
||||
esp_netif_base_config.route_prio -= i*5;
|
||||
esp_netif_t *eth_netif = esp_netif_new(&esp_netif_config);
|
||||
|
||||
#if CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
ESP_LOGI(TAG, "Using Ethernet sublayer");
|
||||
esp_eth_sublayer_config_t sub_config = {
|
||||
.eth_handle = eth_handles[i],
|
||||
};
|
||||
esp_eth_sublayer_handle_t eth_sublayer = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_new(&sub_config, ð_sublayer));
|
||||
esp_eth_sublayer_vlan_handle_t eth_netif_untag = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(eth_sublayer, ESP_ETH_SUBLAYER_UNTAGGED_VID, ð_netif_untag));
|
||||
// attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, eth_netif_untag));
|
||||
#else
|
||||
// attach Ethernet driver to TCP/IP stack
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, esp_eth_new_netif_glue(eth_handles[i])));
|
||||
#endif
|
||||
}
|
||||
|
||||
for (int i = 0; i < eth_port_cnt; i++) {
|
||||
@@ -127,7 +140,12 @@ void app_main(void)
|
||||
|
||||
ptp_deamon_start();
|
||||
#if CONFIG_EXAMPLE_PTP_PULSE_EMAC_PPS
|
||||
esp_eth_handle_t eth_handle = esp_netif_get_io_driver(esp_netif_get_handle_from_ifkey(ETH_IF_KEY));
|
||||
esp_netif_iodriver_handle netif_iodriver = esp_netif_get_io_driver(esp_netif_get_handle_from_ifkey(ETH_IF_KEY));
|
||||
#if CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
esp_eth_handle_t eth_handle = esp_eth_sublayer_vlan_get_eth_handle(netif_iodriver);
|
||||
#else
|
||||
esp_eth_handle_t eth_handle = (esp_eth_handle_t)netif_iodriver;
|
||||
#endif
|
||||
esp_eth_mac_t *mac;
|
||||
ESP_ERROR_CHECK(esp_eth_get_mac_instance(eth_handle, &mac));
|
||||
ESP_ERROR_CHECK(esp_eth_mac_set_pps_out_gpio(mac, CONFIG_EXAMPLE_PTP_PULSE_GPIO));
|
||||
|
||||
8
examples/ethernet/sublayer/CMakeLists.txt
Normal file
8
examples/ethernet/sublayer/CMakeLists.txt
Normal file
@@ -0,0 +1,8 @@
|
||||
# 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.22)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
project(ethernet_sublayer)
|
||||
55
examples/ethernet/sublayer/README.md
Normal file
55
examples/ethernet/sublayer/README.md
Normal file
@@ -0,0 +1,55 @@
|
||||
| Supported Targets | ESP32 | ESP32-P4 | ESP32-S31 |
|
||||
| ----------------- | ----- | -------- | --------- |
|
||||
|
||||
# Ethernet Sublayer Example
|
||||
|
||||
Demonstrates the experimental Ethernet netif sublayer (`esp_eth_sublayer`): one physical Ethernet driver shared by an untagged `esp_netif` (DHCP) and a tagged 802.1Q VLAN `esp_netif` (static IP).
|
||||
|
||||
Uses the [ethernet_init](https://components.espressif.com/components/espressif/ethernet_init/) component for driver setup.
|
||||
|
||||
## How to use example
|
||||
|
||||
### Configure the project
|
||||
|
||||
```
|
||||
idf.py menuconfig
|
||||
```
|
||||
|
||||
See common configurations for Ethernet examples from [upper level](../README.md#common-configurations).
|
||||
|
||||
This example requires enabling `CONFIG_IDF_EXPERIMENTAL_FEATURES` and `CONFIG_ETH_SUBLAYER_SUPPORT`.
|
||||
|
||||
### Build, Flash, and Run
|
||||
|
||||
Build the project and flash it to the board, then run monitor tool to view serial output:
|
||||
|
||||
```
|
||||
idf.py -p PORT build flash monitor
|
||||
```
|
||||
|
||||
(Replace PORT with the name of the serial port to use.)
|
||||
|
||||
(To exit the serial monitor, type ``Ctrl-]``.)
|
||||
|
||||
### Setup VLAN interface on Linux
|
||||
|
||||
Create a tagged 802.1Q interface on the host so you can reach the ESP VLAN netif (defaults: VID `20`, IP `192.168.20.10`):
|
||||
|
||||
```bash
|
||||
# Replace eth0 with your host Ethernet interface (e.g. enp3s0)
|
||||
sudo ip link add link eth0 name eth0.20 type vlan id 20
|
||||
sudo ip addr add 192.168.20.1/24 dev eth0.20
|
||||
sudo ip link set eth0.20 up
|
||||
|
||||
ping 192.168.20.10
|
||||
```
|
||||
|
||||
To remove it later:
|
||||
|
||||
```bash
|
||||
sudo ip link delete eth0.20
|
||||
```
|
||||
|
||||
Match `id` / address to **Example Configuration** if you changed the defaults. The physical link between the host and the board must carry tagged frames (direct cable, or a switch port in trunk/hybrid mode).
|
||||
|
||||
See [upper-level Ethernet README](../README.md) for hardware notes and common troubleshooting.
|
||||
3
examples/ethernet/sublayer/main/CMakeLists.txt
Normal file
3
examples/ethernet/sublayer/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "ethernet_sublayer_example_main.c"
|
||||
PRIV_REQUIRES esp_netif esp_eth
|
||||
INCLUDE_DIRS ".")
|
||||
22
examples/ethernet/sublayer/main/Kconfig.projbuild
Normal file
22
examples/ethernet/sublayer/main/Kconfig.projbuild
Normal file
@@ -0,0 +1,22 @@
|
||||
menu "Example Configuration"
|
||||
|
||||
config EXAMPLE_ETHERNET_VLAN_ID
|
||||
int "VLAN identifier"
|
||||
range 1 4094
|
||||
default 20
|
||||
help
|
||||
802.1Q VLAN ID for the tagged virtual interface created via the Ethernet sublayer.
|
||||
|
||||
config EXAMPLE_VLAN_STATIC_IPV4_ADDR
|
||||
string "VLAN IPV4 Address"
|
||||
default "192.168.20.10"
|
||||
|
||||
config EXAMPLE_VLAN_STATIC_ADDR_MASK
|
||||
string "VLAN Subnet Mask"
|
||||
default "255.255.255.0"
|
||||
|
||||
config EXAMPLE_VLAN_STATIC_ADDR_DEF_GW
|
||||
string "VLAN IPV4 Default Gateway"
|
||||
default "192.168.20.1"
|
||||
|
||||
endmenu
|
||||
143
examples/ethernet/sublayer/main/ethernet_sublayer_example_main.c
Normal file
143
examples/ethernet/sublayer/main/ethernet_sublayer_example_main.c
Normal file
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/socket.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_eth.h"
|
||||
#include "ethernet_init.h"
|
||||
#include "esp_log.h"
|
||||
#include "sdkconfig.h"
|
||||
|
||||
#ifndef CONFIG_ETH_SUBLAYER_SUPPORT
|
||||
#error "This example requires CONFIG_ETH_SUBLAYER_SUPPORT (enable IDF experimental features)"
|
||||
#endif
|
||||
|
||||
static const char *TAG = "eth_sublayer_example";
|
||||
|
||||
/** Event handler for Ethernet events */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/** Event handler for 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 (%s)", esp_netif_get_ifkey(event->esp_netif));
|
||||
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, "~~~~~~~~~~~");
|
||||
}
|
||||
|
||||
static esp_err_t create_vlan_netif_config(uint16_t vlan_id, esp_netif_config_t *cfg)
|
||||
{
|
||||
char *if_key;
|
||||
if (asprintf(&if_key, "ETH_VLAN%d", vlan_id) < 0) {
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
|
||||
esp_netif_inherent_config_t *base = malloc(sizeof(*base));
|
||||
if (base == NULL) {
|
||||
free(if_key);
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
*base = (esp_netif_inherent_config_t)ESP_NETIF_INHERENT_DEFAULT_ETH();
|
||||
base->if_key = if_key;
|
||||
base->if_desc = "eth_vlan";
|
||||
base->route_prio = 30;
|
||||
|
||||
cfg->base = base;
|
||||
cfg->driver = NULL;
|
||||
cfg->stack = ESP_NETIF_NETSTACK_DEFAULT_ETH;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static void free_vlan_netif_config(esp_netif_config_t *cfg)
|
||||
{
|
||||
if (cfg && cfg->base) {
|
||||
free((void *)cfg->base->if_key);
|
||||
free((void *)cfg->base);
|
||||
}
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
uint8_t eth_port_cnt = 0;
|
||||
esp_eth_handle_t *eth_handles = NULL;
|
||||
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
ESP_ERROR_CHECK(ethernet_init_all(ð_handles, ð_port_cnt));
|
||||
if (eth_port_cnt > 1) {
|
||||
ESP_LOGW(TAG, "Multiple Ethernet interfaces detected, using the first one");
|
||||
}
|
||||
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, ð_event_handler, NULL));
|
||||
ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, &got_ip_event_handler, NULL));
|
||||
|
||||
// Create Ethernet sublayer bound to the physical driver
|
||||
esp_eth_sublayer_config_t sub_config = ESP_ETH_SUBLAYER_CONFIG_DEFAULT();
|
||||
sub_config.eth_handle = eth_handles[0];
|
||||
esp_eth_sublayer_handle_t sublayer = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_new(&sub_config, &sublayer));
|
||||
|
||||
// Untagged interface (DHCP)
|
||||
esp_eth_sublayer_vlan_handle_t untagged = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(sublayer, ESP_ETH_SUBLAYER_UNTAGGED_VID, &untagged));
|
||||
esp_netif_config_t eth_cfg = ESP_NETIF_DEFAULT_ETH();
|
||||
esp_netif_t *eth_netif = esp_netif_new(ð_cfg);
|
||||
ESP_ERROR_CHECK(esp_netif_attach(eth_netif, untagged));
|
||||
|
||||
// Tagged VLAN interface (static IP)
|
||||
esp_eth_sublayer_vlan_handle_t vlan = NULL;
|
||||
ESP_ERROR_CHECK(esp_eth_sublayer_vlan_add(sublayer, CONFIG_EXAMPLE_ETHERNET_VLAN_ID, &vlan));
|
||||
esp_netif_config_t vlan_cfg;
|
||||
ESP_ERROR_CHECK(create_vlan_netif_config(CONFIG_EXAMPLE_ETHERNET_VLAN_ID, &vlan_cfg));
|
||||
esp_netif_t *vlan_netif = esp_netif_new(&vlan_cfg);
|
||||
free_vlan_netif_config(&vlan_cfg);
|
||||
ESP_ERROR_CHECK(esp_netif_attach(vlan_netif, vlan));
|
||||
|
||||
esp_netif_dhcpc_stop(vlan_netif);
|
||||
esp_netif_ip_info_t vlan_ip = {0};
|
||||
inet_aton(CONFIG_EXAMPLE_VLAN_STATIC_IPV4_ADDR, &vlan_ip.ip.addr);
|
||||
inet_aton(CONFIG_EXAMPLE_VLAN_STATIC_ADDR_DEF_GW, &vlan_ip.gw.addr);
|
||||
inet_aton(CONFIG_EXAMPLE_VLAN_STATIC_ADDR_MASK, &vlan_ip.netmask.addr);
|
||||
ESP_ERROR_CHECK(esp_netif_set_ip_info(vlan_netif, &vlan_ip));
|
||||
|
||||
ESP_ERROR_CHECK(esp_eth_start(eth_handles[0]));
|
||||
}
|
||||
3
examples/ethernet/sublayer/main/idf_component.yml
Normal file
3
examples/ethernet/sublayer/main/idf_component.yml
Normal file
@@ -0,0 +1,3 @@
|
||||
dependencies:
|
||||
espressif/ethernet_init:
|
||||
version: "~1.4.1"
|
||||
26
examples/ethernet/sublayer/pytest_eth_sublayer.py
Normal file
26
examples/ethernet/sublayer/pytest_eth_sublayer.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# SPDX-FileCopyrightText: 2026 Espressif Systems (Shanghai) CO LTD
|
||||
# SPDX-License-Identifier: Unlicense OR CC0-1.0
|
||||
import platform
|
||||
import subprocess
|
||||
|
||||
import pytest
|
||||
from pytest_embedded import Dut
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
'config, target',
|
||||
[
|
||||
pytest.param('defaults', 'esp32', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('defaults_esp32p4', 'esp32p4', marks=[pytest.mark.eth_ip101]),
|
||||
pytest.param('defaults_esp32s31', 'esp32s31', marks=[pytest.mark.eth_yt8531]),
|
||||
],
|
||||
indirect=['target'],
|
||||
)
|
||||
def test_esp_eth_sublayer(dut: Dut) -> None:
|
||||
# wait for IP on the untagged interface
|
||||
dut_ip = dut.expect(r'esp_netif_handlers: .+ ip: (\d+\.\d+\.\d+\.\d+),').group(1)
|
||||
param = '-n' if platform.system().lower() == 'windows' else '-c'
|
||||
command = ['ping', param, '1', dut_ip]
|
||||
output = subprocess.run(command, capture_output=True)
|
||||
if 'unreachable' in str(output.stdout):
|
||||
raise RuntimeError('Host unreachable')
|
||||
4
examples/ethernet/sublayer/sdkconfig.ci.defaults
Normal file
4
examples/ethernet/sublayer/sdkconfig.ci.defaults
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32"
|
||||
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_IP101=y
|
||||
4
examples/ethernet/sublayer/sdkconfig.ci.defaults_esp32p4
Normal file
4
examples/ethernet/sublayer/sdkconfig.ci.defaults_esp32p4
Normal file
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32p4"
|
||||
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_IP101=y
|
||||
@@ -0,0 +1,4 @@
|
||||
CONFIG_IDF_TARGET="esp32s31"
|
||||
|
||||
CONFIG_ETHERNET_INTERNAL_SUPPORT=y
|
||||
CONFIG_ETHERNET_PHY_YT8531=y
|
||||
5
examples/ethernet/sublayer/sdkconfig.defaults
Normal file
5
examples/ethernet/sublayer/sdkconfig.defaults
Normal file
@@ -0,0 +1,5 @@
|
||||
CONFIG_IDF_EXPERIMENTAL_FEATURES=y
|
||||
CONFIG_ETH_SUBLAYER_SUPPORT=y
|
||||
CONFIG_ETH_SUBLAYER_VLAN_SUPPORT=y
|
||||
CONFIG_ETH_TRANSMIT_MUTEX=n
|
||||
CONFIG_ETH_SUBLAYER_TRANSMIT_MUTEX=y
|
||||
Reference in New Issue
Block a user