owned this note
owned this note
Published
Linked with GitHub
# New RTIC `monotonic` take 2
## Issues with take 1
* Not following the overal `&self` interface of `embedded-time` does not make us directly compatible
* `embedded-hal` is mainly `&(mut) self` based as well
* If we follow them more we will get implementation mainly for free via HALs
## New minor change in design
* Update the codegen so that monotonics are stored
* Have the `spawn` functions access the stored monotonics behind the scenes, allowing for `&(mut) self` interfaces in traits to be used
* Have the codegen provide the `MyMono::now()` method?
```rust
pub trait Monotonic: embedded_time::Clock + embedded_hal::timer::FreeRunningTimer
{}
/// ...
pub trait FreeRunningTimer {
type Ticks;
fn ticks(&self) -> Self::Ticks;
fn restart(&mut self);
fn set_compare(&mut self, val: Self::Ticks);
fn read_compare_flag(&mut self, clear: bool) -> bool;
}
```
# New RTIC `monotonic` take 1
## In app `monotonic` usage
Inside the `app` macro we define monotonic clocks as below using type aliases with attributes:
```rust
#[rtic::app(device = hal::pac)]
mod app {
#[monotonic(binds = Tim1,
priority = 3,
default = true)]
type Fast = hal::Tim1Monotonic;
#[monotonic(binds = RTC0_CMP)] // will run at lowest task priority
type Slow = hal::RTC0Monotonic;
#[init]
fn init(cx: init::Context)
-> (init::LateResources, Monotonics)
{
let rcc = cx.device.rcc;
let tim1 = cx.device.tim1;
let rtc0 = cx.device.rtc0;
// Set CPU speed
let clocks = hal::rcc(rcc, 80_000_000);
// Setup monotonics
let mono_fast = hal::Tim1Monotonic<80_000_000>::new(clocks, tim1);
let mono_slow = hal::RTC0Monotonic<100>::new(clocks, rtc0);
(
init::LateResources {},
// Same order as definition
init::Monotonics(mono_fast, mono_slow),
)
}
#[task]
fn task1(_: task1::Context) {
// Default monotonic
task1::spawn_after(1.seconds()).ok();
// under surface: cx.scheduled + 1.seconds()
task1::spawn_at( /* fixed time */ ).ok();
// Specific monotonic
task1::Fast::spawn_after(1.seconds()).ok();
task1::Slow::spawn_at(/* fixed time */).ok();
}
}
```
## Monotonic trait
In libraries the following trait should be implemented for timers (all based on `embedded-time`):
```rust
/// A monotonic clock / counter
pub trait Monotonic: embedded_time::Clock {
/// Returns the current time, has default implementation.
///
/// Safety: Does only need to return valid results after `init`.
///
/// Note: `embedded_time::Instant<T>` must have its ticks be the ticks
/// of the timer and that the `embedded_time::Clock` implementation
/// must be infallibe.
fn now() -> embedded_time::Instant<T>;
/// Resets the counter to *zero*, this will be called by RTIC
/// at the end of `init`.
unsafe fn reset();
/// Set the compare value of the timer interrupt.
///
/// Note: This function is used by RTIC codegen to set the timer up.
fn set_compare(val: T);
/// Clear the compare interrupt flag.
///
/// Note: This function is used by RTIC codegen to set the timer up.
fn clear_compare_flag();
}
```
## Open questions
* ~~Should we have the `init` function? This will require unsafe code.~~
* ~~An alternative is have RTIC init return the monotonic instance alongside the resources~~
* No
* ~~How to handle `cx.scheduled` etc? As this can be any timer now.~~
* Remove it, this will be handled under the surface by `spawn_after( /* duration: embedded_time::Generic<T> */ )`.
* ~~How to indicate the clock speed of the monotonic timer?~~
* ~~This could be a `const generic`/`typenum` argument in the monotonic implementation or an argument to the attribute, eg:~~
```rust
type Fast = rtic::DwtAndSystickMonotonic<80_000_000>; // Const generic
type Fast = rtic::DwtAndSystickMonotonic<80000000U>; // Typenum
```
* ~~Or it could be a runtime parameter, however it must not change for the duration of the execution.~~
* We require it to be set by the library as it can be initialized and verified.
* It may be useful for the driver to run its own code on interrupt. One usecase is nrf52's rtc: hardware counter is 24bit, but you can make your own "virtual" 32/64bit RTC out of it by counting overflows in the interrupt. To do this you need the interrupt to call driver code, and the driver code itself to optionally signal alarm to rtic. This is impossible to do if RTIC assumes interrupt = alarm.
* ~~This should be possible to do in the monotonic implementation.~~
* This will not be included in this version of `Monotonic`
* ~~Should the default priority be minimum priority unless overridden, or maximum priority unless hidden?~~
* Keep symmetric, so expectations are held.
---
## Naming
We already have `spawn`, and what schedule actually does is spawning the task at some absolute point in time or realtive to the baseline of the current task. One thought would be to use `spawn_at`, and `spawn_after`, to reflect that. (Also a bit shorter than `schedule_at/after.`). Alterntively an even shorter notation is just `at/after`, so the use would be ` task1::at( /* fixed time */ ).ok();`, along these lines, we could potentially drop `spawn` alltogether, so the use would be `task1(/* will be spawned now */).ok();` (not sure if that leaves to much to the intuition).