ChanServ changed the topic of #rust-embedded to: Welcome to the Rust Embedded IRC channel! Bridged to and logged at, code of conduct at
lockna_ has joined #rust-embedded
MikePanetta[m]1 has joined #rust-embedded
M762spr[m]1 has joined #rust-embedded
JamesMunns[m]1 has joined #rust-embedded
lockna has quit [Quit: ZNC 1.8.2 -]
dnm_ has joined #rust-embedded
M762spr[m] has quit [Ping timeout: 268 seconds]
JamesMunns[m] has quit [Ping timeout: 268 seconds]
MikePanetta[m] has quit [Ping timeout: 268 seconds]
dnm has quit [Ping timeout: 268 seconds]
jedugsem[m] has quit [Ping timeout: 268 seconds]
jedugsem[m] has joined #rust-embedded
<ryan-summers[m]> Generally the way I try to handle unwrapping is to unwrap it at the level where it should no longer fail. So in generic driver crates etc. I was pass errors outwards, but in the application-level code I usually end up unwrapping it there. Unwrapping it close to where it occurs has the benefit of showing you the exact failure in the panic information, as mentioned above
<ryan-summers[m]> Also a comment about the new Github discussions format for the meetings: I'm a big fan of it because I usually can't make the meetings but review the outcome during my work hours the next day. The discussions are a nice way for me to asynchronously follow up on things :)
<JamesMunns[m]1> <ryan-summers[m]> "Also a comment about the new..." <- There are usually meeting minutes pushed after every meeting too :D
<JamesMunns[m]1> that being said, discussions do make it easier to continue the discussion async after the meeting, so maybe there's some merit to that
<sourcebox[m]> ryan-summers: Regarding possible API changes in usb-device:
<sourcebox[m]> - Is it generally possible to determine which class has been polled?
<sourcebox[m]> - Would it be possible to get a frame count from the low-level driver?
<JamesMunns[m]1> downside: I have no idea if that's exportable for history reasons
<sourcebox[m]> Some background: I'm doing audio via isochronuous endpoints. The poll() function is called as many times as classes are serviced, but only if a stream is running. The processing for the audio class must ensure that it always runs at 1 ms intervals. Also, some detection if a stream has been started or stopped would be great to flush buffers of invalid samples.
<sourcebox[m]> Currently, I'm just measuring the time between the calls of poll(), but that's a bit hacky and also unreliable because the timing is somewhat weak due to interrupts etc.
<ryan-summers[m]> I'd be happy to review a PR. Indeed it seems odd that the call of classes poll() functions being irregular seems odd to me
<ryan-summers[m]> * I'd be happy to review a PR. Indeed it seems odd that the class poll() function is called irregularly
<sourcebox[m]> I have no PR on that. Just asking questions if something could be done to improve the current behaviour. The timing of the poll() function is of course somewhat instable, especially in an async context.
<JamesMunns[m]1> btw ryan-summers - dirbaio proposed some suggestions of how `embassy-usb` avoids the "lengthy propagation time" by splitting out the class impls from the HAL interfaces, might be worth scrolling back to catch that when it was discussed in the meeting
<JamesMunns[m]1> is the crate that HALs depend on, to allow for the logic level crate (e.g. embassy-usb) to be updated without requiring HAL changes
<sourcebox[m]> Maybe I've not described the problem in a good way: I have to handle all classes in a single call of poll(), but I don't know for which class the function is called.
Darius_ has joined #rust-embedded
Darius has quit [Ping timeout: 255 seconds]
Darius_ is now known as Darius
<BenPh[m]> James Munns: dirbaio bartmassey I've put up an RFC following up from our chat yesterday about having a common interface for time.
<BenPh[m]> * for time: https://github., * .com/rust-embedded/wg/pull/762
<BenPh[m]> My thinking adjusted slightly. I believe there is merit to the idea of a `Clock` trait being an extension of a `Counter` trait. A clock is assumed to be counting something that increments one by one, and has a specification baked into it that describes each count as a measure of time.
<BenPh[m]> This principal (1-by-1, always up, integer counting coupled to a specification of what is being counted) is quite common. pulse counting, time counting, etc. a counting peripheral would implement as a `Counter`, and perhaps even a `Clock`, given that it's a very common use case.
<JamesMunns[m]1> Nice! Definitely remind us to discuss that at the meeting next week.
Leon[m] has joined #rust-embedded
<Leon[m]> Just skimmed this doc.... (full message at <>)
Leon[m] is now known as leons
<BenPh[m]> leons: > <> Just skimmed this doc.... (full message at <>)
<leons> From a second look at it, perhaps you're sidestepping this issue entirely by only exposing scaled measures of time, but I know too little of the embedded HAL ecosystem to be sure :)
<leons> If this is still an issue, this doc might be worth a look too:
<leons> It's not perfect as evident by the PR I sent, but we put a whole lot of effort into trying to encapsulate all these semantics
<leons> Time is hard :)
<BenPh[m]> leons: not exactly sure what you mean by this comment.
<leons> BenPh[m]: Ah, I missed the `Clock: Counter` part. I was slightly confused by `TimeInt` vs. `Instant`, but presumably the `Clock` trait would still expose a `T: TimeInt` value. In that case my original comment still applies. That comment meant to say that if you didn't expose the `TimeInt` at all in the API and always scale and/or wrap internally, those timer-internals wouldn't leak to clients.
<leons> But doing wrapping / scaling internally for all cases does introduce a performance + code-size hit of course
<leons> s/Clock/Counter/
<BenPh[m]> So I bringi in the embedded-time::Clock trait in the RFC. in this case, it would be an extension of counter, and it would be constrained that when something that implements Counter is being extended to Clock, in the context of how embedded-time does it, it's Counter implementation would have to be reading something that implements TimeInt type.
<leons> gotcha, that makes sense. And then the implementation of `Clock` would take care of all the impossible-to-get-right conversion of the arbitrary `TimeInt` type towards a scaled time measure?
<BenPh[m]> The role of the embedded-hal traits wouldn't be to impose what the concept of time is, but rather, it should take a form such that when you're using a Clock extended Counter, you can use it in a platform agnostic way. It would be up to the evolution of the trait to define the appropriate constraints that encapsulate this requirement, and up to the implementors to abide by convention such that it your implementation does what it
<BenPh[m]> says on the box
<JamesMunns[m]1> Ben Ph left a fairly lengthly set of review comments.
<BenPh[m]> the fellas at esp-rs, to implement an embedded-hal clock, would have to work out how to read the register in its implementation, and how to setup so that when it reads a number, it understands how to map that to a time-reading.
<BenPh[m]> They could also, for example, implement `Counter` for their pulse counter peripheral.
<leons> ah, yes, that makes sense. And because implementers know the precise semantics of their hardware, they can know the corner-cases of their timer and implement the appropriate wrapping and scaling logic. Thanks for explaining this! (perhaps worth mentioning that the link to the Tock document I sent above takes a slightly different approach. It attempts to encapsulate the constraints of the underlying timer, but
<leons> leaves it up to the consumer of this interface to then convert a `Ticks` to a time value and handle wrapping. It's interesting to see how these different approaches play out in practice)
<thejpster[m]> does anyone know if panic-probe is known to be broken at the moment?
<thejpster[m]> probe-rs is no longer showing me stack traces when I panic
<thejpster[m]> * I panic on my nRF52.
<thejpster[m]> (in the middle of a training course .... that's on me for not checking before hand though!)
<dirbaio[m]> it works
<dirbaio[m]> * panic-probe itself works okay
<dirbaio[m]> the problem is when you build with optimizations, stacktraces get very unreliable
<thejpster[m]> ahh, hmm, maybe it was rustc updates. I am building Oz
<dirbaio[m]> it's never worked well with optimizations in my experience
<dirbaio[m]> neither with probe-run nor probe-rs (they have different unwinding impls)
<dirbaio[m]> if you have some example of something that could work better, or that did work with probe-run and not with probe-rs then i'd suggest filing an issue in probe-rs
<thejpster[m]> rustc 1.72 works as expected
<dirbaio[m]> interesting
<dirbaio[m]> ah I see you're already discussing it in
<BenPh[m]> <leons> " ah, yes,..." <- Yeah, that's the idea I'm going for. It's based on the same principle of the input pin trait. If you have one of them, you can get a Boolean on if it's high or not. If the platform decides to just go "lol, we just output randomly" then so be it.
<BenPh[m]> I'll give things a bit more time to discuss, then make a simple interface, fork the esp library and imple the clock counter for the timer periphs, much the same way that it's already done with their pin periphs.
<BenPh[m]> Might also do something similar with their pulse counter peripherals
<BenPh[m]> This all came from me writing a field oriented control library, but getting perplexed as to why there wasn't a standard time reader the way there is for pwm, I and O pins.
<BenPh[m]> * This all came from me writing a field oriented control library, and wanting for a standard time reader the way there is for pwm, I and O pins.
holo[m] has joined #rust-embedded
<holo[m]> Hey, do you think its possible to use this libs in no_std environment? (RP Pico W):
K900 has joined #rust-embedded
<K900> No
<K900> It's got like 100 dependencies
<K900> And most of them aren't no_std
<ryan-summers[m]> You can check the libraries main file to see if it says Cfg(no_std) to tell quickly as well
<K900> And there's Tokio in there
<ryan-summers[m]> (Which it doesn't have)
<K900> And reqwest
<JamesMunns[m]1> might be a better place to start
<JamesMunns[m]1> not exactly opentelemetry, but maybe close enough?
<holo[m]> Thanks will look. I want to send my metrics (temperature and others) to otpl collector or other mimir/thanos/prometheus to see them in grafana
<holo[m]> <JamesMunns[m]1> ""; <- Yep this looks like it do at least part of job. Anything for remote logging for no_std?
<holo[m]> Which i can store in loki? Or some other idea?
<K900> It's really not something you do on hardware like that
<K900> You have very limited resources
<K900> And any kind of logging will probably take up most of those
<holo[m]> Is there some standard way how to collect logs from embedded devices? To have them in one place?
<holo[m]> * Is there some standard way how to collect logs from embedded devices? To have them in one place
<JamesMunns[m]1> not generally, today
<JamesMunns[m]1> (or rather, there are a thousand standards, but no One True Standard)
<JamesMunns[m]1> holo[m]: defmt is the most common, but it usually requires a debugger, or a serial port/usb port for collection, and also requires decoding the messages.
<holo[m]> Thanks, will look closer for that metrics in that case. Logs will wait :)
<JamesMunns[m]1> <JamesMunns[m]1> "defmt is the most common, but it..." <- sorry, I should say is the most common *in embedded rust*
Klemens[m] has joined #rust-embedded
<Klemens[m]> In the usb-device lib ( one seems to dynamically allocate the endpoint numbers. Why is it done that way and what is the best way to find the endpoints?
<Klemens[m]> s/numbers/addresses/
<Klemens[m]> or is the idea to just seperate every functionality out into its own usb interface, and in the driver to just earch for the first in and the first out endpoint in those interfaces
<Klemens[m]> s/earch/search/
<AlexandervanSaas> diondokter: Is there a specific reason that functions in `sequential-storage` take an owed `Range<u32>` instead of a reference? I know this is cheap to clone but it's still a bit awkward because `Range` doesn't implement Copy.
<AlexandervanSaas> (The 2024 edition will add this)
<diondokter[m]> AlexandervanSaas: Yep! When 2024 comes around I'm gonna do a nice upgrade :D
<diondokter[m]> It uses the owned value because it's the conceptually 'right' way to use it. That the std doesn't agree (yet) is unfortunate
<AlexandervanSaas> Alright I guess that makes sense
firefrommoonligh has joined #rust-embedded
<firefrommoonligh> <BenPh[m]> "- how do people feel about extra..." <- > <> - how do people feel about extra dependencies: I would like to use `typenum` to represent compile-time values in a much more powerful way... (full message at <>)
<firefrommoonligh> Basically, I do stuff like this all the time;... (full message at <>)
<firefrommoonligh> * Basically, I do stuff like this all the time;... (full message at <>)
<AlexandervanSaas> I was wondering about the wear leveling strategy. Is that based purely on writing to the next page when the current one gets full or is there some randomness involved? In my use case I'm saving logs using a queue. The goal is to download and erase the stored logs before all pages are filled up. Does that mean the pages at the start of the range will get more wear than those at the end?
<firefrommoonligh> Note that there is internal code require to manage wraps based on interrupts, using an atomic u32
<firefrommoonligh> Basically, whenever the timer times out, an atomic var has to increment
<AlexandervanSaas> * diondokter: I was
<AlexandervanSaas> AlexandervanSaas: diondokter: pinging again, because I initial forgot to tag you
<diondokter[m]> Oh I was reading back the meeting of yesterday
<diondokter[m]> <AlexandervanSaas> "I was wondering about the wear..." <- The pages are used cyclically. So unless you manually erase all pages often (which would make it start at the first page again), all pages will see the same amount of erases
<diondokter[m]> So no randomness
<dirbaio[m]> "erase" as in pop all the keys, or "erase" as in do NOR flash erase?
<diondokter[m]> Ah yes, erasing ahead of time would cause more wear in the first pages
<AlexandervanSaas> I think ekv uses a random see to determine where to start.
<diondokter[m]> But I'd just pop them and keep using the queue as is. I see no reason why you'd erase the pages
<dirbaio[m]> ah okay so if you pop all the keys it still "remembers" where the last page was even if the store is empty?
<diondokter[m]> Yeah
<dirbaio[m]> should be OK then :D
<AlexandervanSaas> Ah okay so I could do a "delete" by popping all items instead of erasing
<diondokter[m]> Yep! That's the recommended way
<diondokter[m]> Pop was added before peek even
andreas[m] has joined #rust-embedded
<andreas[m]> <Klemens[m]> "or is the idea to just seperate..." <- Multiple interfaces can be assigned with a function. An endpoint address is advertised by the device using an endpoint descriptor. Endpoint descriptors are subordinate descriptors to an interface descriptor. Endpoint zero never has a descriptor, because every device must support endpoint zero. All this is part of the so called enumeration. It basically all depends on the device
<andreas[m]> class or whatever proprietary protocol a vendor chooses to implement.
<diondokter[m]> Btw, on the time in eh discussion, I just want to note that I've been able to use embassy-time everywhere with easy and fugit only in some places with annoyance.
<diondokter[m]> I'm actually not sure what we'd need a generic time for tbh. Want to delay? Well we already have that. So what else do you want to do with time generically?
<diondokter[m]> s/easy/ease/
<diondokter[m]> Also, with fugit you get the ticks combined with a fraction right? Pretty useless. You end up converting to and from millis, micros or seconds anyways. So the tick time may well just be invisible anyways
<diondokter[m]> * just be implicit/invisible anyways
<diondokter[m]> * Also, with fugit you get the ticks combined with a fraction right? Pretty useless. You end up converting to and from millis, micros or seconds anyways. So the tick duration may well just be implicit/invisible anyways
pkoevesdi[m] has quit [Quit: Idle timeout reached: 172800s]
<dirbaio[m]> there's a few use cases that aren't covered by `DelayNs`:
<dirbaio[m]> - wait for a timeout while also polling something else (can be done with async `DelayNs` and `select`, but not with the blocking `DelayNs`)
<dirbaio[m]> - measure the time something took
<diondokter[m]> Hmmm right
<diondokter[m]> I would not be against adding a clock to eh, but going all out with the (const/tyenum) generics doesn't help I think.... (full message at <>)
<dirbaio[m]> I think we should start with "what do drivers want?" and work backwards from there
<dirbaio[m]> not "what can the hardware usually do, let's traitify it"
<dirbaio[m]> nor "let's take fugit/embassy-time/embedded-time and traitify it"
<diondokter[m]> You two usecases are 'give me time', 'give me another time' and 'what's the difference between these times/instants?'
korken89[m] has joined #rust-embedded
<korken89[m]> <diondokter[m]> "Also, with fugit you get the..." <- The whole idea is that time can be in ticks while interfaces are in human units with optimal, compile time generated conversions. It guarantees minimal loss of precision for any given tick rate of a clock, at the cost of 2 generic parameters.
<diondokter[m]> s/You/Your/
<dirbaio[m]> "give me time" already implies there's a global freerunning clock
<dirbaio[m]> and it sort of implies it never overflows, or that "what's the difference" behaves reasonably under overflow
<diondokter[m]> Either global or through a clock trait
<dirbaio[m]> maybe that's already "too much"? ie more than what drivers need?
<diondokter[m]> Idk... I guess another way to do it is to start a timer that you can then query how much time has elapsed
<dirbaio[m]> yea
<dirbaio[m]> that's what I was thinking too
<dirbaio[m]> do drivers really need "Instant"?
<dirbaio[m]> perhaps "Duration" is enough
<diondokter[m]> I think the instant should be the same as in std as in you're not really able to query anything about it. Only the difference between two instants is valuable
<korken89[m]> dirbaio[m]: You can't implement drift-less systems without an instant to compare against and monotonically increase
<korken89[m]> Processing delays accumulate
<diondokter[m]> diondokter[m]: This could be implemented by a global running embassy-time-like clock, or actual hardware timers
<dirbaio[m]> korken89[m]: drivers still could do that by getting a timer, starting it and then never touching it
<dirbaio[m]> then they'd have to assume it never overflows tho
<korken89[m]> Then it's an instant, not a duration
<diondokter[m]> Yeah, the overflows are hard to solve
<korken89[m]> Unless you define duration as duration since time 0
<dirbaio[m]> well it's a "Duration since I started the timer"
<dirbaio[m]> how often do drivers need to keep a freerunning timer "forever", though?
<korken89[m]> And that's the instant of the timer, given the start of it's epoch
<korken89[m]> Which happens to be 0 in this case
<korken89[m]> E.g RTCs don't start at 0
<diondokter[m]> `clock.start_with_expected_max_running_time(Duration::from_hours(2))?` ???
<diondokter[m]> It'd give an error if it can't handle that much time without overflowing.
<diondokter[m]> But... probably not a nice API
<dirbaio[m]> dirbaio[m]: I think drivers mostly need time for "local" operations? like: wait for the chip to become ready which is indicated by a given GPIO going high , but fail with a timeout error if it takes more than 1s. In this case you just need the timer to not overflow for 1s, which is easy
<diondokter[m]> korken89[m]: The clock itself can start at any point as long as the trait impl keeps track of it
<diondokter[m]> clock.start() could simply get the current time and save it to compare with later
<diondokter[m]> drop(clock) would then just not stop the clock
<korken89[m]> diondokter[m]: Correct, and by that you have instants and durations. Instants being a point in time, and duration being differences of instants.
<dirbaio[m]> what are the use cases of Clock in a driver?
<korken89[m]> Just tying back to the "only durations" comment
<dirbaio[m]> "driver" as in "piece of code to drive some particular chip/sensor/display/whatever that's intended to be portable across HALs"
<dirbaio[m]> not "end-user code"
LucasVella[m] has joined #rust-embedded
<LucasVella[m]> Who sets the value of global_pointer$? If the linker, is it set in the ELF somewhere?
<LucasVella[m]> ah, sorry, this question is RISC-V specific, not rust specific
<diondokter[m]> dirbaio[m]: Measuring time between two or more events
<dirbaio[m]> you don't need a Clock for that, just a timer that you can restart/stop
<dirbaio[m]> start it on first event, measure it on second event
<diondokter[m]> Yeah, both timer and clock are very similar and one can always implement the other
<dirbaio[m]> yeah
<dirbaio[m]> but there's a few big differences: with timers"
<dirbaio[m]> - you only need Duration
<dirbaio[m]> - you can pretend overflows never happen (like, treat them as errors, instead of having to make math work across overflows which might silently get screwed up)
<dirbaio[m]> * but there's a few big differences: with timers
<dirbaio[m]> - you only need Duration
<dirbaio[m]> - you can pretend overflows never happen (like, treat them as errors, instead of having to make math work across overflows which might silently get screwed up)
<diondokter[m]> Real difference is:
<diondokter[m]> - Timer is start and stop and only durations
<diondokter[m]> - Clock is 'always running' (even if not true) and you can only get instants
<diondokter[m]> * Real difference is:
<diondokter[m]> - Timer is start and stop and only durations
<diondokter[m]> - Clock is 'always running' (conceptually) and you can only get instants
<diondokter[m]> True that timer could handle overflows as errors better
<firefrommoonligh> <dirbaio[m]> "and it sort of implies it..." <- Related: This can (in the case of how I do it, does), can be an abstraction where the hardware timer or w/e overflows, but the higher level interface you expose doesn't.
<BenPh[m]> Anything to do with implementation detail shouldn't be handled by embedded-hal. Expectations of behaviour should be encapsulated within the types and constraints, but detail is handled by those that want to make something available under a common interface
<firefrommoonligh> So, I would assume that the high level time interface doesn't overflow (maybe should be a u64 or higher??)
<firefrommoonligh> I kind of wonder if I should wrap the code I have in an Instant mimic
<firefrommoonligh> with .elapsed() etc
<dirbaio[m]> BenPh[m]: the API should impose enough restrictions on implementations so the result is "useful" for drivers
<dirbaio[m]> if you leave everything as implementation details, you can have "valid" implementations that completely fall apart when you try to use it with a timer
<diondokter[m]> firefrommoonligh: Yeah I feel like there's more value in an interface that's convenient and usable for for 95% of usecases than one that is harder to use and still only usable for like 98% of usecases
<dirbaio[m]> BenPh[m]: > <> This example:... (full message at <>)
<BenPh[m]> dirbaio[m]: Agreed.
<dirbaio[m]> so
<dirbaio[m]> if we go the "timer" route
<BenPh[m]> dirbaio[m]: Absolutely not. That deciaion is an impl detail.
<dirbaio[m]> then an impl that overflows in 10ms is compliant?
<dirbaio[m]> like a 16bit timer at 32mhz?
<BenPh[m]> * In reply to
<BenPh[m]> yes, implementations CAN do tricks to avoid overflow, but the question is does the trait mandate implementations do it?
<BenPh[m]> Absolutely not. That decision is an impl detail.
<diondokter[m]> Ok, I think you've convinced me the timer route is better (even though I like clock conceptually better)
<dirbaio[m]> IMO the trait contract has to say something about overflows
<dirbaio[m]> even if it's just a "guideline"
<BenPh[m]> dirbaio[m]: In the same way that something that implements an input pin that just randomly pick hi or low as the state, yeah.
<diondokter[m]> Maybe do it the same as SPI?
<diondokter[m]> You can give a driver a SPI with too high a frequency. It'll likely error.
<diondokter[m]> Maybe that's the same as giving a driver a timer with too short an overflow count
<dirbaio[m]> for example, it could say something like "durations have a maximum value past which they overflow. This value is implementation-defined, but it's recommended it is at least a few seconds. Implementations should document the maximum duration, drivers requiring a long maximum duration should document it so users can pick a compatible implementation"
<BenPh[m]> ...if it compiles, it's valid. It's on the implementor to write a quality implementation.
<diondokter[m]> I think it'd be very valuable that an overflow would return an error
<diondokter[m]> BenPh[m]: I don't think this is ergonomically possible
<dirbaio[m]> BenPh[m]: you can't do "if it compiels it's valid" if an impl is allowed to overflow after 5 seconds but a driver is allowed to expect it won't overflow for at least 1min"
<dirbaio[m]> > <> ...if it compiles, it's valid. It's on the implementor to write a quality implementation.
<dirbaio[m]> * you can't do "if it compiels it's valid" if an impl is allowed to overflow after 5 seconds but a driver is allowed to expect it won't overflow for at least 1min
<dirbaio[m]> s/compiels/compiles/
<diondokter[m]> diondokter[m]: You'd need the duration type to be an associated type of the trait. Possible, but very annoying to deal with in practise
<diondokter[m]> * the trait to account for the bit widths. Possible,
<dirbaio[m]> diondokter[m]: even if you do that, you have to make overflows runtime errors
<BenPh[m]> dirbaio[m]: In that case, there would need to be a system of constraints where the trait encapsulates an overflow, and allows usage contexts to only use an implementation if the overflow is within spec.
<dirbaio[m]> BenPh[m]: ... via typenum? :D
<BenPh[m]> Can certainly be done with typenum.
<dirbaio[m]> I'm not convinced that's workable
<BenPh[m]> Add an associated type named "overflow point" or something. Using the trait, "where clock::overflow < $VALUE"
<BenPh[m]> Easy peasy with typenum.
<dirbaio[m]> what if the max timeout is only known at runtime? like, it's configurable by the user, or it's the result of some complex calculation you can't do at compile time?
<BenPh[m]> Then you would have to fall back to a runtime implementation detail
<dirbaio[m]> then it's not "if it compiles, it works" anymore
<dirbaio[m]> (fwiw I think "if it compiles it works" is not doable, we shouldn't pursue it)
<dirbaio[m]> (but that means we have to do tradeoffs, and the tradeoffs are nonobvious)
<BenPh[m]> !works !=> !valid
<BenPh[m]> That would be a bug in the implementation, and that's perfectly acceptable.
<BenPh[m]> At least, at he trait design level, assuming that the trait design is t a contributing factor
<BenPh[m]> s/is/isnt/, s/t//
<dirbaio[m]> not sure I follow with that distinction. the end goal is to make things that work. "valid but doesn't work" is functionally the same as "not valid".
<diondokter[m]> Tbh, do we need more than something like this?... (full message at <>)
<diondokter[m]> s/micros/elapsed_micros/, s/millis/elapsed_millis/, s/secs/elapsed_secs/
<BenPh[m]> In that case, the design of the input pin trait needs a revamp, because there's nothing stopping me from defining the is-high method as the output of an rng
<BenPh[m]> Some random impl that flips a coin for the input pin trait is valid, but doesn't work.
<diondokter[m]> Like using SPI as analogy again, we don't have an associated const for the frequency.
<diondokter[m]> Drivers cannot do `where FREQ > 1000, FREQ < 32000000`
<dirbaio[m]> BenPh[m]: it's not valid, it doesn't comply with the contract in the trait
<BenPh[m]> All the spi trait does is offer a common interface, so that you don't need to be platform aware when using the spi. If the trait is too restrictive, that's okay.
<dirbaio[m]> the question here is what should the Timer contract say
<dirbaio[m]> Option 1: it never overflows (forces impls to use u64 durations, interrupts to count overflows to "extend" the timer range)
<dirbaio[m]> Option 2: It overflows after an implementation-defined amount of time.
<BenPh[m]> dirbaio[m]: How so? The contract is to implement a required method that outputs a bool. By convention, the source of that bool is an input pin, but nothing's stopping you from compiling with a coin flipper instead
<GrantM11235[m]> The documentation of the trait is part of the contract, it's not just the function signatures
<BenPh[m]> dirbaio[m]: > <> Option 1: it never overflows (forces impls to use u64 durations, interrupts to count overflows to "extend" the timer range)
<BenPh[m]> Option 3: leave it up to the implementor to choose implementation detail
<BenPh[m]> > Option 2: It overflows after an implementation-defined amount of time.
<dirbaio[m]> BenPh[m]: The "contract" includes the trait's documentation, that says the return value should be whether a physical gpio pin is high. Anyway I don't think this conversation is going to lead to fruitful outcomes. you know perfectly what I mean. This is just splitting hairs and arguing on technicalities
<dirbaio[m]> BenPh[m]: This is just option 2. the "implementation-defined amount of time" can be "infinite time"
<BenPh[m]> dirbaio[m]: There's your answer, I spose. If it's a question about implementation detail, it's a question for a platform/hardware/driver specific hal. The role of an embedded-hal feature is to allow platform independent usage of platform specific code, much the way it currently happens with InputPin
<BenPh[m]> At least, that's the understanding I've come to
<dirbaio[m]> I'll spell it again for you: you can't do "platform-independent usage" if you want to run a timer for 10s and the platform-dependent impl might overflow in 1s.
<GrantM11235[m]> InputPin works because driver authors and hal authors agree on what is_high and is_low are supposed to mean
<BenPh[m]> Thats an implementation detail though. If you choose an impl that is not fit for purpose, then that is outside the scope of the design of the trait.
<dirbaio[m]> so we have to either accept the trait will be somewhat platform-dependent (option 2, overflow after a platform-dependent amount of time), or force it to be truly platform-independent (option 1, force all impls to use u64 and overflow extension)
<dirbaio[m]> you keep arguing we should make the trait platform-independent
<dirbaio[m]> yet arguing we should let the platform-dependent detail of the overflow leak through it
<dirbaio[m]> which one is it
<dirbaio[m]> pick one :D
<dirbaio[m]> we can't have both
<BenPh[m]> It's behaviour is based on the implementation. The usage is commen across impls, because its a common interface
<BenPh[m]> If you are running a system that requires a certain time before overflow, then it's on you to choose a clock that meets specs. If you choose to feed an inadequate clock into a function that is `fn no_overflow<C: Clock>(c: C)` then that's on the end user.
<diondokter[m]> Was wondering what the old timer did and this is just a countdown:
<diondokter[m]> BenPh[m]: So you're arguing option 2 here? Runtime overflow error?
<BenPh[m]> Timer and clock are related but different. Timer is an interrupt generator. Clock is a specific type of counter.
<dirbaio[m]> BenPh[m]: so, option 2. so you're OK with the trait being somewhat platform dependent. thanks for clarifying.
dnm_ is now known as dnm
<GrantM11235[m]> If all we care about is a "common interface", we can replace all of embedded-hal with this trait 🤣... (full message at <>)
<diondokter[m]> BenPh[m]: Timers don't have to have interrupts though
<BenPh[m]> The only difference between an incremental encoder and a clock, is that is being counted, and an encoder can be configured to go nackwards
dnm has quit []
dnm has joined #rust-embedded
<dirbaio[m]> GrantM11235[m]: don't forget about `AsyncDoSomething`!
<diondokter[m]> dirbaio[m]: I'm still a hardcore `nb` user. Don't forget a non-blocking impl!
<GrantM11235[m]> dirbaio[m]: No need, just use `DoSomething<(), impl Future<Output = ()>>` !
<diondokter[m]> GrantM11235[m]: Lol that's actually true
<BenPh[m]> diondokter[m]: Maybe we've got a mismatch in vocab. For me, clock is a peripheral that just does a simple ++ on a register at a defined frequency
<dirbaio[m]> all chips call that a "timer"
<dirbaio[m]> * almost all chips
<diondokter[m]> Dario and I were discussing the differences in what we call a clock and a timer
<diondokter[m]> <diondokter[m]> "Real difference is:..." <- > <> Real difference is:... (full message at <>)
<BenPh[m]> Okay. That clears things up.
<GrantM11235[m]> A non-freerunning timer is interesting because the hal could ref-count all the active timers and shut down the hardware timer when it's not in use
<diondokter[m]> The timer could also be built on e.g. embassy-time
<diondokter[m]> Or the monotonic of RTIC
<BenPh[m]> The encapsulation of reading a time-counter deserves to be standardised though.
<diondokter[m]> Yeah, we agree. But the details are important here
cr1901_ is now known as cr1901
<GrantM11235[m]> Would it be useful for powersaving if embassy-time was allowed to shut down the hardware timer when it's not in use? I guess the only time it could shut it down for an extended period of time is if you are waiting for an external interrupt without a timeout
<diondokter[m]> It's used for real time since startup though
<diondokter[m]> That'd break then
<dirbaio[m]> yeah it can't as long as Instant has to be "time since boot"
<GrantM11235[m]> Yeah, it would require removing instant
<diondokter[m]> If you couldn't query the current time and only get relative times between two instants, then I guess something like that could be done
<dirbaio[m]> and anyway if you use an embassy-time impl from a low-freq low-power timer source (like RTC in the nRF) it barely adds any current consumption over the core/ram.
<BenPh[m]> This is actually a good point. Some clocks stop counting time in sleep mode. They are documented as such. It's still perfectly valid for these to be used as clocks. Just don't expect to use it as a wall clock if you sleep.
<dirbaio[m]> probably the same with STM32 LPTIM from LSE/LSI
<GrantM11235[m]> I guess the use case would be to have accurate relative times with a high frequency timer, but use the RTC for absolute/wall time
<GrantM11235[m]> But for that case I guess it is best to use the RTC for embassy-time and use another timer in non-embassy-time mode
<diondokter[m]> <diondokter[m]> "Tbh, do we need more than..." <- > <> Tbh, do we need more than something like this?... (full message at <>)
<GrantM11235[m]> That would require some extra math at runtime, but maybe that's not a dealbreaker
<diondokter[m]> GrantM11235[m]: Well the thing is that if you keep it all being ticks, what do you then do with it?
<diondokter[m]> Say I'm a driver and measure the time and get back it was 12345 ticks. What would I do with that information?
<diondokter[m]> If that's 50ms, then suddenly it has some value
<diondokter[m]> > <> That would require some extra math at runtime, but maybe that's not a dealbreaker... (full message at <>)
<BenPh[m]> diondokter[m]: > <> Well the thing is that if you keep it all being ticks, what do you then do with it?... (full message at <>)
<diondokter[m]> BenPh[m]: My point is that you always have to translate ticks to a 'real' time value like milliseconds, even if the trait specifies the tick time
<dirbaio[m]> anyway, Ben Ph seems we agree about overflows. if you want to move this forward i'd suggest working more on the RFC as James said. It's somewhat incomplete. We need the full trait definitions (and ideally a PoC HAL impl and a driver using it) to evaluate it.
<dirbaio[m]> also, I don't think abstracting a "counter in general" is a good idea. Drivers need "a timer that measures time", or "a counter that counts rising edges on a pin" or "a distance sensor", but never "a thing that can count .... something implementation defined that I don't know what it is"
<diondokter[m]> dirbaio[m]: > <> perhaps this?... (full message at <>)
<diondokter[m]> But the general shape is good
<dirbaio[m]> in other words, there's no utility in a driver being able to do `struct MyDriver<T: Counter> {..}`. You could instantiate that driver with a time counter, an ampere counter, a gpio rising edge counter. What's the driver going to do with it?
<BenPh[m]> I disagree. a clock is a form of counter. It just has some extra expectations, those being that the count is always incrementing, and that each increment correcsponds with a defined duraation.
<dirbaio[m]> I agree a timer is a counter
<dirbaio[m]> I don't agree a driver should be able to say "gimme a counter, ANY kind of counter you want, and i'll do something useful with it"
<diondokter[m]> Yeah, gotta focus on time
<diondokter[m]> Hence timer instead of counter
<GrantM11235[m]> If you just need to get the value of *something* you can use `impl FnMut() -> u32`
<BenPh[m]> it would be up to the driver to constrain the counter. it would say "give me a counter, but only counters where the thing being counted is distance" or "give be a Clock, because i need a counter that incrementally counts discrete units of time"
<dirbaio[m]> if all uses of Counter are going to be constrained, what's the point of having a Counter trait?
<diondokter[m]> In a counter trait, how would you even encode what the time per count tick is?
<BenPh[m]> because the nature of what's being counted matters. I could write this... (full message at <>)
<BenPh[m]> * because the nature of what's being counted matters. I could write this... (full message at <>)
<dirbaio[m]> you're not using Counter anywhere in that example
<BenPh[m]> key point being is that Clock is a countor that counts discrete intervals of time. PosCounter would be an implementatino of a counter for something like an AB encoder that corresponds with something that moves.
<BenPh[m]> ~~implementation~~ extension of a counter
<dirbaio[m]> my question is why do we need Counter
<dirbaio[m]> instead of just trait Clock and trait PosCounter, that don't have any Counter as supertrait
<diondokter[m]> I'm not following. Are you arguing for two traits, Counter and Clock?
<BenPh[m]> If you read the RFC document, it's should all be explained in there.
<diondokter[m]> Got a link? Or are you still working on it?
<BenPh[m]> > Initially intended as a Clock rfc, my thoughts evolved while writing. Though this could be returned to a simple Clock RFC, a clock interface is nothing more than
<BenPh[m]> Reading a generic integer value that increments 1 at a time. For a clock, this is an oscilation count
<BenPh[m]> A specification of a measurement assosciated with each increment. For a clock, this is a duration of time
<dirbaio[m]> the RFC doesn't answer my question
<diondokter[m]> Thanks!
<dirbaio[m]> why is option A better?
<dirbaio[m]> What can you do with option A that you can't do with B
<dirbaio[m]> in other words, why do we need Counter?
<BenPh[m]> because option B says "these are two completely different things" option A says "Clock is one specific extension of counting behavior, and PosCounter is another specific extension of counting behavior"
<dirbaio[m]> please give a concrete example with code
<dirbaio[m]> of a driver that would be possible to write with A, but impossible to write with B
<BenPh[m]> Conceptually, there is nothing different between counting time-steps and position steps. one just lost the ability to go back, and the thing being counted is different.
<dirbaio[m]> the goal of embedded-hal is not abstracting concepts for the sake of it. It's enabling writing drivers that are portable across HALs.
<dirbaio[m]> therefore if everything you can do with A you can also do with B, we should do B
<dirbaio[m]> because it's much simpler
<BenPh[m]> dirbaio[m]: I reject the apparant premise of this requirement. Traits encapsulate behavior. Option B misses the point entirely that counting has a common core of behavior, and changing the thing you're counting is an act of extending the scope of what is being encapsulated.
<GrantM11235[m]> The spi and i2c traits are both about transferring data, but they don't have a common supertrait
<dirbaio[m]> then we're on complete different planes of existence on our opinions on what embedded-hal's goals are
<BenPh[m]> what am I missing?
<dirbaio[m]> dirbaio[m]: this
<dirbaio[m]> the goal of embedded-hal is makeit possible to drivers that are portable across HALs.
<dirbaio[m]> nothing more
<dirbaio[m]> * the goal of embedded-hal is make it possible to drivers that are portable across HALs.
<dirbaio[m]> * the goal of embedded-hal is make it possible to write drivers that are portable across HALs.
<BenPh[m]> ...fair
<dirbaio[m]> all traits in embedded-hal should be a direct response to "a driver needs to do X"
<dirbaio[m]> possible justifications for a Timer/Clock trait are: "a driver needs to measure time between a pin going high and then back low". "A driver needs to wait for at most 1s for the READY pin to go high or fail with a timeout"
<dirbaio[m]> possible justifications for a Counter trait are ....?
<BenPh[m]> I'll go with option B
<dirbaio[m]> * the goal of embedded-hal is to make it possible to write drivers that are portable across HALs.
<BenPh[m]> For me, the justification of Counter is that in entrenches the notion that it is encapsulating counter behavior, and Clock is merely one extension of that encapsulation that corresponds with counting time, thus it would have some extra details such as methods to map the count to milliseconds (for example), and the scaling factor between the count and time.
<dirbaio[m]> that's not "a driver needs to do X"
<dirbaio[m]> that's abstracting for the sake of abstracting
<BenPh[m]> "A drinver needs to perform a read of a value that holds a count, and interprate that value in a defined manner"
<BenPh[m]> be that definition time, distance, or whatever.
<dirbaio[m]> a driver never needs to read a count of "something it has no idea what it is because it could be whatever the user put in the generic param"
<diondokter[m]> Why do we need an eh trait for that? The driver could take a `impl FnMut() -> u32` too that would do the same thing
<BenPh[m]> dirbaio[m]: then it wouldn't be much of a driver, lol. that would be like writing a driver for a clock peripheral and not having any way to determine how many ticks make up a milliseconds :P
<BenPh[m]> ...but I see your point.
<dirbaio[m]> that's exactly what i'm saying
<dirbaio[m]> a driver will never do where T: Counter
<dirbaio[m]> therefore Counter is not useful
<jannic[m]> What are the drivers that would actually use such traits? I don't think the lack of a common API would prevent writing those drivers, they would just be less uniform. But they should still exists. Are there any examples on the awesome-embedded-rust list?
<BenPh[m]> anything that needs to encapsulate the notion of a count of somethnig in discretem measures.
<BenPh[m]> time and distance are the two that come to mind.
<BenPh[m]> Would love pulse counter to be able to just be "something that implements a trait that counts a thing, where that thing encapsulates the notion of distance" and the time_source to be the same, but it's counting a measure of time.
<jannic[m]> But is there any existing one? A concrete example? Not "anything that...".
<dirbaio[m]> that's a "distance counter", not a "counter"
<diondokter[m]> BenPh[m]: I think in 'ideomatic embedded-hal' this would be two separate traits. Timer and DistanceMeasurer.
<diondokter[m]> For both the drivers would not care about the actual count value, only the higher level elapsed time and measured distance
<diondokter[m]> BenPh[m]: > <> so ideally, that would be:... (full message at <>)
<diondokter[m]> Also, a counter can be modelled very easily in other ways.... (full message at <>)
<BenPh[m]> if we go option A, it would be managed in a way kinda like this:... (full message at <>)
<BenPh[m]> the clock count would be the same, but the Output would be constrained and defined to be a time-domain encapsulation, and a measure-of-time encapsulation.
<BenPh[m]> ...but I would say "don't do that, just use a clock, which already extends the definition of a counter to be a counter of time, you just need to specify the length of time for each increment"
<dirbaio[m]> that's incredibly complicated
<dirbaio[m]> please, focus on just time
<dirbaio[m]> there's no way we're merging such an over-abstracted api
<dirbaio[m]> focus on what drivers need out of timers/clocks and make a really good trait that solves that the most effective and simple way possible
<BenPh[m]> I think you've been more than generous with your patience and dealing with my pushback. I'll go ahead with that.
<diondokter[m]> The nerd in me does like playing with the typesystem like that.... (full message at <>)
<diondokter[m]> BenPh[m]: I do want to thank you putting in the effort! It's good to have opinions of lots of people
<diondokter[m]> * thank you for putting in
<dirbaio[m]> <diondokter[m]> "Tbh, do we need more than..." <- > <> Tbh, do we need more than something like this?... (full message at <>)
<dirbaio[m]> can't get simpler than that
AlexandrosLiarok has joined #rust-embedded
<AlexandrosLiarok> It could be useful to have a Counter trait imo, you could then write say a general averaging wrapper if constraining the rep to be convertible to f32
<dirbaio[m]> and it mirrors DelayNs nicely
<diondokter[m]> Yep. Most important decision is how to do the error type and whether to use u32 or u64
<dirbaio[m]> i'd say u32? the Delay trait also uses u32
<diondokter[m]> Fair
<diondokter[m]> Maybe start should be a result too to support external timers
<dirbaio[m]> if Delay is infallible I think Timer should also be
<dirbaio[m]> (except overflow ofc)
<diondokter[m]> BenPh[m]: With that you can get in trouble by a driver requiring u32 and a HAL only exposing u64
<dirbaio[m]> dirbaio[m]: like, we already had the "but what if someone wants to use an external timer that you access over i2c?" conversation for DelayNs, and the conclusion was "nobody does that, all mcus have some internal timer you can use"
<BenPh[m]> A trait that only allows you to measure time in u32 or/XOR u64 is only viable on HALs that are the same.
<BenPh[m]> even worse, it would preasure future hal implementations into using a type that is not ideal.
<BenPh[m]> I think there is scope to restricting to a primitive integer only (perhaps only unsigndes), but ideal, it could be a `String` for all it matters. (probably not string, for reasons that the RFC document gois into)
<diondokter[m]> But the goal is to get a layer that makes stuff work together. Mandating the size of the integer helps with that
<BenPh[m]> dirbaio[m]: there are, however, RTC clocks that count in increments of like milliseconds for u64, and have their own battery. They might be spi based. They are valid candidates for a Clock implementation.
<dirbaio[m]> yeah. you either mandate a fixed u32/u64/whatever, or you have to do a Duration associated type that all drivers must do math with
<dirbaio[m]> BenPh[m]: don't these usually count in Y/M/D H:M:S format? so you have to do a lot of math to convert to ticks
<dirbaio[m]> and you'd use these to track wall-clock time. so you wouldn't want a random driver to just .restart() it at any time
<diondokter[m]> dirbaio[m]: For countdown timers it would make sense to support this. (But I guess that's already what delay is...)
<BenPh[m]> dirbaio[m]: not necisarily. e.g. you could set them to correspond to a POSIX time stamp at some point, then each time you boot up, you read it to get a RTC timestamp that then allows you to do the math to turn that into a date representation.
<dirbaio[m]> do you have a link to some datasheet of an external RTC chip that counts ticks instead of Y/M/D H:M:S?
<diondokter[m]> Hmmm let's take a step back. What does the driver want? Any timer or a specific timer?
<diondokter[m]> If it's a specific timer, like an external RTC, then that's not very generic of the driver.
<diondokter[m]> So really it's any timer and since so many microcontrollers have many timers, you'd just use an internal (infallible) one
<dirbaio[m]> yep
<diondokter[m]> It'd be different if we wanted to have a 'clock' trait that gives back the current date or time since boot. For that it's super reasonable to use an external RTC
dne has quit [Remote host closed the connection]
<dirbaio[m]> if it's since boot, why would you need externl?
<dirbaio[m]> s/externl/external/
<diondokter[m]> Depends on the definition of boot :P
<diondokter[m]> The boot of the mcu or the board?
dne has joined #rust-embedded
<dirbaio[m]> hmhmhmhm
<dirbaio[m]> you can sometimes arrange for internal timers to not get reset on mcu reset. or you can save their state through ram and immediately reload it after reset
<dirbaio[m]> external only makes sense if you want it to have its own battery backup
<dirbaio[m]> which I think is only used if you want to keep actual wall-clock time while you're powered off?
<diondokter[m]> Yep, we had that battery backed wall time
<dirbaio[m]> "battery-backed time since boot" is a bit weird
<diondokter[m]> Hmmm idk. In any case, the interface for wall-time and time since boot would be the same
<dirbaio[m]> no!
<dirbaio[m]> one would return Y/M/D H:M:S, the other would return a tick count
<diondokter[m]> Well...
<diondokter[m]> Time since boot can also be Y/M/D H:M:S
<diondokter[m]> Depends whether that includes leap year stuff or not haha
<diondokter[m]> So yeah... probably better to not make that the same interface I guess
<dirbaio[m]> diondokter[m]: how? "1 month" aka "0000/01/00 00:00:00" makes no sense. it could be 28 days, 29 days, 30 days, 31 days
<diondokter[m]> True
<BenPh[m]> <diondokter[m]> "Hmmm let's take a step back..." <- > <> Hmmm let's take a step back. What does the driver want? Any timer or a specific timer?... (full message at <>)
<diondokter[m]> It's late haha
<diondokter[m]> I need to go to bed
<diondokter[m]> BenPh[m]: But I don't care about the count though. I care about the elapsed milliseconds
<BenPh[m]> ...but were not going to go down that road.. at least not for now.
<diondokter[m]> :P
<BenPh[m]> that's why you would extend the Counter trait in such a way that you can encapsulate the counter as a thing that can count in the domain of elapsed ms
<BenPh[m]> but like i said, that's been ruled out.
<diondokter[m]> diondokter[m]: I'm gonna act on this...
<diondokter[m]> Been good talking to y'all!
<diondokter[m]> Have a good night! (Or other current part of the day in your timezone :P)
<firefrommoonligh> Well, y'all (especially Ben) have inspired me to clean up my timestamp API on several code bases
<firefrommoonligh> Also of note, Duration is part of core
<firefrommoonligh> Now using a custom Instant in a similar way to std::Instant
<firefrommoonligh> And added a free-standing timer-update-interrupt-clear fn
<dirbaio[m]> core::duration is u64 seconds, u32 nanoseconds. it's way overkill
<firefrommoonligh> I think it's worse...
<firefrommoonligh> I think .as_micros() etc is giving u128s. Ask me how I know...
<firefrommoonligh> SO, concur on that lol
<firefrommoonligh> But decided to go with it anyway
<firefrommoonligh> I currently have u128 for the internal (ns) rep in Instant (overkill?), and u64 for internal us/ms reps, but this obviously conflicts with Duration...
<firefrommoonligh> (I mean, you can cast, so not a dealbreaker)
<firefrommoonligh> And u32 is probably fine for ms... IDK. I'm making this up as I go and figured why not keep things similar between embedded and PC...
<firefrommoonligh> Ooh - even millis is 128...
<dirbaio[m]> yeah, wow
<firefrommoonligh> So, I could make a custom Duration type that uses smaller integers, but... this feels harmless?
<firefrommoonligh> * I currently have u128 for the internal (ns) rep in Instant (overkill?), and u64 for methods to get us/ms reps, but this obviously conflicts with Duration...
dne has quit [Remote host closed the connection]
dne has joined #rust-embedded
dne has quit [Remote host closed the connection]
dne has joined #rust-embedded
xnor has quit [Ping timeout: 272 seconds]