-
Notifications
You must be signed in to change notification settings - Fork 171
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
MCPWM 5.0 #132
base: master
Are you sure you want to change the base?
MCPWM 5.0 #132
Conversation
@usbalbin @MabezDev Folks, I'm missing a little bit the context of what is going on here. Can somebody summarize it for me? I'm struggling with the following:
@usbalbin I think it would be very useful if you create a comment that summarizes what these changes are about, and what is their relation to ESP IDF4 vs ESP IDF 5. I think you might be assuming that we have a bit more context than what we actually know about this stuff. :) Thanks! |
Sorry about the confusion. Sure thing! First of, I am no authority on the subject myself and please correct me if I am wrong, but I will do my best :) MCPWM vs LEDCMCPWM, as compared to the other way to generate PWM signals(LEDC) on the ESP devices, has more motor control related features. For example signals can be synced with each other or with an external source, enabling things like phase shifting with very precise timing. PWM signals are internally generated in pairs with the same clock source and can be configured to have completely independent duty or to work in complimentary mode with a dead time. So for example to control the switches of a half bridge without risk of short circuit. There is also things like fault detection, where without the need for software intervention, an action can be performed such as "force output low"(think over current protection). Then we have the capture unit which can decode timing related things such as the duty of a PWM signal or similar.
I started #93 without any knowledge of there being a IDF 5.0 on the horizon. It then turned out that IDF 5.0 changes how MCPWM is exposed in some quite fundamental, very breaking ways. Also the "IDF <= 4.4" style MCPWM API is deprecated in 5.0. Thus I started experimenting with the 5.0 version in parallell. Currently #132 is very much just #93 copypasted with some experimental changes on top(will most likely change a lot and perhaps be rewritten completely just for 5.0 depending on what we want to do). Regarding #93 vs #132 vs #93 + #132, I believe that is a question for you to answer :)
Please let me know if this answered your questions :) Again #132 is nowhere near ready for review yet :) |
This is still quite the mess, and there are a lot of things I am unsure of. However before I get too far down the road, may I request a very light review of your thoughts of the overall picture of this PR?
|
I just noticed there is esp-rs/esp-hal#255 , perhaps there could be some inspiration to take from there? |
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.
The API looks sensible and easy to use. We need to figure out how to support v4.4 as well (a limitation esp-rs/esp-hal#255 isn't bound by), I've dropped a quick comment about that but I'm sure you'll figure out a clean way of managing it :).
Overall I like the direction of this PR, so I think you're safe to move forward.
src/mcpwm/timer_connection.rs
Outdated
O1: OptionalOperator<1, G>, | ||
O2: OptionalOperator<2, G>, | ||
{ | ||
pub fn attatch_operator0<CMPX, CMPY, GENA, GENB>( |
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.
So for v4.4, these bounds don't currently stop you from using timer0 & operator2, right? Perhaps we can somehow use const generics here such that TimerConnection must always match Operator for v4.4 🤔.
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.
Something roughly like(semi made up code with bad names)?
struct CanAttach<const TIMER: u8, const OPERATOR: u8>;
trait Yes;
#[cfg(v4_4)]
impl Yes for CanAttach<0, 0>{}
#[cfg(v4_4)]
impl Yes for CanAttach<1, 1>{}
#[cfg(v4_4)]
impl Yes for CanAttach<2, 2>{}
#[cfg(not(v4_4)]
impl<const TIMER: u8, const OPERATOR: u8> Yes for CanAttach<TIMER, OPERATOR>{}
impl<const N: u8, G, O1, O2> TimerConnection<N, G, NoOperator, O1, O2>
where
G: Group,
O1: OptionalOperator<1, G>,
O2: OptionalOperator<2, G>,
+ CanAttach<N, 0>: Yes,
{
pub fn attatch_operator0<CMPX, CMPY, GENA, GENB>(
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.
Yep! This is basically what I had in mind - maybe one day when const expressions are stable we can simply further.
Yes - will look at it over the weekend. @MabezDev if you can take a look too, that would be appreciated! |
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've tested the example on my s3 board with a logic analyser and the output looks good to me.
I think the number of TODOs is okay and can be resolved later. I think that adding v4.4 support can also be resolved in a follow-up PR, but I'll let @ivmarkov decide whether that should be done here or not.
Just one comment about using a release esp-idf-sys to resolve on my end.
flags.set_update_cmp_on_tep(0); | ||
flags.set_update_cmp_on_tez(1); |
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.
Just remembered espressif/esp-idf#9904 . Do you think tep
should also be set to 1
to avoid that? I have not tested this PR for that problem yet
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.
flags.set_update_cmp_on_tep(0); | |
flags.set_update_cmp_on_tez(1); | |
flags.set_update_cmp_on_tep(1); | |
flags.set_update_cmp_on_tez(1); |
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.
@usbalbin @MabezDev I think we might be using way too many traits and generics where simpler types would suffice. As a result, the driver types are very very generics heavy, whereas the HAL crate has been moving in precisely the opposite direction during the last several months and with the latest release of the HAL. I'm also struggling to see the need for most of these traits?
Am I missing something?
src/mcpwm/timer.rs
Outdated
} | ||
} | ||
|
||
pub struct Timer<const N: u8, G: Group> { |
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.
Having _group
and _timer
undescored means you don't actually use them after the new
constructor. Can we get rid of those? That would mean getting rid of the N
and G
generics on the Timer(Driver)
which simplifies the type.
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.
See discussion. For(not added in this PR) to be able to support some limited version of this for IDF 4.4 later on, we need to be able to limit what Timer
can be attached to what Operator
.
In general a timer can only be connected to operators in the same Group
, this is a hardware limitation. With IDF 4.4 there is also the constraint that Timer<X, G>
can only be connect to Operator<Y, G>
iff X=Y
. This is done using (misspelled) attatch_operator{A} (soon to be attach_operator{A}
).
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.
Anyway, I could not come up with a less generics heavy way to express something like that. I am however very open to suggestions :)
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.
If the G
generic is remaining, that's still not the end of the world, as long as we can still simplify / remove most of the other generics induced by traits in the code.
} | ||
} | ||
|
||
pub struct TIMER<const N: u8, G: Group> { |
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've asked for the removal of as many traits from this impl as possible, but introducing a Timer
trait here, which is a marker trait for a TIMER
peripheral (don't confuse it with the existing Timer
struct which should be renamed to TimerDriver
- see below)), has the big benefit that it will help you to remove a lot of the generics from the driver (TimerDriver
).
|
||
impl<const N: u8, G: Group> crate::peripheral::sealed::Sealed for TIMER<N, G> {} | ||
|
||
impl<const N: u8, G: Group> crate::peripheral::Peripheral for TIMER<N, G> { |
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.
Just to note that implementing Peripheral
only makes sense if you allow - in the TimerDriver::new
constructor - to pass the peripheral not just by moving, but also by &mut ref
, which is not possible right now. I'll elaborate further down in the code how that can become possible.
src/mcpwm/timer.rs
Outdated
} | ||
|
||
impl<const N: u8, G: Group> Timer<N, G> { | ||
pub fn new(timer: TIMER<N, G>, config: TimerConfig) -> Self { |
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.
The timer
peripheral should be passed as (roughly) timer: impl Peripheral<P = impl Timer> + 'd
where Timer
(to be renamed to TimerDriver
) is parameterized by the 'd
lifetime. The current implementation does not allow for taking a &mut ref
to the timer peripheral, which means that dropping the driver will always lose the peripheral.
_timer | ||
}*/ | ||
|
||
pub fn into_connection(self) -> TimerConnection<N, G, NoOperator, NoOperator, NoOperator> { |
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 have some concerns around this method being consuming, but let's discuss this later, after we have covered some of the other changes I've suggested.
Thanks a lot for the review(s)! |
* Rename struct Timer to TimerDriver * Fix typo in fn attach_timerX * Remove OptionalOutputPin
I have now made the two Comparators of every Same thing could be done for the |
|
And BTW you can get rid of the pub struct Generator<G, P: OutputPin> into pub struct Generator<G> Why don't you look into how the other drivers do it? As in the SPI and I2C ones? The idea is that only the constructor of the struct is generified by |
There are also other issues, like the fact that you need to acquaint yourself with the |
There are BTW a bunch of other |
Yes, I know, I think I should be able to fix most/all of those now...
Oh, that is very nice.
Thanks, I'll look into that |
src/mcpwm/operator_config.rs
Outdated
OperatorConfig::empty() | ||
.cmp_x(Default::default()) | ||
.cmp_y(Default::default()) | ||
.gen_a(GeneratorConfig::active_high(pin_a)) | ||
.gen_b(GeneratorConfig::active_high(pin_b)) |
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.
Here for
.gen_a(GeneratorConfig::active_high(pin_a))
due to GeneratorConfig being generic GeneratorChannel, active_high knows that the generator A should only listen to comparator X.
Similarly for .gen_b(GeneratorConfig::active_high(pin_b))
, it knows that generator A should only listen for comparator Y.
I am not quite sure how to achieve something similar using enums
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 have any 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.
If Gen A always listens to Comparator X and Gen B always listens to Comparator Y, then why don't you just make Gen A to own its comparator (Comparator X), and Gen B to own its comparator as well (Comparator Y)?
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.
The default configuration is to have it work that way, yes. It is also they way it works in IDF 4.4.
However in IDF 5.x you are free to have each generator listen to events from none, any or all comparators.
In a future PR when we add support for IDF 4.4 we need to somehow prevent connecting the wrong comparators to the generator.
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.
In a future PR when we add support for IDF 4.4 we need to somehow prevent connecting the wrong comparators to the generator.
This is a bit similar to the discussion about how operators and timers are related
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.
Well I'm also not happy with pushing things to runtime, especially given that one of the major premises of Rust is compile time safety.
But this is all a fine-balance act, right? As in, I've come to realize that simplicity of use and simpler types is just as important as compile-type safety.
Let me put it another way: if we do this one sacrifice - for ESP-IDF 4 only - would that allow us to get rid of most of the generics?
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.
But this is all a fine-balance act, right? As in, I've come to realize that simplicity of use and simpler types is just as important as compile-type safety.
Yes, very much so :)
Let me put it another way: if we do this one sacrifice - for ESP-IDF 4 only - would that allow us to get rid of most of the generics?
Yes, I think atleast GENA/GENB and CMX/CMPY can be removed in that case, and in addition we will have to rethink OperatorConfig
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.
I will give it a try :)
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.
Thank you so much! I know it's been a long ride...
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.
No problem at all, thank you!
The main types the user will be passing around as of right now looks like this struct TimerDriver<const N: u8, G: Group> {...}
struct Operator<'d, const N: u8, G: Group> {...}
struct TimerConnection<const N: u8, G: Group, O0, O1, O2> {...} I suppose What types should get the |
For now, let's just have
|
If that brings additional clarity: we are continuing to (ab)use traits to express patterns where I hoped a simple |
Ok
Other than that, there is also the issue of IDF4 only supporting
Thanks to that, Is it worth the three type parameters? Not sure... :) |
Hey @usbalbin I'm looking to implement/leverage this as a pwm capture method, any pointers on where you left off? |
I have not really looked at the capture thing. However most of the types so far are quite close to how the diagram are drawn here, so I would probably start there and look through the diagrams and functions/structures to figure out how things relate. From a quick glance, the capture thing seems to be quite independent having its own internal timer etc, yet it seems it can be synced with other timers from the same mcpwm unit. As to the state of the code here, I do not quite remember. I have not worked on this for quite a while. I believe it should work but there is likely a lot of room for improvement :) |
I am not working at this right now, and I do not see myself doing so anytime soon. So feel free to use this/finish it as you wish :) |
Hey, I'd very much try to use this as I need the new MCPWM api (5.x) for my application. I tried making |
It was some quite some time since I did anything with this... However, digging through my files: ...
[env]
ESP_IDF_VERSION = { value = "branch:release/v5.0" } Cargo.toml: ...
#esp-idf-svc = { version = "0.45.0" } # <--- Not sure if this is what I based my local version on?
esp-idf-svc = { path = "..." }
esp-idf-sys = { version = "0.32.1", default-features = false, features = ["binstart", "panic_handler", "alloc_handler"] }
... I have changed mcu since, so no guarantees, but I believe this worked when it was written. |
The beginning of a very very rough draft of IDF 5.0 style MCPWM. (mostly reused from #93, thus a lot of code will change)
Counter proposal for #93
Note: This depends on esp-idf/esp-idf-sys#133- MergedStatus as of 2022-12-10