← Back to Articles

Getting Started with Rust on ESP32: Your First Blinking LED

This guide will demonstrate how to set up Rust for ESP32 development and write a simple blinking LED program.

rust esp32 embedded-systems tutorial
Getting Started with Rust on ESP32: Your First Blinking LED

This guide will demonstrate how to set up Rust for ESP32 development and write a simple blinking LED program.

Setting Up Your Development Environment

Step 1: Install Rust

# Install Rust toolchain
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# Reload your shell
source ~/.bashrc
# Verify installation
rustc --version
cargo --version

Step 2: Install ESP Rust Toolchain

The ESP chips use Xtensa or RISC-V architectures, which require special toolchain support:

# Install espup (ESP Rust installer)
cargo install espup

# Install ESP toolchains
espup install
# This creates ~/.espressif and installs the Xtensa Rust compiler

Add the ESP environment to your shell:

# Add to ~/.bashrc
echo 'source $HOME/export-esp.sh' >> ~/.bashrc
source ~/.bashrc

Step 3: Install Flashing Tools

# Install espflash for uploading firmware
cargo install espflash

# Install cargo-espflash wrapper
cargo install cargo-espflash

Creating Your First Project

Generate Project with esp-generate

# Install project generator
cargo install esp-generate

# Create new project
cd ~/Documents/rust-embedded
esp-generate --chip esp32 esp32-blink

Project Structure


esp32-blink/
├── Cargo.toml          # Dependencies and project config
├── src/
│   └── main.rs         # Your code goes here
├── .cargo/
│   └── config.toml     # Build configuration
└── rust-toolchain.toml # Specifies Rust toolchain

Writing the Blink Code

Open src/main.rs in your favorite editor:

#![no_std]
#![no_main]
#![deny(
    clippy::mem_forget,
    reason = "mem::forget is generally not safe to do with esp_hal types, especially those \
    holding buffers for the duration of a data transfer."
)]

use esp_hal::{
    clock::CpuClock,
    gpio::{Level, Output, OutputConfig},
    main,
    time::{Duration, Instant},
};
#[panic_handler]
fn panic(_: &core::panic::PanicInfo) -> ! {
    loop {}
}
extern crate alloc;
// This creates a default app-descriptor required by the esp-idf bootloader.
// For more information see: 
esp_bootloader_esp_idf::esp_app_desc!();
#[main]
fn main() -> ! {
    // generator version: 1.0.1
    let config = esp_hal::Config::default().with_cpu_clock(CpuClock::max());
    let peripherals = esp_hal::init(config);
    esp_alloc::heap_allocator!(#[esp_hal::ram(reclaimed)] size: 98768);
    let mut led2 = Output::new(peripherals.GPIO2, Level::Low, OutputConfig::default());
    loop {
        led2.toggle();
        let delay_start = Instant::now();
        while delay_start.elapsed() < Duration::from_millis(500) {}
    }
    // for inspiration have a look at the examples at https://github.com/esp-rs/esp-hal/tree/esp-hal-v1.0.0/examples/src/bin
}

Building and Flashing

Build the Project

# Build in release mode (optimized)
cargo build --release

Flash to ESP32

# Build and flash in one command
cargo run --release

You should see:

[INFO] Serial port: '/dev/ttyUSB0'
[INFO] Connecting...
[INFO] Using flash stub
Chip type:         esp32 (revision v3.1)
...
[INFO] Flashing has completed!

Troubleshooting Common Issues

“Path ‘/dev/ttyUSB0’ is not readable”

Solution: Permission issue

sudo usermod -a -G dialout $USER
# Log out and back in

Useful Resources