You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@mynewt.apache.org by "paul@wrada.com" <pa...@wrada.com> on 2016/04/05 07:23:31 UTC

Re: PWM API

Thanks for your patience everyone.

I went to write the PWM drivers for Arduino Zero and learned a lot that I
wanted to share.

1) First, I agree that we should set duty cycle in fractional 16-bit.  I
experimented with it and found your observations correct.
2) Arduino (SAMD21G) has two different types of PWM hardware internally,
TC and TCC. There is a key difference that will affect even this simple
API.
3) The multichannel support is challenging to abstract in a Hal, so I
suspect folks that need that will need a specific API for their control
purpose (I.e. synchronous Steppers)

The TC (when running in 16-bit mode) supports only the CC register
(Capture).  This allows us to set the duty cycle of the PWM waveform, the
but period must be specified by the source CLOCK and a binary divide
ratio.   That means that setting of accurate frequency is not really
possible (other than to a factor of 2-16 depending on your luck). For
example, with an 8 Mhz clock driving the PWM, we get frequencies of 122 Hz
(8000000/65536) for the PWM. I can increase the GCLK, but that seems to
give flicker free operation and is the default config of the device. You
can divide this down, but that produces geometrically lower frequencies.
NOTE: when running in 8-bit mode, you can have control over both the CC
and period values and we could get better freq control, but the resolution
is quite poor.  There’s another gotcha with the TC channels.  Each TC
drives two PWM channels, but there is only a single source clock (and a
single period in 8-bit mode) for the two of them so two TC channels cannot
set coarse frequency independently. This is evident on Arduino zero D10
and D12 (also A1 and A2) which run off the same TC.

The TCC (when running in 16-bit mode) supports the CC and period register.
This allows very fined grained control over the PWM frequency and duty
cycle. There is one more catch which is the TCC supports 4 CC channels but
only has one source clock and one Period register (Arduino D13 and D11 and
also A3,A4,D9).   

Which brings me to the discussion here…

Since there is only one source clock per TC or TCC device, I set the
source clock divider in the BSP.  This is the reasonable place to set this
as its the place where you know what is routed where on your board and
what freqs you want. The BSP also sets the source clock frequency.

I created the following API,modifying my proposal taking the advice of the
folks on this thread:

struct hal_pwm* hal_pwm_init(enum system_device_id sysid);
Int hal_pwm_off(struct hal_pwm *ppwm);
Int hal_pwm_enable_duty_cycle(struct hal_pwm *ppwm, uint16_t fraction);
Int hal_pwm_enable_waveform(struct hal_pwm *ppwm, uint32_t on_clocks);
Int hal_pwm_get_clock_freq(struct hal_pwm *ppwm);
Int hal_pwm_get_resolution_bits(struct hal_pwm *ppwm);

NOTE: There are two different ways to enable the device.  One based on
duty cycle and another base on source clocks (exact).

Someone on the thread suggested setting frequency and duty_cycle in a
single Api, but some of the Arduino devices don’t support setting
frequency (see above) and some do, so I though that the API would be
better split.  But, this brings up all the concerns with Greg and Will
below, mainly “What if you had a separate API for frequency and duty etc)…

My desire to make beeps that sound like donkey kong is too strong (go
runtime), so I want to allow an API that sets the frequency.


So based on the following assumptions, I will propose something new.
1) the BSP will set the frequency into the right ballpark since some
controller don’t have fine grained frequency control
2) Aside from that, any super fine tuning of frequency is likely to be for
audio control or other non duty cycle based operation
3) Anyone doing anything super fancy with these multi-channel controllers
will likely use the direct hardware API or a future more complicated
abstractions

I propose to:
1) remove the hal_pwm_enable_waveform (which I wanted) accepting that the
16-bit duty cycle is plenty of fine-grained control

Add one of the two following APIs with a note that they are not supported
on all controllers.

1) hal_pwm_enable_freq(struct hal_pwm *ppwm, uint32_t freq_hz); ― set the
frequency in Hz with 50 % duty cycle.
2) hal_pwm_enable_freq(struct hal_pwm *ppwm, uint32_t freq_hz, uint16_t
duty); ― set the frequency in Hz and duty cycle as fractional 16-bit value;

But either of these begs the question.

1) What if I set the frequency with either of these Apis and then call
hal_pwm_enable_duty_cycle.  Does it reset the frequency or just change the
duty cycle?  How do I restore the default frequency.  Seems like I would
have to do the former (reset the freq).

My plan if I don’t hear better advice, will be to implement #1 and
document that you can set frequency or duty cycle dynamically in the API
but to both. #1.


Paul



On 3/31/16, 2:33 PM, "will sanfilippo" <wi...@runtime.io> wrote:

>Comments…
>
>> On Mar 31, 2016, at 1:59 AM, Greg Stein <gs...@gmail.com> wrote:
>> 
>> On Mar 30, 2016 11:48 PM, "paul@wrada.com" <pa...@wrada.com> wrote:
>>> 
>>> Thanks Greg,
>>> 
>>> That¹s new information for me on the LEDs visuals. Are you talking
>>>about
>>> PWM to control brightness or just visual blink rate?  16-bits seems
>>>large
>>> to me for specifying a duty cycle for brightness, but I have no
>> 
>> Brightness. The human eye easily detects the difference between 1% and
>>2%
>> duty (on) cycle. It is evolved for those low level differences. Imagine
>>an
>> asymptotic curve approaching 100% -- the first couple duty cycle ticks
>>jump
>> upwards. That is the eye seeing the slight change. But that last 30% is
>> pretty level.
>I dont think it is a big deal to use a 16-bit value.
>
>> 
>>> experience.  I was just imagining 8-bit PWM for LED color and
>>>brightness
>>> control kind of like 24-bit RGB.    After a few searches it seems like
>>> most custom LED PWM chips are 12-bits.
>> 
>> Like the 5940? Sure. I've got a couple of those. But in code, we don't
>> usually run around with 12 bit types :-)
>> 
>> The 2811 is 8 bits per channel, but with 24 bits to play with, your
>>fade-up
>> sequence will work great. Just not perfect white the whole way :-)
>> 
>>> I chose channel intentionally, and I¹m open to hearing your comments.
>>>The
>>> chips I looked at have many pin multiplexing options for which pin
>>> connects to which PWM channel; a given channel has a small set (2-3
>>>bits)
>>> of choices.  In some, a pin can connect to one of several channels.
>>>Some
>>> channels can event connect to multiple pins simultaneously with some
>>> complicated synchronization (not covered in this simple API). Boiling
>>>that
>>> down, I felt the channel (actual silicon implementation of PWM) was the
>>> unique resource to acquire.  The BSP, when binding the driver to the
>>>hal
>>> will give the pinmux information, clock source, clock divider, output
>>> settings etc as BSP-specific configuration.   For generic boards like
>>> arduino, these channels will probably be called PWM_D8,PWM_D9 etc so
>>>they
>>> are named like the PWM arduino pins that are printed on the PCB, but on
>>> other custom boards, I think we¹d more likely see them named for the
>>>stuff
>>> they are controlling like SYSTEM_PWM_LED_RED, SYSTEM_PWM_LED_GREEN etc.
>> 
>I like the term channel.
>
>> All good. In this respect, I am not familiar enough with newt's API
>>design
>> to comment.
>> 
>> As an *application* programmer, I want the enable/disable I quoted.
>> Generally, my app is targeted, so I already know the capabilities of my
>> platform and just want to start the damned PWM. No query needed.
>> 
>>> I don¹t mind the idea of rounding to make it fit in a API like Will is
>>> suggesting taking frequency and duty cycle,  but want to ensure that we
>>> have an API that is specified without rounding so there is a ³I know
>>> exactly what I am getting when I call this² method to drive the PWM.
>> 
>> The enable() API as I quoted should be sufficient. The *query* API will
>> inform the app about rounding. "Given the specs I have queried, will my
>> enable() call fit within my tolerance?"
>> 
>> Goal here: enable() is simple. Apps that need rigor can query. I also
>> believe most apps will know their target. They won't even query.
>>Portable
>> apps which query will be the minority.
>> 
>I agree on this one. I think for most cases the enable works and we can
>have some query functions for libraries to obtain the PWM most suited to
>their use (for example).
>
>>> I¹ll have to think more about adding the ³config² to the enable
>>>command.
>>> I was thinking that many PWMs have a synchronous way to change the duty
>>> cycle or period and that folks may want to reconfigure while enabled,
>>>but
>>> that seems like it could also be met by calling enable when its already
>>> enabled.
>> 
>> Yup. Just call enable() again, with the new values.
>> 
>> Consider: if you have a separate API to set duty cycle, what will the
>> reader think about calling that on an active pin? Do they need to call
>>off
>> first? Then set the duty, and call on again? If they set the duty, does
>>it
>> take effect immediately? Or do they need another call? ... But with my
>> proposed single function, it seems clear: just call it, and the pin will
>> run with those new parameters. Done.
>> 
>> Cheers,
>> -g
>