use setOverflow to set the period, setCaptureCompare to set the dutycycle. you can review setPWM codes, it likely call other initialization functions instead of just changing the period and duty cycle.hitachii wrote: Fri Feb 18, 2022 6:00 am 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!
How to dynamically change duty cycle with HardwareTimer library?
Re: How to dynamically change duty cycle with HardwareTimer library?
Re: How to dynamically change duty cycle with HardwareTimer library?
setPWM() API is not designed to change duty only, it is there to fully configure the Timer from scratch.
To change only dutycycle, setCaptureCompare() is the appropriate API.
To change only dutycycle, setCaptureCompare() is the appropriate API.
Re: How to dynamically change duty cycle with HardwareTimer library?
This should be enough example:hitachii wrote: Fri Feb 18, 2022 4:38 am 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.
Code: Select all
// for bluepill
HardwareTimer *PWMtimer = new HardwareTimer(TIM2); // Timer 2
// Channel 1 -> PA0
// Channel 2 -> PA1
// Channel 3 -> PA2
// Channel 4 -> PA3
HardwareTimer *IRQtimer = new HardwareTimer(TIM1); // Timer 1
volatile uint16_t sample; // 0-255
void Update_IT_callback(void)
{
PWMtimer ->setCaptureCompare(1, sample, TICK_COMPARE_FORMAT); // change duty cycle of channel 1
// calculate sample (this should be triangle output at 195.3125 Hz (50000/256)
sample = (sample + 1) & 255; // 8bit sample (0-255) // if greater than 255, there will be constant DC level at 3.3V
}
void setup() {
// put your setup code here, to run once:
sample = 0;
pinMode(PA0, OUTPUT); // Timer 2 Channel 1 // Audio output
PWMtimer ->pause();
PWMtimer ->setPrescaleFactor(1);
PWMtimer ->setOverflow( 255 , TICK_FORMAT); // Overflow in 256 ticks (same for all 4 channels) // PWM base frequency: 72MHz/256=281.25KHz
PWMtimer ->setMode(1, TIMER_OUTPUT_COMPARE_PWM1, PA0); // channel 1
PWMtimer ->resume();
IRQtimer->pause();
IRQtimer->setOverflow(50000, HERTZ_FORMAT); // irq at samplerate
// IRQtimer->setOverflow(20, MICROSEC_COMPARE_FORMAT);
IRQtimer->attachInterrupt(Update_IT_callback); //set interrupt on overflow
IRQtimer->resume();
}
void loop() {
// put your main code here, to run repeatedly:
}
It's your choice do you want calculate sample in interrupt, or calculate samples in main loop and use buffer with head/tail in interrupt.
I recommend reading about timers:
https://github.com/stm32duino/wiki/wiki ... er-library
Re: How to dynamically change duty cycle with HardwareTimer library?
This was incredibly helpful, this is running flawlessly with not one, but two independent PWM channels! It really helped once I understood what capture compare did. Thank you so much.Bakisha wrote: Fri Feb 18, 2022 8:09 pmThis should be enough example:hitachii wrote: Fri Feb 18, 2022 4:38 am 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.
Now that it's working, I have a question regarding good coding practices. Currently in my code, the duty cycles are stored in an array as two global variables. When the callback function happens, the array goes through a for loop to update each variable.
There's already a lot going on in the callback function, and I know that you're supposed to keep interrupts as short as possible, so I was wondering how people may set the duty cycle of each individual timer so that the callback function is a 'safe' size.
Maybe I double the frequency of the timer which has the callback function and have a counter which determines which of the two duty cycles are being updated?
Re: How to dynamically change duty cycle with HardwareTimer library?
If the duty cycle is simply retrieved from a table, perhaps you could get DMA to do it.
But before going down that path, are you sure you have a problem, and if you do, how much is the setCaptureCompare(…) contributing to it. What else are you doing in the callback?
If you have some spare GPIOs, and a way to watch them (eg DSO), you could do things like measure how much time is spent in the callback. Say flip a GPIO on entry and exit to the callback, and/or when you change the duty cycle. Something else I have done on occasion is put the mainline code into a tight loop flipping a GPIO, and then by seeing how long that activity stops for, you can see what effect the interrupts are having, including any overhead outside your callback routine.
But before going down that path, are you sure you have a problem, and if you do, how much is the setCaptureCompare(…) contributing to it. What else are you doing in the callback?
If you have some spare GPIOs, and a way to watch them (eg DSO), you could do things like measure how much time is spent in the callback. Say flip a GPIO on entry and exit to the callback, and/or when you change the duty cycle. Something else I have done on occasion is put the mainline code into a tight loop flipping a GPIO, and then by seeing how long that activity stops for, you can see what effect the interrupts are having, including any overhead outside your callback routine.
Re: How to dynamically change duty cycle with HardwareTimer library?
- use "pwmTim->setCaptureCompare(..." as first line of code in interrupt callback. No matter how long you calculations are, interrupt latency is usually same, so you'll have steady timing. Plus, once value of counter-compare is written, it will apply that value in next timer overflow. From my experiments, there is, at worst, around 3.5uS to enter and same time to exit interrupt callback. More code = more latency.
- know your numbers:
Counter-compare register is unsigned 16bit number, between 0 and overflow (also 16bit unsigned).
For 20uS, overflow is 72*20=1400 cpu (timer) ticks. (if in TICK_FORMAT)
- use integers, not floats for samples, for faster bit-shifting. Multiplication/division is much slower, it will end as unsigned 16bit integer number for counter-compare value anyway (more like 11bit, in your case)
- use 1024 samples, simple will do, no needs for "if/else". It will be even faster if array is in RAM, not in flash memory.
- in main loop, set builtin led to on, in interrupt callback, first line of code is to set led to off. Use your eyes to determine brightness of led. Once is mostly dimmed, you had reached limit (plus, you'll gain 0.7uS ( digitalWrite(LED_BUILTIN,LOW) when you delete that line of code, once testing are finished)
- before timers for interrupts are set, call (in setup) you interrupt callback 1000 times and measure how much it need to execute it (use arduino's micros() )
- imho, two functions in one interrupt callback is faster then two interrupt callbacks with one function each.
- or calculate lot of samples in main loop, and use only head/trail in interrupt
- take all this advices with great reserve, i've being wrong before
Good luck
- know your numbers:
Counter-compare register is unsigned 16bit number, between 0 and overflow (also 16bit unsigned).
For 20uS, overflow is 72*20=1400 cpu (timer) ticks. (if in TICK_FORMAT)
- use integers, not floats for samples, for faster bit-shifting. Multiplication/division is much slower, it will end as unsigned 16bit integer number for counter-compare value anyway (more like 11bit, in your case)
- use 1024 samples, simple
Code: Select all
(saw[(count + phase) & 0x3ff]
- in main loop, set builtin led to on, in interrupt callback, first line of code is to set led to off. Use your eyes to determine brightness of led. Once is mostly dimmed, you had reached limit (plus, you'll gain 0.7uS ( digitalWrite(LED_BUILTIN,LOW) when you delete that line of code, once testing are finished)
- before timers for interrupts are set, call (in setup) you interrupt callback 1000 times and measure how much it need to execute it (use arduino's micros() )
- imho, two functions in one interrupt callback is faster then two interrupt callbacks with one function each.
- or calculate lot of samples in main loop, and use only head/trail in interrupt
- take all this advices with great reserve, i've being wrong before

Re: How to dynamically change duty cycle with HardwareTimer library?
Thank you, I took this advice and applied it to checking the speed of a DAC and it was very helpful in checking its speed!ozcar wrote: Fri Mar 04, 2022 6:52 pm If you have some spare GPIOs, and a way to watch them (eg DSO), you could do things like measure how much time is spent in the callback. Say flip a GPIO on entry and exit to the callback, and/or when you change the duty cycle. Something else I have done on occasion is put the mainline code into a tight loop flipping a GPIO, and then by seeing how long that activity stops for, you can see what effect the interrupts are having, including any overhead outside your callback routine.
This is very good to know, thank you! At first, I was eluded by which functions to put in the interrupts altogether. Putting the capture compare in the interrupt function has definitely yielded the best results. Thank you for all your help, this response will be taken into great consideration.Bakisha wrote: Fri Mar 04, 2022 11:05 pm - use "pwmTim->setCaptureCompare(..." as first line of code in interrupt callback. No matter how long you calculations are, interrupt latency is usually same, so you'll have steady timing. Plus, once value of counter-compare is written, it will apply that value in next timer overflow. From my experiments, there is, at worst, around 3.5uS to enter and same time to exit interrupt callback. More code = more latency.
-
- Posts: 4
- Joined: Sat Mar 12, 2022 12:03 am
Re: How to dynamically change duty cycle with HardwareTimer library?
Thanks, Bakisha, for the helpful explanations!
Regarding your example code:
If I understand the documentation (https://github.com/stm32duino/wiki/wiki ... er-library) correctly, you would have to set this to 256 instead of 255 if you want to count 256 ticks (0..255). The argument is the number of ticks per cycle (256), not the maximum counter value (255). Please correct me if I'm wrong.
Joerg

Code: Select all
PWMtimer ->setOverflow( 255 , TICK_FORMAT); // Overflow in 256 ticks
Joerg
Re: How to dynamically change duty cycle with HardwareTimer library?
Hmmm, now even i am not sure anymore
Goal was not to count cycles, but use setCaptureCompare to set 0% duty cycle with value 0, and 100% with value 255.
And now i am not sure of that edge case. I think CounterCompare need to be greater than overflow to get 100% duty cycle. Overflow of 255 should end up as 254 for overflow register, based on code at https://github.com/stm32duino/Arduino_C ... #L519-L553
Again, i am not sure, these kind of things i usually find out with experiments. Some example code and logic analyzer answer most of my questions, but until i get some time to do it, i can't say i know.

Goal was not to count cycles, but use setCaptureCompare to set 0% duty cycle with value 0, and 100% with value 255.
And now i am not sure of that edge case. I think CounterCompare need to be greater than overflow to get 100% duty cycle. Overflow of 255 should end up as 254 for overflow register, based on code at https://github.com/stm32duino/Arduino_C ... #L519-L553
Again, i am not sure, these kind of things i usually find out with experiments. Some example code and logic analyzer answer most of my questions, but until i get some time to do it, i can't say i know.
-
- Posts: 4
- Joined: Sat Mar 12, 2022 12:03 am
Re: How to dynamically change duty cycle with HardwareTimer library?
The example in the documentation Wiki says this (in slightly broken English):
The documentation also says that both the Overflow and the CaptureCompare range of values is "[1.. 0x10000] (Hardware register will range [0..0xFFFF]". (0x10000 = 65536 = 2^16, 0xFFFF = 65535)
According to my logic, that would mean that for a duty cycle of 0%, you would have to set CaptureCompare to 1 (not 0), and for 100%, you would have to set it to the same value as Overflow. I have no idea why he designed it this way. In my opinion, it would have been more logical to use the actual hardware register range starting at 0 ...
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)
According to my logic, that would mean that for a duty cycle of 0%, you would have to set CaptureCompare to 1 (not 0), and for 100%, you would have to set it to the same value as Overflow. I have no idea why he designed it this way. In my opinion, it would have been more logical to use the actual hardware register range starting at 0 ...