【ESP32-IDF笔记】20-配置以太网网络(W5500)
环境配置
Visual Studio Code :版本1.98.2
ESP32:ESP32-S3
ESP-IDF:V5.4
模块:W5500,SPI通讯协议
组件支持:esp_eth 官方的ethernet 以太网组件
W5500介绍
介绍
W5500 是一款全硬件 TCP/IP 嵌入式以太网控制器,为嵌入式系统提供了更加简易的互联网连接方案。W5500 集成了 TCP/IP 协议栈,10/100M 以太网数据链路层(MAC) 及物理层(PHY),使得用户使用单芯片就能够在他们的应用中拓展网络连接。久经市场考验的 WIZnet 全硬件 TCP/IP 协议栈支持 TCP,UDP,IPv4,ICMP,ARP,IGMP 以及 PPPoE协议。W5500 内嵌 32K 字节片上缓存以供以太网包处理。如果你使用 W5500, 你只需要一些简单的 Socket 编程就能实现以太网应用。这将会比其他嵌入式以太网方案 更加快捷、简便。用户可以同时使用 8 个硬件 Socket 独立通讯。W5500 提供了 SPI(外设串行接口)从而能够更加容易与外设 MCU 整合。而且, W5500 的使用了新的高效 SPI 协议支持 80MHz 速率,从而能够更好的实现高速网络通讯。 为了减少系统能耗,W5500 提供了网络唤醒模式(WOL)及掉电模式供客户选择使用
特点
- 支持硬件 TCP/IP 协议:TCP, UDP, ICMP, IPv4, ARP, IGMP, PPPoE
- 支持 8 个独立端口(Socket)同时通讯
- 支持掉电模式
- 支持网络唤醒
- 支持高速串行外设接口(SPI 模式 0,3)
- 内部 32K 字节收发缓存
- 内嵌 10BaseT/100BaseTX 以太网物理层(PHY)
- 支持自动协商(10/100-Based 全双工/半双工)
- 不支持 IP 分片
- 3.3V 工作电压,I/O 信号口 5V 耐压;
- LED 状态显示(全双工/半双工,网络连接,网络速度,活动状态)
- 48 引脚 LQFP 无铅封装(7x7mm, 0.5mm 间距)
以太网介绍
以太网基本概念
以太网是一种异步的带冲突检测的载波侦听多路访问 (CSMA/CD) 协议/接口。通常来说,以太网不太适用于低功耗应用。然而,得益于其广泛的部署、高效的网络连接、高数据率以及范围不限的可扩展性,几乎所有的有线通信都可以通过以太网进行。
符合 IEEE 802.3 标准的正常以太网帧的长度在 64 至 1518 字节之间,由五个或六个不同的字段组成:目的地 MAC 地址 (DA)、源 MAC 地址 (SA)、类型/长度字段、数据有效载荷字段、可选的填充字段和帧校验序列字段 (CRC)。此外,在以太网上传输时,以太网数据包的开头需附加 7 字节的前导码和 1 字节的帧起始符 (SOF)。
因此,双绞线上的通信如图所示:
前导码和帧起始符
前导码包含 7 字节的 55H
,作用是使接收器在实际帧到达之前锁定数据流。
帧前界定符 (SFD) 为二进制序列 10101011
(物理介质层可见)。有时它也被视作前导码的一部分。
在传输和接收数据时,协议将自动从数据包中生成/移除前导码和帧起始符。
目的地址 (DA)
目的地址字段包含一个 6 字节长的设备 MAC 地址,数据包将发送到该地址。如果 MAC 地址第一个字节中的最低有效位是 1,则该地址为组播地址。例如,01-00-00-F0-00 和 33-45-67-89-AB-CD 是组播地址,而 00-00-00-F0-00 和 32-45-67-89-AB-CD 不是。
带有组播地址的数据包将到达选定的一组以太网节点,并发挥重要作用。如果目的地址字段是保留的多播地址,即 FF-FF-FF-FF-FF-FF,则该数据包是一个广播数据包,指向共享网络中的每个对象。如果 MAC 地址的第一个字节中的最低有效位为 0,则该地址为单播地址,仅供寻址节点使用。
通常,EMAC 控制器会集成接收过滤器,用于丢弃或接收带有组播、广播和/或单播目的地址的数据包。传输数据包时,由主机控制器将所需的目标地址写入传输缓冲区。
源地址 (SA)
源地址字段包含一个 6 字节长的节点 MAC 地址,以太网数据包通过该节点创建。以太网的用户需为所使用的任意控制器生成唯一的 MAC 地址。MAC 地址由两部分组成:前三个字节称为组织唯一标识符 (OUI),由 IEEE 分配;后三个字节是地址字节,由购买 OUI 的公司配置。有关 ESP-IDF 中使用的 MAC 地址的详细信息,请参见 MAC 地址分配。
传输数据包时,由主机控制器将分配的源 MAC 地址写入传输缓冲区。
类型/长度
类型/长度字段长度为 2 字节。如果其值 <= 1500(十进制),则该字段为长度字段,指定在数据字段后的非填充数据量;如果其值 >= 1536,则该字段值表示后续数据包所属的协议。以下为该字段的常见值:
- IPv4 = 0800H
- IPv6 = 86DDH
- ARP = 0806H
使用专有网络的用户可以将此字段配置为长度字段。然而,对于使用互联网协议 (IP) 或地址解析协议 (ARP) 等协议的应用程序,在传输数据包时,应将此字段配置为协议规范定义的适当类型。
数据有效载荷
数据有效载荷字段是一个可变长度的字段,长度从 0 到 1500 字节不等。更大的数据包会因违反以太网标准而被大多数以太网节点丢弃。
数据有效载荷字段包含客户端数据,如 IP 数据报。
填充及帧校验序列 (FCS)
填充字段是一个可变长度的字段。数据有效载荷较小时,将添加填充字段以满足 IEEE 802.3 规范的要求。
以太网数据包的 DA、SA、类型、数据有效载荷和填充字段共计必须不小于 60 字节。加上所需的 4 字节 FCS 字段,数据包的长度必须不小于 64 字节。如果数据有效载荷字段小于 46 字节,则需要加上一个填充字段。
帧校验序列字段 (FCS) 长度为 4 字节,其中包含一个行业标准的 32 位 CRC,该 CRC 是根据 DA、SA、类型、数据有效载荷和填充字段的数据计算的。鉴于计算 CRC 的复杂性,硬件通常会自动生成一个有效的 CRC 进行传输。否则,需由主机控制器生成 CRC 并将其写入传输缓冲区。
通常情况下,主机控制器无需关注填充字段和 CRC 字段,因为这两部分可以在传输或接收时由硬件 EMAC 自动生成或验证。然而,当数据包到达时,填充字段和 CRC 字段将被写入接收缓冲区。因此,如果需要的话,主机控制器也可以对它们进行评估。
注意
除了上述的基本数据帧,在 10/100 Mbps 以太网中还有两种常见的帧类型:控制帧和 VLAN 标记帧。ESP-IDF 不支持这两种帧类型。
配置 MAC 和 PHY
以太网驱动器由两部分组成:MAC 和 PHY。
根据以太网板设计,需要分别为 MAC 和 PHY 配置必要的参数,通过两者完成驱动程序的安装。
MAC 的相关配置可以在 eth_mac_config_t
中找到,具体包括:
-
eth_mac_config_t::sw_reset_timeout_ms
:软件复位超时值,单位为毫秒。通常,MAC 复位应在 100 ms 内完成。 -
eth_mac_config_t::rx_task_stack_size
和eth_mac_config_t::rx_task_prio
:MAC 驱动会创建一个专门的任务来处理传入的数据包,这两个参数用于设置该任务的堆栈大小和优先级。 -
eth_mac_config_t::flags
:指定 MAC 驱动应支持的额外功能,尤其适用于某些特殊情况。这个字段的值支持与以ETH_MAC_FLAG_
为前缀的宏进行 OR 运算。例如,如果要求 MAC 驱动程序在 cache 禁用时仍能正常工作,那么则需要用ETH_MAC_FLAG_WORK_WITH_CACHE_DISABLE
配置这个字段。
PHY 的相关配置可以在 eth_phy_config_t
中找到,具体包括:
-
eth_phy_config_t::phy_addr
:同一条 SMI 总线上可以存在多个 PHY 设备,所以有必要为各个 PHY 设备分配唯一地址。通常,这个地址是在硬件设计期间,通过拉高/拉低一些 PHY strapping 管脚来配置的。根据不同的以太网开发板,可配置值为0
到15
。需注意,如果 SMI 总线上仅有一个 PHY 设备,将该值配置为-1
,即可使驱动程序自动检测 PHY 地址。 -
eth_phy_config_t::reset_timeout_ms
:复位超时值,单位为毫秒。通常,PHY 复位应在 100 ms 内完成。 -
eth_phy_config_t::autonego_timeout_ms
:自动协商超时值,单位为毫秒。以太网驱动程序会与链路另一端的设备进行自协商,以确定连接的最佳双工模式和速率。此值通常取决于电路板上 PHY 设备的性能。 -
eth_phy_config_t::reset_gpio_num
:如果开发板同时将 PHY 复位管脚连接至了任意 GPIO 管脚,请使用该字段进行配置。否则,配置为-1
。 -
eth_phy_config_t::hw_reset_assert_time_us
:PHY 复位引脚被置为有效状态的时间(以微秒为单位)。将该值配置为0
,即可使用芯片默认的复位时长。 -
eth_phy_config_t::post_hw_reset_delay_ms
:PHY 硬件复位完成后的等待时间(以毫秒为单位)。将该值配置为0
,即可使用芯片默认的等待时长,配置为-1
,表示执行 PHY 硬件复位后不等待。
ESP-IDF 在宏 ETH_MAC_DEFAULT_CONFIG
和 ETH_PHY_DEFAULT_CONFIG
中为 MAC 和 PHY 提供了默认配置。
创建 MAC 和 PHY 实例
以太网驱动是以面向对象的方式实现的。对 MAC 和 PHY 的任何操作都应基于实例。
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_EXAMPLE_ETH_PHY_ADDR; // 根据开发板设计更改 PHY 地址
phy_config.reset_gpio_num = CONFIG_EXAMPLE_ETH_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_EXAMPLE_ETH_SPI_MISO_GPIO,.mosi_io_num = CONFIG_EXAMPLE_ETH_SPI_MOSI_GPIO,.sclk_io_num = CONFIG_EXAMPLE_ETH_SPI_SCLK_GPIO,.quadwp_io_num = -1,.quadhd_io_num = -1,
};
ESP_ERROR_CHECK(spi_bus_initialize(CONFIG_EXAMPLE_ETH_SPI_HOST, &buscfg, 1));
// 配置 SPI 从机设备
spi_device_interface_config_t spi_devcfg = {.mode = 0,.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,.spics_io_num = CONFIG_EXAMPLE_ETH_SPI_CS_GPIO,.queue_size = 20
};
/* dm9051 ethernet driver is based on spi driver */
eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(SPI2_HOST, &spi_devcfg);
w5500_config.int_gpio_num = spi_eth_module_config->int_gpio;
w5500_config.poll_period_ms = spi_eth_module_config->polling_ms;
esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);
esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);
安装驱动程序
安装以太网驱动程序需要结合 MAC 和 PHY 实例,并在 esp_eth_config_t
中配置一些额外的高级选项(即不仅限于 MAC 或 PHY 的选项):
-
esp_eth_config_t::mac
:由 MAC 生成器创建的实例(例如esp_eth_mac_new_esp32()
)。 -
esp_eth_config_t::phy
:由 PHY 生成器创建的实例(例如esp_eth_phy_new_ip101()
)。 -
esp_eth_config_t::check_link_period_ms
:以太网驱动程序会启用操作系统定时器来定期检查链接状态。该字段用于设置间隔时间,单位为毫秒。 -
esp_eth_config_t::stack_input
:在大多数的以太网物联网应用中,驱动器接收的以太网帧会被传递到上层(如 TCP/IP 栈)。经配置,该字段为负责处理传入帧的函数。可以在安装驱动程序后,通过函数esp_eth_update_input_path()
更新该字段。该字段支持在运行过程中进行更新。 -
esp_eth_config_t::on_lowlevel_init_done
和esp_eth_config_t::on_lowlevel_deinit_done
:这两个字段用于指定钩子函数,当去初始化或初始化低级别硬件时,会调用钩子函数。
ESP-IDF 在宏 ETH_DEFAULT_CONFIG
中为安装驱动程序提供了一个默认配置。
esp_eth_config_t config = ETH_DEFAULT_CONFIG(mac, phy); // 应用默认驱动程序配置
esp_eth_handle_t eth_handle = NULL; // 驱动程序安装完毕后,将得到驱动程序的句柄
esp_eth_driver_install(&config, ð_handle); // 安装驱动程序
以太网驱动程序包含事件驱动模型,该模型会向用户空间发送有用及重要的事件。安装以太网驱动程序之前,需要首先初始化事件循环。有关事件驱动编程的更多信息,请参考 事件循环库。
/** 以太网事件的事件处理程序 */
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); // 注册以太网事件处理程序(用于在发生 link up/down 等事件时,处理特定的用户相关内容)
启动以太网驱动程序
安装驱动程序后,可以立即启动以太网。
esp_eth_start(eth_handle); // 启动以太网驱动程序状态机
连接驱动程序至 TCP/IP 协议栈
现在,以太网驱动程序已经完成安装。但对应 OSI(开放式系统互连模型)来看,目前阶段仍然属于第二层(即数据链路层)。这意味着可以检测到 link up/down 事件,获得用户空间的 MAC 地址,但无法获得 IP 地址,当然也无法发送 HTTP 请求。ESP-IDF 中使用的 TCP/IP 协议栈是 LwIP,关于 LwIP 的更多信息,请参考 LwIP。
要将以太网驱动程序连接至 TCP/IP 协议栈,需要以下三步:
- 为以太网驱动程序创建网络接口
- 将网络接口连接到以太网驱动程序
- 注册 IP 事件处理程序
有关网络接口的更多信息,请参考 Network Interface。
/** 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 事件,从而保证在执行用户定义的处理程序时,系统处于预期状态。
以太网驱动程序的杂项控制
以下功能只支持在安装以太网驱动程序后调用。
- 关闭以太网驱动程序:
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);
数据流量控制
受 RAM 大小限制,在网络拥堵时,MCU 上的以太网通常仅能处理有限数量的帧。发送站的数据传输速度可能快于对等端的接收能力。以太网数据流量控制机制允许接收节点向发送方发出信号,要求暂停传输,直到接收方跟上。这项功能是通过暂停帧实现的,该帧定义在 IEEE 802.3x 中。
暂停帧是一种特殊的以太网帧,用于携带暂停命令,其 EtherType 字段为 0x8808
,控制操作码为 0x0001
。只有配置为全双工操作的节点组可以发送暂停帧。当节点组希望暂停链路的另一端时,它会发送一个暂停帧到 48 位的保留组播地址 01-80-C2-00-00-01
。暂停帧中也包括请求暂停的时间段,以两字节的整数形式发送,值的范围从 0
到 65535
。
安装以太网驱动程序后,数据流量控制功能默认禁用,可以通过以下方式启用此功能:
bool flow_ctrl_enable = true;
esp_eth_ioctl(eth_handle, ETH_CMD_S_FLOW_CTRL, &flow_ctrl_enable);
需注意,暂停帧是在自动协商期间由 PHY 向对等端公布的。只有当链路两端都支持暂停帧时,以太网驱动程序才会发送暂停帧。
应用示例
首先在ESP32IDF 配置中勾选使用W5500
w5500_config.h
#ifndef _W5500_CONFIG_H
#define _W5500_CONFIG_H
#ifdef __cplusplus
extern "C"{
#endif/*
说明:基于ESP32IDF 官方的ethernet 以太网驱动实现W5500
需要包含组件:esp_eth
代码基于只挂在一个W5500以太网口的情况,挂在多个网口是需另外处理
*/
#include "esp_eth_driver.h"
#include "esp_err.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"// W5500 静态IP设置
#define W5500_STATIC_IP 0 //1-使用静态IP,0-使用动态IP
#if W5500_STATIC_IP
#define W5500_IP "192.168.1.168" // IP地址
#define W5500_GateWwy "192.168.1.1" //网关
#define W5500_NetMask "255.255.255.0" // 子网掩码
#endif/// @brief W5500 配置的结构体 ,没用到的设置为-1
typedef struct
{spi_host_device_t host_id;gpio_num_t miso_io_num;gpio_num_t mosi_io_num;gpio_num_t sclk_io_num;gpio_num_t cs_io_num;gpio_num_t int_io_num;gpio_num_t rst_io_num;/* data */
}w5500_config_t;/*** @brief 根据乐鑫物联网开发框架配置初始化以太网驱动程序** @param[in] cfg 一些需要配置的参数* @param[out] eth_handles_out 已初始化的以太网句柄* @param[out] eth_cnt_out 已初始化的以太网数量* @return* - ESP_OK 初始化成功* - ESP_ERR_INVALID_ARG 当传递无效指针时* - ESP_ERR_NO_MEM 当没有内存来分配以太网驱动程序句柄数组时* - ESP_FAIL 在任何其他故障情况下*/
esp_err_t w5500_init(w5500_config_t *cfg);/*** @brief 注销w5500*/
esp_err_t w5500_deinit(void);// W5500 测试示例
void w5500_test(void);
#ifdef __cplusplus
}
#endif#endif
w5500_config.c
#include "w5500_config.h"
#include "esp_log.h"
#include "esp_check.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include "esp_eth.h"
#include "esp_event.h"
#include <stdio.h>
#include <string.h>#define CONFIG_EXAMPLE_ETH_SPI_INT0_GPIO GPIO_NUM_9
#define CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ 16 // 16MHztypedef struct
{uint8_t spi_cs_gpio;int8_t int_gpio;uint32_t polling_ms;int8_t phy_reset_gpio;uint8_t phy_addr;uint8_t *mac_addr;
} spi_eth_module_config_t;static const char *TAG = "W5500->";static bool gpio_isr_svc_init_by_eth = false; // 表示我们已经初始化了GPIO中断服务例程(ISR) 服务。
static spi_host_device_t host_id = SPI2_HOST;
esp_eth_handle_t eth_handles = NULL;/** 以太网事件的事件处理程序 */
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;}
}/** 获取到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, "~~~~~~~~~~~");
}/*** @brief SPI总线初始化(供以太网SPI模块使用)** @return* - ESP_OK 成功*/
static esp_err_t spi_bus_init(w5500_config_t *cfg)
{ESP_LOGI(TAG, "SPI bus init start!");// Init SPI busspi_bus_config_t buscfg = {.miso_io_num = cfg->miso_io_num,.mosi_io_num = cfg->mosi_io_num,.sclk_io_num = cfg->sclk_io_num,.quadwp_io_num = -1,.quadhd_io_num = -1,};ESP_ERROR_CHECK(spi_bus_initialize(cfg->host_id, &buscfg, SPI_DMA_CH_AUTO));host_id = cfg->host_id;ESP_LOGI(TAG, "SPI bus init DONE!");return ESP_OK;
}/*** @brief 以太网SPI模块初始化** @param[in] spi_eth_module_config 特定的SPI以太网模块配置* @param[out] mac_out 可选地返回以太网MAC对象* @param[out] phy_out 可选地返回以太网物理层(PHY)对象* @return
* - 如果初始化成功,返回`esp_eth_handle_t`* - 如果初始化失败,返回`NULL` */
static esp_eth_handle_t eth_init_spi(spi_eth_module_config_t *spi_eth_module_config, esp_eth_mac_t **mac_out, esp_eth_phy_t **phy_out)
{esp_eth_handle_t ret = NULL;// 将通用MAC和PHY配置初始化为默认值eth_mac_config_t mac_config = ETH_MAC_DEFAULT_CONFIG();eth_phy_config_t phy_config = ETH_PHY_DEFAULT_CONFIG();// 根据板卡特定配置更新物理层(PHY)配置。phy_config.phy_addr = spi_eth_module_config->phy_addr;phy_config.reset_gpio_num = spi_eth_module_config->phy_reset_gpio;// 安装通用输入输出中断服务程序(GPIO ISR)处理程序,以便能够处理串行外设接口(SPI)以太网模块的中断if (spi_eth_module_config->int_gpio >= 0){ESP_ERROR_CHECK(gpio_install_isr_service(0));gpio_isr_svc_init_by_eth = true;}// 为特定的SPI模块配置SPI接口spi_device_interface_config_t spi_devcfg = {.mode = 0,.clock_speed_hz = CONFIG_EXAMPLE_ETH_SPI_CLOCK_MHZ * 1000 * 1000,.queue_size = 20,.spics_io_num = spi_eth_module_config->spi_cs_gpio,};// W5500以太网驱动基于SPI驱动eth_w5500_config_t w5500_config = ETH_W5500_DEFAULT_CONFIG(host_id, &spi_devcfg); //// 设置SPI模块使用的剩余GPIO编号和配置。w5500_config.int_gpio_num = spi_eth_module_config->int_gpio;w5500_config.poll_period_ms = spi_eth_module_config->polling_ms;esp_eth_mac_t *mac = esp_eth_mac_new_w5500(&w5500_config, &mac_config);esp_eth_phy_t *phy = esp_eth_phy_new_w5500(&phy_config);// 将以太网驱动程序初始化为默认设置并安装它。esp_eth_handle_t eth_handle = NULL;esp_eth_config_t eth_config_spi = ETH_DEFAULT_CONFIG(mac, phy);ESP_GOTO_ON_FALSE(esp_eth_driver_install(ð_config_spi, ð_handle) == ESP_OK, NULL, err, TAG, "SPI Ethernet driver install failed");// SPI以太网模块可能没有烧录出厂MAC地址,我们可以手动设置。if (spi_eth_module_config->mac_addr != NULL){ESP_GOTO_ON_FALSE(esp_eth_ioctl(eth_handle, ETH_CMD_S_MAC_ADDR, spi_eth_module_config->mac_addr) == ESP_OK,NULL, err, TAG, "SPI Ethernet MAC address config failed");}if (mac_out != NULL){*mac_out = mac;}if (phy_out != NULL){*phy_out = phy;}return eth_handle;
err:if (eth_handle != NULL){esp_eth_driver_uninstall(eth_handle);}if (mac != NULL){mac->del(mac);}if (phy != NULL){phy->del(phy);}return ret;
}/// @brief 将以太网驱动程序附加到TCP/IP协议栈,同时设置静态IP
/// @param
/// @return
esp_err_t w5500_set_static_ip(void)
{ESP_LOGI(TAG, "start set static IP");if (eth_handles == NULL){ESP_LOGI(TAG, "Ethernet handles cannot be NULL");return ESP_ERR_INVALID_ARG;}esp_netif_t *eth_netifs = NULL;esp_eth_netif_glue_handle_t eth_netif_glues;//为SPI以太网创建esp-netif实例 esp_netif_inherent_config_t esp_netif_config = ESP_NETIF_INHERENT_DEFAULT_ETH();// 当仅使用一个以太网接口且不需要修改默认的esp-netif配置参数时,可以调用ESP_NETIF_DEFAULT_ETH(),此时的为动态IP#if W5500_STATIC_IP //是否使用静态IPesp_netif_config_t cfg = { // 应用以太网的默认网络接口配置.base = &esp_netif_config,.stack = ESP_NETIF_NETSTACK_DEFAULT_ETH,}; #elseesp_netif_config_t cfg = ESP_NETIF_DEFAULT_ETH(); // 应用以太网的默认网络接口配置,动态分配IP#endifeth_netifs = esp_netif_new(&cfg); 为以太网驱动程序创建网络接口eth_netif_glues = esp_eth_new_netif_glue(eth_handles);// 将以太网驱动程序附加到TCP/IP协议栈ESP_ERROR_CHECK(esp_netif_attach(eth_netifs, eth_netif_glues));#if W5500_STATIC_IP // 设置静态IP地址 ESP_ERROR_CHECK(esp_netif_dhcpc_stop(eth_netifs)); // 注意接口不能用错esp_netif_ip_info_t ip_info;memset(&ip_info, 0, sizeof(esp_netif_ip_info_t));ip_info.ip.addr = esp_ip4addr_aton((const char *)W5500_IP);ip_info.netmask.addr = esp_ip4addr_aton((const char *)W5500_GateWwy);ip_info.gw.addr = esp_ip4addr_aton((const char *)W5500_NetMask);esp_err_t err = 0;err = esp_netif_set_ip_info(eth_netifs, &ip_info);if (ESP_OK == err)ESP_LOGI(TAG, "Set static IP OK");elseESP_LOGI(TAG, "Set static IP error: %d", err);
#endifreturn ESP_OK;
}
esp_err_t w5500_init(w5500_config_t *cfg)
{ESP_LOGI(TAG, "W5500 init start!");esp_err_t ret = ESP_OK;ESP_GOTO_ON_FALSE(cfg != NULL, ESP_ERR_INVALID_ARG, err, TAG, "invalid arguments: initialized handles array or number of interfaces");eth_handles = malloc(sizeof(esp_eth_handle_t));ESP_GOTO_ON_FALSE(eth_handles != NULL, ESP_ERR_NO_MEM, err, TAG, "no memory");// 初始化SPIESP_GOTO_ON_ERROR(spi_bus_init(cfg), err, TAG, "SPI bus init failed");// 获取本地macuint8_t base_mac_addr[ETH_ADDR_LEN];ESP_GOTO_ON_ERROR(esp_efuse_mac_get_default(base_mac_addr), err, TAG, "get EFUSE MAC failed"); // 获取ESP32出厂编程的基本 MAC 地址uint8_t local_mac[ETH_ADDR_LEN];esp_derive_local_mac(local_mac, base_mac_addr); // 从通用MAC地址派生本地MAC地址。// 初始化特定的SPI以太网模块配置(片选GPIO、中断GPIO等)spi_eth_module_config_t spi_eth_module_config = {.int_gpio = cfg->int_io_num,.spi_cs_gpio = cfg->cs_io_num,.phy_reset_gpio = cfg->rst_io_num,.mac_addr = local_mac,.phy_addr = 1,.polling_ms = 0,};eth_handles = eth_init_spi(&spi_eth_module_config, NULL, NULL);w5500_set_static_ip(); //将以太网驱动程序附加到TCP/IP协议栈,同时设置静态IP// 注册用户定义的事件处理程序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));ESP_ERROR_CHECK(esp_eth_start(eth_handles));ESP_GOTO_ON_FALSE(eth_handles, ESP_FAIL, err, TAG, "SPI Ethernet init failed");return ret;
err:free(eth_handles);return ret;
}esp_err_t w5500_deinit(void)
{if (eth_handles == NULL){ESP_LOGI(TAG, "Ethernet handles cannot be NULL");return ESP_ERR_INVALID_ARG;}esp_eth_mac_t *mac = NULL;esp_eth_phy_t *phy = NULL;if (eth_handles != NULL){esp_eth_get_mac_instance(eth_handles, &mac);esp_eth_get_phy_instance(eth_handles, &phy);ESP_RETURN_ON_ERROR(esp_eth_driver_uninstall(eth_handles), TAG, "Ethernet %p uninstall failed", eth_handles);}if (mac != NULL){mac->del(mac);}if (phy != NULL){phy->del(phy);}spi_bus_free(host_id);
#if (CONFIG_EXAMPLE_ETH_SPI_INT0_GPIO >= 0) || (CONFIG_EXAMPLE_ETH_SPI_INT1_GPIO > 0)// 我们安装了GPIO中断服务例程(ISR),所以也需要卸载它。// 不过这里要小心,因为该服务可能被其他功能使用!if (gpio_isr_svc_init_by_eth){ESP_LOGW(TAG, "uninstalling GPIO ISR service!");gpio_uninstall_isr_service();}
#endiffree(eth_handles);return ESP_OK;
}void w5500_test(void)
{// 初始化TCP/IP网络接口,即esp-netif(在应用程序中应仅调用一次)ESP_ERROR_CHECK(esp_netif_init());// 创建在后台运行的默认事件循环。ESP_ERROR_CHECK(esp_event_loop_create_default());w5500_config_t w5500_cfg = {.host_id = SPI2_HOST,.miso_io_num = GPIO_NUM_13,.mosi_io_num = GPIO_NUM_11,.sclk_io_num = GPIO_NUM_12,.cs_io_num = GPIO_NUM_10,.int_io_num = GPIO_NUM_9,.rst_io_num = GPIO_NUM_NC,};ESP_ERROR_CHECK(w5500_init(&w5500_cfg));
}
log输出
在main文件添加头文件,并调用w5500_test()
#include "w5500_config.h"w5500_test();
初始化正常时,LOG输出如下