diff --git a/text/0000-common-counter-interface.md b/text/0000-common-counter-interface.md index e32058d..a3cdb3b 100644 --- a/text/0000-common-counter-interface.md +++ b/text/0000-common-counter-interface.md @@ -23,6 +23,12 @@ named `LifetimeMoveCount: embedded_hal::count::Counter`, and constrain it such t Their `LifetimeMoveCount` defers to `Counter` to get a read of movement, but its own impls defines long-term caching of data (e.g. non volatile storage, or send reports to a server, etc). +**Core Principal** + +> the goal of embedded-hal is to make it possible to write drivers that are portable across HALs. + + + # Motivation [motivation]: #motivation @@ -63,13 +69,16 @@ standard for this, the way there is for things like gpio pins, i2c, etc. The core influence comes from two sources: -- `embedded-hal`: Just as this crate makes no impositions on specific implementation, but rather, encapsulates interfaces that - represent the most fundamental of behavior. +- `embedded-hal`: This RFC aims to make no impositions on specific implementation, but rather, make it easy to write highly portable embedded-rust - `embedded-time`: An independant endeavour that does a great job of already encapsulating this core idea. This is the code defining the `embedded_time::Clock` trait. I have changed the comments for the context of this RFC: + +
+ Expand to view annotated summary `embedded_time::Clock` trait + ```rust -// I would rename this to `TickCount`. A consistent oscilator is not necisarily a clock. It could be a PWM at 50% duty cycle, +// I would rename this. A consistent oscilator is not necisarily a clock. It could be a PWM at 50% duty cycle, // or an ab encoder tracking total distance moved, etc. The only assumption is that it is an incremental counter, and that // each increment has a specific meaning (be it a meanure of time, distance, etc.). pub trait Clock: Sized { @@ -118,10 +127,75 @@ pub trait Clock: Sized { } ``` -Up to this point, I've taken the concept of `Clock` and generalised it to `Counter`. I beleive that the `Clock` trait fits -in by being a trait extension of a `Counter` trait. Such an extension would further constrain the type that is being measured -with an aditional requirement that it is a representation of time (e.g. MilliSecs<...>), and perhaps add methods that are -clock-specific. +
+ +Below is an initial draft design. + +
+Expand to view pseudo-rust illustrative concept. + +```rust +/// Base encapsulation for reading a clock perihpereal. Intended typical use-case is +/// a HAL implementation adds this trait to a HAL type, and uses this interface to +/// read a clock/timer register. Through implementation of this trait, this object +/// will carry with it the means to read a clocks tick counter, and communicate the +/// total duration this represents. +pub trait TimeCount: Sized { + /// Typically u32 or u64 (or held within a type-wrapper) that corresponds with the + /// data type held within the peripheral register being read. + type RawData; + + /// The length of time that each clock-tick measures. Setting up a clock with a 80MHz, + /// a HAL might opt to set this as `fugit::TimerDuration`. + /// + /// Another option might something that expresses the measure as a frequency, in which + /// case, something that encapsulates "eighty million" (as opposed to "one eighty + /// millionth"). + /// + /// META: This might merit being a const instead. + /// META: Should it also be documented about the Mul constraint? + type TickMeasure: Duration + Mul + Default; + + /// A combinasion of raw count and measure. 80k ticks with 80MHz => 1 second. + type TimeMeasure: Duration; + + + /// Intended as an interface to a raw-register read. + fn try_now_raw(&self) -> Result; + + /// Interprates the tick-count of `try_now_raw`, and scales based on `TickMeasure` + fn try_now(&self) -> Result { + Self::TickMeasure::default() * self.try_now_raw()? + } +} +``` + +
+ +And here is an initial thought as to how to handle the need to address wrapping. + +
+`/// There are many sc... + +```rust +/// There are many scenarios where a time-based tick-counter needs to handle the case +/// where the counter overflows. This trait is intended to provide a HAL with the means +/// of providing a `TimeCount` implementor with specialised interupt-based overflow +/// tracking. +pub trait TimeWrap: TimeCount { + /// A HAL implementation might register the provided method to handle an overflow + /// interupt and increment a field that tracks overflow. + /// + /// META: I honestly don't know the best way to do this. This might prove to be + /// a key technical challenge + fn register_wrap_interupt(self, ???); +} +``` +
+ +This RFC initially proposed a base `Counter` trait, where a `Clock` trait would extend +it in a manner to handle time. This has changed. For background context, the initial +illustration of this concept is included here. ```rust pub trait Clock: Counter @@ -134,6 +208,27 @@ where ::Output: Into> } ``` +### Upstreamed feedback + +James Munn shared a valuable summary of their thoughts following a lengthy discussion in DMs: + +> you need to provide an interface that is reasonable for all parties, within reasonable use case [expectations] of all of them + +
+It is expected that... + +- ..a timer/counter will need to be shared - there are relatively few of them on many chips +- ..users will want high precision in some cases, 1k-1M are very reasonable number +- ..some timers have a limited range - 16/24/32 bit are common, but many chips only have 16/24 +- ..programs will be expected for months to years at a time +- ..some users want to use the lowest possible power design they can +- ..not all chips have atomics, and may need to use critical sections +- ..users may have other interrupts, some of which may have higher priority than your timer/counter interrupt +- ..users may want to use a global timer inside of other interrupts + +
+ + # How We Teach This [how-we-teach-this]: #how-we-teach-this @@ -150,22 +245,16 @@ various modules that define a common interface for things like gpio pins, I2c, e # Alternatives [alternatives]: #alternatives -embassy-time has an encapsulation of time, however that lives in an async-first context. -many MCU hals have their own way of encapsulating time, and other incremental counts. -fugit is an embedded-first encapsulation of mapping time-oscilator counts to a measurement of time in SI units. +- `embassy-time` has an encapsulation of time, however that lives in an async-first context. It also forces design +choices onto reverse dependencies, such as forced into using u64 for their `Duration` and `Instant` primitives. +- many MCU hals have their own way of encapsulating time, and other incremental counts. +- fugit is an embedded-first encapsulation of mapping time-oscilator counts to a measurement of time in SI units. +- `fugit` covers a lot of other base-functionalities in terms of embedded-first time-encapsulation. # Unresolved questions [unresolved]: #unresolved-questions -How to move forward? I propose an `embedded-count` added to the rust-embedded repository, initially just a placeholder, -I'll fork it, and begin initial work. At an appropriate time, it will be upstreamed, and v0.0.2 will be released. If all goes -well, its form will be representative of this RFC. - -Are we comfortable with using `typenum` instead of const generics. This will remove the limitations of const generics -entirely, including the need for nightly features, at the risk of interface ergonomics, and compromising user familiarity. -I wish to explore the use of this crate, though I feel it's a core requirement that the change in UX to be trivial. I.e. other -than theneed to use `typenum::U5` where `5u32/64/size/8` would be used to set a generic const. How should we approach assosciated error types? @@ -176,4 +265,20 @@ be used to count something, would necissarily have an ordering between any two p be put into providing the maths where compatable. in pseudo-rust: `impl Add for for B::Output` with where-clauses that constrain that the output types can do the Add. +### How to move forward? + +Initially, it was thought to add `embedded-count` added to the rust-embedded repository, initially just a placeholder, and so on. + +This has been updated. I have concerns about the chicken-egg dilema, and counsil is sought to mitigate this. At time of writing: + +1. Create out-of-workinggroup repo and build up the interface(s) +2. Fork some hals and update to use the interface(s) +3. Fork existing projects, update to clocks portably. +4. Continue working on my `SimpleFOC-rs` project, writing clocks in a portable manner +### Typenum + +Are we comfortable with using `typenum` instead of const generics. This will remove the limitations of const generics +entirely, including the need for nightly features, at the risk of interface ergonomics, and compromising user familiarity. +I wish to explore the use of this crate, though I feel it's a core requirement that the change in UX to be trivial. I.e. other +than theneed to use `typenum::U5` where `5u32/64/size/8` would be used to set a generic const.