Skip to content

docs/machine: Specify new class machine.PWM. #4237

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

Closed
wants to merge 4 commits into from

Conversation

dpgeorge
Copy link
Member

As discussed previously in #2283, it would be good to have a simple PWM class in the machine module to allow for generating PWM output in a way that is portable across the different ports. Such functionality may already be available in one way or another (eg through a Timer object), but because configuring PWM via a Timer is very port-specific, and because it's a common thing to do, it's beneficial to have a top-level construct for it.

The proposal here aims to provide all required functionality in a minimal way. Some points about it:

  • you can set frequency, or period of the PWM output
  • you can set the duty in time (eg microseconds for the pulse width) or as a percentage ratio (eg 50%)
  • because for most PWM implementations, changing the frequency/period will alter the duty cycle, changing the frequency/period on the object also allows to re-set the duty cycle in one call, namely pwm.init(freq=..., duty_u16=...); counter to this, there's no way to just change the frequency/period.

It might be interesting to compare this with the proposal for ADC, #4213, because in a way they are related. With the PWM class I didn't go the route of adding a PWMBlock for further configuration, but instead showed an alternative to this, by having a block parameter to the constructor which would allow to have finer control over which underlying hardware generator was used for the PWM object.

@dhalbert
Copy link
Contributor

CircuitPython API is here: https://circuitpython.readthedocs.io/en/latest/shared-bindings/pulseio/PWMOut.html. I don't expect you'll copy it but we provide similar functionality. One thing we include in the constructor is whether the frequency will vary or not. On SAMD chips (and maybe others) we can use multiple output channels of a single timer as long as they share the same frequency. We can vary the duty cycle but not the frequency per channel. So we can create more PWMOut objects than timers that way.

Interestingly the nRF52 has separate PWM peripherals from its timers.

@dpgeorge
Copy link
Member Author

CircuitPython API is here: https://circuitpython.readthedocs.io/en/latest/shared-bindings/pulseio/PWMOut.html. I don't expect you'll copy it but we provide similar functionality.

Thanks, I missed that (found analogOut and just assumed that was PWM).

Since CircuitPython uses properties it makes it hard to copy the class verbatim. But copying the functionality to use methods would give something like:

pwm = PWM(pin, *, frequency, duty_cycle, variable_frequency)
pwm.frequency([value]) # get or set current freq in Hz
pwm.duty_cycle([value]) # get or set current duty cycle as ratio out of 65535

That's simple. But I think it's important to be able to set the period as well as frequency (eg what about 1.5Hz?), and for things like servo motors to be able to specify the duty as an absolute time (eg microseconds) not just a fraction of the cycle period.

@boochow
Copy link
Contributor

boochow commented Oct 15, 2018

Hi,
I am implementing PWM class for Raspberry Pi port now, so I'm very concerned with this topic.
I want to understand more about the block parameter.
My understanding of this new API is that:

Assuming that the source clock is 19.2 MHz,

PWM.init(pin, tick_hz=960000, duty_u16=32768, freq=1000)

sets the clock prescaler to 20.0 and results 1 KHz 50% duty.
This is identical to

PWM.init(pin, tick_hz=960000, duty_ticks=480, period=960)

Raspberry Pi PWM hardware has:

  1. selectable clock source, 19.2MHz or 216MHz or 500MHz or 1GHz
  2. 24bit prescaler, 12 bit integer part, 12 bit fractional part
  3. 32 bit period register, 32 bit duty register
  4. some additional parameters, such as MASH filter function to control fractional prescaler, PWM mode selection flags (Mark/Space enable, serializer mode, DMA, FIFO, etc.)

The API seems fairly good for me but in some cases I may want to set the prescaler value and additional parameters explicitly.

How should I design the block parameter to do that?
I haven't get what the block argument is.

@dpgeorge
Copy link
Member Author

How should I design the block parameter to do that?
I haven't get what the block argument is.

Some MCUs have more than one "hardware unit" that can generate PWM on any given pin. The idea of the block argument is that it allows the user to specify which "hardware unit" to use to generate the signal. In the case of RPi it seems it only has one "hardware unit" so this parameter is not useful for it.

The API seems fairly good for me but in some cases I may want to set the prescaler value and additional parameters explicitly.

In general such parameters should be additional keywords to the init method and/or constructor.

