GithubHelp home page GithubHelp logo

fluentech / embedded-time Goto Github PK

View Code? Open in Web Editor NEW
86.0 6.0 16.0 659 KB

Time(ing) library (Instant/Duration/Clock/Timer/Period/Frequency) for bare-metal embedded systems

License: Apache License 2.0

Rust 99.93% PowerShell 0.07%
timers duration-types embedded hardware milliseconds conversion periods frequency datarate

embedded-time's Introduction

embedded-time   CI crates.io docs.rs

embedded-time provides a comprehensive library of Duration and Rate types as well as a Clock abstractions for hardware timers/clocks and the associated Instant type for in embedded systems.

Additionally, an implementation of software timers is provided that work seemlessly with all the types in this crate.

use embedded_time::{duration::*, rate::*};

let micros = 200_000_u32.microseconds();                // 200_000 ╬╝s
let millis: Milliseconds = micros.into();               // 200 ms
let frequency: Result<Hertz,_> = millis.to_rate();      // 5 Hz

assert_eq!(frequency, Ok(5_u32.Hz()));

Motivation

The handling of time on embedded systems is generally much different than that of OSs. For instance, on an OS, the time is measured against an arbitrary epoch. Embedded systems generally don't know (nor do they care) what the real time is, but rather how much time has passed since the system has started.

Drawbacks of the standard library types

Duration

  • The storage is u64 seconds and u32 nanoseconds.
  • This is huge overkill and adds needless complexity beyond what is required (or desired) for embedded systems.
  • Any read (with the exception of seconds and nanoseconds) requires arithmetic to convert to the requested units
  • This is much slower than this project's implementation of what is analogous to a tagged union of time units.

Instant

  • The Instant type requires std.

Drawbacks of the time crate

The time crate is a remarkable library but isn't geared for embedded systems (although it does support a subset of features in no_std contexts). It suffers from some of the same drawbacks as the core::Duration type (namely the storage format) and the Instant struct dependency on std. It also adds a lot of functionally that would seldom be useful in an embedded context. For instance it has a comprehensive date/time formatting, timezone, and calendar support.

Background

What is an Instant?

In the Rust ecosystem, it appears to be idiomatic to call a now() associated function from an Instant type. There is generally no concept of a "Clock". I believe that using the Instant in this way is a violation of the separation of concerns principle. What is an Instant? Is it a time-keeping entity from which you read the current instant in time, or is it that instant in time itself. In this case, it's both.

As an alternative, the current instant in time is read from a Clock. The Instant read from the Clock has the same precision and width (inner type) as the Clock. Requesting the difference between two Instants gives a Duration which can have different precision and/or width.

Overview

The approach taken is similar to the C++ chrono library. Durations and Rates are fixed-point values as in they are comprised of integer and scaling factor values. The scaling factor is a const Fraction. One benefit of this structure is that it avoids unnecessary arithmetic. For example, if the Duration type is Milliseconds, a call to the Duration::integer() method simply returns the integer part directly which in the case is the number of milliseconds represented by the Duration. Conversion arithmetic is only performed when explicitly converting between time units (eg. Milliseconds --> Seconds).

In addition, a wide range of rate-type types are available including Hertz, BitsPerSecond, KibibytesPerSecond, Baud, etc.

A Duration type can be converted to a Rate type and vica-versa.

Definitions

Clock: Any entity that periodically counts (ie an external or peripheral hardware timer/counter). Generally, this needs to be monotonic. A wrapping clock is considered monotonic in this context as long as it fulfills the other requirements.

Wrapping Clock: A clock that when at its maximum value, the next count is the minimum value.

Timer: An entity that counts toward an expiration.

Instant: A specific instant in time ("time-point") read from a clock.

Duration: The difference of two instants. The time that has elapsed since an instant. A span of time.

Rate: A measure of events per time such as frequency, data-rate, etc.

Imports

The suggested use statements are as follows depending on what is needed:

use embedded_time::duration::*;    // imports all duration-related types and traits
use embedded_time::rate::*;        // imports all rate-related types and traits
use embedded_time::clock;
use embedded_time::Instant;
use embedded_time::Timer;

Duration Types

Units Extension
Hours hours
Minutes minutes
Seconds seconds
Milliseconds milliseconds
Microseconds microseconds
Nanoseconds nanoseconds
  • Conversion from Rate types
use embedded_time::{duration::*, rate::*};

