
ESP32除了可以使用无线网络外还可以通过外接PHY模块来使用有线网络(Ethernet:以太网)
下面是ESP32与LAN8720间RMII PHY的连接定义,用于以太网通信,下面几个针脚必须按定义连接:

下面是ESP32与LAN8720间RMII PHY SMI的连接定义,用于ESP32读写LAN8720的寄存器,下面的针脚理论上可以连接到ESP32任何支持输出的针脚上:

使用步骤
引用以太网库#include <ETH.h>;
声明一个对象ETHClass myETH,默认的已经声明了一个名为ETH的对象;
使用begin()方法启动以太网连接;
常用方法说明
bool begin(uint8_t phy_addr, int power, int mdc, int mdio, eth_phy_type_t type, eth_clock_mode_t clock_mode)
启用以太网连接,参数说明如下:
phy_addr:LAN8720写0或1、TLK110写31;
power:设置ESP32某管脚,该管脚可以用来管理LAN8720供电,使能时输出高电平,失能时为低电平,写-1则不使用;
mdc:mdc管脚编号,写-1则不使用;
mdio:mdio管脚编号,写-1则不使用;
type:PHY类型,ETH_PHY_LAN8720或ETH_PHY_TLK110;
clock_mode:时钟模式,可选值ETH_CLOCK_GPIO0_IN、ETH_CLOCK_GPIO0_OUT、ETH_CLOCK_GPIO16_OUT、ETH_CLOCK_GPIO17_OUT,详细介绍见后文;
bool config(IPAddress local_ip, IPAddress gateway, IPAddress subnet, IPAddress dns1 = (uint32_t)0x00000000, IPAddress dns2 = (uint32_t)0x00000000)
设置IP地址、网关地址、子网掩码、dns地址;
const char * getHostname()
获取主机名字;
bool setHostname(const char * hostname)
设置主机名字;
bool fullDuplex()
检查是否为全双工通讯;
uint8_t linkSpeed()
获取传输速度,单位Mbps;
IPAddress localIP()
获取IP地址;
IPAddress subnetMask()
获取子网掩码;
IPAddress gatewayIP()
获取网关地址;
IPAddress dnsIP(uint8_t dns_no = 0)
获取dns服务器地址;
String macAddress()
获取mac地址;
使用演示
数据通讯
使用下面代码进行以太网通讯演示:
#include <ETH.h> //引用以使用ETH
#include <WiFiUdp.h>
#define ETH_ADDR 1
#define ETH_POWER_PIN -1
#define ETH_MDC_PIN 23
#define ETH_MDIO_PIN 18
#define ETH_TYPE ETH_PHY_LAN8720
#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
WiFiUDP Udp; //创建UDP对象
unsigned int localUdpPort = 2333; //本地端口号
void setup()
{
Serial.begin(115200);
Serial.println();
ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE); //启用ETH
while(!((uint32_t)ETH.localIP())) //等待获取到IP
{
}
Serial.println("Connected");
Serial.print("IP Address:");
Serial.println(ETH.localIP());
Udp.begin(localUdpPort); //启用UDP监听以接收数据
}
void loop()
{
int packetSize = Udp.parsePacket(); //获取当前队首数据包长度
if (packetSize) //如果有数据可用
{
char buf[packetSize];
Udp.read(buf, packetSize); //读取当前包数据
Serial.println();
Serial.print("Received: ");
Serial.println(buf);
Serial.print("From IP: ");
Serial.println(Udp.remoteIP());
Serial.print("From Port: ");
Serial.println(Udp.remotePort());
Udp.beginPacket(Udp.remoteIP(), Udp.remotePort()); //准备发送数据
Udp.print("Received: "); //复制数据到发送缓存
Udp.write((const uint8_t*)buf, packetSize); //复制数据到发送缓存
Udp.endPacket(); //发送数据
}
}

上面例子中启用以太网等待获取到IP,然后用UDP进行了通讯测试;
事件响应
使用下面代码进行以太网事件演示:
#include <ETH.h> //引用以使用ETH
#define ETH_ADDR 1
#define ETH_POWER_PIN -1
#define ETH_MDC_PIN 23
#define ETH_MDIO_PIN 18
#define ETH_TYPE ETH_PHY_LAN8720
#define ETH_CLK_MODE ETH_CLOCK_GPIO17_OUT
void printETHInfo(void) //打印基本信息
{
Serial.print("ETH MAC: ");
Serial.print(ETH.macAddress());
Serial.print(", IPv4: ");
Serial.print(ETH.localIP());
if (ETH.fullDuplex())
{
Serial.print(", FULL_DUPLEX");
}
Serial.print(", ");
Serial.print(ETH.linkSpeed());
Serial.println("Mbps");
}
void WiFiEvent(WiFiEvent_t event)
{
switch (event)
{
case SYSTEM_EVENT_ETH_START: //启动ETH成功
Serial.println("ETH Started");
break;
case SYSTEM_EVENT_ETH_CONNECTED: //接入网络
Serial.println("ETH Connected");
break;
case SYSTEM_EVENT_ETH_GOT_IP: //获得IP
Serial.println("ETH GOT IP");
printETHInfo();
break;
case SYSTEM_EVENT_ETH_DISCONNECTED: //失去连接
Serial.println("ETH Disconnected");
break;
case SYSTEM_EVENT_ETH_STOP: //关闭
Serial.println("ETH Stopped");
break;
default:
break;
}
}
void setup()
{
Serial.begin(115200);
Serial.println();
WiFi.onEvent(WiFiEvent); //注册事件
ETH.begin(ETH_ADDR, ETH_POWER_PIN, ETH_MDC_PIN, ETH_MDIO_PIN, ETH_TYPE, ETH_CLK_MODE); //启用ETH
}
void loop()
{
delay(10000);
esp_eth_disable(); //关闭eth
}

上面演示时在获取到IP地址后我将网线拔了,手动触发了SYSTEM_EVENT_ETH_DISCONNECTED事件;
有事件的话其实上个例子中while(!((uint32_t)ETH.localIP())){} //等待获取到IP这句就可以用事件来处理了;
PHY地址
PHY地址LAN8720写0或1、TLK110写31,LAN8720使用0还是1由芯片复位时RXER引脚电平决定,当该引脚接下拉电阻或者浮空(芯片内部下拉)时,地址就为0,当引脚接上拉电阻时地址为1;
上文使用的模块电路中RXER脚外接了上拉电阻,所以地址写1;
时钟
理论上LAN8720和ESP32需要有同一个时钟源用于以太网通讯,ESP32提供了四种方式来处理时钟:

第一种方式是外部输入,LAN8720和ESP32接入同一个外部50Mhz时钟;
第二种方式由ESP32(GPIO0)提供时钟给LAN8720,信号质量可能不怎么样;
第三种方式由ESP32(GPIO16)提供时钟给LAN8720;
第四种方式由ESP32(GPIO17)提供时钟给LAN8720,最合适用于LAN8720;
上文使用的模块并没有引出时钟到外部针脚,也没法选择使用时钟来源,所以上面使用时相当于LAN8720使用了模块自带的时钟,而ESP32虽然设置为时钟输出模式,但其实没有真正输出给LAN8720,这种情况下虽然能够正常通讯,但是不是那么符合理论设计。
另外如果使用外部时钟连接到GPIO0时还需要特别注意电路设计:
通讯应用
除UDP外,Ethernet也可以使用WiFiClient和WiFiServer等通讯功能;