-
Notifications
You must be signed in to change notification settings - Fork 69
Feature/embedded time #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- Updated all examples to use embedded-time - Updated all tests to use embedded-time
A step further is getting rid of |
@David-OConnor I was thinking the exact same thing. In that case interfaces would have to be changed (and not only the arguments). Unless panicking is acceptable in that case. |
I like the |
@strom-und-spiele agreed, I didn't like Instead, something like this maybe?
This looks a little bit better.
I can also use the
|
Nice. Thanks for thinking this over and proposing the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm (positively) surprised how little change is needed to adopt embedded-time. IMHO this speaks a lot for adopting it.
My change requests are minor nits and the conversion feature you proposed in #192 (comment).
I personally prefer having a lot of TODO boxes to check (and get a dopamine dose every time I do so) over missing on a change, so if I added redundant comments, the intention was to be helpful not to annoy. :)
So I have started changing functions taking a frequency to what I proposed above.
I'm lazy so I personally prefer calling this:
Over this:
Or this:
|
Is this finalized? Do you plan to make additional changes? |
@David-OConnor & @strom-und-spiele There is one last thing I was toying with and it has to do with the I'm really unsure about this though and maybe i'll just abandon that idea all together (in that case this PR is finalized). This example was written in another toy application so I didn't have access to the This is only an example. Timer / CountDown implementation
Instantiating the timer
Using the timer
Maybe I've missed the point regarding the |
Agree on duration instead of rate. The Hz abstraction doesn't even let you set a duration > 1s. For an example of how to set duration, ref my PR here: https://github.com/stm32-rs/stm32f3xx-hal/pull/166/files It doesn't use /// Set the timer period, in seconds. Overrides the period or frequency set
/// in the constructor.
/// This allows you to set periods greater than 1hz.
pub fn set_period(&mut self, period: f32, clocks: &clocks::Clocks) -> Result<(), ValueError> {
// PSC and ARR range: 0 to 65535
// (PSC+1)*(ARR+1) = TIMclk/Updatefrequency = TIMclk * period
// APB1 (pclk1) is used by Tim2, 3, 4, 6, 7.
// APB2 (pclk2) is used by Tim8, 15-20 etc.
// todo: It appears there's a (fixed?) 2x multiplier on APB1
// timers; it's twice `pclk1`. See clocks diagram in RM, or `Clock Configuration`
// tool in STM32CubeIDE.
let tim_clk = clocks.calc_speeds().pclk1 * 1_000_000. * 2.;
// We need to factor the right-hand-side of the above equation (`rhs` variable)
// into integers. There are likely clever algorithms available to do this.
// Some examples: https://cp-algorithms.com/algebra/factorization.html
// We've chosen something quick to write, and with sloppy precision;
// should be good enough for most cases.
// - If you work with pure floats, there are an infinite number of solutions: Ie for any value of PSC, you can find an ARR to solve the equation.
// - The actual values are integers that must be between 0 and 65_536
// - Different combinations will result in different amounts of rounding errors. Ideally, we pick the one with the lowest rounding error.
// - The aboveapproach sets PSC and ARR always equal to each other.
// This results in concise code, is computationally easy, and doesn't limit
// the maximum period. There will usually be solutions that have a smaller rounding error.
let max_val = 65_535;
let rhs = tim_clk * period;
// todo: Round instead of cast?
let arr = (rhs.sqrt() - 1.) as u16;
let psc = arr;
if arr > max_val || psc > max_val {
return Err(ValueError {})
}
self.tim.arr.write(|w| unsafe { w.bits(u32::from(arr)) });
self.tim.psc.write(|w| unsafe { w.bits(u32::from(psc)) });
Ok(())
}
/// Reset the countdown; set the counter to 0.
pub fn reset_countdown(&mut self) {
self.tim.cnt.write(|w| unsafe { w.bits(0) });
}
} You would need to do 3 things:
|
@David-OConnor Yeah I was looking at the issue you brought up regarding timer durations greater than 1 second. Another idea I've been playing with (not for this PR) but isn't even close to prime time is creating a The plan is to allow the user to configure the Example of complex setup:
Example of a simple setup:
This is still a pie in the sky idea, but I have started to try and map out the potential different branches one might take when building a |
I like that. I like how the Keep in mind the reason for simple though - having the standard |
@David-OConnor When you say "I'm not clear on how the existing method works, but it doesn't produce accurate results for me" are you talking about how the I agree, I also like the simple abstractions; the timer returned from the builder would still implement The |
Yes - this part: let frequency = timeout.into().0;
let timer_clock = $TIMX::get_clk(&self.clocks);
let ticks = timer_clock.0 * if self.clocks.ppre1() == 1 { 1 } else { 2 }
/ frequency;
let psc = crate::unwrap!(u16::try_from((ticks - 1) / (1 << 16)).ok());
// NOTE(write): uses all bits in this register.
self.tim.psc.write(|w| w.psc().bits(psc));
let arr = crate::unwrap!(u16::try_from(ticks / u32::from(psc + 1)).ok());
// TODO (sh3rm4n)
// self.tim.arr.write(|w| { w.arr().bits(arr) });
self.tim.arr.write(|w| unsafe { w.bits(u32::from(arr)) }); The formula that relates timer period(freq), ARR, PSC, and timer clocks is in a comment in my code above. I don't understand how this relates to it. |
I think there is a small issue in the
Also, switching the type for the I still convert the duration into a frequency
Anything greater than 1 second will return 0 for the frequency. Really the only solution I can think of for periods > 1s would be to do what you have already done with the |
Good catch on PCLK1. I can confirm from the Cube IDE that an APB1 prescaler above 1 causes the timer clock multiplier to be 2 instead of 1. Also, my code doesn't differentiate between timers using apb1 or apb2, although there's a comment indicating it. (These both work correctly in the original version) I think the sqrt approach I made works for long durations, but may not be precise enough for short ones, where you'd need to have one of the values (PSC, ARR) smaller than the other. I can't comment on the >1s issue, since I haven't looked at the Duration type you're using. |
edit: I understand your >1 s point. It's not about the edit: I think it makes sense to split the PSC and ARR calculation into a standalone, pure function, then call it from /// Calculate values required to set the timer period: `PSC` and `ARR`. This can be
/// used for initial timer setup, or changing the value later.
fn calc_period_vals(period: f32, clocks: &clocks::Clocks) -> Result<(u16, u16), ValueError> {
// PSC and ARR range: 0 to 65535
// (PSC+1)*(ARR+1) = TIMclk/Updatefrequency = TIMclk * period
// APB1 (pclk1) is used by Tim2, 3, 4, 6, 7.
// APB2 (pclk2) is used by Tim8, 15-20 etc.
// todo: It appears there's a (fixed?) 2x multiplier on APB1
// timers; it's twice `pclk1`. See clocks diagram in RM, or `Clock Configuration`
// tool in STM32CubeIDE.
let mult = if let clocks::ApbPrescaler::Div1 = clocks.apb1_prescaler {
1.
} else {
2.
}
let tim_clk = clocks.calc_speeds().pclk1 * 1_000_000. * mult;
// We need to factor the right-hand-side of the above equation (`rhs` variable)
// into integers. There are likely clever algorithms available to do this.
// Some examples: https://cp-algorithms.com/algebra/factorization.html
// We've chosen something quick to write, and with sloppy precision;
// should be good enough for most cases.
// - If you work with pure floats, there are an infinite number of solutions: Ie for any value of PSC, you can find an ARR to solve the equation.
// - The actual values are integers that must be between 0 and 65_536
// - Different combinations will result in different amounts of rounding errors. Ideally, we pick the one with the lowest rounding error.
// - The aboveapproach sets PSC and ARR always equal to each other.
// This results in concise code, is computationally easy, and doesn't limit
// the maximum period. There will usually be solutions that have a smaller rounding error.
let max_val = 65_535;
let rhs = tim_clk * period;
let arr = (rhs.sqrt() - 1.).round() as u16;
let psc = arr;
if arr > max_val || psc > max_val {
return Err(ValueError {})
}
Ok((psc, arr))
} /// Set the timer period, in seconds. Overrides the period or frequency set
/// in the constructor.
pub fn set_period(&mut self, period: f32, clocks: &clocks::Clocks) -> Result<(), ValueError> {
let (psc, arr) = calc_period_vals(period, clocks)?;
self.tim.arr.write(|w| unsafe { w.bits(arr.into()) });
self.tim.psc.write(|w| unsafe { w.bits(psc.into()) });
Ok(())
} |
I think the timer clock computation makes more sense in the rcc or clocks module, so you can just do this: let tim_clk = clocks.calc_speeds().timclock1 * 1_000_000.; |
I can confirm that the sqrt approach I took is imprecise at high frequencies, eg kHz range. The original algorithm fairs better there. |
@David-OConnor So I think I'm going to keep the I would prefer to open a different PR for changing the timer. I like your approach of allowing folks to adjust the period but what about the Someone would have to first call the But I guess given how general the |
I think this goes back to your idea of simple and complex: Unfortunately, I don't think there's a way around that limitation with the I think the answer, as you said, is to leave |
I have not gone through all comments and changes in detail, just wanted to ask if this PR is still kinda WIP or if it is worth to fully review it to get it into the master branch :) |
@Sh3Rm4n All work is complete and it’s ready for a full review. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wow, this is pretty cleanly applicable without too much change.
I'm only unhappy about the Result
change.
Regarding #192 (comment), I know that the current clocks generation results into ugly code, but this is on my todo list for a long time.
let clocks = rcc
.cfgr
.use_hse( 8u32.MHz( ).into( ) )?
.sysclk( 48u32.MHz( ).into( ) )?
.pclk1( 24u32.MHz( ).into( ) )?
.freeze( &mut flash.acr );
which is your 2nd proposal, which does look pretty clean to me. It is not as clever as the TryInto
constrain, but it also does not need to unnecessarily introduce a change in the function signature (aka introducing Result
as the return type).
Thanks for your contribution! I think my suggestion is an easy fix. Otherwise, this looks pretty good ✨
- Remove generic TryFrom constraint on functions taking a Rate type - Make all functions taking a Rate type take the Hertz type instead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mostly ergonomic suggestions and pointing out some missed Result<Self,...>
to Self
reversions.
This looks promising. 👍
EDIT: Ah and before I forget: I'm always happy about a changelog entry. Note, that this is a breaking change.
src/spi.rs
Outdated
@@ -424,9 +424,9 @@ macro_rules! hal { | |||
freq: F, | |||
clocks: Clocks, | |||
apb2: &mut $APBX, | |||
) -> Self | |||
) -> Result<Self, <F as TryInto<Hertz<u32>>>::Error> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use missed reverting this part :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, not sure how I missed these..
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want to do a minor version bump since not only is this a breaking change to the public API but also changes within the implementation?
So v0.6.1
to 0.7.0
?
Another note: If you are not comfortable applying my suggestion about the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some minor nits, but this looks really good now overall! :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice. Looks good to me! Thank you for your work and patience.
This was accidentally removed in stm32-rs#192
Replaces the custom time module with embedded-time.
This change addresses both #191 and #185.
This change will allow folks to create custom device drivers without depending on a specific device crate for their custom duration/rate types.