Building an Application

This chapters covers fundamental concepts required to build an Ariel OS application.

Obtaining Peripheral Access

Embassy defines a type for each MCU peripheral, which needs to be provided to the driver of that peripheral. These peripheral types, which we call Embassy peripherals or peripheral ZSTs, are Zero Sized Types (ZSTs) that are used to statically enforce exclusive access to a peripheral. These ZSTs indeed are by design neither Copy nor Clone, making it impossible to duplicate them; they can only be moved around.

Drivers therefore require such ZSTs to be provided to make sure that the caller has (a) access to the peripheral and (b) is the only one having access, since only a single instance of the type can exist at any time. Being ZSTs, they do not carry any data to the drivers, only their ownership is meaningful, which is enforced by taking them as parameters for drivers.

If you are used to thinking about MCU peripherals as referenced by a base address (in the case of memory-mapped peripherals), you can think of these ZSTs as abstraction over these, with a zero-cost, statically-enforced lock ensuring exclusive access.

These Embassy types are defined by Embassy HAL crates in the respective peripherals modules. In Ariel OS applications, the only safe way to obtain an instance of an Embassy peripheral is by using the define_peripherals! macro, combined with a spawner or task. The group_peripherals! macro can also be useful.

Example

The define_peripherals! macro allows to define a Ariel OS peripheral struct, an instance of which can be obtained with spawner or task:

ariel_os::hal::define_peripherals!(LedPeripherals { led: P0_13 });

Multiple Ariel OS peripheral structs can be grouped into another Ariel OS peripheral struct using the group_peripherals! macro:

ariel_os::hal::group_peripherals!(Peripherals {
    leds: LedPeripherals,
    buttons: ButtonPeripherals,
});

Similarly to LedPeripherals, an instance of the Peripherals Ariel OS peripheral struct thus defined can be obtained with spawner or task.

The spawner and task Ariel OS macros

Unlike traditional Rust programs, Ariel OS applications do not have a single entrypoint. Instead, multiple functions can be registered to be started during boot. Functions can currently be registered as either spawners or tasks:

  • spawner functions are non-async and should be used when no async functions need to be called. They are provided with a Spawner instance and can therefore be used to spawn other async tasks.
  • task functions are async functions that are statically allocated at compile-time. They are especially useful for long-running, async tasks. They must also be used to use Ariel OS configuration hooks, which can be requested with their associated macro parameter, and allow to provide configuration during boot. Please refer to the documentation of task for a list of available hooks and to Configuration Hooks to know more about hook usage.

Both of these can be provided with an instance of an Ariel OS peripheral struct when needed, using the peripherals macro parameters (see the macros' documentation) and taking that Ariel OS peripheral struct as parameter.

The Embassy peripherals obtained this way are regular Embassy peripherals, which are compatible with both Ariel OS portable drivers and Embassy HAL crates' HAL-specific drivers.

Examples

Here is an example of the task macro (the pins module internally uses define_peripherals!) from the blinky example:

#[ariel_os::task(autostart, peripherals)]
async fn blinky(peripherals: pins::LedPeripherals) {
    let mut led = Output::new(peripherals.led, Level::Low);

    loop {
        led.toggle();
        Timer::after(Duration::from_millis(500)).await;
    }
}

Configuration Hooks

TODO