Skip to main content
RAltm
Senior
February 19, 2026
Solved

Most efficient way to generate four 50% duty cycle frequencies?

  • February 19, 2026
  • 12 replies
  • 708 views

Hello,

I want to generate four 50% duty cycle frequencies up to ~12kHz. It should be possible to enable/disable single outputs. Currently no special high requirements about accuracy, etc. Using a STM32U0 device (56MHz max).

My first thought was to use an upcounting timer with four output compare channels where the timer itself is free running (ARR set to max. value). The compare channels are set to toggle on match and on each OC interrupt, the corresponding CCR register value is set to the next equivalent ARR value to toggle again. For example, if the timer is running at 1MHz and 1kHz frequency is needed, 1000 will be added to the CCR. To start/stop the output, I'd modify the corresponding OCxM value between toggle and frozen mode.

However, I'm not sure if this is the most efficient way, especially regarding overhead and complexity. Another solution which came to my mind was to use a DDS approach with a (low power) timer, constantly firing interrupts at the needed resolution. On each interrupt, a variable (per channel) is increment by a given value and toggling the output (simple GPIO output) on overflow.

For the first approach I assume the accuracy is better since the output is controlled by hardware, but it would have jitter if two or more compare interrupts occur at the same time (might depend on how the calculation for the next CCR value is performed). The second approach would enable me to serve all outputs simultaneously within a single interrupt.

Any recommendations or additional pros/cons for those approaches?

Regards

Best answer by MM..1

Waste three timers is based on what you need waste. For complete MCU offloaded generator you require 4 timers every with one channel. Zero instructions is used for hold signals on setup freq.

Oposite force is use one timer = waste many instructions on every ISR.

Both is efficient own way.

12 replies

TDK
February 19, 2026

Have one timer output the same PWM on each of 4 channels.

Change CCRx value to something above ARR when you want to turn off the channel. Put them back at their previous value when you want to turn it back on.

"If you feel a post has answered your question, please click ""Accept as Solution""."
RAltm
RAltmAuthor
Senior
February 19, 2026

Hi @TDK 


@TDK wrote:

Have one timer output the same PWM on each of 4 channels.


By PWM you mean output compare with toggle on match, so basically the first approach I mentioned, right? If not, then I don't understand how using PWM could be used to generate four independand frequencies since in PWM mode the ARR defines the frequency.


@TDK wrote:

Change CCRx value to something above ARR when you want to turn off the channel. Put them back at their previous value when you want to turn it back on.


I assumed this is also possible, but I couldn't find anything in the reference manual if this is allowed or would result in undefined behaviour. It would be easier than modifying the OCxM bits.

Regards 

TDK
February 19, 2026

Not toggle on match. PWM mode 1. CCRx should be half of (ARR+1) to give a 50% square wave.

TDK_0-1771542939650.png

 

"If you feel a post has answered your question, please click ""Accept as Solution""."
gregstm
Senior II
February 20, 2026

Another possible approach - use DMA (circular mode) to write to the GPIO BSRR register (to set/reset bits directly), you could clock the DMA from the LSE or similar. I use this technique a lot for Led displays.

gbm
Super User
February 20, 2026

If you cannot afford using 4 timers, the approach you outline seems to be the next good candidate. 12 kHz * 4 is close to nothing for the MCU, assuming you don't use HAL interrupt handling slowdown facility. Just write your own, register-based code (will be much shorter than HAL-based). Actually, it's 3 lines of C code per channel in the timer ISR:

if (TIMx->SR & TIM_SR_CCyIF)
{
 TIMx->SR = ~TIM_SR_CCyIF;
 TIMx->CCRy += halfperiod[y];
}

 

My STM32 stuff on github - compact USB device stack and more: https://github.com/gbm-ii/gbmUSBdevice
LCE
Principal II
February 20, 2026

> ... assuming you don't use HAL interrupt handling slowdown facility.

 

Made my day! :D :D :D

RAltm
RAltmAuthor
Senior
February 20, 2026

@TDK 

Maybe I wasn't clear enough, sorry: I need different frequencies per channel and the frequencies will change during runtime. This can't be achieved using a single timer and channel PWM mode since ARR defines the frequency.

@gregstm 
Interesting idea using DMA. I'll check if it would be feasible.

@gbm 
It's not that I can't afford using four timers, it's more like that I don't want to waste three timers if it could be done with one timer :) Would be bad practice, I think.
Good point with avoiding HAL for the IRQ, I think writing a naked handler should be possible.

Thank you all for your answers.

Regards

