Bare-Metal Ada on the ESP32-S3 A step-by-step guide to running Ada on the ESP32-S3 with no ESP-IDF, no FreeRTOS, and no Python.

Step 39 of 56

W5500: Ethernet with the stack on the chip

A hardwired TCP/IP controller with eight hardware sockets — and a layered driver that ends up looking like GNAT.Sockets.

The stack is not yours

The WIZnet W5500 is an SPI slave carrying an on-chip 10/100 PHY, a MAC, and a hardwired TCP/IP stack exposing eight independent hardware sockets. You are not writing a TCP implementation against it; you are driving one that already exists in silicon.

The driver is built in layers, which is worth knowing because it decides which package you should be reaching for:

LayerWhat it gives you
ESP32S3.W5500 The SPI frame transport, hardware/software reset, common registers (identity, network config) and PHY link status.
.Sockets The socket engine: TCP and UDP over the eight hardware sockets, with a self-contained Socket handle and a Status error model.
.DHCP A minimal DHCP client — a software protocol over UDP, so it sits above the socket engine rather than in the chip.
.Net_Device The W5500 as a concrete Net_Devices.Device, so it can back the chip-neutral GNAT.Sockets facade.
.Interrupts The INTn line. The base layer configures it as a pulled-up input but does not use it — the first pass is polling.

The SPI frame, and why CS is a GPIO

Every access is one frame in Variable Length Data Mode: a 16-bit offset, a control byte, then N data bytes, with the offset auto-incrementing inside the frame. VDM keeps the bus shareable rather than demanding an exclusive transaction per register.

CS is driven as a plain GPIO, not routed to the SPI peripheral — exactly as with SD over SPI — because it has to be held low across all three phases of the frame, and the peripheral's own CS pulses per transfer. Another concrete case for SPI's CS_Pin. The chip runs in SPI mode 0.

Two levels of concurrency

Each frame takes the SPI host's session for its own transfer and releases it — the "lock the bus only as long as necessary" idiom shared with the other SPI drivers — so every W5500 access is atomic against any other task or device on that bus.

The socket layer then adds per-socket ownership on top. The eight sockets are genuinely independent, so different tasks can drive different sockets simultaneously, with the transport serialising the shared bus underneath. That is the arrangement that makes a multi-socket application straightforward rather than a locking exercise.

What is not there yet

A useful pairing: give the W5500 its address from the eFuse MAC block's Ethernet entry — a real manufacturer-assigned address rather than one you invented, which matters as soon as two of your boards share a segment.