Skip to content
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

PWM support #26

Closed
therealprof opened this issue Dec 30, 2018 · 29 comments · Fixed by #138
Closed

PWM support #26

therealprof opened this issue Dec 30, 2018 · 29 comments · Fixed by #138

Comments

@therealprof
Copy link
Member

I started implementing PWM for this crate in two slightly different ways:

Approach one

https://github.com/stm32-rs/stm32f0xx-hal/tree/features/pwm

This uses the typical approach of initialising the PWM peripheral with the timer and pins to use for PWM. The code is loosely based on https://github.com/stm32-rs/stm32f1xx-hal/blob/master/src/pwm.rs and I can see a number of advantages and disadvantages with this.

Advantages:

  • Prevents misconfigurations of timer, channel and GPIO combinations

Disadvantages:

  • Does not allow using one channel for multiple GPIOs at once
  • Earth shattering number of timer, channel and GPIO combinations need to be encoded
  • Someone might want to use PWM without assigned GPIO which is not possible in the current design

Approach two

https://github.com/stm32-rs/stm32f0xx-hal/tree/features/pwm-alternate

This approach only binds the timer to provide 4 PWM channels without any association to a GPIO.

Advantages:

  • Allows for much more flexible configuration of zero or more GPIOs per channel

Disadvantages:

  • No sanity checking for correctly configured GPIO pins and channel relations

I'm a bit on the fence which variant we should go for. Approach one is rather limiting, especially if we want to support advanced uses of PWM (and maybe other nice timer features). It is also very complex to implement for all chips and all possible combinations at once. On the other hand it would be great to have a way of telling a user that some timer/channel/GPIO combination will not work or is incorrectly configured.

Ideas?

CC @zklapow

@HarkonenBade
Copy link
Member

I'd be very interested to take a crack at doing some trait witchcraft in the hope of getting the flexibility of the second solution while maintaining compile time verification. I'll see what I can do, though im not certain how much free time I will have to look at it immediately.

@david-sawatzke
Copy link
Member

I think we should do the first variant. Being able to use multiple is also possible with variant one (by just configuring a pin to the af, without doing anything else with it), but that isn't the most used variant.

I'd also propose adding an Unused pin, implementing all the special function traits, if some pin is not going to be used. For example, having an Rx only UART, or not needing MISO with SPI. I've implemented a WIP Ws2812 driver https://github.com/david-sawatzke/ws2812-spi-rs using spi, that only needs the mosi pin, but with the current design sck and miso also need to be reserved.

Implementing PWM this way would make using it the most common way (one pin per channel) really obvious and safe, while also allowing the other variants with obvious patterns in the code.

As an aside:

I think having a type safe interface is important, even though it is a lot of overhead. Maybe we could consolidate all the different bindings for gpios in the future in a bindings.rs file, with all the afs implemented in one spot, this would also make comparing it with the data sheet easier.

@zklapow
Copy link
Contributor

zklapow commented Dec 31, 2018

I agree with everything @david-sawatzke said.

I prefer the first approach because it gives us more compile time guarantees which is one of the main things I appreciate about doing embedded dev with Rust. I definitely like the idea of having an Unused pin.

@therealprof
Copy link
Member Author

I think we should do the first variant. Being able to use multiple is also possible with variant one (by just configuring a pin to the af, without doing anything else with it), but that isn't the most used variant.

Unlike most other peripherals which are really only useful if they're actually connected to something this is not the case for the PWM (or other timer functions). While the PwmPin traits specifically target GPIO pins, the PWM mode of the timers can do a whole lot more which is completely unrelated from those traits, like trigger interrupt handlers or other timers. As I mentioned due to the way the timers work, defining all Timer/Channel/Pin combinations is going to be a ton of work. I'm not convinced defining Unused pins is the best way to go here.

But you're right we should have this for other peripherals...

NB: We already have those filler types here https://github.com/stm32-rs/stm32f4xx-hal/ and could reuse that approach.

@therealprof
Copy link
Member Author

@zklapow

I prefer the first approach because it gives us more compile time guarantees which is one of the main things I appreciate about doing embedded dev with Rust. I definitely like the idea of having an Unused pin.