TDK
February 20, 2026

> Maybe I wasn't clear enough, sorry: I need different frequencies per channel and the frequencies will change during runtime. This can't be achieved using a single timer and channel PWM mode since ARR defines the frequency.

Ahh, that certainly changes things.

You can't generate 4 different frequencies with one timer, so you are left with the other options. If you need them to have zero-jitter, you'll need to use four timers. If a small amount of jitter is acceptable, either DMA or interrupt will do.

(Well, technically you could update CCRx every iteration and use toggle on match to get different frequencies and use DMA to update. Pretty involved, though.)

"If you feel a post has answered your question, please click ""Accept as Solution""."
ST Technical Moderator
February 23, 2026

Hello @RAltm 
Well, I think the simplest and easiest way is to make a timer drive the others like @gbm. have mentioned
you can use TIM1 to drive TIM2 and TIM3 using ITR0 and use ETR for the others (based on STM32U083).

If you want different duty cycles, you can use DMA with circular mode or linked lists.
BR
Gyessine

In order to give better visibility on the answered topics, please click on 'Best answer' on the reply which solved your issue or answered your question.
RAltm
RAltmAuthor
Senior
February 24, 2026

Thank your for the suggestions.

The first version is up and running, using one timer and four OC channels with toggle on match. On each match, the ISR adds the needed value for a given frequency to the current CCR value. As has been mentioned, this approach will generate overhead - I'll check how much time is spent within the ISR. Calculating the real efficiency is tricky because the frequencies can change during runtime. The worst case would be all four channels on maximum frequency, but this will be very rare.

For the DMA approach writing to the timer registers, it seems that this is only feasible when using four timers. I'll check this after the project is finished (this would be the time when I know how many timers are left :) ).

@gbm 
I've to admit that the current version uses HAL :o As mentioned, I'll check the ISR duration and then I'll implement the naked IRQ handler to see the difference with and without HAL.

@TDK 
As mentioned, no special (high) requirements for jitter, etc 

@MM..1 
As mentioned, if enough timers are free after the project is completed, I might go for the almost non-instructions needed approach.

@Gyessine 
I'm not sure if I understood your suggestion correctly: when driving TIM2/3 with TIM1, how shall they generate four different frequencies with 50% duty cycle? 
Thank you for the DMA links, those are very interesting.

@all
The initial question was about the most efficient way, based on your answers my (current) conclusion is: there's more than one way and it depends... So, for now, I've an approach to proceed with (one timer and ISR with and without HAL) and now I know how to optimize it at the end by "wasting" timers :)
I've chosen MM..1s answer as solution since the statement "Both is efficient own way." is true. Thank you all for your valuable answers.

Regards

MM..1
Super User
February 25, 2026

Next one timer type is GPIO DMA BSRR table setup. Valid only for fixed frequency multipliers. For example 1kHz , 2kHz , 5kHz, 8kHz with less precision :

#define PB2_SET (1U << 2)
#define PB3_SET (1U << 3)
#define PB5_SET (1U << 5)
#define PB9_SET (1U << 9)

#define PB2_RST (1U << (2 + 16))
#define PB3_RST (1U << (3 + 16))
#define PB5_RST (1U << (5 + 16))
#define PB9_RST (1U << (9 + 16))

const uint32_t gpio_dma_table[16] =
{
/*0 */ PB2_SET | PB3_SET | PB5_SET | PB9_SET,
/*1 */ PB9_RST,
/*2 */ PB9_SET,
/*3 */ PB5_RST | PB9_RST,
/*4 */ PB3_RST | PB9_SET,
/*5 */ PB5_SET | PB9_RST,
/*6 */ PB9_SET,
/*7 */ PB5_RST | PB9_RST,
/*8 */ PB2_RST | PB3_SET | PB5_SET | PB9_SET,
/*9 */ PB9_RST,
/*10*/ PB9_SET,
/*11*/ PB5_RST | PB9_RST,
/*12*/ PB3_RST | PB9_SET,
/*13*/ PB5_SET | PB9_RST,
/*14*/ PB9_SET,
/*15*/ PB5_RST | PB9_RST,
};

HAL_DMA_Start(
 &hdma_tim2_up,
 (uint32_t)gpio_dma_table,
 (uint32_t)&GPIOB->BSRR,
 16
);

__HAL_TIM_ENABLE_DMA(&htim, TIM_DMA_UPDATE);
HAL_TIM_Base_Start(&htim);

for precision table size required is 80 (1x2x5x8) 32bit memory array. Here is wasted memory...