ethernet: add eth2ap example

This example illustrates how to do Layer2 packet forwarding bussiness between Wi-Fi and Ethernet.
pull/7874/head
suda-morris 2019-05-10 13:52:23 +08:00
rodzic 126b687c75
commit 813c9dcf22
9 zmienionych plików z 523 dodań i 0 usunięć

Wyświetl plik

@ -0,0 +1,4 @@
cmake_minimum_required(VERSION 3.5)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(eth2ap)

Wyświetl plik

@ -0,0 +1,8 @@
#
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
# project subdirectory.
#
PROJECT_NAME := eth2ap
include $(IDF_PATH)/make/project.mk

Wyświetl plik

@ -0,0 +1,114 @@
# eth2ap Example
(See the README.md file in the upper level 'examples' directory for more information about examples. To try a more complex application about Ethernet to WiFi data forwarding, please go to [iot-solution](https://github.com/espressif/esp-iot-solution/tree/master/examples/eth2wifi).)
## Overview
![eth2ap](eth2ap.png)
The similarities on MAC layer between Ethernet and Wi-Fi make it easy to forward packets from Ethernet to Wi-Fi and vice versa. This example illustrates how to implement a simple "router" which only supports forwarding packets between Ethernet port and Wi-Fi AP interface. In this case, the Ethernet should play the role of WAN (i.e. it can access outside network) so that a mobile device could get access to the Internet when it gets connected to ESP32 through Wi-Fi.
**Note:** In this example, ESP32 works like a *bridge* between Ethernet and Wi-Fi, and it won't perform any actions on Layer3 and higher layer, which means there's no need to initialize the TCP/IP stack.
## How to use this example
### Hardware Required
To run this example, it's recommended that you have an official ESP32 Ethernet development board - [ESP32-Ethernet-Kit](https://docs.espressif.com/projects/esp-idf/en/latest/hw-reference/get-started-ethernet-kit.html). This example should also work for 3rd party ESP32 board as long as it's integrated with a supported Ethernet PHY chip. Up until now, ESP-IDF supports three Ethernet PHY: `TLK110`, `LAN8720` and `IP101`, additional PHY drivers should be implemented by users themselves.
### Configure the project
Enter `make menuconfig` if you are using GNU Make based build system or enter `idf.py menuconfig` if you are using CMake based build system. Then go into `Example Configuration` menu.
* Choose PHY device under `Ethernet PHY Device`, by default, the **ESP32-Ethernet-Kit** has an `IP101` on board.
* Set PHY address under `Ethernet PHY address`, it should depend on the PHY configuration of your hardware. You'd better consult the schematic of the board. By default, the PHY address of **ESP32-Ethernet-Kit** is *1*.
* Check whether or not to control the power of PHY chip under `Use PHY Power (enable / disable) pin`, (if set true, you also need to give the GPIO number of that pin under `PHY Power GPIO`).
* Set SMI MDC/MDIO GPIO number according to board schematic, by default they are set as below:
| Default Example GPIO | RMII Signal | Notes |
| -------------------- | ----------- | ------------- |
| GPIO23 | MDC | Output to PHY |
| GPIO18 | MDIO | Bidirectional |
* Select one kind of RMII clock mode under `Ethernet RMII Clock Mode` option. Possible configurations of the clock are listed as below. By default, ESP32-Ethernet-Kit use the `GPIO0 input` mode, which gives a good performance when enabling Ethernet and Wi-Fi at the same time.
| Mode | GPIO Pin | Signal name | Notes |
| -------- | -------- | ------------ | ------------------------------------------------------------ |
| external | GPIO0 | EMAC_TX_CLK | Input of 50MHz PHY clock |
| internal | GPIO0 | CLK_OUT1 | Output of 50MHz APLL clock |
| internal | GPIO16 | EMAC_CLK_OUT | Output of 50MHz APLL clock |
| internal | GPIO17 | EMAC_CLK_180 | Inverted output of 50MHz APLL clock (suitable for long clock trace) |
* External RMII clock must be connected to `GPIO0`.
* ESP32 can generate the RMII clock(50MHz) using its internal APLL. But if the APLL has already been used for other peripheral (e.g. I²S), you'd better choose the external clock.
* Set the SSID and password for Wi-Fi ap interface under `Wi-Fi SSID` and `Wi-Fi Password`.
* Set the maximum connection number under `Maximum STA connections`.
### Build and Flash
To build and flash the example, enter `make -j4 flash monitor` if you are using GNU Make based build system or enter `idf.py build flash monitor` if you are using CMake based build system.
(To exit the serial monitor, type ``Ctrl-]``.)
See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects.
## Example Output
### Step 1: Initialize Ethernet and Wi-Fi (AP mode)
```bash
I (508) example: Power On Ethernet PHY
I (518) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (518) emac: emac reset done
I (518) example: Ethernet Started
......
I (538) wifi: wifi driver task: 3ffc7fbc, prio:23, stack:3584, core=0
I (538) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (538) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE
I (568) wifi: wifi firmware version: ec61a20
I (568) wifi: config NVS flash: enabled
I (568) wifi: config nano formating: disabled
I (568) wifi: Init dynamic tx buffer num: 32
I (568) wifi: Init data frame dynamic rx buffer num: 32
I (578) wifi: Init management frame dynamic rx buffer num: 32
I (588) wifi: Init management short buffer num: 32
I (588) wifi: Init static rx buffer size: 1600
I (588) wifi: Init static rx buffer num: 10
I (598) wifi: Init dynamic rx buffer num: 32
```
### Step 2: Ethernet Connects to Router/Switch/PC (with DHCP server enabled)
```bash
I (4518) example: Ethernet Link Up
```
### Step 3: Start Wi-Fi AP
```bash
I (4618) phy: phy_version: 4100, 2a5dd04, Jan 23 2019, 21:00:07, 0, 0
I (4618) wifi: mode : softAP (30:ae:a4:c6:87:5b)
I (4628) wifi: Total power save buffer number: 16
I (4628) wifi: Init max length of beacon: 752/752
I (4628) wifi: Init max length of beacon: 752/752
```
### Step 4: Wi-Fi station (e.g. mobile phone) connects to ESP32's Wi-Fi
```bash
I (10168) wifi: new:<1,0>, old:<1,0>, ap:<1,1>, sta:<255,255>, prof:1
I (10168) wifi: station: c4:0b:cb:ec:9a:84 join, AID=1, bgn, 20
I (10258) example: AP got a station connected
```
Now your mobile phone should get access to the Internet.
## Troubleshooting
* Got error message `emac: emac rx buf err` when running the example.
* This example just forwards the packets on the Layer2 between Wi-Fi and Ethernet, it won't do any Layer3 business. So make sure you have disabled the `CONFIG_ETH_EMAC_L2_TO_L3_RX_BUF_MODE`. By default, this option is false in the `sdkconfig.defaults` file.
* Got error message `example: WiFi send packet failed: -1` when running the example.
* Ethernet process packets faster than Wi-Fi on ESP32, so have a try to enlarge the value of `FLOW_CONTROL_WIFI_SEND_DELAY_MS`.
* Wi-Fi station doesn't receive any IP via DHCP.
* All Layer 3 (TCP/IP functions) on the ESP32 are disabled, including the SoftAP DHCP server. This means that devices must be able to access another DHCP server (for example on a Wi-Fi router connected via ethernet) or should use statically assigned IP addresses.

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 87 KiB

Wyświetl plik

@ -0,0 +1,6 @@
set(COMPONENT_SRCS "eth2ap_example_main.c")
set(COMPONENT_ADD_INCLUDEDIRS ".")
register_component()

Wyświetl plik

@ -0,0 +1,111 @@
menu "Example Configuration"
choice EXAMPLE_PHY_MODEL
prompt "Ethernet PHY Device"
default EXAMPLE_PHY_IP101
help
Select the PHY driver to use for the example.
config EXAMPLE_PHY_IP101
bool "IP101"
help
IP101 is a single port 10/100 MII/RMII/TP/Fiber Fast Ethernet Transceiver.
Goto http://www.icplus.com.tw/pp-IP101G.html for more information about it.
config EXAMPLE_PHY_TLK110
bool "TLK110"
help
TLK110 is an Industrial 10/100Mbps Ethernet Physical Layer Transceiver.
Goto http://www.ti.com/product/TLK110 for information about it.
config EXAMPLE_PHY_LAN8720
bool "LAN8720"
help
LAN8720 is a small footprint RMII 10/100 Ethernet Transceiver with HP Auto-MDIX Support.
Goto https://www.microchip.com/LAN8720A for more information about it.
endchoice
config EXAMPLE_PHY_ADDRESS
int "Ethernet PHY Address"
default 1
range 0 31
help
PHY Address of your PHY device. It depends on your schematic design.
choice EXAMPLE_PHY_CLOCK_MODE
prompt "Ethernet RMII Clock Mode"
default EXAMPLE_PHY_CLOCK_GPIO0_IN
help
Select external (input on GPIO0) or internal (output on GPIO0, GPIO16 or GPIO17) RMII clock.
config EXAMPLE_PHY_CLOCK_GPIO0_IN
bool "GPIO0 Input"
help
Input of 50MHz RMII clock on GPIO0.
config EXAMPLE_PHY_CLOCK_GPIO0_OUT
bool "GPIO0 Output"
help
Output the internal 50MHz RMII clock on GPIO0.
config EXAMPLE_PHY_CLOCK_GPIO16_OUT
bool "GPIO16 Output"
help
Output the internal 50MHz RMII clock on GPIO16.
config EXAMPLE_PHY_CLOCK_GPIO17_OUT
bool "GPIO17 Output (inverted)"
help
Output the internal 50MHz RMII clock on GPIO17 (inverted signal).
endchoice
config EXAMPLE_PHY_CLOCK_MODE
int
default 0 if EXAMPLE_PHY_CLOCK_GPIO0_IN
default 1 if EXAMPLE_PHY_CLOCK_GPIO0_OUT
default 2 if EXAMPLE_PHY_CLOCK_GPIO16_OUT
default 3 if EXAMPLE_PHY_CLOCK_GPIO17_OUT
config EXAMPLE_PHY_USE_POWER_PIN
bool "Use PHY Power (enable / disable) pin"
default y
help
Use a GPIO "power pin" to power the PHY on/off during operation.
When using GPIO0 to input RMII clock, the reset process will be interfered by this clock.
So we need another GPIO to control the switch on / off of the RMII clock.
if EXAMPLE_PHY_USE_POWER_PIN
config EXAMPLE_PHY_POWER_PIN
int "PHY power pin"
default 5
range 0 33
help
Set the GPIO number used for powering on/off the PHY.
endif
config EXAMPLE_PHY_SMI_MDC_PIN
int "Ethernet SMI MDC gpio number"
default 23
range 0 33
help
GPIO number used for SMI clock signal.
config EXAMPLE_PHY_SMI_MDIO_PIN
int "Ethernet SMI MDIO gpio number"
default 18
range 0 33
help
GPIO number used for SMI data signal.
config EXAMPLE_WIFI_SSID
string "Wi-Fi SSID"
default "eth2ap"
help
Set the SSID of Wi-Fi ap interface.
config EXAMPLE_WIFI_PASSWORD
string "Wi-Fi Password"
default "12345678"
help
Set the password of Wi-Fi ap interface.
config EXAMPLE_MAX_STA_CONN
int "Maximum STA connections"
default 4
help
Maximum number of the station that allowed to connect to current Wi-Fi hotspot.
endmenu

Wyświetl plik

@ -0,0 +1,4 @@
#
# "main" pseudo-component makefile.
#
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)

Wyświetl plik

@ -0,0 +1,275 @@
/* eth2ap (Ethernet to Wi-Fi AP packet forwarding) Example
This example code is in the Public Domain (or CC0 licensed, at your option.)
Unless required by applicable law or agreed to in writing, this
software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
CONDITIONS OF ANY KIND, either express or implied.
*/
#include <string.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "esp_event_loop.h"
#include "esp_event.h"
#include "esp_log.h"
#include "esp_eth.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "esp_private/wifi.h"
#include "driver/gpio.h"
#include "sdkconfig.h"
// Choose the default phy config according to Kconfig
#if CONFIG_EXAMPLE_PHY_LAN8720
#include "eth_phy/phy_lan8720.h"
#define DEFAULT_ETHERNET_PHY_CONFIG phy_lan8720_default_ethernet_config
#elif CONFIG_EXAMPLE_PHY_TLK110
#include "eth_phy/phy_tlk110.h"
#define DEFAULT_ETHERNET_PHY_CONFIG phy_tlk110_default_ethernet_config
#elif CONFIG_EXAMPLE_PHY_IP101
#include "eth_phy/phy_ip101.h"
#define DEFAULT_ETHERNET_PHY_CONFIG phy_ip101_default_ethernet_config
#endif
#define FLOW_CONTROL_QUEUE_TIMEOUT_MS (100)
#define FLOW_CONTROL_QUEUE_LENGTH (10)
#define FLOW_CONTROL_WIFI_SEND_TIMEOUT_MS (100)
static const char *TAG = "example";
typedef struct {
void *packet;
uint16_t length;
} flow_control_msg_t;
static xQueueHandle flow_control_queue = NULL;
static bool s_sta_is_connected = false;
static bool s_ethernet_is_connected = false;
static uint8_t s_eth_mac[6];
#ifdef CONFIG_EXAMPLE_PHY_USE_POWER_PIN
/**
* @brief power control function for phy
*
* @param enable: set true to enable PHY power, set false to disable PHY power
*
* @note This function replaces the default PHY power on/off function.
* If this GPIO is not connected on your device (and PHY is always powered),
* you can use the default PHY-specific power on/off function.
*/
static void phy_device_power_enable_via_gpio(bool enable)
{
assert(DEFAULT_ETHERNET_PHY_CONFIG.phy_power_enable);
if (!enable) {
/* call the default PHY-specific power off function */
DEFAULT_ETHERNET_PHY_CONFIG.phy_power_enable(false);
}
gpio_pad_select_gpio(CONFIG_EXAMPLE_PHY_POWER_PIN);
gpio_set_direction(CONFIG_EXAMPLE_PHY_POWER_PIN, GPIO_MODE_OUTPUT);
if (enable) {
gpio_set_level(CONFIG_EXAMPLE_PHY_POWER_PIN, 1);
ESP_LOGI(TAG, "Power On Ethernet PHY");
} else {
gpio_set_level(CONFIG_EXAMPLE_PHY_POWER_PIN, 0);
ESP_LOGI(TAG, "Power Off Ethernet PHY");
}
vTaskDelay(1);
if (enable) {
/* call the default PHY-specific power on function */
DEFAULT_ETHERNET_PHY_CONFIG.phy_power_enable(true);
}
}
#endif
/**
* @brief gpio specific init
*
* @note RMII data pins are fixed in esp32 as follows:
* TXD0 <=> GPIO19
* TXD1 <=> GPIO22
* TX_EN <=> GPIO21
* RXD0 <=> GPIO25
* RXD1 <=> GPIO26
* CLK <=> GPIO0
*
*/
static void eth_gpio_config_rmii(void)
{
phy_rmii_configure_data_interface_pins();
phy_rmii_smi_configure_pins(CONFIG_EXAMPLE_PHY_SMI_MDC_PIN, CONFIG_EXAMPLE_PHY_SMI_MDIO_PIN);
}
// Forward packets from Wi-Fi to Ethernet
static esp_err_t pkt_wifi2eth(void *buffer, uint16_t len, void *eb)
{
if (s_ethernet_is_connected) {
if (esp_eth_tx(buffer, len) != ESP_OK) {
ESP_LOGE(TAG, "Ethernet send packet failed");
}
}
esp_wifi_internal_free_rx_buffer(eb);
return ESP_OK;
}
// Forward packets from Ethernet to Wi-Fi
// Note that, Ethernet works faster than Wi-Fi on ESP32,
// so we need to add an extra queue to balance their speed difference.
static esp_err_t pkt_eth2wifi(void *buffer, uint16_t len, void *eb)
{
esp_err_t ret = ESP_OK;
flow_control_msg_t msg = {
.packet = buffer,
.length = len
};
if (xQueueSend(flow_control_queue, &msg, pdMS_TO_TICKS(FLOW_CONTROL_QUEUE_TIMEOUT_MS)) != pdTRUE) {
ESP_LOGE(TAG, "send flow control message failed or timeout");
ret = ESP_FAIL;
}
return ret;
}
// This task will fetch the packet from the queue, and then send out through Wi-Fi.
// Wi-Fi handles packets slower than Ethernet, we might add some delay between each transmitting.
static void eth2wifi_flow_control_task(void *args)
{
flow_control_msg_t msg;
int res = 0;
uint32_t timeout = 0;
while (1) {
if (xQueueReceive(flow_control_queue, &msg, pdMS_TO_TICKS(FLOW_CONTROL_QUEUE_TIMEOUT_MS)) == pdTRUE) {
timeout = 0;
if (s_sta_is_connected && msg.length > 4) {
do {
vTaskDelay(pdMS_TO_TICKS(timeout));
timeout += 2;
res = esp_wifi_internal_tx(ESP_IF_WIFI_AP, msg.packet, msg.length - 4);
} while (res == -1 && timeout < FLOW_CONTROL_WIFI_SEND_TIMEOUT_MS);
if (res != ESP_OK) {
ESP_LOGE(TAG, "WiFi send packet failed: %d", res);
}
}
esp_eth_free_rx_buf(msg.packet);
}
}
vTaskDelete(NULL);
}
// Event handler for Ethernet
static void eth_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
switch (event_id) {
case ETHERNET_EVENT_CONNECTED:
ESP_LOGI(TAG, "Ethernet Link Up");
s_ethernet_is_connected = true;
esp_eth_get_mac(s_eth_mac);
esp_wifi_set_mac(WIFI_IF_AP, s_eth_mac);
ESP_ERROR_CHECK(esp_wifi_start());
break;
case ETHERNET_EVENT_DISCONNECTED:
ESP_LOGI(TAG, "Ethernet Link Down");
s_ethernet_is_connected = false;
ESP_ERROR_CHECK(esp_wifi_stop());
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 Wi-Fi
static void wifi_event_handler(void *arg, esp_event_base_t event_base,
int32_t event_id, void *event_data)
{
switch (event_id) {
case WIFI_EVENT_AP_STACONNECTED:
ESP_LOGI(TAG, "Wi-Fi AP got a station connected");
s_sta_is_connected = true;
esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_AP, pkt_wifi2eth);
break;
case WIFI_EVENT_AP_STADISCONNECTED:
ESP_LOGI(TAG, "Wi-Fi AP got a station disconnected");
s_sta_is_connected = false;
esp_wifi_internal_reg_rxcb(ESP_IF_WIFI_AP, NULL);
break;
default:
break;
}
}
static void initialize_ethernet(void)
{
eth_config_t config = DEFAULT_ETHERNET_PHY_CONFIG;
config.phy_addr = CONFIG_EXAMPLE_PHY_ADDRESS;
config.gpio_config = eth_gpio_config_rmii;
config.clock_mode = CONFIG_EXAMPLE_PHY_CLOCK_MODE;
config.tcpip_input = pkt_eth2wifi;
config.promiscuous_enable = true;
#ifdef CONFIG_EXAMPLE_PHY_USE_POWER_PIN
/* Replace the default 'power enable' function with an example-specific one that toggles a power GPIO. */
config.phy_power_enable = phy_device_power_enable_via_gpio;
#endif
ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event_handler, NULL));
ESP_ERROR_CHECK(esp_eth_init_internal(&config));
ESP_ERROR_CHECK(esp_eth_enable());
}
static void initialize_wifi(void)
{
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler, NULL));
ESP_ERROR_CHECK(esp_wifi_init_internal(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
wifi_config_t wifi_config = {
.ap = {
.ssid = CONFIG_EXAMPLE_WIFI_SSID,
.ssid_len = strlen(CONFIG_EXAMPLE_WIFI_SSID),
.password = CONFIG_EXAMPLE_WIFI_PASSWORD,
.max_connection = CONFIG_EXAMPLE_MAX_STA_CONN,
.authmode = WIFI_AUTH_WPA_WPA2_PSK
},
};
if (strlen(CONFIG_EXAMPLE_WIFI_PASSWORD) == 0) {
wifi_config.ap.authmode = WIFI_AUTH_OPEN;
}
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_AP, &wifi_config));
}
static esp_err_t initialize_flow_control(void)
{
flow_control_queue = xQueueCreate(FLOW_CONTROL_QUEUE_LENGTH, sizeof(flow_control_msg_t));
if (!flow_control_queue) {
ESP_LOGE(TAG, "create flow control queue failed");
return ESP_FAIL;
}
BaseType_t ret = xTaskCreate(eth2wifi_flow_control_task, "flow_ctl", 2048, NULL, (tskIDLE_PRIORITY + 2), NULL);
if (ret != pdTRUE) {
ESP_LOGE(TAG, "create flow control task failed");
return ESP_FAIL;
}
return ESP_OK;
}
void app_main()
{
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(initialize_flow_control());
initialize_ethernet();
initialize_wifi();
}

Wyświetl plik

@ -0,0 +1 @@
CONFIG_ETH_EMAC_L2_TO_L3_RX_BUF_MODE=n