7.3 KiB
Working setup, derived from an actual first-time bring-up. Every failure mode below was hit and fixed, in order. Follow this straight through and you skip all of it. (AI generated)
Board reference
Waveshare ESP32-S3-ETH — ESP32-S3 + onboard W5500 ethernet chip over SPI (not RMII — there's no on-chip MAC/PHY involved, W5500 has its own MAC and does everything over SPI).
Confirmed pin mapping (verify against your board's silkscreen — Waveshare pin assignments have varied slightly across production batches):
| Signal | GPIO |
|---|---|
| SCLK | 13 |
| MOSI (SDO) | 11 |
| MISO (SDI) | 12 |
| CS | 14 |
| INT | 10 |
| RST | 9 |
| SPI host | SPI2 |
| Clock | 20–25 MHz |
W5500 has no burned-in MAC address — you must supply one (a locally-administered address like 02:00:00:12:34:56 works fine).
Why std (esp-idf-svc), not no_std (esp-hal)
esp-generate scaffolds esp-hal (no_std) projects. That toolchain has no W5500/ethernet driver and no full HTTP server story — its focus is Wi-Fi/BLE via esp-radio and embassy. For ethernet + a standard HTTP server, use the std toolchain via esp-idf-svc, which wraps ESP-IDF's C ethernet driver and httpd component.
Rule of thumb: if the board has W5500/LAN8720/other wired-ethernet hardware, use esp-idf-template (std). Reach for esp-generate (no_std) only for pure Wi-Fi/BLE projects.
1. Toolchain
espup install
. $HOME/export-esp.sh # re-run in every new shell, or add to your shell rc
Confirm the Xtensa target is present:
rustc --print target-list | grep xtensa
2. Scaffold the project
Do not use esp-generate. Use:
cargo install cargo-generate
cargo generate esp-rs/esp-idf-template cargo
Prompt answers that matter:
| Prompt | Answer | Why |
|---|---|---|
| MCU | esp32s3 |
matches the board |
| Configure advanced options | Yes | need to reach STD toggle |
| STD support | Yes | pulls in esp-idf-svc; this is the whole point |
| ESP-IDF version | v5.3.4 |
most mature crates.io compatibility; avoid master/newest unless you have a reason |
| Use git esp-idf-* crates | false | crates.io releases are stable and documented; git HEAD drifts mid-project |
| Install location | global | ESP-IDF toolchain reused across projects, saves re-downloading (~1–2 GB) |
| Dev Containers | false | flashing real hardware over USB is simpler done natively |
| Wokwi simulation | false | Wokwi doesn't model W5500/ethernet peripherals |
| CI files | false | add later if/when you push to GitHub |
3. Known environment issues and fixes
These hit in this order on a fresh Arch-based system (Omarchy); adjust distro-specific commands as needed, but expect similar issues on any non-Ubuntu-LTS host.
a) esp-clang: error while loading shared libraries: libxml2.so.2
Rolling-release distros ship a newer libxml2 soname (.so.16+) than the pinned esp-clang toolchain expects (.so.2). Symlink it:
ldconfig -p | grep libxml2 # find your installed version, e.g. libxml2.so.16
sudo ln -s /usr/lib/libxml2.so.16 /usr/lib/libxml2.so.2 # adjust path/version to match
sudo ldconfig
Verify against the actual binary, not just ldconfig -p (which may not reflect symlinks immediately):
/path/to/.embuild/espressif/tools/esp-clang/*/esp-clang/bin/clang --version
If a failed build already deleted the tool directory during its own error-recovery, just rebuild — cargo build reinstalls it.
b) Missing esp-mqtt submodule / CMake git_submodule_check errors
The managed ESP-IDF checkout under .embuild/espressif/esp-idf/<version>/ needs its git submodules initialized — several ESP-IDF components (mqtt, lwip, mbedtls, wifi/bt libs, etc.) are submodules, and IDF's build requires them present even if your project doesn't use those components.
cd .embuild/espressif/esp-idf/<version>
git submodule update --init --recursive
cd -
cargo build --release
This is a one-time ~500MB–1GB download. If it's still broken afterward (partial/shallow clone issues), delete and let embuild re-clone fresh:
rm -rf .embuild/espressif/esp-idf/<version>
cargo build --release
c) Building for host instead of the chip (Unsupported target 'x86_64-unknown-linux-gnu')
.cargo/config.toml must set:
[build]
target = "xtensa-esp32s3-espidf"
esp-idf-template generates this correctly — if you hit this, something overwrote or is missing that file, or espup's toolchain isn't active in the current shell (. $HOME/export-esp.sh).
d) SPI DMA: check_trans_valid: txdata transfer > host maximum / w5500 read-write failures
The default SpiDriverConfig has DMA disabled, which caps transfer size below what the W5500 ethernet driver needs to move full frames. Symptom: link never comes up, wait_netif_up() hangs, log fills with w5500_spi_write failed / w5500_spi_read failed. Fix — enable DMA explicitly when constructing the SpiDriver:
use esp_idf_svc::hal::spi::{config::DriverConfig, Dma, SpiDriver};
let spi_driver = SpiDriver::new(
peripherals.spi2,
pins.gpio13, // SCLK
pins.gpio11, // MOSI
Some(pins.gpio12), // MISO
&DriverConfig {
dma: Dma::Auto(4096),
..Default::default()
},
)?;
e) esp-idf-svc API drift across versions
The EthDriver::new_spi signature has changed across esp-idf-svc releases — older versions took raw MOSI/MISO/SCLK pins directly; 0.51+ takes a pre-built SpiDriver plus INT/CS/RST only (9 args total: spi, int, cs, rst, chipset, baudrate, mac_addr, phy_addr, sysloop). If code from an older tutorial/example doesn't compile, check the exact signature for your pinned version:
cargo doc --open -p esp-idf-svc
# or browse https://docs.rs/esp-idf-svc/<your-version>/esp_idf_svc/eth/
Trust the compiler's error over memory or old examples — it names the exact expected type/arg count.
4. sdkconfig.defaults
CONFIG_ETH_SPI_ETHERNET_W5500=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
Without the first line, the W5500 driver component isn't built into ESP-IDF at all — you'll get a runtime or link failure referencing missing eth symbols.
5. Cargo.toml essentials
[dependencies]
esp-idf-svc = { version = "0.51", features = ["std"] }
anyhow = "1"
log = "0.4"
6. Build / flash / monitor
cargo build --release
espflash flash --monitor target/xtensa-esp32s3-espidf/release/<binary-name>
Watch the serial log for Got IP: ... — that confirms both SPI-to-W5500 link and DHCP succeeded. No Got IP line + streaming w5500.mac errors = revisit DMA config or pin mapping (item d/a above).
Summary: what each fix actually solved
| Problem | Root cause | Fix |
|---|---|---|
| Config menu had no ethernet option | Wrong scaffolding tool (esp-generate = no_std/esp-hal) |
Use esp-idf-template (std) instead |
| Build targeted host, not chip | Missing/wrong .cargo/config.toml target |
Ensure target = "xtensa-esp32s3-espidf" |
esp-clang failed to load |
Host libxml2 soname too new | Symlink .so.16 → .so.2 |
| CMake submodule errors | Managed ESP-IDF git checkout incomplete | git submodule update --init --recursive |
EthDriver::new_spi type errors |
API changed across esp-idf-svc versions | Match compiler-reported signature exactly, don't trust memorized old examples |
| W5500 never links / SPI transfer errors | DMA disabled by default on SPI bus | Dma::Auto(4096) in DriverConfig |