Microseconds(500_u32).to_rate() == Ok(Kilohertz(2_u32))
  • Conversion to/from Generic Duration type
use embedded_time::{duration::*};

Seconds(2_u64).to_generic(Fraction::new(1, 2_000)) == Ok(Generic::new(4_000_u32, Fraction::new(1, 2_000)))
Seconds::<u64>::try_from(Generic::new(2_000_u32, Fraction::new(1, 1_000))) == Ok(Seconds(2_u64))

core Compatibility

  • Conversion to/from core::time::Duration

Benchmark Comparisons to core duration type

Construct and Read Milliseconds
use embedded_time::duration::*;

let duration = Milliseconds::<u64>(ms); // 8 bytes
let count = duration.integer();

(the size of embedded-time duration types is only the size of the inner type)

use std::time::Duration;

let core_duration = Duration::from_millis(ms); // 12 bytes
let count = core_duration.as_millis();

(the size of core duration type is 12 B)

Rate Types

Frequency

Units Extension
Mebihertz MiHz
Megahertz MHz
Kibihertz KiHz
Kilohertz kHz
Hertz Hz

Data Rate

Units Extension
MebibytePerSecond MiBps
MegabytePerSecond MBps
KibibytePerSecond KiBps
KiloBytePerSecond KBps
BytePerSecond Bps
MebibitPerSecond Mibps
MegabitPerSecond Mbps
KibibitPerSecond Kibps
KilobitPerSecond kbps
BitPerSecond bps

Symbol Rate

Units Extension
Mebibaud MiBd
Megabaud MBd
Kibibaud KiBd
Kilobaud kBd
Baud Bd
  • Conversion from/to all other rate types within the same class (frequency, data rate, etc.) and base (mega, mebi, kilo, kibi). For example, MiBps (mebibytes per second) --> Kibps (kibibits per second) and MBps (megabytes per second) --> kbps (kilobits per second).

  • Conversion from Duration types

use embedded_time::{duration::*, rate::*};

Kilohertz(500_u32).to_duration() == Ok(Microseconds(2_u32))
  • Conversion to/from Generic Rate type
use embedded_time::rate::*;

Hertz(2_u64).to_generic(Fraction::new(1,2_000)) == Ok(Generic::new(4_000_u32, Fraction::new(1,2_000)))
Hertz::<u64>::try_from(Generic::new(2_000_u32, Fraction::new(1,1_000))) == Ok(Hertz(2_u64))

Hardware Abstraction

  • Clock trait allowing abstraction of hardware timers/clocks for timekeeping.

Timers

  • Software timers spawned from a Clock impl object.
  • One-shot or periodic/continuous
  • Blocking delay
  • Poll for expiration
  • Read elapsed/remaining duration

Reliability and Usability

  • Extensive tests
  • Thorough documentation with examples
  • Example for the nRF52_DK board

Features

  • serde: Enables serde::Deserialize and serde::Serialize implementations for concrete units.

Notes

Some parts of this crate were derived from various sources:

License: MIT OR Apache-2.0

embedded-time's People

Contributors

bobmcwhirter avatar eldruin avatar hannobraun avatar korken89 avatar mattico avatar ptaylor-us avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

embedded-time's Issues

Feature parity with bitrate crate?

Hello!

I currently time/frequency/bitrate types from bitrate crate. I saw few discussions around this crate in embedded-hal and rtic-rs repos and decided to give it a try. However, some features from bitrate crate are absent from embedded-time:

  • bitrate types (Bps);
  • extensions types for numerics (32.hz);
  • Copy is not implemented.

Do you want to make this crate go-to library for all frequency/time/bitrate related needs or should I stick with bitrate?

Thanks.

Change `Clock` trait to require interior mutable state?

I believe this this change must be made (now(&mut self) --> now(&self)) or every Timer function that needs to access the clock must be passed a &mut of the clock. I don't like the latter for the following reasons:

  • Makes usage much more verbose
  • Allows usage of different clocks with the same Timer

I think the last item is the greatest concern. What do you think @eldruin? How much more difficult would this make implementation? In my example implementation (using peripheral [not external]) timers, it doesn't change anything as the peripherals are all interior mutable already.

Duration calculated from clock instants cannot be used with concrete Durations

