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.
Tip
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:
spawnerfunctions are non-asyncand should be used when noasyncfunctions need to be called. They are provided with aSpawnerinstance and can therefore be used tospawnotherasynctasks.taskfunctions areasyncfunctions that are statically allocated at compile-time. They are especially useful for long-running,asynctasks. 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 oftaskfor 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.
Tip
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