cancel
Showing results for 
Search instead for 
Did you mean: 

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

RAltm
Senior

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

12 REPLIES 12
Gyessine
ST Employee

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
Senior

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

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