---
title: "ESP32-S3 + WIZnet W5500 — MicroPython Ethernet (MACRAW) Guide"
url: "https://maker.wiznet.io/josephsr/projects/esp32-s3-wiznet-w5500--micropython-ethernet-macraw-guide/"
markdown_url: "https://maker.wiznet.io/josephsr/projects/esp32-s3-wiznet-w5500--micropython-ethernet-macraw-guide/md"
type: "WCC: WIZnet Created Content"
author: "Joseph"
editor: "WIZnet"
editor_url: "https://maker.wiznet.io/"
original_author: "Joseph"
published: "2026-08-25"
language: "en"
tags: ["MicroPython", "ESP32-W5500-Dev-Kit"]
hardware: ["WIZnet W5500"]
likes: 0
views: 201
comments: 0
source: "WIZnet Makers (https://maker.wiznet.io/)"
---

# ESP32-S3 + WIZnet W5500 — MicroPython Ethernet (MACRAW) Guide

> This guide walks through flashing MicroPython onto an ESP32-W5500-Dev-V1 board and verifying Ethernet functionality end to end, starting from a blank board.

Original author: Joseph

## Components

- **WIZnet W5500** x 1 ([docs](https://docs.wiznet.io/Product/Chip/Ethernet/W5500))

WIZnet parts: W5500 ([Datasheet](https://docs.wiznet.io/Product/Chip/Ethernet/W5500/datasheet?utm_source=maker&utm_medium=project&utm_campaign=w5500), [product hub](https://maker.wiznet.io/products/w5500/))

## Documents and links

- [esp32-w5500-devkit-examples](https://github.com/simryang/esp32-w5500-devkit-examples) (code)

## Article

**ESP32-S3 + WIZnet MicroPython series**

1. **W5500 MACRAW guide** (this article)

2. [W5500 TOE guide](https://maker.wiznet.io/josephsr/projects/esp32%2Ds3%2Dwiznet%2Dw5500%2D%2Dmicropython%2Dethernet%2Dtoe%2Dguide/)

3. W6300 MACRAW guide (coming soon)

4. W6300 TOE guide (coming soon)

**Board** ESP32-W5500-Dev-V1 ([product page](https://4911b20c-90c2-42bd-88bd-b2534477f318.frame.claudeusercontent.com/_f/1787636268-88bd/) — coming soon)

**Firmware** MicroPython v1.28.0, ESP32_GENERIC_S3-SPIRAM_OCT MACRAW mode — stock, unmodified

The W5500 acts as a dumb SPI Ethernet MAC/PHY; TCP/IP is handled by MicroPython's own lwIP stack via the built-in `network.LAN` driver.

Every step below was flashed, run, and verified on real hardware — follow it in order and you should get the same result.

**Board specs**

| Item | Value |
| --- | --- |
| MCU | ESP32-S3-WROOM-1 |
| SPIRAM | Octal SPIRAM |
| Ethernet chip | WIZnet W5500 (SPI interface) — used in MACRAW mode in this guide |
| Ethernet SPI pins | see section 1 |

## 00 Environment setup

On your PC:

### Install the Python tools

```plaintext
pip install esptool mpremote
```

`esptool` flashes firmware onto the board; `mpremote` connects to the board's REPL, copies files, and runs scripts.

### Download the MicroPython firmware

Matching the Octal SPIRAM in the specs above, this guide picks board type **ESP32_GENERIC_S3** and variant **SPIRAM_OCT** on micropython.org/download (grabbing the plain variant or FLASH_4M would still boot, but PSRAM wouldn't be usable). If you're using a different board, check its module name and SPIRAM type (Octal vs. Quad, or none) first and pick the matching variant.

This guide is based on MicroPython v1.28.0 — to grab that exact build directly, use ESP32_GENERIC_S3-SPIRAM_OCT-20260406-v1.28.0.bin.

### Connect the board and find its port

Plug the board in over USB. On Windows, check Device Manager → "Ports (COM & LPT)" for the assigned COM number (e.g. `COM5`). Replace `COMx` below with whatever you find.

### Flash the firmware

```plaintext
esptool.py --port COMx erase_flash
esptool.py --port COMx --baud 460800 write_flash 0x0 ESP32_GENERIC_S3-SPIRAM_OCT-v1.28.0.bin
```

If your board has a BOOT button and isn't detected, hold BOOT while plugging in USB, or press it right before running `erase_flash`.

### Verify the connection

```plaintext
mpremote connect COMx
```

You should see the MicroPython banner and a `>>>` prompt. Exit the REPL with `Ctrl+]` or `Ctrl+X`.

### Get the example files onto the board

This guide's examples live in a repository: github.com/simryang/esp32-w5500-devkit-examples

```plaintext
git clone https://github.com/simryang/esp32-w5500-devkit-examples.git
cd esp32-w5500-devkit-examples
```

Copy the shared module over first:

```plaintext
mpremote connect COMx fs cp net_init.py :net_init.py
```

The remaining examples are run in place, one at a time, starting in section 3.

#### Official examples/tests referenced

This guide's examples were written against the following paths in the official MicroPython repository.

`examples/network/` — generic network examples, not tied to any specific interface (the official repo has no W5500-specific example):

| File | What it does |
| --- | --- |
| http_client.py | Minimal HTTP GET client — **basis for example 08** |
| http_server.py | Basic HTTP server |
| http_server_simplistic.py | Minimal HTTP server (no comments) |
| http_server_simplistic_commented.py | Same as above with line-by-line comments — **basis for example 09** |
| https_client.py | HTTP client with TLS |
| https_client_nonblocking.py | HTTPS client using non-blocking sockets |
| https_server.py | HTTP server with TLS |

`tests/` — used to confirm the exact shape of the plain `socket` API (error handling, timeouts, etc.), reflected in examples 03–07. Repository: github.com/micropython/micropython/tree/v1.28.0/tests

## 01 Hardware wiring

Confirmed from the board schematic (`ESP32-W5500-Dev-V1_SCH.pdf`) — **do not guess these pins on a different board**, re-check the schematic:

| Signal | GPIO |
| --- | --- |
| SCK | 12 |
| MOSI | 11 |
| MISO | 13 |
| CS | 10 |
| INT | 14 |
| RESET | 9 |

## 02 Shared init module

Every example imports `init_lan()` from this module. It brings the SPI bus and `network.LAN` interface up and blocks (up to `dhcp_timeout_s`) until DHCP assigns an address. On the ESP32 port, W5500 support ships through the generic `network.LAN` class, not `network.WIZNET5K`.[\[1\]](https://4911b20c-90c2-42bd-88bd-b2534477f318.frame.claudeusercontent.com/_f/1787636268-88bd/#ref1)

```plaintext
# net_init.py
import time
import network
from machine import Pin, SPI

PIN_SCK = 12
PIN_MOSI = 11
PIN_MISO = 13
PIN_CS = 10
PIN_INT = 14
PIN_RESET = 9


def init_lan(dhcp_timeout_s=15):
    spi = SPI(1, sck=Pin(PIN_SCK), mosi=Pin(PIN_MOSI), miso=Pin(PIN_MISO))

    lan = network.LAN(
        spi=spi,
        phy_type=network.PHY_W5500,
        phy_addr=0,
        cs=Pin(PIN_CS),
        int=Pin(PIN_INT),
        reset=Pin(PIN_RESET),
    )
    lan.active(True)

    for _ in range(dhcp_timeout_s):
        if lan.isconnected():
            return lan
        time.sleep(1)

    raise RuntimeError("W5500: no link/DHCP within %ds (ifconfig=%s)" % (dhcp_timeout_s, lan.ifconfig()))
```

If you already copied `net_init.py` to the board in section 0, each example below now runs on its own:

```plaintext
mpremote connect COMx run 01_link.py
```

## 03 Verify functionality with the examples — all 10 verified PASS on hardware

Run each example with `mpremote connect COMx run <filename>`. The Ethernet cable must be plugged in and your router must be able to hand out a DHCP lease.

| # | File | What it verifies | Basis |
| --- | --- | --- | --- |
| 01 | `01_link.py` | Bring up the link, print active/isconnected | quickref[\[1\]](https://4911b20c-90c2-42bd-88bd-b2534477f318.frame.claudeusercontent.com/_f/1787636268-88bd/#ref1) |
| 02 | `02_dhcp.py` | Print ifconfig() (ip/subnet/gateway/dns) after DHCP | quickref[\[1\]](https://4911b20c-90c2-42bd-88bd-b2534477f318.frame.claudeusercontent.com/_f/1787636268-88bd/#ref1) |
| 03 | `03_dns.py` | Resolve a domain via `socket.getaddrinfo` | stdlib socket, per tests/ (section 0) |
| 04 | `04_tcp_client.py` | TCP client — connect, send, recv, close | stdlib socket |
| 05 | `05_tcp_server.py` | TCP echo server (accept loop) | stdlib socket |
| 06 | `06_udp_client.py` | UDP client — sendto/recvfrom with timeout | stdlib socket |
| 07 | `07_udp_server.py` | UDP echo server | stdlib socket |
| 08 | `08_http_get.py` | Minimal HTTP GET | adapted from http_client.py (section 0) |
| 09 | `09_http_server.py` | Minimal HTTP server, port 80 | adapted from http_server_simplistic_commented.py (section 0) |
| 10 | `10_reconnect_stress.py` | 5× active(False)/active(True) cycles + 120s passive link/heap poll | original |

04–07 need a peer running on your PC before you run them — e.g. for `04_tcp_client.py`, start a listener on the same network first with something like `ncat -l 5000 -k -e /bin/cat`.

![](https://maker.wiznet.io/upload/ckeditor5/634078363%5F1787670698%2Epng)

Sample 05 (TCP echo server, accept loop):

```plaintext
import socket
from net_init import init_lan

LISTEN_PORT = 5000

lan = init_lan()
print("listening on", lan.ifconfig()[0], LISTEN_PORT)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(("0.0.0.0", LISTEN_PORT))
s.listen(1)

while True:
    conn, addr = s.accept()
    print("client connected:", addr)
    try:
        while True:
            data = conn.recv(256)
            if not data:
                break
            print("recv:", data)
            conn.send(data)  # echo
    finally:
        conn.close()
        print("client disconnected:", addr)
```

Sample 10 (reconnect stress, original) recovered in 2207–2214 ms across all 5 cycles, stayed connected through the entire 120 s passive poll that followed, and held heap flat between 8,317,728–8,317,824 bytes free — no leak signal.

### Run logs

Real output from re-verifying all 10 examples on real hardware (board IP `192.168.7.23`, PC IP `192.168.7.2`):

```plaintext
mpremote connect COMx run <filename>
```

01_link.py

```plaintext
link up, active = True connected = True
```

02_dhcp.py

```plaintext
ip: 192.168.7.23
subnet: 255.255.255.0
gateway: 192.168.7.1
dns: 8.8.8.8
```

03_dns.py

```plaintext
www.wiznet.io -> 183.111.138.249
```

04_tcp_client.py — with a TCP listener already running on the PC

```plaintext
connected to 192.168.7.2 5000
received: b'hello from PC listener\n'
```

05_tcp_server.py — PC connected as a client

```plaintext
listening on 192.168.7.23 5000
client connected: ('192.168.7.2', 39816)
recv: b'Wiznet\n'
recv: b'esp SoM + w5500 devkit test\n'
recv: b'To quit, press Ctrl + C \x0e\x0e\n'
client disconnected: ('192.168.7.2', 39816)
```

06_udp_client.py

```plaintext
sent to 192.168.7.2 5000
received from ('192.168.7.2', 5000) : b'hello from PC listener\n'
```

07_udp_server.py

```plaintext
UDP echo listening on 192.168.7.23 5000
recv from ('192.168.7.2', 56907) : b'hello from PC UDP client'
```

08_http_get.py

```plaintext
b'HTTP/1.1 200 OK\r\nDate: Tue, 25 Aug 2026 05:15:23 GMT\r\nServer: Apache\r\n...
Content-Type: text/html\r\n\r\n<html>...<h1>http://info.cern.ch - home of the first website</h1>...</html>\n'
```

09_http_server.py — PC ran curl http://192.168.7.23/

```plaintext
device: http://192.168.7.23/
device: client: ('192.168.7.2', 14633)
PC:     HTTP/1.0 200 OK
PC:     Hello #0 from ESP32-S3 + W5500!
```

10_reconnect_stress.py — full log, 5 cycles + 120 s passive poll

```plaintext
initial link OK: ('192.168.7.23', '255.255.255.0', '192.168.7.1', '8.8.8.8')
cycle 0 recovered in 2214 ms, free heap: 8317744
cycle 1 recovered in 2207 ms, free heap: 8317744
cycle 2 recovered in 2207 ms, free heap: 8317744
cycle 3 recovered in 2207 ms, free heap: 8317728
cycle 4 recovered in 2207 ms, free heap: 8317728

passive poll for 120 s
t=0s   connected=True free_heap=8317728
t=5s   connected=True free_heap=8317824
...
t=115s connected=True free_heap=8317824
```

## 04 Known gotcha — OSError: -202 on DNS/external hosts

If 03_dns.py or any external connection fails, link up + DHCP OK

1. Isolate the failure — try a raw `socket.connect()` to the gateway on port 80 (should succeed) vs. `8.8.8.8:53` (times out if this is the cause).

2. If the gateway connects but nothing external does, the router is very likely MAC-filtering. Register the board's MAC (printed via `network.LAN().config('mac')` or visible in DHCP client logs on the router) in the router's allow-list.

3. This is **not** a DNS-server problem — switching the resolver (e.g. 1.1.1.1 → 8.8.8.8) will not fix it if the real cause is routing/MAC filtering.

---

## References

**[1] ESP32 port quickref** — documents the exact `network.LAN(spi=, phy_type=, phy_addr=, cs=, int=, reset=)` constructor signature used in this guide. Note that `network.WIZNET5K` only exists on the STM32/RP2040 ports and targets the chip's hardware TOE mode, so it doesn't apply here.

- Rendered docs: docs.micropython.org/en/v1.28.0/esp32/quickref.html

- Source: github.com/micropython/micropython/blob/v1.28.0/docs/esp32/quickref.rst

#### MicroPython

- Official homepage: micropython.org

- ESP32-S3 firmware download page: micropython.org/download/ESP32_GENERIC_S3

- ESP32 port general info: docs.micropython.org/en/latest/esp32/general.html

- ESP32 port README (repository): github.com/micropython/micropython/blob/master/ports/esp32/README.md

#### Espressif (ESP32 vendor)

- Official ESP32-S3 product page: espressif.com/en/products/socs/esp32-s3 (no MicroPython mention — only ESP-IDF/Arduino/Zephyr)

- Developer Portal MicroPython workshop — Espressif's own domain pointing to MicroPython's official docs: developer.espressif.com/workshops/micropython-jupyter-notebooks-in-browser

#### Community (corroborating, not load-bearing)

- Community GitHub projects referencing W5500 + `network.LAN` on ESP32 exist, but the constructor signature and behavior in this guide were verified directly against the official sources above and real hardware.

---

Source: https://maker.wiznet.io/josephsr/projects/esp32-s3-wiznet-w5500--micropython-ethernet-macraw-guide/
