Environment Setup for ESP Development in Rust

Wu Yu Wei published on
5 min, 861 words

ESP series are the targets I always want to explore in Rust. Because they have many convenient built-in features like integrated Wi-Fi and dual mode Bluetooth. Unfortunately, both rustc and llvm don’t provide the build target for xtensa by default which means usually we must build the xtensa version for both. Furthermore, it’s not just about the compiler. There’s the whole toolchain from original ecosystem needs to be setup too. In our case, this means installing xtensa-esp32-elf toolchain, esptool, and maybe esp-idf properly. This post is going to provide a guide to setup the development environment with esp32 as an example.

Docker image for xtensa

In order to make cross compilation easier, the rust embedded community proposes a crate called cross which can build the docker image to setup required components for your targets and then build/flash your project from it. While original cross doesn’t have the Dockerfile for xtensa targets, there’s a fork for it thanks to @reitermarkus. Before building the docker image, I highly recommend adding your user to docker group to prevent any environment variable issue. Also make sure you already have Docker and jq installed. If you already have them, just follow instructions below to install the custom cross and required image:

git clone -b xtensa https://github.com/reitermarkus/cross
cd cross
cargo install --path . --force
./build-docker-image.sh xtensa-esp32-none-elf

Project Template

Next, we want to setup the project template that can build and flash our binary to the chip on the fly. Before we start, make sure you have esptool.py installed, this is how we going to flash the binary. Once you have it installed, we can clone the template and start building:

git clone https://github.com/wusyong/esp32-hello.git
git submodule update --init --recursive

This template also provides the ffi of esp-idf and wraps some basic functions to the esp-idf-hal. The main application is right under the app/ directory, there are more examples in the app/examples. To build and flash the application, we could just use the build script:

./build.sh [--chip <chip> (default: esp32)] [--release] [--package <package> (default: app)] [--example <example>] [--flash-baudrate <baud> (default: 460800)] [--erase-flash]

As you can see it's basically calling cross to build and esptool to flash. If you are also using esp32, you probably don't need to edit environment variables in the script. But make sure the SERIAL_PORT is correct just in case (it's /dev/ttyUSB0 for my device), or it's not going to flash successfully. To build the application in release, simply call:

./build.sh

And if you want to build any example:

./build.sh --release --example thread_local

State of the ESP ecosystem in Rust

In this section, I would like to talk about the ecosystem of it and evaluate is it worth it for ESP development using Rust. You don't need to proceed if you just want to setup the environment. It is basically complete.

I do consider ESP series are not the first-class citizen among the rust embedded community. You can’t simply cross compile it like other targets (stm, nrf for instance). And despite there’s an esp-rs organization which provides many helpful crates and even a quick start repository, there are still some obstacles and missing parts I can image many people would just give up if they cannot resolve. Building the compiler for xtensa is not trivial already, it is also very time consuming. It took me an hour or two to build the llvm and rustc. And toolchains can be outdated too! A while ago I have a dockerfile for me to build the imagine. But many tools have changed, and some are not compatible anymore. Even llvm asked me to use new variable LLVM_EXPERIMENTAL_TARGETS_TO_BUILD which is ** undocumented**! Furthermore, many crates are so difficult to seek and acquire. You think something so straight forward like esp32-hal should already on crates.io but it’s actually not. Other crates often scatter across the community, and you have to do the search yourself. But on the other hand, I feel like this is inevitable nature in FOSS. The community is moving for sure, just slowly. We saw that AVR target was merged into Rust recently. It definitely could also happen to ESP in the future.

The reason for me to keep going is probably the potential to develop in Rust. Here’s the example for Wi-Fi scanning in the esp-hello template. You can see how elegant and ergonomic it is. It’s even possible to use async programming to achieve the goal! Indeed, it still depends on esp-idf and FreeRTOS. But it is totally possible to just build an async runtime for it. I know some developers are already exploring this. I am exciting to see more powerful crates and tools for embedded in the future.