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

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

Open
wants to merge 1 commit into
base: master
from

Conversation

@dpgeorge
Copy link
Member

commented Oct 14, 2018

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

This comment has been minimized.

Copy link

commented Oct 14, 2018

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

This comment has been minimized.

Copy link
Member Author

commented Oct 15, 2018

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Member Author

commented Oct 17, 2018

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Member Author

commented Oct 18, 2018

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor

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

This comment has been minimized.

Copy link
Contributor

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
3 participants
You can’t perform that action at this time.