This issue is not about about sacrificing compile time guarantees but about usability and flexibility. The Unused pin approach is not only more cumbersome to implement and use but also limits the usability because (as mentioned) you can actually drive multiple pins with one channel (and sometimes this is an important feature) which this model can not support.

@david-sawatzke
Copy link
Member

It does support it by virtue of just being able to configure other pins to the right af. This is (afaict) also what you'd need to do in approach 2, so no usability is lost, and it does make the more unusual and more often wrong approach of driving multiple pins with the same pwm channel possible, but hard to do accidentally

@therealprof
Copy link
Member Author

It does support it by virtue of just being able to configure other pins to the right af.

And how is that a safe interface?

This is (afaict) also what you'd need to do in approach 2,

No, the idea is a different one: You configure a channel and then you can assign the correct pins in the correct configuration to the appropriate channel, turning them into a PwmPin and granting access to the channel control.

so no usability is lost, and it does make the more unusual and more often wrong approach of driving multiple pins with the same pwm channel possible, but hard to do accidentally

Sorry, I could not disagree more. It is very common to drive as many pins as possible with the least amount of timers (and potentially also use some extended features like safety mode) or to use complimentary PWM signals. This is a very important use case and should be just as safe and sane to use as the "fade this single LED" implementation.

@david-sawatzke
Copy link
Member

You configure a channel and then you can assign the correct pins in the correct configuration to the appropriate channel, turning them into a PwmPin and granting access to the channel control.

I don't see it.

The basic api is

let (c1, c2, c3, c4) = tim4(TIM4, Hertz(4), clocks).split();
c1.enable();
c1.set_duty(10);

And the pins just implement the CHx traits, which aren't used anywhere, so there you'd just need to set them to correct af and leave them alone, like the multi pin approach with method 1.

If @HarkonenBade implements a method to attach multiple pins, we should distinguish complimentary and normal channels anyway.

I do agree that it would be nice if the additional pins where type-checked somewhere, but that's not what approach 2 is doing (afaict).

@HarkonenBade
Copy link
Member

Mmm, Also part of me feels like an appropriate thing to do would be to handle the AF selection internally. It feels like a more pleasing interface to just require say an gpioa::PA0<MODE> and internally we select the right AF for that pin.

@therealprof
Copy link
Member Author

@david-sawatzke

I don't see it.
I do agree that it would be nice if the additional pins where type-checked somewhere, but that's not what approach 2 is doing (afaict).

Yeah, because it's not implemented yet and the main reason why I put it up for discussion (and possibly someone to come up with a clever implementation, cf. #26 (comment))

If @HarkonenBade implements a method to attach multiple pins, we should distinguish complimentary and normal channels anyway.

Seems you're quite the complexity junkie. 😏

@david-sawatzke
Copy link
Member

Yeah, because it's not implemented yet

Ah, sorry, forgot about that

Seems you're quite the complexity junkie.

I'd hate to have a long debugging session with the exact wrong values until i look in the data sheet and see that it's inverted, so i think that's somewhat necessary

@therealprof
Copy link
Member Author

@david-sawatzke

I'd hate to have a long debugging session with the exact wrong values until i look in the data sheet and see that it's inverted, so i think that's somewhat necessary

There're so many ways to mess up the setup. An inverted output will mostly likely be the very least of your problems and is easily corrected, too.

@therealprof
Copy link
Member Author

Mmm, Also part of me feels like an appropriate thing to do would be to handle the AF selection internally. It feels like a more pleasing interface to just require say an gpioa::PA0 and internally we select the right AF for that pin.

Not sure there's a lot of benefit in that approach. Sounds like a ton of complexity (if possible to implement at all) with little benefit. If the interface is decent then rustc is quite good at pointing problems with wrong mode selections...

error[E0277]: the trait bound `stm32f0xx_hal::gpio::gpioa::PA2<stm32f0xx_hal::gpio::Alternate<stm32f0xx_hal::gpio::AF2>>: stm32f0xx_hal::serial::TxPin<stm32f0::stm32f0x2::USART2>` is not satisfied
  --> examples/serial_echo.rs:26:22
   |