When a calculation is performed using two Instants from a given Clock, the resulting Duration is of type duration::Generic<Clock::T>. It appears that this type does not implement the necessary Try, TryFrom, Into, or TryInto traits to be used for a comparison against a concrete duration, such as duration::Seconds(1).

Example:

fn test<C: Clock>(clock: C) {
    let start = clock.try_now().unwrap();
    let end = clock.try_now().unwrap();

    let duration = start.checked_duration_since(&end).unwrap();

    let duration_secs: Result<Seconds<C::T>, _> = duration.try_into();
    if duration_secs.unwrap() > Seconds(1) {
        println!("Greater than 1 second);
    }
}

The result of attempting to convert the Generic into a Seconds duration fails with the following message:

error[E0277]: the trait bound `embedded_time::duration::units::Seconds: core::convert::TryFrom<embedded_time::duration::Generic<<C as embedded_time::clock::Clock>::T>>` is not satisfied
   --> src/mqtt_client.rs:406:83
    |
406 |         let duration: Result<embedded_time::duration::Seconds<u32>, _> = duration.try_into();
    |                                                                                   ^^^^^^^^ the trait `core::convert::TryFrom<embedded_time::duration::Generic<<C as embedded_time::clock::Clock>::T>>` is not implemented for `embedded_time::duration::units::Seconds`
    |
    = note: required because of the requirements on the impl of `core::convert::TryInto<embedded_time::duration::units::Seconds>` for `embedded_time::duration::Generic<<C as embedded_time::clock::Clock>::T>`

Add a `Timer` type/trait

I think it's important to make a distinction between a clock and a timer. These are my current working definitions:

  • Clock: Any entity that periodically counts (ie a hardware timer/counter peripheral). Generally, this needs to be monotonic. A wrapping clock is considered monotonic in this context as long as it fulfills the other requirements.
  • Timer: An entity that counts toward an expiration.

In terms of a Clock interrupt/wake feature I was working on, a timer is based on a clock and holds the instant at which the timer expires. Upon expiration a number of different things could happen including an interrupt/wake, a semaphore, etc.

Features I would expect for a clock:

  • get an instant

Features I would expect from a timer:

  • set the duration
  • start
  • restart (not resume)
  • poll the status (not-running, running-enabled, running-disabled, expired)
  • set the action to be taken upon expiration
  • enable/disable action (enable/disable interrupt)
  • block until expired (wait/delay)

Minimum features

  • Duration should have a constant(?) Ratio value setting the period of one tick in seconds (a Duration with millisecond precision should have a Ratio value of 1/1_000)
  • A Clock trait to be implemented on system/hardware-dependent type (ie. CYCCNT, Timer(s), etc.).

Round Conversion Into Lower Precision Durations?

Currently, converting from 1_999.milliseconds() to Seconds yields 1.seconds(). I feel like it makes a lot of sense to round these values, but the current behavior may actually be the expectation of the user given that arithmetic operations don't round either.

How do I declare a Timer field in a struct?

Hello,
I'm moving to this library to manage time durations in my system. I was able to substitute my previous u32 timestamp checks and wanted to use the Timer type to articulate period tasks.

To this end I'm trying to add a Timer field to a struct that then executes a task every second. Unfortunately the Timer struct requires 4 type parameters, out of which two (State and Type) are privately owned by the timer module.
How am I supposed to declare a struct field of type Timer<Periodic, Running, Clock, Duration>?

Considering that the state of the timer (Running, Armed,...) is part of the type, I fear that mine is a use case that was not considered, as I wouldn't be able to stop or start the timer from my statically declared field. Is there no way to achieve what I want to?

Result based interfaces and `fmt`-bloat / code-noise

Feedback on embedded-time

Hi,

Thanks for the presentation you did for us in the RTIC meeting, and after this meeting I'd like to come with some feedback of issues/improvements I see with the current implementation.

In this issue I'll focus on the interface. It's a bit general for all designs based on embedded-hal.


When it comes to Result based interfaces, as we see in embedded-hal the ideas behind it is important to have a look at.
The idea is that, for example for an GPIO, that the GPIO may fail if it is on an expansion (i.e. maybe I2C or different) and one want to provide feedback to the programmer of there kind of issues.

As I understood the same logic has been used here with the Clock trait.

The question that I think embedded-hal has not really thought about are 2-fold:

1. The issue of fmt bloat in embedded systems.

As soon as one starts using Result based interface on things that are used extensively as "will never fail" (i.e. .unwrap()) one enters fmt bloat land. This means that if you care about fmt bloat you are not allowed to use .unwrap()!

And this is where the pain starts. Either one has to start using interfaces as:

let _ = interface.operation(); 
// Or
interface.operation().ok();

Which is not too bad, however already here we are starting to taint the code with noise that does not provide clarity nor help, only code noise.
And if continue to interface that have a return type it gets worst quickly.

let value = if let Ok(value) = interface.operation_with_output() {
    value
} else {
   unreachable!() // Or panic!("some error")
};

One would might think "but use expect", however this has the same fmt bloat issues as unwrap().
This comes down to the unfortunate conclusion that you either:

  1. Make a macro to do this expansion.
  2. Make a generic closue function to do this expantion.
  3. unwrap() and live with code bloat.
  4. Do it manually.

The worst part is that 1, 2 and 3 - all these leads to obfuscating the code! We simply wanted to have a clear and ergonomic operation as:

let value = interface.operation_with_output();

So the conclusion for having non-fmt bloat interfaces that are ergonomic is to not use Result for types that are meant to be used as infallible operation.

2. What recovery options exists in practice?

And this leads to the elephant in the room: We never know if an operation can fail.
This statement is very true however, in my opinion, if the error has come this far something is very wrong.

Lets have a thought experiment where the question is: Does it make sense to have a Result based interface here?

For this let us assume we have a GPIO expander over I2C which is shared by multiple devices.
And lets assume that there is a lockup on the bus that would lead to the GPIO command failing.
Now the really interesting question comes: What recovery options exists in practice at the GPIO call-site?

If there has been a lockup on the bus it is the I2C driver implementers' responsibility to do bus fault handling, the user of a GPIO can not solve this issue.
So what will boiling this issue up to user give, rather than putting the responsibility on the driver?
One could argue here that the user might have something special to unlock a bus. But then we have moved the issue away from where it should be handled again!
This must be handled in the I2C driver! Ofcourse the I2C driver can have in its documentation that it does not handle lock-up and that is fine, it is documented.

The counter argument here is then: But what if the user has a pin to power-cycle the bus to get rid of the lockup condition? This can't be known by the I2C driver right?
I say wrong here! I2C is a known bus to lockup so any implementer of an I2C driver must take this into condition and should have an interface to add lockup clearing, such as power-cycling.
Plus many I2Cs has device specific errors, so handling the error in a generic way in device crates is simply impossible.

Because at the call-site of a GPIO pin we are so far from handling I2C lockups that it does not make sense to handle it.
It will cause code-noise that will obfuscate the intent and fmt bloat for 99.9% of use-cases where people do unwrap().
And, in my strong opinion, this is wrong! The issue has somehow been moved from a driver implementer to the user whom has almost 0% chance to handle the issue.

This reasoning I use here generalizes to any external interface I know of.
External RTCs, SPI based IO expanders, I2C based IO expanders, etc etc.
We have somehow agreed in embedded-hal that it is the users responsibility rather than the driver implementer's, which is madness from my view the logic and reasoning that came to this result does not make sense in the analysis presented here and is a very unfortunate design decision in the ecosystem.

Hence I recommend to not use Result based interface for operation that should in 99.9% of the cases be infallible.

Bound `Instant::duration_since_epoch()` to where `Clock: Infinite`

The duration_since_epoch() only makes sense if the Clock from which the Instant was returned never (practically speaking) wraps. This could be a Clock with sufficient low precision or large inner type to virtually ensure it never overflows during any single "turned-on" period of time and the Clock starts at 0 on reset.

Fallibility of `Clock` methods for I2C RTC device driver implementations

First of all, thank you for taking the time to develop embedded-time!

I had a quick look at if implementing Clock for I2C RTC devices since I wrote a few drivers but at the moment this seems not possible due to fallibility of the methods.

As any interaction over I2C can fail for several reasons (there is undergoing discussion about this at rust-embedded/embedded-hal#229), the methods must provide a way to return an error.
This includes Clock::now() and by extension also Clock::delay().

For embedded-time I guess an associated Error type would make sense so that any implementation can have its own error definitions.

However, this would make sense if embedded_time::Clock implementations on top or I2C RTC device drivers are desired and may be unnecessary for HAL (timer/counter-based) implementations. This would be a project focus question.

Add lossless duration handling within a given `Clock` domain

Given the following call (which will not compile) where instant and now are two instants from SysClock:

SysClock::delay(instant.duration_since(&now).unwrap());

SysClock may have any Period/precision, but the call of duraction_since() requires a Duration implementing type be specified. This is not optimal, particularly in generic or library situations as it requires an arbitrary decision about the precision.

Error trait is not easy to use in external device driver implementations

I implemented Clock::now() on my ds323x I2C/SPI RTC driver.
The implementation was quite straight forward, the whole thing is in this file: embedded_time.rs
And here is an example: embedded_time_linux.rs

However, this was only this easy after the removal of embedded_time::Error. Drivers are generic over the underlying peripheral error types. In this case I2C and I/O pin errors. Normally no requirements are made on these generic types and the error type looks like this for example:

#[derive(Debug)]
pub enum Error<CommE, PinE> {
    /// I²C/SPI bus error
    Comm(CommE),
    /// Pin setting error
    Pin(PinE),
    /// Invalid input data provided
    InvalidInputData,
}

Since the Error trait requires Debug and Eq, this requirement needs to be propagated to the generic error types and this is not very good because these are implementation-defined and are sometimes not implemented, making it suddenly impossible to use the driver if the MCU error implementation does not implement Eq, for example.

Implement TimeInt for unsigned ints

I'm trying to port our current time unit types in stm32f1xx_hal to use this crate. Ideally, We currently use u32 as the inner type for our types, which I think makes sense since negative frequencies, or durations to wait for a timer for example don't make much sense.

It looks like the TimeInt type requires From<i32> and Into<i64> to be implemented for the inner type. Is there a reason for that?

Remove auto-widening in some operations

I thought that auto-widening of values to prevent failures was a good move, but it causes terrible code bloat. The user should be able to choose whether it's worthwhile bringing in 64-bit arithmetic.

Non-blocking interface for `Timer::wait()`?

I think it could be useful for the Clock::delay() method to have a non-blocking interface like: fn delay<Dur>(dur: Dur) -> nb::Result<(), ()>.
This would return Err(nb::Error::WouldBlock) as long as the delay is ongoing and Ok(()) once finished.

This would give the user more control on polling and what to do while waiting and is the usual interface for other blocking methods (see here for example).

However, I am unsure if it would be possible to implement this in the provided Clock::delay() method implementation since the start would need to be kept constant until completion.

Fails to compile with the latest nightly compiler

Using the latest nightly rust compiler (rustc 1.49.0-nightly (dd7fc54eb 2020-10-15)) this library fails to compile with the following errors:

    Checking embedded-time v0.10.0
error[E0284]: type annotations needed: cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`
   --> C:\Users\N3xed\.cargo\registry\src\github.com-1ecc6299db9ec823\embedded-time-0.10.0\./src\instant.rs:182:66
    |
182 |         if add_ticks <= (<Clock::T as num::Bounded>::max_value() / 2.into()) {
    |                                                                  ^ cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`

error[E0284]: type annotations needed: cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`
   --> C:\Users\N3xed\.cargo\registry\src\github.com-1ecc6299db9ec823\embedded-time-0.10.0\./src\instant.rs:220:66
    |
220 |         if sub_ticks <= (<Clock::T as num::Bounded>::max_value() / 2.into()) {
    |                                                                  ^ cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`

error[E0284]: type annotations needed: cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`
   --> C:\Users\N3xed\.cargo\registry\src\github.com-1ecc6299db9ec823\embedded-time-0.10.0\./src\instant.rs:273:60
    |
273 |             .cmp(&(<Clock::T as num::Bounded>::max_value() / 2.into()))
    |                                                            ^ cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`

error: aborting due to 3 previous errors

For more information about this error, try `rustc --explain E0284`.
error: could not compile `embedded-time`

I could help to implement a fix, but I have absolutely no idea what causes these compile errors. This could also be a bug in the nightly rust compiler (but I don't know, so I'm posting it here).

Breaks docs.rs build

It looks like the upgrade from Rust nightly 1.48 to 1.49 (which docs.rs uses) breaks the docs build now. To my understanding, this comes from embedded-time:

error[E0284]: type annotations needed: cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`
   --> /home/jreimann/.cargo/registry/src/github.com-1ecc6299db9ec823/embedded-time-0.10.0/./src/instant.rs:182:66
    |
182 |         if add_ticks <= (<Clock::T as num::Bounded>::max_value() / 2.into()) {
    |                                                                  ^ cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`

error[E0284]: type annotations needed: cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`
   --> /home/jreimann/.cargo/registry/src/github.com-1ecc6299db9ec823/embedded-time-0.10.0/./src/instant.rs:220:66
    |
220 |         if sub_ticks <= (<Clock::T as num::Bounded>::max_value() / 2.into()) {
    |                                                                  ^ cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`

error[E0284]: type annotations needed: cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`
   --> /home/jreimann/.cargo/registry/src/github.com-1ecc6299db9ec823/embedded-time-0.10.0/./src/instant.rs:273:60
    |
273 |             .cmp(&(<Clock::T as num::Bounded>::max_value() / 2.into()))
    |                                                            ^ cannot satisfy `<<Clock as Clock>::T as Div<_>>::Output == <Clock as Clock>::T`

Add selective wrapping arithmetic

Wrapping arithmetic is needed where a counter may roll over at some point. Using wrapping arithmetic, allows for the full width of the counter to be considered regardless of the current count value.

Looking at the RTFM CYCCNT implementation as a reference

  • Instance is a i32
  • Duration is a u32

Wrapping arithmetic is used when finding the difference of two instances. This is a signed wrapping_sub and panics if the result is negative (in the expression I1-I2, I1 must be a "later" instance than I2. In what situations could the wrapping_sub mis-interpret the ordering of the instances?

When adding or subtracting a duration from an instance, the duration (stored as a u32) must be less than i32::MAX and is interpreted as an i32 for the wrapping arithmetic. So why is it stored as a u32 in the first place? (#7)

Wrapping arithmetic is only used when working with instances. This is because it is the instances that are derived from some counter(s) that might wrap around.

Modify RTFM and complete example for nRF52-DK using embedded-time

Mostly, this involves modifying how RTFM interfaces with the System Timer (eg. SysTick). Currently, it's a rather "raw" interface requiring the Monotonic implementation to implement a From in which the value is converted to System Timer ticks. See rtic-rs/rtic#309.

embedded-time must first support simple comparisons and conversions not just among time units (seconds, milliseconds, etc.) (implemented transparently) but also among different underlying integer types. The latter must be done manually using the custom TryMapFrom and TryMapInto traits.

Reduce `Result` usage

Brought up in #51. This probably be largely achieved by using static enforcement. In addition, depending on the error, just panicing may be appropriate.

Unhappy with the second type parameter required on `Instant` type

I arrived at the current design after much churning and attempting different approaches. I've arrived at a structure of Instant<impl Duration<RepType>, RepType>. Since the impl Duration...` isn't allowed in many cases, it becomes this:

pub struct Instant<T, R>(pub T, PhantomData<R>)
where
    T: Duration<R>,
    R: TimeRep;

impl<T, R> Instant<T, R>
where
    T: Duration<R>,
    R: TimeRep,
{
    pub fn new(duration: T) -> Self
    where
        T: Duration<R>,
    {
        Self(duration, PhantomData)
    }

    pub fn duration_since_epoch(self) -> T {
        self.0
    }
}

This is the only way I found to bound T as implementing Duration.

I feel like there must be a better way.

Signed/Unsigned Inner Types

I began this journey after finding that the core::Duration was an unsigned value. I then investigated the time crate and found that while it does have signed Duration, it is focused more toward "wall-clock" time considerations. This led me to great this project.

I began my development with the use-case of a non-wrapping timer (typically chaining two 32-bit peripheral timers in a microcontroller). However, if this project is to be compatible with wrapping timers, that may fundamentally change its design.

Signed durations can be useful when calculating the difference between two instances. It allows the subtraction to be done in either order returning a logical result. For example, in the expression x - y, if x is earlier than y, a negative duration is returned. However, if wrapping timers are to be supported, a negative duration will never be returned as subtracting a "higher-value" instance from a "lower-value" assumes the timer wrapped, and a positive duration is returned.

add frequency / data rate types

These would obviously be some sort of reciprocals of the Period type. What functions/methods would be reasonable for these types?

  • division
  • multiplication
  • reciprocal to Period

It's unlikely that these values would not be whole numbers. Therefore, they could likely just be an integer tuple struct.

Add try_into_(rate/duration)

try_from_rate and try_from_duration are implemented, but it would be nice to provide the try_into_ options as well.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.