How to dynamically change duty cycle with HardwareTimer library?

Post here first, or if you can't find a relevant section!
ag123
Posts: 1655
Joined: Thu Dec 19, 2019 5:30 am
Answers: 24

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

Post by ag123 »

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!
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.
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 »

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.
User avatar
Bakisha
Posts: 139
Joined: Fri Dec 20, 2019 6:50 pm
Answers: 5
Contact:

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

Post by Bakisha »

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.
This should be enough example:

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:

}
Please note that this (untested) code is for bluepill. If you are using something different, make sure what timer is on what pin.
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
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: Fri Feb 18, 2022 8:09 pm
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.
This should be enough example:
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.

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?
ozcar
Posts: 143
Joined: Wed Apr 29, 2020 9:07 pm
Answers: 5

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

Post by ozcar »

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.
User avatar
Bakisha
Posts: 139
Joined: Fri Dec 20, 2019 6:50 pm
Answers: 5
Contact:

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

Post by Bakisha »

- 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

Code: Select all

(saw[(count + phase) & 0x3ff]
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 :D Good luck
hitachii
Posts: 22
Joined: Sat Jan 29, 2022 8:59 pm

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

Post by hitachii »

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.
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!
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.
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.
Laserjones
Posts: 4
Joined: Sat Mar 12, 2022 12:03 am

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

Post by Laserjones »

Thanks, Bakisha, for the helpful explanations! :) Regarding your example code:

Code: Select all

PWMtimer ->setOverflow( 255 , TICK_FORMAT); // Overflow in 256 ticks 
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
User avatar
Bakisha
Posts: 139
Joined: Fri Dec 20, 2019 6:50 pm
Answers: 5
Contact:

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

Post by Bakisha »

Hmmm, now even i am not sure anymore :lol:
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.
Laserjones
Posts: 4
Joined: Sat Mar 12, 2022 12:03 am

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

Post by Laserjones »

The example in the documentation Wiki says this (in slightly broken English):

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)
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 ...
Post Reply

Return to “General discussion”