-
Notifications
You must be signed in to change notification settings - Fork 794
Under the hood
instead of using a non async cortex_m::entry
fn as our main function macro embassy offers embassy::main
helper macro. For instance a simplified async function spawned on an embassy executor looks like this:
#[embassy::main]
async fn main(spawner: Spawner, dp: Peripherals) {
unwrap!(spawner.spawn(timer1()));
}
#[embassy::task]
async fn timer1() {
loop {
Timer::after(Duration::from_secs(60)).await;
}
}
But what are those macros all doing for us? Lets expand the embassy::main
macro first
use embassy::util::Forever;
// we make a lazily created static using the embassy version of [lazy_static](https://docs.rs/lazy_static/) or the upcoming [core::lazy::SyncOnceCell](https://github.com/rust-lang/rust/issues/74465) which allocates the static space for something we'll need to initialize at once at runtime
static EXECUTOR: Forever<embassy::executor::Executor> = Forever::new();
#[cortex_m_rt::entry]
fn main() -> ! {
// once we hit runtime we create that executor finally
let executor = EXECUTOR.put(embassy::executor::Executor::new());
// init our specific device based on embassy feature flags
// its preferred to use embassy's init to receive the peripherals singleton as embassy needs to take some of them and you dont want to conflict. That's why embassy::main returns you the Peripherals object
let p = embassy_nrf::init(Default::default());
// then no difference here, we spawn our tasks on the executor. Note the executor never returns
executor.run(|spawner|{
unwrap!(spawner.spawn(timer1()));
})
}
#[embassy::task]
async fn timer1() {
loop {
Timer::after(Duration::from_secs(60)).await;
}
}
OK simple enough, but a nice cleanup for using the macro. Note this isn't the whole truth as embassy::main allows you to provide an async function for your main! Well that's just another task it spawns for you under the hood. So whats going on with those tasks? We could replace our embassy::task
macro like so:
use ::embassy::executor::raw::TaskStorage;
type F = impl core::future::Future + 'static;
// create an array of TaskStorage, just one because we're only making one task
static POOL: [TaskStorage<F>; 1usize] = [TaskStorage::new(); 1usize];
// creates a NEW non async function called timer1 and wraps our renamed timer1 async fn as task inside of it
fn timer1() -> embassy::executor::SpawnToken<impl ::core::future::Future + 'static> {
async fn task() {
loop {
Timer::after(Duration::from_secs(60)).await;
}
}
// returns the result of spawning our task into that static pool
TaskStorage::spawn_pool(&POOL, move || task())
}
Embassy just copies this one time for each of your embassy::task
calls. You could obviously write it all by hand making a static POOL of size N many tasks and spawn into the same POOL, but there is not much difference in cost to making N POOLs of size 1 instead.