cancel
Showing results for 
Search instead for 
Did you mean: 

Increase PWM Resolution

RWazlawick
Associate II

Hi!

Recently, I've been trying to develop a PWM generation module, in a frequency range of 1Hz to 100kHz with a duty cycle step of 1%. I'm using STM32F103C8 Bluepill to do the job.

I can implement this just fine. However, in this range I obtain errors of 6kHz at the top (higher frequencies) and I wondered if I could improve my sofwate in some way. My initial configuration was using TIM1 (up to 72MHz), a period of 100 to generate de 1% step. The prescaler varies according to the selected frequency.

So, I made some research and found the Dithering Technique, but reading the application note of ST, I could not understand very well how it works. I even tested the given example and worked just fine. In some moment I've found someone in this very community who also had doubts about dithering and a certain held my attention

"Imagine the PWM has 100 step (100%) with granularity of 1%.

If over 4 pulses period you generate say 45%, 45%,45%,45% 4 pulses, you get 45%

If over 4 pulses period you generate say 45%, 45%, 45%, 46% 4 pulses, you get 45.25%"

So, what I did next:

  • Configured a second timer (TIM4) with a frequency x10 my first frequency adjusted in TIM1;
  • Enabled the TIM4 global interrupt;
  • Adjusted my duty cycle step to 5% in TIM1 (Period = 20) so I could improve frequency resolution;
  • From a given duty cycle (for instance 42%), came up with a simple algorithm using 10 values with 5% step to achieve the 42%. Example: (45% + 45% + 45% +45% + 40% + 40% + 40% + 40% + 40% + 40% = 420% / 10 = 42%;

Thus, for 10 interrupts of TIM4 I would have 1 period in TIM1. For each interrupt in TIM4, I adjust one of the ten values calculated, I expected the amount would be the 42% and my wave form would have the 42% of duty cycle. However, what it is actually happening is that the last vector from the ten values is adjusted as dutycycle.

The interrupts are occuring correctly and timers seem to be synchronized; Below it is the interupt from TIM4 part.

 if(htim->Instance == TIM4)

 {

if(htim1.Instance->CNT == 0)

{

flag  = 1;

}

if (flag == 1)

{

     HAL_GPIO_TogglePin(SAIDA_*****_GPIO_Port, TEST_O);

   uint32_t dc = dc_calc[period_TIM1]*(htim1.Instance->ARR+1)/100;

/* Converts dutycyle (0 - 100 )to a (0 - 20) step*/

__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, dc ); /*update*/

   if(period_TIM1 >= 9)

 {

period_TIM1 = 0;

flag    = 0;

 }

 else{

  period_TIM1++;

 }

  }

 }

}

Is it my approach wrong? Is there a better way to do it?

Maybe someone could clarify dithering in STM32 implementation for me?

Thanks in advance.

1 ACCEPTED SOLUTION

Accepted Solutions

> I want to develop a PWM module, with frequencies of 1Hz - 100kHz (step of 1Hz) and duty cycles of 1% step.

Well, you can't, not with a fixed source frequency (here, 72MHz system frequency of the mcu) and dividers (that's what the timer is). You can achieve only integer subdivision of the principal frequency, i.e. 72MHz/N; and the the duty resolution is given by N. So, if you'd insist on 1% duty step, you'd need N=K*100, and you'll be constrained to 72kHz, 36kHz etc.

Dithering does not actually change the duty cycle. It is just a method how to reduce the impact of limited duty cycle resolution for the typical application of PWM, which is replacement for DAC. It means, only *after analog filtering* the resulting DC voltage is, with dithered PWM, as if the duty step was smaller than it naturally is.

While you most probably won't be able to achieve exactly what you described above, there's one more degree of freedom - you can change the primary frequency (using the two cascaded PLLs, again resulting in quite severe constraints as there are only integer dividers and "multipliers" available (okay there's the 6.5x setting in PLL1, but that's just 13/5 anyway). This, provided you don't need a fixed primary frequency for some other reasons - UART, USB, whatever depending on timing.

JW

PS Yes that's the same as mine, I have the Polish variant

View solution in original post

12 REPLIES 12
TDK
Guru
if(htim1.Instance->CNT == 0)
{
    flag  = 1;
}

The timer is freerunning. By the time your interrupt fires and the CPU starts executing it, it's very unlikely that CNT will be 0. Not sure what you're trying to achieve with the flag.

If you're just trying to adjust one value every cycle, why not something simpler?

period_TIM1 = (period_TIM1 + 1) % 10;
uint32_t dc = dc_calc[period_TIM1] * (htim1.Instance->ARR + 1) / 100;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, dc);

