Files
escape_gallery_obsidian_vault/Projects/ESP32-S3-ETH/ESP32-S3-ETH Rust Project - Empty Directory to Ready-to-Code.md

295 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
End-to-end setup for a Waveshare ESP32-S3-ETH board (W5500 ethernet over SPI), using the `std` Rust toolchain (`esp-idf-svc`). By the end you'll have a project that connects to ethernet, gets a DHCP lease, and is structured to build real functionality on top of.
This walkthough was AI generated
---
## 0. Hardware reference
Confirm these against your board's silkscreen before wiring anything extra — Waveshare's 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|
|SPI clock|2025 MHz|
The W5500 has no burned-in MAC address — you supply one in software (any locally-administered address, e.g. `02:00:00:12:34:56`, works).
---
## 1. Prerequisites
- USB-C cable, board connected to your dev machine.
- ~5GB free disk space (toolchain + ESP-IDF + submodules).
- Linux/macOS/WSL. (Windows native works too but paths below assume a POSIX shell.)
- `git`, a C compiler, `python3`, and standard build tools already on the system (`build-essential` on Debian/Ubuntu, or equivalent).
---
## 2. Install the Rust + Espressif toolchain
```bash
# Rust itself, if not already installed
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
# espup: installs the Xtensa-patched Rust toolchain ESP32-S3 needs
cargo install espup
cargo install ldproxy
espup install
# Load the Xtensa toolchain into your shell (needed every new shell)
. $HOME/export-esp.sh
```
Add the `. $HOME/export-esp.sh` line to your shell rc file (`.bashrc`/`.zshrc`/`config.fish`) so you don't have to re-run it manually every session.
Verify the Xtensa target is present:
```bash
rustc --print target-list | grep xtensa
```
You should see `xtensa-esp32s3-none-elf` and similar in the output.
---
## 3. Scaffold the project
Use `esp-idf-template`, **not** `esp-generate` — the latter scaffolds `no_std`/`esp-hal` projects, which have no W5500/ethernet driver and no standard HTTP server story.
```bash
cargo install cargo-generate
cargo generate esp-rs/esp-idf-template cargo
```
You'll be prompted interactively. Answers for this board/use case:
|Prompt|Answer|Why|
|---|---|---|
|Project name|your choice||
|MCU|`esp32s3`|matches this board|
|Configure advanced options?|Yes|need to reach the STD toggle below|
|STD support|**Yes**|pulls in `esp-idf-svc`; without this you're back on `no_std`|
|ESP-IDF version|`v5.3.4`|most stable match to current esp-idf-svc/hal crates; avoid `master`|
|Use git (not crates.io) esp-idf-* crates|**false**|crates.io releases are stable and documented; git HEAD drifts underneath you|
|Installation location of managed ESP-IDF|**global**|shared across projects, avoids re-downloading ~12GB per project|
|Configure Dev Containers?|false|flashing real hardware over USB is simpler done natively|
|Configure Wokwi simulation?|false|Wokwi doesn't model the W5500/ethernet peripheral|
|Add CI files?|false|add later if/when you push to GitHub|
This produces:
```
your-project/
├── .cargo/config.toml
├── Cargo.toml
├── build.rs
├── rust-toolchain.toml
├── sdkconfig.defaults
└── src/main.rs
```
---
## 4. Confirm the generated build target
Open `.cargo/config.toml` and confirm:
```toml
[build]
target = "xtensa-esp32s3-espidf"
```
If this is missing or wrong, `cargo build` will silently try to compile for your host machine instead of the chip and fail with `Unsupported target 'x86_64-unknown-linux-gnu'` (or similar). The template sets this correctly by default — just verify it.
---
## 5. Configure sdkconfig.defaults
Edit `sdkconfig.defaults` (created empty or near-empty by the template) to include:
```
CONFIG_ETH_SPI_ETHERNET_W5500=y
CONFIG_ESP_MAIN_TASK_STACK_SIZE=8000
CONFIG_HTTPD_MAX_REQ_HDR_LEN=1024
```
The first line is required — without it, ESP-IDF doesn't build the W5500 driver component in at all, and your ethernet code will fail to link or fail at runtime. The other two are reasonable general-purpose values (headroom for the main task stack, generous HTTP header limit if you add a server later).
---
## 6. Configure Cargo.toml
Replace the `[dependencies]` section with at minimum:
```toml
[dependencies]
esp-idf-svc = { version = "0.51", features = ["std"] }
anyhow = "1"
log = "0.4"
```
Add more crates as your project needs them — see the companion note on porting non-embedded dependencies if you're bringing code over from another platform.
---
## 7. First build (this installs ESP-IDF itself)
```bash
cargo build --release
```
The **first** build does a lot of one-time work: downloads ESP-IDF, sets up a Python virtualenv, installs `xtensa-esp-elf`, `esp-clang`, `cmake`, `ninja`, and more. Expect this to take 1020+ minutes and several GB of downloads. Subsequent builds are fast.
### Known first-build failure points
These are common enough to check for proactively rather than debug blind:
**a) `esp-clang: error while loading shared libraries: libxml2.so.2`** Rolling-release Linux distros (Arch-based, etc.) ship a newer libxml2 soname than the pinned esp-clang binary expects. Fix:
```bash
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
ls -la /usr/lib/libxml2.so.2
```
The final command should show a symlink of libxml2.so.2 to libxml2.so.16
**b) `Missing esp-mqtt submodule` / CMake `git_submodule_check` errors** The managed ESP-IDF checkout needs its git submodules initialized (several components are submodules even if your project doesn't use them):
```bash
cd .embuild/espressif/esp-idf/v5.3.4
git submodule update --init --recursive
cd -
cargo build --release
```
If that's still broken, delete and let it re-clone fresh: `rm -rf .embuild/espressif/esp-idf/v5.3.4`.
A successful build ends with a normal `Compiling <your-project>` / `Finished release` output.
---
## 8. Write a minimal ethernet-only main.rs
This is the "ready for proper code" checkpoint — confirms SPI, the W5500, and DHCP all work before you build real functionality on top:
```rust
use std::thread;
use std::time::Duration;
use esp_idf_svc::eth::{BlockingEth, EspEth, EthDriver, SpiEthChipset};
use esp_idf_svc::eventloop::EspSystemEventLoop;
use esp_idf_svc::hal::peripherals::Peripherals;
use esp_idf_svc::hal::prelude::*;
use esp_idf_svc::hal::spi::{config::DriverConfig, Dma, SpiDriver};
use log::info;
fn main() -> anyhow::Result<()> {
esp_idf_svc::sys::link_patches();
esp_idf_svc::log::EspLogger::initialize_default();
let peripherals = Peripherals::take()?;
let sysloop = EspSystemEventLoop::take()?;
let pins = peripherals.pins;
let spi_driver = SpiDriver::new(
peripherals.spi2,
pins.gpio13, // SCLK
pins.gpio11, // MOSI
Some(pins.gpio12), // MISO
&DriverConfig {
dma: Dma::Auto(4096), // required — default (no DMA) can't move full ethernet frames
..Default::default()
},
)?;
let eth_driver = EthDriver::new_spi(
spi_driver,
pins.gpio10, // INT
Some(pins.gpio14), // CS
Some(pins.gpio9), // RST
SpiEthChipset::W5500,
20.MHz().into(),
Some(&[0x02, 0x00, 0x00, 0x12, 0x34, 0x56]), // locally-administered MAC
None, // default PHY address
sysloop.clone(),
)?;
let eth = EspEth::wrap(eth_driver)?;
let mut eth = BlockingEth::wrap(eth, sysloop)?;
info!("Starting eth...");
eth.start()?;
info!("Waiting for DHCP lease...");
eth.wait_netif_up()?;
let ip_info = eth.eth().netif().get_ip_info()?;
info!("Got IP: {:?}", ip_info);
loop {
thread::sleep(Duration::from_secs(1));
}
}
```
---
## 9. Flash and verify
```bash
cargo install espflash # one-time, if not already installed
cargo build --release
espflash flash --monitor target/xtensa-esp32s3-espidf/release/<your-project-name>
```
If Linux user needs permission for port access:
```shell
sudo usermod -aG uucp $USER # Arch Systems
sudo usermod -aG dialout $USER # Debian Systems
```
Then reboot.
Watch the serial monitor. Success looks like:
```
I (...) esp_idf_svc::eth: Driver initialized
I (...) hello_eth: Starting eth...
I (...) hello_eth: Waiting for DHCP lease...
I (...) hello_eth: Got IP: ...
```
If it hangs after "Waiting for DHCP lease..." with repeating `w5500.mac: w5500_spi_write failed` / `w5500_spi_read failed` errors in the log, the SPI bus doesn't have DMA enabled — double check the `Dma::Auto(4096)` line in step 8 is present.
If it hangs with no errors at all and no IP, check physically: ethernet cable plugged into a live switch/router port, link LED on the RJ45 jack lit.
---
## 10. You're ready to build on this
At this point you have a project that: builds for the correct target, links the W5500 driver, brings up ethernet, and gets a DHCP lease. From here, typical next additions:
- An HTTP server (`esp_idf_svc::http::server::EspHttpServer`)
- GPIO for LEDs/buttons/sensors (`esp_idf_svc::hal::gpio::PinDriver`)
- SPI/I2C peripherals sharing the bus alongside the W5500 (own CS pin, same or different SPI host)
- Async networking (`tokio`, `async-nats`, etc.) — needs additional rustflags and feature trimming; see the note on porting non-embedded async code if relevant to your project
---
## Quick-reference: build error → fix
|Error|Cause|Fix|
|---|---|---|
|`Unsupported target 'x86_64-unknown-linux-gnu'`|`.cargo/config.toml` target missing/wrong, or Xtensa toolchain not loaded in shell|Check `target = "xtensa-esp32s3-espidf"`; re-run `. $HOME/export-esp.sh`|
|`esp-clang: ... libxml2.so.2`|Host libxml2 soname mismatch|Symlink to your installed version (§7a)|
|`Missing esp-mqtt submodule` / CMake errors|Managed ESP-IDF checkout missing submodules|`git submodule update --init --recursive` in the IDF checkout (§7b)|
|`txdata transfer > host maximum` / w5500 read/write failures|SPI DMA disabled|`Dma::Auto(4096)` in `DriverConfig`|
|`EthDriver::new_spi` argument/type errors|API differs across esp-idf-svc versions|Match the compiler's reported signature exactly, not older examples/tutorials|
|No W5500 driver at runtime|`CONFIG_ETH_SPI_ETHERNET_W5500=y` missing from sdkconfig.defaults|Add it, rebuild|