26 |         let serial = Serial::usart2(p.USART2, (gpioa.pa2.into_alternate_af2(), gpioa.pa15.into_alternate_af1()), 115_200.bps(), clocks);
   |                      ^^^^^^^^^^^^^^ the trait `stm32f0xx_hal::serial::TxPin<stm32f0::stm32f0x2::USART2>` is not implemented for `stm32f0xx_hal::gpio::gpioa::PA2<stm32f0xx_hal::gpio::Alternate<stm32f0xx_hal::gpio::AF2>>`
   |
   = help: the following implementations were found:
             <stm32f0xx_hal::gpio::gpioa::PA2<stm32f0xx_hal::gpio::Alternate<stm32f0xx_hal::gpio::AF1>> as stm32f0xx_hal::serial::TxPin<stm32f0::stm32f0x2::USART2>>
   = note: required by `<stm32f0xx_hal::serial::Serial<stm32f0::stm32f0x2::USART2, TXPIN, RXPIN>>::usart2`

Maybe it would more sense to implement the Into trait for all GPIOs and change the impls to anything that intos into a particular mode. I just did a quick check and rustc is not capable of inferring the type though so this might also require deeper changes. Still sounds like a good idea anyway...

@bentwire
Copy link
Contributor

Anything ever get decided on this? Would love to have PWM support integrated in to the HAL!

@therealprof
Copy link
Member Author

@bentwire Nope. Slow going at the moment...

@bentwire
Copy link
Contributor

I may be of assistance. I'm new to rust but could probably help with this at least some. :-)

@cbaechler
Copy link

Is there any progress on this topic? If not, maybe I could help with the implementation of PWM support in this crate. As I am still new to rust, I would probably need some more extensive review. However I do have knowledge about embedded systems and ARM in particular. Also, I would have some hardware (nulceo-f042k6) and oscilloscope at hand to check if it works.

I have checked the PWM implementations in other stm32f*xx-hal crates and wonder a bit, why each crate takes a different approach, since to my understanding ARM Cortex-M0 and M3 are not too different. Recently in the stm32f1xx-hal crate there was a PR merged regarding PWM support and I wonder if it could be ported to the stm32f0xx-hal crate.

@therealprof
Copy link
Member Author

@cbaechler

Recently in the stm32f1xx-hal crate there was a PR merged regarding PWM support and I wonder if it could be ported to the stm32f0xx-hal crate.

The different families have different generations and versions of peripherals. Some of the code might be portable with a bit of effort, other code is better written from scratch. Timers are especially tricky in this regard because even on the same model they're a usually a few different which makes coming up with a general approach even harder.

@cbaechler
Copy link

@therealprof

Timers are especially tricky in this regard because even on the same model they're a usually a few different which makes coming up with a general approach even harder.

Thanks for your explanation, I was not aware of this. I also see in stm32f1xx-hal the handling of the AF pins is done quite differently.

@HarkonenBade
Copy link
Member

I find myself really needing PWM all of a sudden. Did we have any general opinions about this stuff?

@therealprof
Copy link
Member Author

Well, we do have implementations, e.g. in stm32f4xx-hal which we could snatch.

@HarkonenBade
Copy link
Member

Are there any that mesh well with how GPIOs and similar are implemented in the f0-hal?

@therealprof
Copy link
Member Author

I don't see why https://github.com/stm32-rs/stm32f4xx-hal/blob/master/src/pwm.rs wouldn't work for F0 in a pinch. I think the timers in the F4 are pretty much feature complete already so F0 should have mostly the same concepts and implementation.

@kiranshila
Copy link

Any movement on this? PWM seems like a pretty critical feature.

@therealprof
Copy link
Member Author

Any movement on this? PWM seems like a pretty critical feature.

We're open for PRs 24/7. 😉

@kiranshila
Copy link

I've just started reading through the f4 pwm implementation - so I'll give it my best effort. I'm pretty new to Rust so it may be a bit over my head.

@kiranshila
Copy link

I would also be happy to sponsor this feature.

@jonfin jonfin mentioned this issue May 24, 2021
@jonfin
Copy link
Contributor

jonfin commented May 24, 2021

@kiranshila sorry, that I steal you the fun to implement it. I didn't saw your comment until I had it implemented.

@kiranshila
Copy link

I'm glad you did! I got pretty overwhelmed with trying to figure out which variants had which timers.

@bors bors bot closed this as completed in 73b0fac May 25, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

8 participants