If you feel a post has answered your question, please click "Accept as Solution".
RWazlawick
Associate II

It was to begin my adjusment of duty cycles always in the beginning of the period of TIM1, when CNT = 0; That instance is from TIM1 and the interrupt is from TIM4.

TDK
Guru

It doesn't feel like you need the complexity of another timer. Just populate the values in dc_calc and let it cycle through them in the interrupt. If you wanted to free up the CPU, you could even do it with DMA.

If you feel a post has answered your question, please click "Accept as Solution".
RWazlawick
Associate II

But if I want a specifically a duty cycle of 42%, would I achieve it by populating dc_calc with my calculated values? Cause my timer is configured with a 5% step of duty cycle (to improve my frequency resolution) and I adjusted this other timer in order to get the 1% step.

berendi
Principal

So TIM4 is generating interrupts at 10x the output frequency, at 1 MHz? That's not going to work. You can't do much work if there is a new interrupt request at every 72 CPU cycles, about 30 of them taken up by interrupt entry/exit overhead. Then HAL would need another maybe 400 cycles to figure out which callback function to call. Forget it.

But I too fail to understand why do you need the other timer at all. You can just calculate the array of 10 duty cycle values in advance, and change them at each update event of TIM1. HAL might barely be able to handle to keep up at 100 kHz (or might not), don't even think about using any HAL function in an interrupt handler at that frequency. Ever.

A simple interrupt handler with an array of precalculated values could do it, although noticeably impacting system performance, it would work. Set the preload flag on TIM1 channel 1 (TIM_CCMR1_OC1PE) beforehand to prevent glitches in the output at low duty cycle values.

volatile uint16_t dc_precalc[10];
void TIM1_UP_IRQHandler(void) {
  static unsigned int index;
  TIM1->SR = ~TIM_SR_UIF; // clear the interrupt flag
  TIM1->CCR1 = dc_precalc[index]; // set the duty cycle from the precalculated array
// try both variants below, don't know which one is faster
#if 1
  index = (index + 1) % 10;
#else
  index += 1;
  if(index >= 10)
    index = 0;
#endif
}

If it is working, consider using DMA as TDK above has suggested. There are useful application notes for the STM32F1 series on register level programming of timers and DMA.

RWazlawick
Associate II

Thank you, I appreciate your suggestions.

Related to use another TIM: I was looking for way to achieve a duty cycle in a 1% step even if configured for a 5% step. So the whole idea was to change my duty cycle in fractions of period, so the final average would be, for the example below, 42%.

0693W000001pIfSQAU.jpg

The lower waveform is the output PWM going to the filter?

And the upper is the timer in which interrupt you modify the duty of lower one?

Which one is TIM1 and which one is TIM4?

Note, that the resulting waveform will have frequency components of not only the PWM "baseline" but also of the lower frequency "modulation". In other words, depending on the particular filter you use, you'll still "see" some of the modulation frequency on the output - maybe less than if you'd simply use a lower frequency PWM capable of the required resolution, but still being present. It depends on your application. how much this matters.

PWM is generally a cheap but inferior method of DAC. Use real DAC if you want a stable output with high resolution.

Maybe you should tell us what did you want to achieve at the first time.

JW

PS. Namesake, germanized version of the name?

TDK
Guru

So cycle through the 10 values and set them up as

45 45 45 45 40 40 40 40 40 40

You'll get a net 42% duty cycle.

I still don't see why this needs a separate timer.

If you're trying to do something more complex, you'll need to explain it better.

If you feel a post has answered your question, please click "Accept as Solution".
RWazlawick
Associate II

The lower is TIM4 and upper is TIM1.

Actually, the upper waveform is my PWM output. Now the lower is where I modify the duty cycles (5% step) of the upper in little fractions so I can result in the desired duty cycle (of 1% step).

I want to develop a PWM module, with frequencies of 1Hz - 100kHz (step of 1Hz) and duty cycles of 1% step. But configuring my proc to this I obtain errors of more than 5kHz in the high frequencies, so I was looking for an alternative to decrease my error.

I've already tried the dithering technique, but I was not able to understand the implementation very well.

PS. It is czech, origin from Vaclav name.