Created: April, 24, 2024 Last Modified: April, 24, 2024
Setting up Embassy on ESP32
Setting up Embassy on both no-std and std environments is pretty straight forward, thanks to a lot of work that has been done in both the esp-hal and esp-idf-hal crates. This tutorial assumes you have rust and cargo installed. There are also some prerequisites for general ESP32 rust development needed such as espup, more information on that can be found here. Cargo-generate is also needed for both of these examples which can be installed through cargo with this command
cargo install cargo-generate
Embassy in no-std
Let's start with the no-std environment p and going since that is likely the most common use case for embassy. First let's create a basic no-std ESP32 project using cargo generate
cargo generate esp-rs/esp-template
This will ask you a few questions like what target to configure for. After its done move into your newly created project and run
. $HOME/export-esp.sh
cargo run --release
The first line sets up our environment to see the ESP32 tool chain. You only need to run this once per terminal session. By default, a runner will be set up to call espflash, it should build and flash your ESP32 board if connected. If everything ran correctly you should have the monitor showing "INFO - Hello World!" Repeatedly.
Adding Embassy
At this point you should have a pretty basic main.rs that looks something like this.
#![no_std]
#![no_main]
use esp_backtrace as _;
use esp_hal::{clock::ClockControl, delay::Delay, peripherals::Peripherals, prelude::*};
#[entry]
fn main() -> ! {
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::max(system.clock_control).freeze();
let delay = Delay::new(&clocks);
esp_println::logger::init_logger_from_env();
loop {
log::info!("Hello world!");
delay.delay(500.millis());
}
}
All this does is create a main function as our entry point, then sets up the esp logger and prints hello world in a loop. In order to add embassy to this we first need to import the correct embassy packages. In Cargo.toml we need to add the following two lines.
embassy-executor = "0.5.0"
embassy-time = {version = "0.3.0", features = ["generic-queue-8"]}
Next we need to enable some embassy related features in esp-hal, modify the esp-hal line in Cargo.toml to match this.
esp-hal = { version = "0.17.0", features = [ "esp32s3", "embassy", "embassy-time-timg0", "embassy-executor-thread"] }
Your Cargo.toml dependency section should look similar to this now
[dependencies]
esp-backtrace = { version = "0.11.0", features = [
"esp32s3",
"exception-handler",
"panic-handler",
"println",
] }
esp-hal = { version = "0.17.0", features = [ "esp32s3",- "embassy", "embassy-time-timg0", "embassy-executor-thread"] }
esp-println = { version = "0.9.0", features = ["esp32s3", "log"] }
log = { version = "0.4.20" }
embassy-executor = "0.5.0"
embassy-time = {version = "0.3.0", features = ["generic-queue-8"]}
Main.rs changes
Now that we have the required dependencies we can modify our main.rs to use the embassy executor. In order to use the embassy executor we need to change main to async and annotate it with #[main]. Main also needs to accept a spawner parameter that will be used to spawn the tasks. While we're at it let's create a task to test it's all working. With all those changes your main.rs should look like this.
#![no_std]
#![no_main]
use embassy_executor::Spawner;
use embassy_time::{Duration, Timer};
use esp_backtrace as _;
use esp_hal::{
clock::ClockControl,
embassy::{self},
peripherals::Peripherals,
prelude::*,
timer::TimerGroup,
};
#[embassy_executor::task]
async fn run() {
loop {
log::info!("Hello from an embassy thread");
Timer::after(Duration::from_millis(1_000)).await;
}
}
#[main]
async fn main(spawner: Spawner) {
let peripherals = Peripherals::take();
let system = peripherals.SYSTEM.split();
let clocks = ClockControl::max(system.clock_control).freeze();
let timg0 = TimerGroup::new_async(peripherals.TIMG0, &clocks);
embassy::init(&clocks, timg0);
esp_println::logger::init_logger_from_env();
spawner.spawn(run()).ok();
loop {
log::info!("Hello from Main");
Timer::after(Duration::from_millis(1_000)).await;
}
}
With those changes if you build and reflash you should get messages from both main and the embassy thread. You now have a basic ESP32 Embassy based project!
Embassy in STD Environment
Similar to the no-std ESP-RS provides a template, with cargo generate we can create a new project based on it.
cargo generate esp-rs/esp-idf-template cargo
As with the bare metal example you'll need to run this to set up the dev environment correctly.
. $HOME/export-esp.sh
Next lets add embassy-time as a dependency Add this line to your Cargo.toml
embassy-time = {version = "0.3.0", features = ["generic-queue-8"]}
That's all we need to install to get the project ready to run embassy based tools. Next let's look at what needs to be changed in main.rs.
Main Modifications
The standard environment is a little different. Instead of running the embassy executor we'll use the built-in async executor that esp-idf-hal provides.
In order to run programs written to run in embassy all you have to do is call them from block_on
We can make this a little simpler and easier to call multiple async functions by first creating a async function.
use embassy_time::{Duration, Timer};
use esp_idf_svc::hal::task::block_on;
fn main() {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_svc::sys::link_patches();
// Bind the log crate to the ESP Logging facilities
esp_idf_svc::log::EspLogger::initialize_default();
log::info!("Hello, world!");
block_on(async_main());
}
async fn async_main() {
task().await;
}
async fn task() {
loop {
println!("Hello from a task");
Timer::after(Duration::from_secs(1)).await;-
}
}
That's all it takes to have async up and running on the standard environment. Full code can be found at GitHub