Open PTaylor-us opened 4 years ago
I see only duration types. What about frequencies and baudrates?
I see only duration types. What about frequencies and baudrates?
@burrbull There is a Period
type that is an i32
rational::Ratio
. I use it in the durations to specify the "magnitude" of the value. I'm assuming these types would be whole numbers, so no need for a Ratio. If you can help me with some use-cases, I could certainly add those types.
PTaylor-FluenTech/embedded-time#15
@burrbull Initial frequency-type functionality has been added in v0.5.0
Thank you for your work very much.
Looks excellent. One thing that could be useful is a bridge to core::time
, which is only Duration
at the moment.
@therealprof While I don't think that core::time
is particularly usable in embedded systems, I do recognize the benefit of having that bridge when it's needed. I'll add it to my list, and thank you very much for the suggestion.
@therealprof Added with v0.5.2
I'm curious whether it would be appropriate to using embedded-time
in the embedded-hal
crate or if that would make it too non-general. If it wouldn't be appropriate, I would love any feedback in ways I can make embedded-time
usage with embedded-hal
more seamless.
Looks good to me in general, appreciate the effort. I'll need to find some time to check it out in detail though.
This looks really great - are there any plans to slowly integrate this into embedded-hal through PRs? I don't think this will see widespread adoption otherwise since most of the downstream embedded crates are already using their own time primitives etc. It would be nice to see your crate added as an embedded-hal dependency and then exposed through the embedded-hal public API at very least. Though I suspect people would be more comfortable just cloning the functionality to embedded-hal to keep the control within the embedded org...
embedded-time is trying to solve a different problem than embedded-hal so I don't think integration here makes too much sense. What we can do is encourage HAL impls to use embedded-time rather than rolling their own types and this is probably best done by someone doing PRs to do the switcheroo. I happen to be the maintainer of a few HAL impls so if anyone is looking for crates which could serve as the first stepping stone to general adoption feel free to use do PRs against https://github.com/stm32-rs/stm32f0xx-hal or https://github.com/stm32-rs/stm32f4xx-hal and I will help driving that to completion.
@therealprof Thanks for clearing up the embedded-hal question, I wasn't at all sure whether it would be a good fit there. I will do whatever I can do to support PRs for individual HAL impls including opening them up myself unless I have updates needed in the embedded-time crate itself.
After some feedback from a HAL implementor, I created the this PR (FluenTech/embedded-time#22) which would change all the "inner" types to unsigned. I would love to get some feedback about this change before I merge it.
@therealprof I am a little confused, actually, about what you said about embedded-hal. I'm still trying to wrap my head around what the objective of embedded-hal
is. Device-specific HALs impl embedded-hal
so that other code (primarily device drivers) can be written abstracted from the actual device being used. The goal (in my opinion) would be to provide complete, generic, access to the high-level functionality. Obviously, there will be very device-specific details that would be impractical to include. I think that for device drivers to work with time-related features (clocks, timers, durations, instants, frequency, etc) in a generic way, embedded-hal
would be the place it needs to reside. Perhaps, I misunderstand the purpose of embedded-hal
?
The purpose of embedded-hal
is to facilitate the interfacing between hardware and drivers/applications. As you've said yourself embedded-time
is mostly hardware independent.
I think that for device drivers to work with time-related features (clocks, timers, durations, instants, frequency, etc) in a generic way, embedded-hal would be the place it needs to reside.
I don't see it why it needs to be an integral part of embedded-hal
instead of a dependency. But requirements and expectations can change...
I don't see it why it needs to be an integral part of
embedded-hal
instead of a dependency. But requirements and expectations can change...
Sorry, I wasn't commenting on where the code should be, but rather saying that (whether as a dependency or direct integration), it seems to me that embedded-hal
is where it should be "used".
The purpose of embedded-hal is to facilitate the interfacing between hardware and drivers/applications. As you've said yourself embedded-time is mostly hardware independent.
Would it make sense to split it up? The only hardware-abstraction part of it is the Clock
trait. However, it does depend on the TimeInt
and Duration
traits as well as Instant
and Period
types -- so basically everything. Being relatively new to Rust, I can't visualize what ideal outcome would look like.
I guess as long as embedded-hal
exposes the interfaces necessary to impl the Clock
trait, drivers could still utilize embedded-time
. However, I'm fairly certain that it currently does not.
Would it make sense to split it up? The only hardware-abstraction part of it is the Clock trait. However, it does depend on the TimeInt and Duration traits as well as Instant and Period types -- so basically everything.
I'd wait and see where this goes. I can totally see this being useful for a lot of applications, if it gets picked up by other crates and turns out to be useful I'd have no regrets integrating it more deeply.
At the moment I see most of the usefulness in the HAL impls themselves with a hint of use in the timer related stuff in e-h.
I guess as long as embedded-hal exposes the interfaces necessary to impl the Clock trait, drivers could still utilize embedded-time. However, I'm fairly certain that it currently does not.
We could also do it the other way around and provide all the fun clock, timer and countdown traits via embedded-time
and phase it out from embedded-hal
. They're only borderline useful as-is and could be much more powerful with an proper time calculations, basically we're only using simple units (plus some copy and paste simple conversion in the HAL impls).
Also it would be great if we could have something like a monotonic system clock, real time clock, alarms, scheduling...
Also it would be great if we could have something like a monotonic system clock, real time clock, alarms, scheduling...
As things sit right now, the Clock
trait can be implemented for any monotonic source. That could be a peripheral timer, RTC, etc. I have an implementation for the nrf52 with two of the 32-bit peripheral timers chained together and that is my "system clock". I've also done an example using the RTC.
The next release will probably be with Timer
s added and I'm hoping to be able to use those to schedule tasks executed by the Clock
impl's interrupt.
@therealprof I'm certain you have spent much more time thinking about where embedded rust is right now, where we want to go, and how to get there, so I would greatly appreciate any guidance.
I dont think embedded time is trying to solve a different problem at all - Just look at all the issues noted in the original post mentioning the need for some crucial clock traits like periods and frequencies, and to tie them to the std lib. Could the traits and Period struct at least be moved into embedded-hal to ensure comparability?
I don't think it's wise to expect the ecosystem to depend on a 3rd party crate to ensure cross-ecosystem compatibility for timings - that should be something that embedded-hal does since it is the "blessed" crate for the normal hardware abstractions one would expect no?
Hey thanks for working on this! There's lots of useful stuff here, and how / what we integrate is an interesting question.
I think i'd echo @therealprof in that for now I would continue this as a separate project, and to try out integrating it with HALs and other components. This gives us space to experiment a bit (as we require for new hal additions) and means we're not tied to the HAL for releases (you may have noticed there's a lot going on at the moment).
To me it seems that the possible steps from there are:
a) we could review and bless embedded-time
, and take over ownership (and maintenance) of the repo / crate (so it's separate but, first party).
b) we could look to integrate the validated traits with the hal directly
c) we could do a bit of both and move the types directly applicable to hardware in but keep the less-general ones separate.
I think the key component delineation to me is whether this is intended to be std
compatible and how they can be implemented:
std
or no_std
then it would be better in the separate library (ie. using features and type aliasing, Instant::now()
if we elected to provide this)Timer::start(), Timer::now()
) it should be in the halTimer::instant()
might use but not reflect a hardware primitive), it's a bit ambiguous but i would lean towards having it in the libraryHowever I can also see the benefits to going in either direction.
In terms of moving traits into the hal, it is reasonably straightforward to add a trait and then import and re-export that from the original crate, so we can move traits into the hal later should this be desired, without breaking any external dependencies.
I just release v0.6.0 of embedded-time. With feedback and advice from @eldruin and @TheZoq2. I made a number of changes.
Clock
implementationsClock
impl object.Clock
trait to allow stateful implementations (added &self
) to methodsI think i'd echo @therealprof in that for now I would continue this as a separate project, and to try out integrating it with HALs and other components. This gives us space to experiment a bit (as we require for new hal additions) and means we're not tied to the HAL for releases (you may have noticed there's a lot going on at the moment).
I like that idea. I think embedded-hal (as well as embedded-time) are both still pretty fluid. It's my intention to continue development in a way that serves my purposes, but also keeps an eye toward maybe being suitable for incorporation at some later date.
I think the key component delineation to me is whether this is intended to be
std
compatible and how they can be implemented:
- if it were to provide an abstraction over
std
orno_std
then it would be better in the separate library (ie. using features and type aliasing,Instant::now()
if we elected to provide this)- if it is directly implementable on hardware (or used in this, for example
Timer::start(), Timer::now()
) it should be in the hal- and if it's implementable using hardware components, but does not reflect physical hardware (for example,
Timer::instant()
might use but not reflect a hardware primitive), it's a bit ambiguous but i would lean towards having it in the library
There is only one part that must implemented in a hardware-specific manner and that's the Clock
trait. Everything else is hardware-agnostic.
Here's a rough snapshot of some of the clock and instant interfaces:
struct ClockImpl {
<hardware-specific state goes here>
}
impl embedded_time::Clock for ClockImpl {
fn now(&self) -> Instant {
<reading of hardware goes here>
}
}
let my_clock = ClockImpl
let instant_1 = my_clock.now()
let instant_2 = instant_1 + 10.seconds()
let some_timer = my_clock.new_timer(2.seconds()).into_oneshot().start()
...
let remaining = some_timer.remaining()
let elapsed = some_timer.elapsed()
some_timer.wait() // blocks until expiration
let elapsed_time = my_clock.duration_since(instant_1)
let dur_until_instant_2 = my_clock.duration_until(instant_2)
And here is an actual Clock
implementation from the example:
pub struct SysClock {
low: nrf52::pac::TIMER0,
high: nrf52::pac::TIMER1,
capture_task: nrf52::pac::EGU0,
}
impl SysClock {
pub fn take(
low: nrf52::pac::TIMER0,
high: nrf52::pac::TIMER1,
capture_task: nrf52::pac::EGU0,
) -> Self {
Self {
low,
high,
capture_task,
}
}
}
impl time::Clock for SysClock {
type Rep = u64;
const PERIOD: time::Period = <time::Period>::new(1, 16_000_000);
type ImplError = Infallible;
fn now(&self) -> Result<time::Instant<Self>, time::clock::Error<Self::ImplError>> {
self.capture_task.tasks_trigger[0].write(|write| unsafe { write.bits(1) });
let ticks =
self.low.cc[0].read().bits() as u64 | ((self.high.cc[0].read().bits() as u64) << 32);
Ok(time::Instant::new(ticks as Self::Rep))
}
}
time::Clock
looks super useful hey, are there any soundness issues with reading from the two timers sequentially? (ie. what happens if high rolls over while you're reading the low section)?
@ryankurte, Thanks for taking a look at the crate. There are a lot of changes happening at the moment.
With this chip, I can trigger a signal from software that causes both timers to capture atomically (capture_task
), then I just read the captured values out of each.
ahh nice, it looks like a similar approach is possible on ST cores but, might require checking for overflow.
Some new releases of embedded-time
Fraction
type in the duration
and rate
module
use duration::*
) to support Generic
usage which uses Fraction
README.md
and crates.io crates-io.md
are now generated using cargo-readme
From
implementations for infallible intra-rate and intra-duration conversionstry_from()
/try_into()
rather than the try_convert_
methodstry_into_generic()
with to_generic()
try_from_
(duration/rate) with to_duration()
and to_rate()
Option
s from checked_
methods rather than Result
sClock
error type (ImplError
)As a developer who is quite new to Rust but has a decent amount of experience with C/C++ and STM32 development, my thoughts on this topic when developing a device driver:
embedded-hal
only. Dependency hell is something that fears a lot of embedded developers since they are used to have control over the whole code except from a few vendor libraries.core::time::Duration
type seems strange in the embedded world because we are mostly dealing with microseconds and milliseconds and u32 for nanoseconds overflow too quickly.I just started with embedded development in rust and I want to say that I am in line with @sourcebox's points above.
I am trying to write a simple embedded-hal driver on the ESP32C3. I need to measure how long a GPIO pin stays high. For that I do not need a sophisticated timer: access to a monotonic timer (tick count + frequency) is sufficient. It would be especially interesting on the ESP32C3 where there are only 2 general purpose timers. With access to the system clock I would not need to consume a timer.
For what it's worth, I think basic time access functionalities should be intergrated in embedded-hal
. Having them in a separate crate just makes life more difficult for the implementer of hardware-specific HAL that would have to track and implement traits from multiple crates. And if they don't, the ecosystem will fragment itself with partial HAL implementations that do not deliver the expected hardware abstraction.
I see that this discussion started years ago. Is there still life in it? How does it move forward?
I never wanted to write my own crate for dealing with time, but to have a solution for my own projects until something "official" is released:
https://github.com/sourcebox/emtick-rs
I don't know if the general concept behind it is good enough as solution within e-h, maybe it could be discussed. And yes: the code for the conversions between ticks/ms/µs looks scary and expensive, but at least in my own testing, the compiler did a decent job of optimizing it.
Hi, @sourcebox , what are the main differences between your implementation and fugit? I saw that yours does not have any dependency, and that is always desirable (I believe). However, it would be great to have a performance comparative. Also, memory footprint should be studied.
I agree with you, we just need a decent toolbox for dealing with time and timers in embedded systems. It would be great if e-h adopts/integrates either fugit or your new crate. The main drawback of your solution (I think) is that several HALs already use fugit, and maybe it is worth it to adopt it in e-h as well. I'll take a look to your code later with more time.
As far as I understand fugit, it tries to eleminate runtime cost completely. This however leads to the fact that you have to deal with types like Duration::<u32, 1, 1_000>
and pass them around, which I'm personally not a big fan of.
My solution is a more pragmatic one. It should be easier to use but has some runtime overhead when converting from/to natural time units. So doing this should be kept minimal. The amount of impact on overall performance is hard to decide. In my code, time calculations are typically used rarely compared to the overall coverage. Optimizing this to the max would not have too much effect globally. But this may vary depending on the use case.
I would suggest that you do a real world example using fugit
, my crate, maybe embedded-time
etc. and do some comparision in terms of ease-of-use and performance.
@PTaylor-us I've been using the Clock
trait, and I am very grateful for the existance of embedded-time
. Thank you for your work.
The project seems stale: I haven't seen any updates, or activity in the issues/PR space. Would you be happy to see this taken over by someone and/or absorbed into the embedded-hal
crate?
For what it's worth, I think basic time access functionalities should be intergrated in
embedded-hal
@sourcebox 100%. I would add that the more advanced functionalities can be incrementally added as the community asks for them. Starting with a trait with an API that sources a tick-count and mapping to seconds (I say seconds because it's the standard unit of time, but ideally, this would be generic between seconds, us, ms, etc). The more advanced stuff can be added down the line.
Quoting @dirbaio in the chat for a comparison of embedded-time
vs. fugit
:
there's definitely interest indeed, the problem is it's very unclear how to do it. Decisions that must be done is:
- Bit width. Hardcoded, configurable via Cargo features, configurable via generics?
- Tick rate: Hardcoded, configurable via Cargo features, configurable via generics? Const generics? typenum? Just a frequency value? or a full NUM/DENOM fraction?
- Who gets to choose these settings? the HAL implementing the trait, or the driver using the trait?
- Single global clock, or multiple clocks? If multiple clocks, can they have different settings?
Example 1: embassy-time:
- bit width: hardcoded to u64, so it never overflows. (prioritizing convenience over efficiency)
- tick rate: configurable via cargo features. Some HALs choose it, some let the end user choose.
- Single global clock, so you can do Instant::now() from anywhere without having to pass around stuff.
Example 2: fugit
- bit width: configurable with generics
- tick rate: full NUM/DENOM fraction, configurable with const generics.
- Who chooses? the HAL implementation (I think the HAL can choose to be generic so the end user chooses? but either way the driver can't choose)
- Multiple clocks, each can have its own settings.
as you can see they're polar opposites. So, adding some Clock/Instant/Duration traits to embedded-hal means we have to make SOME choice on these questions and if we choose X then use cases that would be better suited by Y would suffer
I've put together an RFC in the wg repo: https://github.com/rust-embedded/wg/pull/762
I believe I've addressed most of the points:
Wrapper(u8)
, u128, SomeTypeThatCounts
, and so onInputPin
actually is. for all it matters, it could just be a struct that just randomly chooses high or low. What matters is that a) the trait encapsulates the act of reading something that is asumed to be increasing one at a time and b) a specification of what is being counted, be it a measurement of time, distance moved (e.g. with ab-encoders), etc
I have just released
embedded-time
to handle clocks, instants, durations more easily in embedded contexts. It follows a similar idea to the C++std::chrono
library. I'd love to get some feedback, suggestions, PRs, etc.embedded-time
provides a comprehensive library for implementing abstractions over hardware and work with clocks, instants, durations, periods, and frequencies in a more intuitive way.core::time::Duration
Example Usage:
Related:
122 #207 #24 #46 #201 #129 #103 #59 #186