Skip to main content
RAltm
Senior
February 19, 2026
Solved

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

  • February 19, 2026
  • 8 replies
  • 516 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.

8 replies

TDK
Super User
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
Super User
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""."
February 19, 2026

Your first approach (one free‑running timer + 4 output-compare channels in toggle mode, and in each CC interrupt you add the next half‑period to CCRx) is usually the cleanest solution on STM32 when you need different frequencies on multiple pins.

Why it’s efficient enough (even on STM32U0)

Worst case at 12 kHz, each channel toggles twice per cycle:

  • 12 kHz → 24,000 toggles/second per channel
  • 4 channels → 96,000 compare events/second total

On a 56 MHz MCU, ~100k interrupts/second is typically fine if your ISR is short (just clear flags + add an increment). This is far lighter than a DDS-style “high rate tick interrupt,” which would need a much higher interrupt frequency to get decent resolution.

About jitter and “simultaneous” compares

  • The pin toggling happens in hardware exactly at the compare match, not when your ISR runs.
  • The ISR only schedules the next toggle by updating CCRx.
  • If two channels match at the same timer count, both outputs still toggle correctly at that moment. In the ISR you’ll just see multiple CC flags set and handle them in one go.

The only real failure case is if your ISR is so slow that it doesn’t update CCRx before the next compare event for that channel. At 12 kHz you have plenty of time margin.

How to structure the ISR (simple + low overhead)

Use one ISR and handle all channels that fired:

  • Read which CCx flags are set
  • Clear those flags
  • For each fired channel: CCRx += halfPeriodTicks[channel]

This keeps the per-event cost very small and predictable.

Start/stop outputs (simpler than changing OCxM)

Instead of switching OCxM between “toggle” and “frozen,” it’s usually cleaner to:

  • Disable/enable the channel output using CCxE (in TIMx_CCER)
  • Optionally also disable/enable that channel’s interrupt (CCxIE) so you’re not servicing it while “off”

When re-enabling, you can also set a fresh phase so it starts cleanly, for example:

  • CCRx = CNT + halfPeriodTicks before turning CCxE back on

Timer choice tip

If you can, use a 32-bit timer (often TIM2) so CCR wrap-around never becomes a headache. If you must use 16-bit timers, wrap still works, but it’s easier to make mistakes.

If you later want “better” frequency accuracy

If half-period ticks aren’t integers at your chosen timer clock, you can:

  • increase timer clock (less quantization), or
  • use a small remainder accumulator (like Bresenham) so the average period is accurate
    By the way, if you want a quick break after coding/testing, BK66 Game APK is a simple Android option many users in Pakistan try for short sessions—install the official version and keep your account details safe.
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
Principal
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
Super User
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""."
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

To give better visibility on the answered topics, please click on Accept as Solution 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
Chief III
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...