A bare-metal Rust workspace for ESP32 microcontroller projects using esp-hal with optional Embassy async support.
This project provides templates and examples for running Rust on an ESP32-WROOM-32 module without an operating system (no_std). It supports both blocking (synchronous) and async (Embassy) programming models.
- Target: ESP32 (Xtensa LX6 dual-core)
- Module: ESP32-WROOM-32
- Bootloader: esp-idf compatible
- Rust installed via rustup
- USB cable for connecting to your ESP32 board
cargo install espup --locked
espup installFor the current shell session:
. $HOME/export-esp.shTo make it permanent, add to your shell config:
# For bash
echo '. $HOME/export-esp.sh' >> ~/.bashrc
# For zsh
echo '. $HOME/export-esp.sh' >> ~/.zshrc# Flashing tool
cargo install espflash --locked
# Project generator (optional, for creating new projects)
cargo install esp-generate --locked
# Debugging tools (optional)
cargo install probe-rs-toolscargo build --releasecargo run --release --bin testThis will compile, flash to the connected ESP32, and open a serial monitor.
espflash flash target/xtensa-esp32-none-elf/release/<binary-name>espflash monitoresp32-rust-workspace/
├── Cargo.toml # Project dependencies and binary definitions
├── build.rs # Build script for linker configuration
├── rust-toolchain.toml # Specifies the esp Rust toolchain
├── docs/ # Documentation
│ ├── std-vs-no_std.md
│ ├── converting-to-embassy.md
│ ├── rust-macros.md
│ ├── esp32-pinout-reference.md
│ ├── esp32-cheatsheet.md
│ ├── rust-cheatsheet.md
│ └── rust-project-organization.md
└── src/
├── lib.rs # Shared library code
├── bin/ # Binary targets (defined in Cargo.toml)
├── examples/ # Working example projects
│ ├── tp223_touch_sensor.rs # TP223 touch sensor with Embassy
│ └── lm75_temperature.rs # LM75 I2C temperature sensor
└── scraps/ # Templates and work-in-progress
├── template_embassy.rs # Template using esp_hal_embassy (async)
├── template_blocking.rs # Template using blocking esp_hal
├── test.rs # LED blink test
└── touch.rs # Touch input test
Use this template for projects that benefit from async/await:
- Non-blocking delays with
Timer::after_millis().await - Task spawning with
#[embassy_executor::task] - Power-efficient (CPU sleeps during delays)
- Best for: multi-tasking, power-sensitive applications
Use this template for simple, straightforward projects:
- Blocking delays with busy-wait loops
- No async runtime overhead
- Simpler mental model
- Best for: simple applications, learning esp-hal basics
Demonstrates using a TP223 capacitive touch sensor (TK43 module) with Embassy async:
- GPIO input with edge detection
- Async polling with non-blocking delays
- Touch/release event detection
Demonstrates I2C communication with an LM75 temperature sensor:
- I2C master setup on GPIO21 (SDA) and GPIO22 (SCL)
- Device detection and error handling
- Temperature reading with 0.5°C resolution
- Output in Celsius and Fahrenheit
cargo run --release --bin lm75_temperatureWiring:
| LM75 Pin | ESP32 Pin |
|---|---|
| VCC | 3.3V |
| GND | GND |
| SDA | GPIO21 |
| SCL | GPIO22 |
| Crate | Version | Purpose |
|---|---|---|
esp-hal |
0.23 | Hardware abstraction layer for ESP32 |
esp-hal-embassy |
0.6 | Embassy async runtime integration |
esp-backtrace |
0.15 | Panic handler with backtrace support |
esp-println |
0.13 | Serial output (println! macro) |
embassy-executor |
0.7 | Async task executor |
embassy-time |
0.4 | Async timers and delays |
embedded-hal |
1.0 | Hardware abstraction traits for drivers |
critical-section |
1.2 | Interrupt-safe critical sections |
This is a bare-metal (no_std) project. You cannot use standard library crates designed for desktop applications. See docs/std-vs-no_std.md for details.
env_logger,logwith std backends - useesp-printlninsteadstd::time- useesp_hal::timeorembassy_timeinsteadstd::io- useembedded-iotraits instead- Most crates that don't explicitly support
no_std
Use esp-println for debugging:
use esp_println::println;
println!("Hello from ESP32!");
println!("Counter: {}", counter);The following pins have special functions on the ESP32-WROOM-32:
Bootstrap pins (usable, but check datasheet):
- GPIO0, GPIO2, GPIO5, GPIO12, GPIO15
Reserved pins (used by the module's flash - do not use):
- GPIO6-11, GPIO16, GPIO20
See docs/esp32-pinout-reference.md for detailed pinout information.
- Create your
.rsfile insrc/bin/or reference a file elsewhere - Add a
[[bin]]entry toCargo.toml:
[[bin]]
name = "my_project"
path = "./src/bin/my_project.rs"- Build and run:
cargo run --release --bin my_projectThe ESP toolchain environment isn't loaded. Run:
. $HOME/export-esp.shYou've added a dependency that requires the standard library. Remove the incompatible dependency and find a no_std alternative.
Your code compiles but produces no visible output. Add esp-println and use println!() for debug output.
Make sure to initialize Embassy with a timer:
let timg0 = TimerGroup::new(peripherals.TIMG0);
esp_hal_embassy::init(timg0.timer0);MIT