tinygo icon indicating copy to clipboard operation
tinygo copied to clipboard

rp2040: implement PWMGroup func to get peripheral from pin

Open charlie-haley opened this issue 2 years ago • 12 comments

This PR implements a PWMGroup function which allows for easier fetching of the pwm group via the pin.

Related to this issue: https://github.com/tinygo-org/tinygo/issues/2304

charlie-haley avatar Jan 09 '22 20:01 charlie-haley

Hey @deadprogram could you trigger the tests again/review this PR? I believe the failure was a transient issue

charlie-haley avatar Jan 20 '22 19:01 charlie-haley

This actually solves issue #2583. I think it should be properly discussed there as the API result of solving this issue should propagate to other targets.

What should the name be for this function? I prefer PWMPeripheral over PWMGroup.

soypat avatar Jan 28 '22 03:01 soypat

This actually solves issue #2583. I think it should be properly discussed there as the API result of solving this issue should propagate to other targets.

What should the name be for this function? I prefer PWMPeripheral over PWMGroup.

I opted for PWMGroup as PWMPeripheral was already being used in this package. We could always change PWMPeripheral to a local function if we don't think it's providing much benefit

charlie-haley avatar Jan 30 '22 14:01 charlie-haley

Sorry that I am only now finally looking at this more closely.

Maybe it should be called PWMforPin() that way it can be implemented for all PWM interfaces, including ones that are organized a bit differently, but have the same issue of trying to answer that question of which PWM for a given pin.

For example, the implementation for SAMD21 would be returning a machine.TCC0.

Other than proposing the name change, agree with @charlie-haley and @soypat we need this, or something like it.

deadprogram avatar Jul 22 '22 16:07 deadprogram

Thinking a bit I can imagine a couple solutions and drawbacks


func PWMforPin(Pin) (pwmGroup,error)
  • This function signature cannot be shared between targets- non portability for cases where
    • Other non-PWMer methods of pwmGroup are used
    • Implementing this same signature for MCUs with an exported PWM type would let users then use type in other functions and structs. This is not an issue resulting from this suggested change but can be aggravated by standardizing this function signature

func PWMforPin(Pin) (PWMer,error)
  • Needs an exported PWMer interface definition in machine package, which is not quite idiomatic. We use drivers package for this.

func PWMforPin(Pin) (pwmer,error)

I actually like this one the best, strangely. pwmer is an unexported interface definition.

  • Interface serves as a guarantee of the functionality one may expect while preventing users from depending on method sets that may change.
  • It also doubles as a railguard, preventing users from using functionality that may otherwise not be present in other MCUs

Edit:

  • If a user wants the concrete type for an MCU they may always cast it.

soypat avatar Jul 22 '22 22:07 soypat

func PWMforPin(Pin) (pwmer,error) as long as the interface is the same seems like a good approach to me.

deadprogram avatar Jul 24 '22 15:07 deadprogram

Sorry for the slow response to this PR, other things have been getting in the way! I've updated the PR with the feedback, I was unsure on where to put the pwmer interface so it's currently sat in the pwm.go file

Should I be retroactively updating the PWM for other boards as part of the this PR? AFAIK it's only the atmega328p, atmega1280 and nrf528xx that have support? @soypat @deadprogram

charlie-haley avatar Aug 29 '22 19:08 charlie-haley

The reason that the PWM peripheral types are different between chips is that a single chip can have multiple different kinds of PWM with different features. So this won't really work:

func PWMforPin(Pin) (pwmGroup,error)

And I also don't think we should define an interface here, precisely because it varies by chip. It is perhaps possible to return a very generic pwmer type that only supports the most core features (like Configure() and Top()) that can then be type-asserted by users of the PWMForPin function. That works, but to be honest I'm not a big fan of it either.

Perhaps a better question is: why do we need this at all? I think the main reason people want a function like PWMForPin() is because it is currently difficult to know which pins can be used by which PWM peripherals. It requires knowing a fair amount of chip internals. And that's not good. To fix this, I have been working on better documentation. It would take a lot of work (and be very error-prone) to document this for every board. However, doing this per chip is doable. What I'd like to see is documentation like this:

