ESP32C5 First Rust Impressions

Page content

Why ESP32C5?

I’m researching hardware options for an upcoming project, and the ESP32 family appears to be the best fit for our current requirements.

For years, a major drawback of Espressif chips was limited support to 2.4GHz WiFi only. Why does this matter? Having a 2.4GHz device on a WiFi network degrades performance for all other devices, forcing users to set up separate WiFi routers just for 2.4GHz devices. Additionally, the initial configuration proved frustrating—when your phone is on 5GHz, the ESP Touch protocol doesn’t work reliably.

The ESP32C5 changes this equation: it supports both 2.4GHz and 5GHz WiFi.

ESP32C5 Boards

I ordered two ESP32C5 boards from Olimex for evaluation:

Olimex boards are wonderful as always, with the added bonus that they’re fully open source. Getting a simple “Hello World” application working using ESP-IDF in C was straightforward, and both boards work well.

However, for the upcoming project, I want to use Rust. Rust’s ideology aligns well with embedded systems—for devices running continuously, you want everything preallocated with no dynamic memory allocations. When you use dynamic memory allocations, fragmentation eventually occurs, leading to out-of-memory failures. It’s that simple.

State of Rust on ESP32

A Challenging Situation

Nearly 10 years ago, I started a project based on the ESP8266. I ended up as Espressif’s beta tester—the SDK worked when things aligned, but it was constantly changing with numerous bugs. It took nearly three years to get working SSL support with proper certificate handling.

Today’s situation with Rust support feels like deja-vu.

The Chip Architecture Problem

ESP32 chips come in two families: Xtensa-based and RISC-V-based architectures.

  • Xtensa: Not available in upstream Rust, requiring the espup tool for toolchain installation. Setup is tedious, with constant environment tweaking and IDE configuration challenges.
  • RISC-V: More straightforward setup with upstream compiler support, though better integrated overall.

Additionally, RISC-V requires Rust nightly, while Xtensa requires the espup-installed toolchain.

Available Rust SDK Options

Several options exist for Rust development on ESP32:

  • esp-hal - The no_std route (officially supported by Espressif): This is the officially supported path. However, support lags behind C development, and there is no support for ESP32C5 yet, even in the master branch.

  • esp-idf-hal - HAL SDK wrapper: A community-driven effort with minimal official Espressif support. It’s a HAL wrapper implementing the embedded-hal traits. While it’s under development, proper ESP32C5 support is not yet present. I’ve managed to compile basic code, but the process was bumpy. More details below.

Both can use FreeRTOS or Embassy as concurrency frameworks.

The Documentation Challenge

Many examples exist across the web, but due to poor initial API design and constantly changing APIs within the SDKs (semantic versioning is not followed), it’s a jungle to navigate and get something working—especially with esp-idf-hal.

For reference, the best option is to study the SDK examples in their source code. While they lack discussion and context, they’re current with the existing APIs. However, some examples don’t even build or work, leaving you largely on your own.

Setting Up Rust Development with esp-idf-hal

Building with esp-idf-hal requires cloning and patching several repositories. This process is non-trivial but necessary for ESP32C5 support.

Prerequisites

You’ll need the following repositories:

Clone these to a local directory for patching.

Step 1: Generate Project

Use esp-generate to create your project:

cargo install cargo-generate
cargo generate --git https://github.com/esp-rs/esp-idf-template.git --name my_project

Step 2: Patch the Main Project

Override your project’s Cargo.toml to use local checkouts of the development branches. In your project’s [patch.crates-io] section, add:

[patch.crates-io]
esp-idf-hal = { path = "../esp-idf-hal" }
esp-idf-sys = { path = "../esp-idf-sys" }
esp-idf-svc = { path = "../esp-idf-svc" }
embuild = { path = "../embuild" }

Update your [dependencies] section:

[dependencies]
esp-idf-svc = { path = "../esp-idf-svc" }
esp-idf-sys = { path = "../esp-idf-sys" }
# ... other dependencies

Step 3: Patch esp-idf-hal

In the esp-idf-hal’s Cargo.toml, add:

[patch.crates-io]
esp-idf-sys = { path = "../esp-idf-sys" }

Step 4: Patch esp-idf-svc

In the esp-idf-svc’s Cargo.toml, add:

[patch.crates-io]
esp-idf-sys = { path = "../esp-idf-sys" }
esp-idf-hal = { path = "../esp-idf-hal" }
esp-idf-svc = { path = "../esp-idf-svc" }

Step 5: Build Your Project

Build with nightly Rust:

cargo +nightly build

Or set nightly as the default for your project (helpful for IDE integration):

rustup override set nightly

Conclusion: A Difficult Choice

For new projects requiring WiFi in 2025, choosing a chip without 5GHz support is unwise. This practically limits you to the ESP32C5. However, the lack of proper Rust support creates a significant problem. Even if some devices have functional Rust support, maintaining separate codebases across different targets is tedious and error-prone.

Until Rust becomes a first-class citizen in the Espressif ecosystem, I’m sticking with C.

The experience has been frustrating—not because Rust itself is problematic, but because the tooling, documentation, and hardware support haven’t matured enough for production embedded work. The ESP32C5’s hardware capabilities are excellent, but the development experience falls short of modern standards.