@boochow
Copy link
Contributor

boochow commented Oct 17, 2018

The idea of the block argument is that it allows the user to specify which "hardware unit" to use to generate the signal.

Ah, I see. I could pass a timer object as block arg, for example, to specify which timer to be used for generating PWM signal ( in the case of STM32).

In general such parameters should be additional keywords to the init method and/or constructor.

OK, I'll use kw arguments for PWM optional parameters and add a clock manager class for specifying clock-related settings (clock source, prescaler, etc.).

Then another question:
Specifying tick_hz should configure the hardware registers (prescaler for example), or is merely kept in the instance property to map duty_ticks value to quantitative time length?

@dpgeorge
Copy link
Member Author

Specifying tick_hz should configure the hardware registers (prescaler for example), or is merely kept in the instance property to map duty_ticks value to quantitative time length?

It's the latter: the idea of tick_hz is that it should be specified together with period to set the units of the period.

But keep in mind that this here is just a proposal, it's not yet concrete! Probably it will change.

@boochow
Copy link
Contributor

boochow commented Oct 18, 2018

Thanks, now I can start implementing my PWM class. I got that this is not final, but anyway I need some interface specification and this is a very good start point.

@boochow
Copy link
Contributor

boochow commented Oct 19, 2018

Only one of freq and period should be specified at a time.

I think specifying freq AND period WITHOUT tick_hz could be useful.
In this case the user can determine PWM frequency and the number of ticks in a period.
tick_hz can be calculated as freq*period.

@boochow
Copy link
Contributor

boochow commented Oct 20, 2018

I have implemented the PWM class for Raspberry Pi, and I think tick_hz parameter may not be necessary (if the API will not include freq() method). It is used to convert ticks and frequencies but since we have no pwm.freq() or other methods which require argument represented in Hz, the only chance of using tick_hz is in the constructor.
duty_ticks() and duty_u16() only require the current period value and do not require tick_hz.

Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge
Copy link
Member Author

I've updated this to simplify it, to remove the period, ticks_hz and duty_ticks parts because IMO the "ticks" stuff is a bit confusing. It's now pretty minimal but hopefully functional enough to use as a initial version of a cross-port PWM API.

@chrismas9
Copy link
Contributor

This looks good as long as STM32 implementation allows block to specify the timer. Use case is RGB LEDs which may be steady 24bit (fast PWM) or blinking 3bit where frequency and duty cycle are variable, eg 1Hz 50% blue, tank half full, 2Hz 20% orange, tank nearly empty. One LED on PA1/2/4 on TIM5, the other on PA3/15/PB3 on TIM2

dpgeorge added a commit that referenced this pull request Apr 30, 2021
This adds an initial specification of the machine.PWM class, to provide a
way to generate PWM output that is portable across the different ports.
Such functionality may already be available in one way or another (eg
through a Timer object), but because configuring PWM via a Timer is very
port-specific, and because it's a common thing to do, it's beneficial to
have a top-level construct for it.

The specification in this commit aims to provide core functionality in a
minimal way.  It also somewhat matches most existing ad-hoc implementations
of machine.PWM.

See discussion in #2283 and #4237.

Signed-off-by: Damien George <damien@micropython.org>
@dpgeorge
Copy link
Member Author

This was squashed and merged in 9e1b25a

@dpgeorge dpgeorge closed this Apr 30, 2021
@dpgeorge dpgeorge deleted the docs-machine-pwm branch April 30, 2021 06:45
@dpgeorge
Copy link
Member Author

This looks good as long as STM32 implementation allows block to specify the timer.

There is currently no block argument specified. It could be that PWMBlock is a better concept (and then also ADCBlock). Still to be decided!

ksekimoto pushed a commit to ksekimoto/micropython that referenced this pull request Jul 16, 2021
This adds an initial specification of the machine.PWM class, to provide a
way to generate PWM output that is portable across the different ports.
Such functionality may already be available in one way or another (eg
through a Timer object), but because configuring PWM via a Timer is very
port-specific, and because it's a common thing to do, it's beneficial to
have a top-level construct for it.

The specification in this commit aims to provide core functionality in a
minimal way.  It also somewhat matches most existing ad-hoc implementations
of machine.PWM.

See discussion in micropython#2283 and micropython#4237.

Signed-off-by: Damien George <damien@micropython.org>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants