How to dynamically change duty cycle with HardwareTimer library?

Post here first, or if you can't find a relevant section!
hitachii
Posts: 22
Joined: Sat Jan 29, 2022 8:59 pm

How to dynamically change duty cycle with HardwareTimer library?

Post by hitachii »

Hello,

I am having trouble understanding how to dynamically set the duty cycle of a PWM pin according to the Full PWM Configuration example code here: https://github.com/stm32duino/STM32Exam ... ration.ino

I would like to be able to arbitrarily change the duty cycle while the program is running. This will be for an audio application, so the duty cycle will determine a changing voltage after the proper hardware filtering.

The only thing that I could think to do is use a reference variable as the capture compare percentage, which did not compile. How can I achieve my goal? Thank you very much.
ag123
Posts: 1668
Joined: Thu Dec 19, 2019 5:30 am
Answers: 25

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by ag123 »

try using setCaptureCompare
https://github.com/stm32duino/wiki/wiki ... er-library
from the API wiki page

Code: Select all

 MyTim->setCaptureCompare(channel, 50); // Default format is TICK_FORMAT. 50 ticks
 MyTim->setCaptureCompare(channel, 50, TICK_FORMAT)
 MyTim->setCaptureCompare(channel, 50, MICROSEC_COMPARE_FORMAT); // 50 microseconds    between counter reset and compare
 MyTim->setCaptureCompare(channel, 50, HERTZ_COMPARE_FORMAT); // 50 Hertz -> 1/50    seconds between counter reset and compare
 MyTim->setCaptureCompare(channel, 50, RESOLUTION_8B_COMPARE_FORMAT); // used for    Dutycycle: [0.. 255]
 MyTim->setCaptureCompare(channel, 50, RESOLUTION_12B_COMPARE_FORMAT); // used for   Dutycycle: [0.. 4095]
hitachii
Posts: 22
Joined: Sat Jan 29, 2022 8:59 pm

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by hitachii »

This is actually what I've been trying. When I declare the class outside of the function, then call setCaptureCompare in the loop, nothing is sent to the pin producing PWM at all.

When using the example code as is, it works to send a static duty cycle, but I do not know how to adjust the duty cycle while the code is running. Say I had a potentiometer and wanted to change the cycle from 25% to 50%. Is this possible?
ag123
Posts: 1668
Joined: Thu Dec 19, 2019 5:30 am
Answers: 25

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by ag123 »

https://github.com/stm32duino/wiki/wiki ... er-library
set the timer mode appropriately

Code: Select all

    MyTim->setMode(channel, TIMER_OUTPUT_COMPARE_PWM1, pin);

Code: Select all

typedef enum {
  TIMER_DISABLED,                         // == TIM_OCMODE_TIMING           no output, useful for only-interrupt
  // Output Compare
  TIMER_OUTPUT_COMPARE,                   // == Obsolete, use TIMER_DISABLED instead. Kept for compatibility reason
  TIMER_OUTPUT_COMPARE_ACTIVE,            // == TIM_OCMODE_ACTIVE           pin is set high when counter == channel compare
  TIMER_OUTPUT_COMPARE_INACTIVE,          // == TIM_OCMODE_INACTIVE         pin is set low when counter == channel compare
  TIMER_OUTPUT_COMPARE_TOGGLE,            // == TIM_OCMODE_TOGGLE           pin toggles when counter == channel compare
  TIMER_OUTPUT_COMPARE_PWM1,              // == TIM_OCMODE_PWM1             pin high when counter < channel compare, low otherwise
  TIMER_OUTPUT_COMPARE_PWM2,              // == TIM_OCMODE_PWM2             pin low when counter < channel compare, high otherwise
  TIMER_OUTPUT_COMPARE_FORCED_ACTIVE,     // == TIM_OCMODE_FORCED_ACTIVE    pin always high
  TIMER_OUTPUT_COMPARE_FORCED_INACTIVE,   // == TIM_OCMODE_FORCED_INACTIVE  pin always low

  //Input capture
  TIMER_INPUT_CAPTURE_RISING,             // == TIM_INPUTCHANNELPOLARITY_RISING
  TIMER_INPUT_CAPTURE_FALLING,            // == TIM_INPUTCHANNELPOLARITY_FALLING
  TIMER_INPUT_CAPTURE_BOTHEDGE,           // == TIM_INPUTCHANNELPOLARITY_BOTHEDGE

  // Used 2 channels for a single pin. One channel in TIM_INPUTCHANNELPOLARITY_RISING another channel in TIM_INPUTCHANNELPOLARITY_FALLING.
  // Channels must be used by pair: CH1 with CH2, or CH3 with CH4
  // This mode is very useful for Frequency and Dutycycle measurement
  TIMER_INPUT_FREQ_DUTY_MEASUREMENT,

  TIMER_NOT_USED = 0xFFFF  // This must be the last item of this enum
} TimerModes_t;
setOverflow sets the period

