How to dynamically change duty cycle with HardwareTimer library?
How to dynamically change duty cycle with HardwareTimer library?
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.
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.
Re: How to dynamically change duty cycle with HardwareTimer library?
try using setCaptureCompare
https://github.com/stm32duino/wiki/wiki ... er-library
from the API wiki page
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]
Re: How to dynamically change duty cycle with HardwareTimer library?
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?
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?
Re: How to dynamically change duty cycle with HardwareTimer library?
https://github.com/stm32duino/wiki/wiki ... er-library
set the timer mode appropriately
setOverflow sets the period
set capture compare sets the trigger point within that period
e.g.
note that ticks is divided down from PCLK by
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.
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;
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
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]
Code: Select all
MyTim->setOverflow(1000); // period 1000 ticks
MyTim->setCaptureCompare(channel, 500); // capture compare 500 ticks
Code: Select all
MyTim->setPrescaleFactor(div);
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.
Re: How to dynamically change duty cycle with HardwareTimer library?
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.
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.
Re: How to dynamically change duty cycle with HardwareTimer library?
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.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?
Re: How to dynamically change duty cycle with HardwareTimer library?
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.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
For anyone else in the future with a similar issue also, I found this thread: viewtopic.php?f=62&t=394
Re: How to dynamically change duty cycle with HardwareTimer library?
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 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.
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)
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.
Re: How to dynamically change duty cycle with HardwareTimer library?
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.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
Re: How to dynamically change duty cycle with HardwareTimer library?
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!
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!