Skip to content
Jacob Rosenthal edited this page Oct 9, 2021 · 1 revision

under the hood of embassy::main and embassy::task

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.

Clone this wiki locally