Code: Select all

     MyTim->setOverflow(10000); // Default format is TICK_FORMAT. Rollover will occurs when timer counter counts 10000 ticks (it reach it count from 0 to 9999)
     MyTim->setOverflow(10000, TICK_FORMAT);
     MyTim->setOverflow(10000, MICROSEC_FORMAT); // 10000 microseconds
     MyTim->setOverflow(10000, HERTZ_FORMAT); // 10 kHz
set capture compare sets the trigger point within that period

Code: Select all

 MyTim->setCaptureCompare(channel, 50); // Default format is TICK_FORMAT. 50 ticks
    MyTim->setCaptureCompare(channel, 50, TICK_FORMAT)
    MyTim->setCaptureCompare(channel, 50, MICROSEC_COMPARE_FORMAT); // 50 microseconds    between counter reset and compare
    MyTim->setCaptureCompare(channel, 50, HERTZ_COMPARE_FORMAT); // 50 Hertz -> 1/50    seconds between counter reset and compare
    MyTim->setCaptureCompare(channel, 50, RESOLUTION_8B_COMPARE_FORMAT); // used for    Dutycycle: [0.. 255]
    MyTim->setCaptureCompare(channel, 50, RESOLUTION_12B_COMPARE_FORMAT); // used for   Dutycycle: [0.. 4095]
e.g.

Code: Select all

MyTim->setOverflow(1000); // period 1000 ticks
MyTim->setCaptureCompare(channel, 500); // capture compare 500 ticks
note that ticks is divided down from PCLK by

Code: Select all

    MyTim->setPrescaleFactor(div);
note that set capture compare has that additional channel parameter, you need to choose the channel that goes to your pin.
For that check the specs sheets and read the hardware timer section in the ref manual.
That would give you a better idea of how it works.
ag123
Posts: 1668
Joined: Thu Dec 19, 2019 5:30 am
Answers: 25

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by ag123 »

if you want to setCaptureCompare on the fly, one way is to do it from within the timer interrupt callback. i.e. set the output compare register (i.e. capture compare) to a new value when the interrupt fires.
there are some limits with this approach, as interrupts has a limited bandwidth, you need to keep codes as lean and as efficient as possible within the timer callback. timer interrupt callback typically has a limit about 500khz about max. inefficient codes or lots of processing or sub calls within the timer call back interrupt would degrade that to well less than 500 khz

this unfortunately is also the way to make custom waveforms other than fixed period and duty cycle PWM. it is severely limited by how fast your timer interrupt can run. e.g. if a timer interrupt callback that 'does nothing' is 500 khz. the more codes or less efficient codes (e.g. multiple calls etc) you put in that timer callback, this timer khz would be severely lower like 1/2 to perhaps 1/10 of it 50 khz (for inefficient codes)

I think some have tried with these techniques e.g. setOverflow and/or setCaptureCompare from within the timer callback to drive neopixels, which has hard to achieve sub microseconds intervals in its protocols.
https://cdn-shop.adafruit.com/datasheets/WS2812B.pdf
however, apparently some have successfully implemented driving those led strips from within a timer interrupt. efficient codes is necessary to make that happen.