Pin Hardware pin UART I2C SPI PWM
D0 PB09 UART1 (RX) - - TC4 (channel 1)
D1 PB08 UART1 (TX, RX) - - TC4 (channel 0)

I started work on this in https://github.com/tinygo-org/tinygo-site/pull/272. But that's just the start: that will only generate some basic information about pins. I would like to extend this to cover all peripherals like in the table above.

What do you think about this? Would this be good enough to make PWM (and other peripherals) usable?

(Sidenote: TC4 is currently unimplemented, it would be implemented as a different peripheral from the TCC peripherals because it has a different and more limited feature set).

aykevl avatar Sep 05 '22 01:09 aykevl

Here is a PR to add PWM peripheral information to tinygo.org: https://github.com/tinygo-org/tinygo-site/pull/288

aykevl avatar Sep 05 '22 15:09 aykevl

Perhaps a better question is: why do we need this at all? I think the main reason people want a function like PWMForPin() is because it is currently difficult to know which pins can be used by which PWM peripherals. It requires knowing a fair amount of chip internals. And that's not good.

The main motivation for this, at least for me, is the rp2040 is quite a unique case when it comes to microcontrollers, technically every single GPIO can be used as PWM output, this means there's some overlap between which GPIO exposes which PWM peripheral and they can clash (e.g GP18/GP19 and GP2/GP3 can both expose PWM1A/PWM1B)

Being able to fetch the PWM peripheral by pin, for the rp2040 at least, makes sense - this also aligns with functionality provided by MicroPython and the Pico SDK.

Considering this issue is more present with the rp2040 over other MCU's, should we just go back to the original implementation like below, then we only need to expose this function to rp2040 users?

PWMforPin(pin Pin) (*pwmGroup, err error) {

charlie-haley avatar Sep 05 '22 16:09 charlie-haley

The main motivation for this, at least for me, is the rp2040 is quite a unique case when it comes to microcontrollers, technically every single GPIO can be used as PWM output, this means there's some overlap between which GPIO exposes which PWM peripheral and they can clash (e.g GP18/GP19 and GP2/GP3 can both expose PWM1A/PWM1B)

I don't think the rp2040 is unique in this at all. The samd21/samd51 chips also have multiple pins per PWM output. For example, the Circuit Playground Express can attach TCC0 channel 0 to four different pins.

Also, I don't understand why PWMForPin would be more necessary when every PWM output can connect to multiple pins? I would argue it's the opposite: when you have to use PWM instances (aka "slices") explicitly, it's a lot more obvious that you're using the same PWM instance for two different pins. With PWMForPin, you might try to configure two pins with a particular PWM output and wonder why they both have the same frequency (because they happen to use the same PWM peripheral).

aykevl avatar Sep 05 '22 16:09 aykevl

Ayke, A quick drive-by response to your worry about obfuscating the underlying hardware with an API: You can't use a pwmGroup right away after calling PWMforPin- you still need the channel corresponding to the pin. This idea is ubiquitous throughout the pwmGroup API:

func (p *pwmGroup) Set(channel uint8, value uint32)

I don't think the abstraction is lost by adding PWMForPin.

Opinion of mine: with every passing day I come to think representing PWM with an unexported interface ain't so bad. I'm not sure what board would be unrepresentable with a properly defined, tiny interface. This may also be a terrible idea, I have not put much thought into it.

soypat avatar Sep 05 '22 22:09 soypat

Of all the RP2040 related ideas I find this one to be the most pressing. I'd really like to solve this as soon as possible as I keep running into this issue (https://github.com/tinygo-org/tinygo/issues/2583) every time I use PWMs on the RP2040.

Before solving this issue though I think https://github.com/tinygo-org/drivers/issues/491 is part of the critical path. I think we could probably define the PWM interface there. That would be a godsend of a solution for both the Netdev driver model and this PWM interface dilemma since it would solve issues posted here. It is by far the most idiomatic way of solving this in my honest opinion.

soypat avatar Feb 24 '23 00:02 soypat