-
Notifications
You must be signed in to change notification settings - Fork 69
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
Low resolutions of pwm affect the frequency #195
Comments
Are there any tradeoffs to setting the resolution to |
Hi @seanybaggins, original author of that particular module here. The issue you're running into is a variant of the one mentioned in #138. In that one, the resolution is set so high that the prescaler becomes too coarse to yield a reasonably close approximation to the desired frequency. In your case, the resolution is set so low that prescaler overflows. We have to set the prescaler so that: I also link #138, because it goes into details about why this is more challenging to abstract away that I was originally hoping. In fact, this interface was, I felt, the best hope for doing so, and you're not the first person to bump up against a leak. As a tl;dr the issue is that you only have a limited number of bits, and must share them between the frequency accuracy and resolution fineness. By putting them all in resolution, certain frequencies become essentially unachievable. It's also challenging to be strict here. This is configuration, so it's probably reasonable to I think the first thing to do is allow more flexibility in the ways that these values can be configured. As an example, a user should probably be able to set the values exactly if they so desire. I've got a rough sketch of that I'll open as a PR for more feedback shortly. Hopefully we can find something that moves the needle towards fewer gotchas :) |
There are 3 things that affect frequency (or period): PSC register value, ARR register value, and timer clock speed. The current implementation works well for some frequency and clock combos, but not others. The current code includes two parallel implementations: One in the timer: 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());
// Set the "resolution" of the duty cycle (ticks before restarting at 0)
// Oddly this is unsafe for some timers and not others
//
// NOTE(write): not all timers are documented in stm32f3, thus marked unsafe.
// This write uses all bits of this register so there are no unknown side effects.
#[allow(unused_unsafe)]
tim.arr.write(|w| unsafe {
w.arr().bits(res)
});
// Set the pre-scaler
// TODO: This is repeated in the timer/pwm module.
// It might make sense to move into the clocks as a crate-only property.
// TODO: ppre1 is used in timer.rs (never ppre2), should this be dynamic?
let clock_freq = clocks.$pclkz().0 * if clocks.ppre1() == 1 { 1 } else { 2 };
let prescale_factor = clock_freq / res as u32 / freq.0;
// NOTE(write): uses all bits of this register.
tim.psc.write(|w| w.psc().bits(prescale_factor as u16 - 1)); Code from a fork I'm working on: It also is naive, and produces imprecise results at high frequencies. Check out the comments for a description of what's going on, and possible ways to address this. /// 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.
let tim_clk = clocks.calc_speeds().timer1 * 1_000_000.;
// 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))
} This is fixable; we need a suitable algorithm for solving the equation using integers, as closely as possible, without too much computation. |
Here's what's going on specifically, as @IamfromSpace pointed out re the overflow: timer clock = 16,000,000. Related: There's a (usually small) error in the Current: What is should be: If you used the freq setup in either the original |
Yeah, looking back at it, I was fairly sure that the resolution is off by one :/. The frequency is correct though I think. And it does seem like there must be a simple/cheap alg that maximizes resolution against a target frequency. Period freqs that are co-prime with the timer freq are troublesome in that the naive maximum resolution will be too small to be useful (whenever PSC is large enough, ARR will be 1). In this case we need to somehow determine what the appropriate level of frequency accuracy is. |
Could you clarify what problems being co-prime causes? I've been treating PSC and ARR as interchangeable, which may not be right. (Although it seems to work in practice, so far) |
Ah, yes, and treating them the same makes perfect sense in many contexts—because often only frequency matters. PWM a place where that breaks down, because resolution is either somewhat or very important. For something like a servo, you need a high enough resolution that you can express the positions. If you range 180 across 5-10% duty cycle, then 3600 is going to allow you to express down to the degree. If you need to hit exact marks (say 10 degrees), then just 360 does the trick, but 577 misses them, even though it’s higher resolution. Also, when we only care about frequency, it still makes sense to prioritize a larger PSC, as ARR is just soaking up bits, it doesn’t make it more accurate.
edit: In thinking about this more, this case is actually the easy one--we can just ignore the prescale_factor and only set the resolution to the appropriate divider. We get a large resolution and as accurate a result as we possibly could. There are other more challenging cases though. |
Thank you for the detailed explanation. I've been using timers to fire interrupts and use 50%-duty PWM; this explains why I didn't notice a problem in my tests - and why the PWM module lets you specify ARR, but not the timer. Lets say you're using that clock and target pwm freq. What trouble could you run into from setting I'm showing the values for PSC and ARR in your post result in a freq of 585.5Hz. Is this to demonstrate that when needing a low ARR (Resolution?), we can't set PSC high enough to get the right freq? Thank you. |
Right, so 247 for 5-10% only gives you 12 useful values, and for 0-180 your jumps are about 15deg, which isn’t a lot of control. And it just becomes hard to say what’s better, because more resolution creates more freq drift. A 1171 frequency is certainly a worst case—I’m not sure there’s any reasonable value. But it does a good job of illustrating the trade-off. |
Just edited my previous comment, realized that my previous statement wasn't quite correct: only the prescaler determines the accuracy of the frequency when the resolution is fixed. If ARR has no constraints (like, be exactly x or be a multiple of y), then we get down to the original problem that you, @David-OConnor, had mentioned, how do we quickly and accurately factor our two dividers? And instead of an even split, we likely want to have a lop-sided one, where resolution is prioritized. Probably best to continue this discussion on #196, but I wanted to have this correction here :) |
using this code
I get a pwm signal that I expect. A 50% duty cycle with a 1 second period.
If I change
resolution = 200
then I get a signal that has a period of about 1/10 of the original.The text was updated successfully, but these errors were encountered: