-
Notifications
You must be signed in to change notification settings - Fork 200
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
Add PWM SetDuty trait. #430
Conversation
e9274a6
to
73623ff
Compare
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.
seems broadly good to me!
Duty is no longer an unconstrained associated type. u16 has been chosen because it gives enough dynamic range without being too big.
if we do need more (or less) resolution in the future it should be non-breaking to swap to a default associated type + markers (à la vec) and const MAX... i'd probably prefer this from the get-go, (and maybe there's some overlap with timer widths) but am okay either way.
This is the alternative that I mentioned during the meeting. It brings back the pub trait Pwm {
// Required methods
fn get_max_duty(&self) -> u16;
fn set_duty(&mut self, duty: u16);
// Provided methods
fn set_off(&mut self) {
self.set_duty(0)
}
fn set_on(&mut self) {
self.set_duty(self.get_max_duty())
}
fn set_fraction(&mut self, num: u16, denom: u16) {
let duty = num as u32 * self.get_max_duty() as u32 / denom as u32;
self.set_duty(duty as u16)
}
fn set_percent(&mut self, percent: u8) {
self.set_fraction(percent as u16, 100)
}
} |
i like the |
@GrantM11235 All autoimplemented methods should be marked with related: https://rust.godbolt.org/z/44bn77Msx |
Some more notes in no particular order:
|
for drivers this might be an issue if they're passing on input - now they have to do the sanity check of the input values and the HAL might well do it again. i think it'd be good if this were specified. by the way: would the API profit if it'd use something like |
Some drivers will want to clamp to the max value, some drivers will want to return an error, and some (most?) drivers won't need to do any sanity checking at all if all possible inputs map to a valid duty value. Therefore the most efficient option is for each driver to implement their preferred behavior, and to not require any sanity checking in the HALs
That trait returns the maximum value that can be represented by a particular type. It doesn't help us in this case because the max duty value is not represented in the type system. If the max duty value was represented in the type system (for example with const generics), that would prevent dynamic configuration/reconfiguration of the timer period |
It might be nice to not allow panicking in drivers, just because unavoidable panic paths are pretty annoying. If the operation returns a Result anyway, couldn't it just return an error condition (which we could add to ErrorKind) for this situation? We could allow it to silently do something else like clamp or wrap or whatever if it likes. I expect few implementations will be able to accept all u16 as valid duties, not least because insisting on max being 100% duty is likely to mean HALs will have to special-case that value anyway. I expect it's more likely HALs will do something like accept an 8-bit or 10-bit or whatever value and mask off whatever's supplied as the most-convenient option, or otherwise clamp? |
I think that a bad duty value is a "garbage in, garbage out" situation. I think that HALs should be encouraged to use a I don't think that there should be an ErrorKind for it because that would imply that all implementations need to check it. Some food for thought, including:
|
embedded-hal/src/pwm.rs
Outdated
/// The caller is responsible for ensuring that `num` is less than or equal to `denom`, | ||
/// and that `denom` is not zero. | ||
#[inline] | ||
fn set_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { | ||
let duty = num as u32 * self.get_max_duty() as u32 / denom as u32; |
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.
Rather than leaving this to prose, why not use the core type? That way, the compiler will know not to handle a zero-division even when it can't const-propagate the denominator.
/// The caller is responsible for ensuring that `num` is less than or equal to `denom`, | |
/// and that `denom` is not zero. | |
#[inline] | |
fn set_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> { | |
let duty = num as u32 * self.get_max_duty() as u32 / denom as u32; | |
/// The caller is responsible for ensuring that `num` is less than or equal to `denom`, | |
/// and that `denom` is not zero. | |
#[inline] | |
fn set_fraction(&mut self, num: u16, denom: core::num::NonZeroU16) -> Result<(), Self::Error> { | |
let duty = num as u32 * self.get_max_duty() as u32 / denom.get() as u32; |
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.
We discussed this at this week's meeting but didn't reach a consensus. Using NonZero ensures the function can't panic, but makes for a less ergonomic interface, especially since the majority of applications are likely to specify a constant denominator.
Discussed in today's meeting. Last bikeshed item: renamed "duty" to "duty cycle". I think it should be good to go. @rust-embedded/hal |
f18714d
to
0d91330
Compare
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.
Fine with me.
bors r+ |
1 similar comment
bors r+ |
Already running a review |
In some cases one PWM timer may be able to adjust frequency on the run and also have multiple output channels with independent duty cycles. In those cases there may be one timer type responsible for the frequency or period and one or more types for the channels. Then the channels does not know what the current period is and thus can not know the max duty value. With all of the Same thing the other way around, say the above was somehow resolved. If we managed to Please correct me if I am wrong but I, atleast in the above case, I believe Again please let me know if I am missing something :) |
The main goal of the traits is to support HAL-independent drivers. For example, a "RGB LED driver" would take 3 If the traits did model "PWM peripheral" and "PWM output channel", the RGB LED driver would have to be aware of whether the chip configures max_duty per peripheral or per channel, whether different R/G/B channels are in the same PWM peripheral or not... This makes the trait harder/impossible to use for drivers without HAL compat issues. (The previous PWM trait in EH0.2 worked similarly to what you mention, it was scrapped due to this reason (and due to unconstrained Time types #324 too, but that's another story)) HALs can still implement the traits for chips where max_duty is per-PWM-peripheral instead of per-channel. There's a few options:
The HALs don't necessarily have to use the |
This adds back a version of the 0.2 trait
PwmPin
.cc #358
WG meeting chatlog
Differences to 0.2:
enable()
anddisable()
are gone. I think they were underspecified (for example, is the pin high or low when disabled?), and not very useful in practice. Disabling can be achieved already by setting duty to 0% or 100%. If the HAL cares about power consumption, it can transparently disable the timer and set the pin to fixed high/low when duty is 0% or 100%.u16
has been chosen because it gives enough dynamic range without being too big.u8
might give too little dynamic range for some applications,u32
might be annoyingly big for smaller archs like AVR/MSP430.0..u16::MAX
instead of0..get_max_duty()
. This makes the HAL instead of the user responsible for the scaling, which makes using the trait easier. Also, if the HAL wants to optimize for performance, it can set the hardware period to be a power of 2, so scaling is just a bit shift.SetDuty
instead ofPwmPin
, because we might have aSetFrequency
or similar in the future.get_duty()
, because I think in practice most drivers don't need it, and implementing it in HALs is annoying. They either have to read it back from hardware and unscaling it (losing precision), or storing the unscaled value inself
(wasting RAM). We could add aGetDuty
orStatefulSetDuty
in the future if it turns out this is wanted, but I hope it won't be.