various neopixel 'libraries' didn't even venture there and simply use spi. but using timers has better control over the precise timings.
in a sense, hardware timer may be used to 'bit bang' an aribtrary spi like waveform if you'd like, lots of limits for bandwidth and depends on your code prowess.
Last edited by ag123 on Fri Feb 11, 2022 9:45 am, edited 1 time in total.
ABOSTM
Posts: 60
Joined: Wed Jan 08, 2020 8:40 am
Answers: 7

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by ABOSTM »

When using the example code as is, it works to send a static duty cycle, but I do not know how to adjust the duty cycle while the code is running. Say I had a potentiometer and wanted to change the cycle from 25% to 50%. Is this possible?
As it works with the example code (I mean PWM is output on the pin), then you just need to add extra call to setCaptureCompare() whenever you need to change dutycycle.
hitachii
Posts: 22
Joined: Sat Jan 29, 2022 8:59 pm

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by hitachii »

ag123 wrote: Fri Feb 11, 2022 9:19 am if you want to setCaptureCompare on the fly, one way is to do it from within the timer interrupt callback. i.e. set the output compare register (i.e. capture compare) to a new value when the interrupt fires.
there are some limits with this approach, as interrupts has a limited bandwidth, you need to keep codes as lean and as efficient as possible within the timer callback. timer interrupt callback typically has a limit about 500khz about max. inefficient codes or lots of processing or sub calls within the timer call back interrupt would degrade that to well less than 500 khz
This is crucial information, thank you so much! Luckily, I'm going at about 50khz at the moment with fairly slim callbacks, but the code will change and that is great to know.

For anyone else in the future with a similar issue also, I found this thread: viewtopic.php?f=62&t=394
User avatar
Bakisha
Posts: 140
Joined: Fri Dec 20, 2019 6:50 pm
Answers: 5
Contact:

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by Bakisha »

From my audio experiments with STM32, i found that using two timers gives excellent results. One timer is used for interrupt callback at samplerate (20uS for 50kHz, but can be much less), and second timer at much higher frequency (around 250KHz) of which you are changing duty cycle.
Depending of your project, but, for example, if you need 8bit resolution, you could set overflow of 256 and simple

Code: Select all

MyTim2->setCaptureCompare(channel, volume, TICK_FORMAT)
on 72MHz CPU will give 281KHz base PWM frequency.
If your project use percentage, setting overflow at 100 (in TICK_FORMAT), will give you base frequency of 720KHz (for 72MHz CPU).
Simple 100 Ohm/100 nF lowpass filter + 10uF for high-pass will remove most of high-frequency stuff.
It all depends on quality of your audio output. Yes, you could set base PWM frequency same as samplerate frequency, i'm just saying that it doesn't need to be on same timer and same frequency.
hitachii
Posts: 22
Joined: Sat Jan 29, 2022 8:59 pm

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by hitachii »

Bakisha wrote: Sat Feb 12, 2022 12:15 pm From my audio experiments with STM32, i found that using two timers gives excellent results. One timer is used for interrupt callback at samplerate (20uS for 50kHz, but can be much less), and second timer at much higher frequency (around 250KHz) of which you are changing duty cycle.
Depending of your project, but, for example, if you need 8bit resolution, you could set overflow of 256 and simple
This sounds like it would be useful for my application. Would you happen to have an example of this? My main hurdle with this is actually figuring out how to do the code. I'm definitely doing something wrong, but I haven't been able to change the duty cycle using the HardwareTimer library at all thanks.
hitachii
Posts: 22
Joined: Sat Jan 29, 2022 8:59 pm

Re: How to dynamically change duty cycle with HardwareTimer library?

Post by hitachii »

Wanted to update that using setCaptureCompare in the loop indeed works!

For some reason setPWM does not work for me when it's inside of the loop function. If anyone has success with this, please feel free to elaborate, but I've got setCaptureCompare sending separate duty cycles out to different channels now. This is great!
Post Reply

Return to “General discussion”