How to Generate 3 Phase PWM

This is 6th tutorial in the STM32 Timer series, and today we will cover yet another Timer synchronization feature where we will generate a 3 phase PWM waveform.

In the previous tutorial we saw how the Trigger mode can be used to start the counter of the slave timer. The slave counter only starts once the counter of the master timer overflows. But let’s assume a situation where we want to start the slave counter when the master counter has reached a certain value. This is basically the requirement for generating a 3 phase or 2 phase PWM, and we can’t achieve it with the previous method we used.

So today in this tutorial we will see how can we control the start of the slave counter by setting a threshold for the master counter.


Below is the clock setup for the project.

Here both the APB Timer clocks are running at 90 MHz. We will use the timers 1, 2 and 3 to create the 3 PWM signals. Here TIM1 is connected to the APB2 bus and TIM2 and 3 are connected to the APB1 bus. I am keeping the clock same for these buses so that we can use the same configuration for the timers.

The internal Trigger system for these Timers is shown below

Here you can see the TIM2 as a slave can be controlled by the master TIM1 using the ITR0 signal. Similarly, the TIM3 can be controlled by the TIM2 using the ITR1 signal.

We will use these signals to trigger the counter of the slave Timers, when the master counter reaches a predefined value.

  • Basically the TIM1 will generate a PWM signal on channel 1
  • When the counter 1 will reach 33% of the ARR value, TIM1 Output Compare on channel 2 will go high.
  • This will trigger the ITR0 signal, and TIM2’s counter will start at that moment.
  • Similarly, when the counter 2 will reach 33% of the ARR value, TIM2 Output compare on channel 2 will go high and it will trigger the ITR1 signal.
  • TIM3 counter will start at this moment.


Below is the setup for all three Timers

  • Here the TIM1 is the master for TIM2, so there is no slave mode for it.
  • TIM2 is the slave for TIM1, which can be triggered by the ITR0 signal. It is also the master for TIM3.
  • TIM3 is only the slave for TIM2, which can be triggered by the ITR1.

All the timers have the channel 1 set as the PWM output, and channel 2 as the Output compare (Except TIM3 since it’s not the master).
The output compare signal is used as the trigger source for the next Timer in line.

Since both the APB Timer clocks are running at the same 90MHz frequency, I have used the similar configuration for all three timers.
Here the timers will run at 100 Hz frequency with the PWM duty as 40%.
You can check the PWM tutorial if you don’t understand this

The pulse value (AKA CCR value) for the output compare channel is set at 3333. This is basically the 33% of the ARR value, which is 9999.

The Pinout

The pinout is shown below

These 3 pins are basically the PWM pins from each timer. I have connected them to the 3 channels of the Logic Analyzer.

Some insight into the code

There is not much to do in the coding part. Since we are using the PWM and output compare, we will simply start both the functions for the timers.

  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  HAL_TIM_OC_Start(&htim1, TIM_CHANNEL_2);

  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  HAL_TIM_OC_Start(&htim2, TIM_CHANNEL_2);

  HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
  /* USER CODE END 2 */

Here I have started the PWM for channel 1 and Output Compare for channel 2 for TIM1 and TIM2. Since TIM3 is not the master for any timer, we will only start the PWM for it.


First let’s see the frequency and duty cycle for the PWM signal.

Here you can see the frequency is approximately 100 Hz with the duty of 40%. This is as per the configuration we did for the PWM signal.

Now we will see the three waveforms we got.

Here I have set the three markers at the rising edges of all three PWMs.
On the right you can see the timestamps of these rising edges relative to the TIM1.

We know the time period of the PWM signal is 10ms, so the PWM2 starts at 3.3ms i.e. 33% of 10ms. Similarly the PWM3 starts at 6.6ms i.e 66% of 10ms.
Basically all three signals have 120 degrees phase difference between them.

This difference will remain constant throughout.

Check out the Video Below


You can help with the development by DONATING
To download the code, click DOWNLOAD button and view the Ad. The project will download after the Ad is finished.

9 Comments. Leave new

  • Jorge Hernandez
    June 20, 2024 10:09 PM

    Thanks a lot your post.

    I need to adjust the shift phase of signals dinamically in the main program. I have tried changing the value of CCR2 of timers TIM1 and TIM2, but it does not work. Here is the sentences:

    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, 200);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, 200);

    Do you know if the shift phase can be adjusted dinamically and How I can do it?


  • Can this method work for 4 phase ?

  • Thank you

  • Thamanoon Kedwiriyakarn
    March 8, 2023 2:12 PM

    Here I am using the ADC to adjust the frequency max. 50Hz

     while (1)
      adc_val = ADC_Read();
    a = (uint16_t)(((float)(adc_val)/4095)*20000);    // cal Autoreload Max. 50Hz
      b = (uint16_t)((float)(a)*0.333);          // Phase 120 Degree
      c = a/2;
    __HAL_TIM_SET_AUTORELOAD(&htim1, a);
    __HAL_TIM_SET_AUTORELOAD(&htim2, a);
    __HAL_TIM_SET_AUTORELOAD(&htim3, a);
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_2, b);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_2, b);
    //__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_2, b);
    __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, c);
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, c);
    __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, c);
      /* USER CODE BEGIN 3 */

  • Thamanoon Kedwiriyakarn
    March 8, 2023 10:48 AM

    Here the code for calculate and set the CCR.
     while (1)

    adc_val = ADC_Read();
      a = (uint16_t)(((float)(adc_val)/4095)*10000);    // cal duty cycle
    //a = (uint16_t)(((adc_val)/4095)*10000);        // cal duty cycle
      __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, a);
      __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, a);
      __HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, a);

    • Thamanoon Kedwiriyakarn
      March 8, 2023 11:16 AM

      Here is for ADC read function, the ADC set up can be done by CubeMX, sir.

      uint16_t ADC_Read(void)
      uint16_t adcVal;

      /* Enable ADC and start ADC conversion */
      /* Wait for ADC conversion to be completed */
      HAL_ADC_PollForConversion(&hadc1, 1);
      /* Get ADC value from ADC data register */
      adcVal = HAL_ADC_GetValue(&hadc1);
      /* Stop ADC conversion and disable ADC */

      return adcVal;

  • Thamanoon Kedwiriyakarn
    March 8, 2023 9:58 AM

    Hello, I have an idia to use ADC to adjust speed(CCR), Thanks You very much, sir.

  • Thamanoon Kedwiriyakarn
    February 6, 2023 6:06 PM

    Thank you very much, Sir


Leave a Reply

Your email address will not be published. Required fields are marked *

Fill out this field
Fill out this field
Please enter a valid email address.


Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We don't have any banner, Flash, animation, obnoxious sound, or popup ad. We do not implement these annoying types of ads!

We need money to operate the site, and almost all of it comes from our online advertising.

Please add to your ad blocking whitelist or disable your adblocking software.