Ethernet instead of Wi-Fi
Wi-Fi is right until the board is inside a metal cabinet, or has to answer within ten milliseconds every time, or must survive somebody changing the router password. Then you want a cable, and there are two very different ways to get one.
What a cable actually buys
The two routes
Native MAC plus an external PHY — LAN8720 or similar, over RMII. Fast, needs six specific pins plus MDC and MDIO, and exists only on the classic ESP32 and the P4. The clock arrangement is fiddly and it is where most first attempts stall.
An SPI module — W5500 or ENC28J60. Four pins on any GPIO, works on every chip, roughly 8 Mbit/s in practice. The W5500 also runs its own TCP stack, so it uses less of the ESP32's RAM.
For nearly every project that wants a cable, the SPI module is the right answer.
Turn the radio off
If the board is wired, WiFi.mode(WIFI_OFF) saves 60–80 mA and removes a whole
category of interference. Do it explicitly — the radio does not switch itself
off just because you never called begin.
The code
The classic ESP32 has an Ethernet MAC built in and needs an external PHY. Six RMII pins are fixed by the hardware and belong to Ethernet for ever once you use them.
#include <ETH.h>
static bool up = false;
void onEvent(arduino_event_id_t e) {
if (e == ARDUINO_EVENT_ETH_GOT_IP) {
up = true;
Serial.println(ETH.localIP());
}
if (e == ARDUINO_EVENT_ETH_DISCONNECTED) up = false;
}
void setup() {
Serial.begin(115200);
Network.onEvent(onEvent);
// PHY address, MDC, MDIO, power pin, PHY type, clock mode
ETH.begin(ETH_PHY_LAN8720, 1, 23, 18, 16, ETH_CLOCK_GPIO0_IN);
}
void loop() {
if (up) { /* the network is ready */ }
}ETH.begin returns before the link is up. Wait for the ETH_CONNECTED event rather than assuming, exactly as with Wi-Fi.
An SPI module such as the W5500 works on every chip and any pins. Slower than RMII and enormously easier to wire, which is the right trade for most projects.
from machine import Pin, SPI
import network, time
spi = SPI(2, baudrate=20_000_000, sck=Pin(18), mosi=Pin(23), miso=Pin(19))
eth = network.LAN(0, phy_type=network.PHY_W5500, phy_addr=0,
spi=spi, cs=Pin(5), int=Pin(4))
eth.active(True)
while not eth.isconnected():
time.sleep_ms(100)
print(eth.ifconfig())The W5500 has its own TCP stack on board, so it costs the ESP32 far less RAM than the native MAC does. That matters on a C3.
When it does not work
RMII clocking. The PHY and the ESP32 must agree on which end generates the 50 MHz clock, and getting it backwards gives you exactly this - a physical link and no traffic.
On an RMII design GPIO 0 often carries the clock, and GPIO 0 is also a strapping pin. Adding anything else to it breaks both.
Then use the SPI route. A W5500 costs four pins, works on the C3 and C6 which have no Ethernet MAC at all, and gives up throughput you probably were not using.
PoE splitters have a startup surge and many cheap ones cannot hold 3.3 V through a Wi-Fi burst. If the board is wired, switch the radio off entirely.
However it got on the network, the next chapter is what it can serve once it is there.
Hello world from the board →Edit this page — content/esp32/ethernet-instead-of-wifi.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.