ESP32 (esp-hal, bare-metal Rust)
Single-node starter on ESP32-C3 using the bare-metal esp-hal Rust
path — no ESP-IDF — running under the Espressif QEMU fork (OpenETH
ethernet). For the ESP-IDF component path (C / C++ apps), see
ESP32 (ESP-IDF component).
Prereqs.
nros setup esp32is the single command that prepares your machine. It fetches a prebuilt esp-hal toolchain and the chosen RMW host daemon from a pinned index into the shared store at~/.nros/sdk— you do not hand-install cross-compilers, and you do not need ROS 2 installed.
Setup
Build the in-tree nros CLI (Phase 218):
source ./activate.sh # OR: direnv allow / source ./activate.fish
just setup-cli # builds packages/cli/target/release/nros
Provision the board (and RMW):
nros setup esp32 --rmw zenoh # --rmw defaults to zenoh; xrce | cyclonedds also valid
This pulls the SDK sources nano-ros owns (zenoh-pico + mbedtls
submodules for zenoh; analogous for xrce / cyclonedds) and lands the
RMW host daemon (zenohd for zenoh, the Micro-XRCE-DDS agent for
xrce) under ${NROS_HOME:-~/.nros}/sdk (the activate file puts the
in-tree CLI on PATH; legacy ${NROS_HOME:-~/.nros}/bin/ install
remains supported transitionally). esp-hal itself is a Cargo dependency the
example pulls in at build time, not a separately-installed toolchain;
the only cross-toolchain you may need to add by hand is the rustup
target — once per host:
rustup target add riscv32imc-unknown-none-elf # ESP32-C3
Project layout
Each example is a standalone Cargo package targeting
riscv32imc-unknown-none-elf (ESP32-C3). The board crate
(nros-board-esp32-qemu) wraps the OpenETH / esp-hal init.
ESP32-S3 (Xtensa) is NOT supported today. The tutorial targets the RISC-V ESP32-C3 only. Xtensa targets do not ship via
rustup(they require theespuptoolchain installer) and the in-tree board crate is RISC-V only; this gap is tracked separately.
examples/qemu-esp32-baremetal/rust/talker/
├── Cargo.toml
├── .cargo/config.toml # target = riscv32imc-unknown-none-elf
├── nros.toml # ethernet transport + zenoh locator
├── package.xml
├── generated/ # codegen output — build.rs runs
│ # `nros generate-rust` on first
│ # `cargo build`; gitignored.
└── src/main.rs # esp-hal init → nros_app_main
Configure
nros.toml carries the transport stack. The QEMU ESP32 board uses an
ethernet transport via nros-board-esp32-qemu. Verbatim from
examples/qemu-esp32-baremetal/rust/talker/nros.toml:
# nano-ros config (direct mode). See
# docs/design/0004-configuration-and-transports.md.
[node]
domain_id = 0
[[transport]]
kind = "ethernet"
ip = "10.0.2.50/24"
mac = "02:00:00:00:00:01"
gateway = "10.0.2.2"
locator = "tcp/10.0.2.2:7454"
Build
# QEMU ESP32 (qemu-system-riscv32). `just esp32 build-qemu` (which
# `just esp32 talker` depends on) builds the QEMU-board variant; the
# example's build.rs invokes `nros generate-rust` automatically, so
# the `generated/` dir populates on first build (gitignored).
just esp32 build-qemu
Run
# QEMU ESP32. First bring up zenohd on the esp32 fixture port (7454):
just esp32 zenohd &
# Then boot the talker binary in qemu-system-riscv32 (esp32c3):
just esp32 talker
# Expected serial output (per src/main.rs):
# Declaring publisher on /chatter (std_msgs/Int32)
# Publisher declared
# Published: 0
# Published: 1
# ...
# Verify from stock ROS 2 on the same network:
source /opt/ros/humble/setup.bash
export RMW_IMPLEMENTATION=rmw_zenoh_cpp
# Talker publishes best-effort; stock `ros2 topic echo` defaults to
# RELIABLE, so the QoS-mismatched echo silently delivers nothing.
# Force best-effort to receive:
ros2 topic echo /chatter std_msgs/msg/Int32 --qos-reliability best_effort
Readiness signal. QEMU ESP32: ~15 seconds after a warm cache —
the just esp32 talker recipe re-runs build-qemu every invocation,
so a first / cold run adds ~25 s of build time on top. If no
Published: line:
- Wrong locator → talker logs
zenoh open failedand retries. Confirmzenohdis reachable on the host IP (10.0.2.2:7454). - Confirm
.cargo/config.tomltarget isriscv32imc-unknown-none-elf(ESP32-C3). The tutorial does not support ESP32-S3 (Xtensa) yet. - See Troubleshooting — First 10 Minutes.
GitHub source
- QEMU ESP32 talker:
examples/qemu-esp32-baremetal/rust/talker/ - Board crate:
packages/boards/nros-board-esp32-qemu/
Next
- Subscriber + service + action peer directories under the same
examples/qemu-esp32-baremetal/rust/. - ESP-IDF component path for C / C++ apps: ESP32 (ESP-IDF component).
- ESP32-S3 (Xtensa) — not supported today. The Xtensa toolchain
does not ship via
rustup(it requiresespup), and there is no in-tree Xtensa board crate. Stick with ESP32-C3 (RISC-V) for now.