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 spawner
s or task
s:
spawner
functions are non-async
and should be used when noasync
functions need to be called. They are provided with aSpawner
instance and can therefore be used tospawn
otherasync
tasks.task
functions areasync
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 oftask
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