-
Notifications
You must be signed in to change notification settings - Fork 183
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
Implement full embedded_hal::Pwm #176
Implement full embedded_hal::Pwm #176
Conversation
Merge upstream master
LGTM. cc @geomatsi |
I am not seeing that warning in the TravisCI build logs. very strange. |
This is because you've added But I don't understand sense |
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.
Thanks for the PR. Nice to see such well documented code and description!
I left some minor nitpicks in the comments, as well as a note about the Copy
and Clone
implementations probably being unsound.
I have been calling the channel in this PR as PwmChannel, but the embedded_hal API calls it PwmPin. Should we use PwmPin here? I like channel as that is the way that the ST documentation has it. Also, different channels could point to different pins. With that thought I might suggest that the embedded_hal API be updated/adjusted to match this nomenclature.
I think both names are fine so we can probably keep it like this
Array based Channels
I see your point here, but I don't think arrays is the answer as that would add a potential for runtime errors which we are trying to avoid throughout this crate.
A better option might be to always return a tuple of length 4, but with ()
or struct NoChannel
in the unused slots.
@burrbull, The Clone / Copy is needed against the What is your specific question concerning Deref? |
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.
Great work! I'll use it for playing music on my alarm clock! (doing it manually using unsafe now)
@justacec See this draft burrbull@42acde3 |
Ok, so, what I was getting to earlier about the edit: |
@burrbull For some reason, I noticed that I cannot specify that I want you to re-review the code. Not sure why. That being said, can you please take a look. Thanks. |
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.
Looks good, apart from the panic which I'd like to get rid of, but perhaps we could do that in another PR
{ | ||
c | ||
} else { | ||
panic!("Unused channel") |
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.
Still not a fan of this panic, and I'm not sure if check_used is even nessecary anymore
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.
@TheZoq2 It took me a bit to understand as this was a @burbull construction, but the function seems to make sense to me. Without the function there is no way for the per-channel functions to know if that channel is, in-fact, being used. As an example, lets say that I configure the Timer to use only Channel 3 passing a single pin. But then I call something like:
mypwn.enable(Channel::C1);
Without the check_used
method, the function would go ahead and set the bit. Although, I am not sure that are any real negative consequences of this.
This was the real reason behind me using a macro in my original incarnation of this functionality. The macro expanded to only include channels that were active and would default to doing nothing through a default match. In this incantation, this has been shifted to be run-time vice compile-time.
Now, concerning the panic. Although I had originally default to no action, I actually see no issue with this panic as it will aggressively inform the user that he/she has something amis. I kind of feel that this is a better approach than doing nothing. In the do nothing variant, I could mis-type something and work for hours trying to figure out why my channel duty is not acting correctly; only to find out that I had put the wrong channel in there. If there was a panic, the compiler would yell at me (I think). If I am mis-understanding that it would occur at the compiler phase and it would be runtime, I would have a potential backtrace which would point to the exact place that needed to be checked.
@burrbull I am cc'ing you here to see if you have any specific comments on this above what I have put here.
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.
Now, concerning the panic. Although I had originally default to no action, I actually see no issue with this panic as it will aggressively inform the user that he/she has something amis.
Agreed, panic is better than doing nothing
If there was a panic, the compiler would yell at me (I think). If I am mis-understanding that it would occur at the compiler phase and it would be runtime,
Panics happen at runtime, if possible I would like to move this to compile time but that is a difficult task.
If I am mis-understanding that it would occur at the compiler phase and it would be runtime, I would have a potential backtrace which would point to the exact place that needed to be checked.
Yes, but backtraces can be a bit of a pain to get a hold of in embedded contexts
But again, we can probably take care of this in the future in another PR, so unless burbull has additional comments, I think this is good to go
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 don't know how to do this check compile time because channel
can be not constant.
In fact we need possibility to write const fn
in trait implementation and variant of assert
which fails in compile when it is possible and fail in runtime in other cases.
Alternative for this is only one: to use 4 pins in predefined order and 4 channels everywhere and to use |
@burrbull In that case it would always return a size 4 tuple then right. |
@burrbull So, I have now been looking into trying to implement this. Right now I am trying to find out how to adjust the existing BTW: I am returning a size 5 tuple so that channel 1 will be dereferenced as Thanks for any direction. |
Please no proc macro and no 5 element tuple. If you want logical fields, better to create a struct with chan1, chan2... fields. |
|
Guys, Sorry I have been absent the past several weeks. My work has really stepped up and I have not been able to dedicate as much time to trying to work on my personal projects, which includes learning Rust. I did attempt to get something working which would return a full object vice the Tuple to get around the issue that I had raised earlier. I think that I was very close but, not quite there. I think that you guys would probably be able to implement a fix for that pretty quickly. I would like to propose that we go ahead and merge this pull request in it's current state as it maintains the current API and provides a fuller implementation of the Thoughts? |
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 think despite some minor flaws it's a really solid improvement and it has simmered for long enough so let's land this.
Would you mind:
- adding a CHANGELOG entry
- opening up issues for the comments you couldn't address
- squashing the commits a bit
?
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.
Thanks. Will merge this now.
This change implements the full
embedded_hal::Pwm
andembedded_hal::PwmPin
API defined atembedded_hal
Documentation LinkMotivation
This PR was prompted by the desire to be able to adjust the frequency of the timer after construction through the
set_period()
andget_period()
Pwm
trait methods. The changes were done in such a way as to maintain compatibility with the current API.Coding Concept
The primary idea was to shift the current
stm32f1xx_hal::pwm::Pwm
struct to bePwmChannel
and then devise a newPwm
struct which held the channels. While this sounds potentially not necessary, it was required in order to have a landing space for theset_period
andget_period
methods in theembedded_hal::Pwm
trait. Implementing this method on an individual channel is not preferred because the operation adjusts the Prescaler (PSC) and Auto Reload Register (ARR) of the entire timer which affects all the channels, not just one. Implementing the desired method on thePwmChannel
struct could lead to unintended consequences.Time Units
The
embedded_hal
API requires that a time based type be defined for thePwm
trait. This is difficult as the natural timescale for the MCU is in fractional seconds of high order. If one wanted to use milliseconds (the currently defined time unit in thestm32fxx_hal
crate) periods greater than 2 seconds could not be specified because of the 32-bit field depth. If one was to try to use microseconds, there could be difficulty getting particular frequencies because of the integer nature of the unitBecause of this, I have defined the time unit in the
Pwm
trait to beHertz
. I do realize this is not a "time". This unit is just a bit easier to work with. In order to make it so end users can pass time like units, I have implementedInto<Hertz>
for bothMilliSecond
andMicroSecond
this allows for adjusting the period like the following:Compatibility
In order to maintain compatibility with the previous API, I have made
Pwm
Deref
andDerefMut
in such a way that it returns a reference to the channels. I have further madePwmChannel
Clone
andCopy
. This is safe as thePwmChannel
does not occupy any space as it is a struct holding nothing butPhantomData
elements. The struct and its fields carry only type information.Random Thoughts
PwmChannel
, but theembedded_hal
API calls itPwmPin
. Should we usePwmPin
here? I like channel as that is the way that the ST documentation has it. Also, different channels could point to different pins. With that thought I might suggest that theembedded_hal
API be updated/adjusted to match this nomenclature.Potential Improvements
While coding this up I recognized a few potential improvements but decided to not implement them right now in order to get a working base. Below is a list of these ideas which were not implemented:
Array based Channels
Currently, the channels are held in a compile time tuple with length specified by the number of channels in the
PINS
object. Indexing these channels is then done by their order in the tuple. This results in cases where the timersC3
channel is indexed as 0, not 3. It might be more clear if thePwmChannel
objects are stored asOption
in a fixed length array of size 5 (leave the first entry as aNone
so the numbers are more logical when compared with the ST manuals). That way ifC3
is the only pin defined, it is accessed by:In the event that a user asks for a pin that was not initialized, he/she would get a None and would need to handle that situation.