Skip to main content
Associate III
January 27, 2026
Solved

Timer synchronisation

  • January 27, 2026
  • 15 replies
  • 598 views

I am using a STM32G4 MCU and STM32CubeIDE. 

I want to synchronise two timers (TIM3 and TIM15) that are generating PWM signals so that they both start at the same time. 

TIM3 is set to 250 kHz and the PWM is modulated via DMA on channel 1.  The modulated signal is a 10 kHz sine wave.

TIM15 is set to 10 kHz and the PWM is a simple 50% duty cycle.

I need the timers to start together to ensure that the PW Modulated 10 kHz sine wave (TIM3) is synchronised to the 10 kHz PWM (TIM15).

I thought it would be straight forward enough. 

I have configured TIM15 as master.  In CubeMX I have set Master/Slave Mode (MSM bit) to "Enable (Trigger delayed for master/slaves simultaneous start)" and Trigger Event Selection to "Enable (CNT_EN)".

I have configured TIM3 as a slave.  In CubeMX I have set Slave Mode to "Trigger Mode" and Trigger Source to "ITR6" (per the reference manual).

To the CubeIDE generated code I have added:

 /* USER CODE BEGIN 2 */
 HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, BDPos_CCRValue_Buffer, CCRValue_BufferSize);
 HAL_TIM_PWM_Start(&htim15, TIM_CHANNEL_1);

 /* USER CODE END 2 */

BDPos_CCRValue_Buffer and CCRValue_BufferSize are a pointer to the sine wave data buffer and the buffer size.

When I run the code the timers are not synchronised.  After debugging through the code I found that TIM3 starts immediately on executing HAL_TIM_PWM_Start_DMA and does not wait for TIM15 to be started.  TIM15 starts when this code is executed:

 /* Enable the Capture compare channel */
 TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
void TIM_CCxChannelCmd(TIM_TypeDef *TIMx, uint32_t Channel, uint32_t ChannelState)
{
 uint32_t tmp;

 /* Check the parameters */
 assert_param(IS_TIM_CC1_INSTANCE(TIMx));
 assert_param(IS_TIM_CHANNELS(Channel));

 tmp = TIM_CCER_CC1E << (Channel & 0x1FU); /* 0x1FU = 31 bits max shift */

 /* Reset the CCxE Bit */
 TIMx->CCER &= ~tmp;

 /* Set or reset the CCxE Bit */
 TIMx->CCER |= (uint32_t)(ChannelState << (Channel & 0x1FU)); /* 0x1FU = 31 bits max shift */
}

TIM15 starts when the last line is executed (ie after /* Set or reset the CCxE Bit */).

Is there a way to make TIM3 wait for TIM15 to start?

Thanks.

 

Best answer by Repwoc

In summary, when using CubeMX + HAL or LL:

1. To ensure that a slave timer starts when the master timer starts, the master timer must be initialised before the slave timer is initialised (ie MX_TIMmaster_Init() must run before MX_TIMslave_Init()) and the slave timer must be started before the master timer (eg with HAL_TIM_PWM_Start());

2. To align the 10 kHz PWM from TIM15 (slave) with the 10 kHz PW Modulated sine wave from TIM3 (master)  I set the TIM3 (master) Trigger Event Selection TRGO to "Update Event" to ensure that the slave timer started at the same time as the first DMA request to get sine wave data.

15 replies

TDK
Super User
January 28, 2026

An update event sets CNT back to 0 (if upcounting) and loads/updates ARR if preloading is enabled.

"If you feel a post has answered your question, please click ""Accept as Solution""."
RepwocAuthor
Associate III
January 28, 2026

OK thanks I guess that's what the manual says but it adds "and generates an update of the registers".

I've switched back to HAL (because LL made no difference).

I tried switching the run order of the MX generated timer initialisation functions, MX_TIM3_Init() and MX_TIM15_Init(), so that the master timer is initialised before the slave timer.  Now after both timers are initialised TIM3 CEN bit is NOT set and the TIM3 signal does NOT start until TIM15 is started.  I feel like I'm getting somewhere.  Of course it means I'll need to make this edit each time I regenerate the code so I'll review the timer selections eg use TIM1 instead of TIM15.

The next issue is the 10 kHz waveforms are not aligned.  There seems to be a few microseconds of delay between starting TIM15 (master) and TIM3 (slave) starting.  Is this to ne expected?  Is there a way to adjust this?

TDK
Super User
January 28, 2026

> There seems to be a few microseconds of delay between starting TIM15 (master) and TIM3 (slave) starting.  Is this to ne expected?  Is there a way to adjust this?

This is expected. It takes some time for the logic to propagate so master and slave will be a few ticks off. It should be the same amount of ticks each time. Two options:

  • Adjust CNT of the slave forward to compensate.
  • Set both of the timers to slaves in trigger mode and use a separate master timer that isn't related to an output signal to enable them.

The RM talks about this some.

TDK_0-1769611412818.png

 

"If you feel a post has answered your question, please click ""Accept as Solution""."
waclawek.jan
Super User
January 28, 2026

Shouldn't be *micro*seconds though, given tens of megahertzs clock...

Even

  • setting up timebase and PWM in both TIM (no master-slave), except setting TIMx_CR1.CEN
  • disabling interrupts
  • setting TIMx_CR1.CEN in both timers
  • reenabling interrupts

should yield consistently sync within fraction of microseconds, at those clocks

JW

RepwocAuthor
Associate III
January 28, 2026

Thanks they both sound like great ideas.

I tried swapping the master/slave modes of both timers, so TIM3 becomes the master and TIM15 the slave.  This solves the MX_TIMx_Init() run order issue too.  I expected the startup delay to now show up on TIM15 as it is now the slave but the signals look identical to before I switched master/slave modes.  So I expect that the few ticks of slave startup delay is an insignificant amount of time whereas the delay I'm seeing is 4 uS, which happens to correspond to the TIM3 period, ie one pulse.

Could the perceived delay be caused by DMA?  When is the first DMA request sent - on timer startup or on the first timer update?

RepwocAuthor
Associate III
January 28, 2026

So I changed the master Trigger Event Selection TRGO to "Update Event" instead of "Enable (CNT_EN)" so that TIM15 (slave) starts on the first update event of TIM3 (ie 4 uS after starting TIM3).  Now the 10 kHz signals align perfectly.

Thanks for your input chaps.  I think this is solved.  It only took me about three days :(

RepwocAuthorBest answer
Associate III
January 28, 2026

In summary, when using CubeMX + HAL or LL:

1. To ensure that a slave timer starts when the master timer starts, the master timer must be initialised before the slave timer is initialised (ie MX_TIMmaster_Init() must run before MX_TIMslave_Init()) and the slave timer must be started before the master timer (eg with HAL_TIM_PWM_Start());

2. To align the 10 kHz PWM from TIM15 (slave) with the 10 kHz PW Modulated sine wave from TIM3 (master)  I set the TIM3 (master) Trigger Event Selection TRGO to "Update Event" to ensure that the slave timer started at the same time as the first DMA request to get sine wave data.