This is the second tutorial in the timer series in STM32. I have already covered how to cover PWM output.
Today in this tutorial we willl see how to use PWM input Mode to measure the frequency and duty of the input signal.

I will be using STM32F446RE controller, But the code should work in any other STM32 microcontroller too.

CubeMX Setup


Clock Setup
  • The system is running at 180 MHz
  • The APB1 Timer clock (TIMER 2 Clock) is at 90 MHz
  • The APB2 Timer Clock (TIMER 1 Clock) is at 180 MHz

Timer 1 (PWM out)

TIM Setup
  • We already know that the APB2 Timer Clock is running at 180 MHz.
  • Using the prescalar of 1800 will set the PWM output Frequency at 100 KHz.
  • The channel 1 is being used as the PWM Out channel.
  • The duty cycle part will be covered in the code itself.

Timer 2 (PWM IN)

  • The clock source is internal clock.
  • The combined channels has been selected in the PWM Input mode on channel 1
  • Note that only one Pin (PA0) has been selected as the input Pin, it is actually the channel 1 pin
  • The channel 2 is internally connected to the channel 1, and this is to measure the duty cycle
  • The prescalar is 0, so the timer clock will be same as APB 2 Timer clock, that is 90 MHz
  • The polarity for the channel 1 is set to the rising edge, and it is set to falling edge for the channel 2.
  • The IC Selection for the Channel 1 is direct, and here we are going to connect our input signal (PA0)
  • THe IC selection for the channel 2 is inDirect, and it is internally connected to the channel 1.
  • The rest of the configuration is explained below

Internal Clock Division

This field indicates the division ratio between the timer clock (CK_INT) frequency and the dead-time and sampling clock (tDTS) used by the dead-time generators and the digital filters (ETR, TIx)

In the main setup, I have set it to NO DIVISION. This means the Sampling Clock will be same as the Internal clock, i.e. 90 MHz

Prescalar Division Ratio

This bit basically defines how often we want to do the capture on the input signal.

The CubeMX did not let me configure this part. But I will configure it later in the main code.

Setting this to 8 events would make it easier for the rest of the code to run. This way the captures will be limited, and less number of interrupts will be triggered.

Input Filter

This basically defines the sampling frequency. Also how many events does it take to validate a transaction.

The IC Filter configures the Smapling Frequency. It also configures the low pass filter, but I will update the information about that some other day.

For now I am choosing the filter as 0, so the Fsample = FDTS , which will be same as the internal clock.

Some Insight into the CODE

HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);   // main channel
HAL_TIM_IC_Start(&htim2, TIM_CHANNEL_2);   // indirect channel
  • Here I am starting the input capture for the Channel 1 in the interrupt mode, and channel 2 in the normal mode.
  • This way the interrupt handler will only be called, when the rising edge gets captured. And that much is enough to get it working\

Once the rising edge is captured, the Input Capture callback will be called, and we will write the rest of the code inside it.

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)  // If the interrupt is triggered by channel 1
		// Read the IC value
		ICValue = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);

		if (ICValue != 0)
			// calculate the Duty Cycle
			Duty = (HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) *100)/ICValue;

			Frequency = 90000000/ICValue;
  • First we will check if the interrupt is called by the channel 1. This is just a formality, since we are using only one of the channel in interrupt mode.
  • Then we will read the Input capture value.
  • In the first capture, this value will be 0. You will see this in the figure below
  • In the second capture, we will get some non zero value, and this will be the period of the incoming signal.
  • We will also read the capture for the falling edge. This will be the time, for which the signal was HIGH.
  • Signal High Time / Period will give us the duty cycle of the signal.
  • Also, the timer clock / ICValue gives us the frequency of the signal.

The calculation used in the code above is explained below with the figure from the reference manual.

As I mentioned above, the CubeMX did not let me configure the IC Prescalar. So I am configuring it in the code itself.

I made a small change in the Timer 2 Configuration. TIM_ICPSC_DIV1 to TIM_ICPSC_DIV8

  sConfigIC.ICPrescaler = TIM_ICPSC_DIV8;   // This was set to TIM_ICPSC_DIV1
  sConfigIC.ICFilter = 0;
  if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)


Check out the Picture from the debugger.

The Frequency is around 200 KHz, and the Duty Cycle is 49%. There is a limit of how much you can measure, and I did perform some experiments to check that.

You can see the video to understand this limitation.

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.

7 Comments. Leave new

  • If I change the prescaler of TIM2, can I measure lower frequcy PWM

  • Is it possible to simulate this example with Proteus (PWM/Timers) for F103 or F401 ?
    Thank you for all STM32 tutorials

  • Hi, do u know how to setup it by DMA?

  • Hello, very good example. I wanted to know how do you connect signals at hardware level. You are using two channels, TIM_CHANNEL_2 and TIM_CHANNEL_3... Does it mean you are using two pins to get the PWM signal? If so, how do you connect them?
    • Input pin Only connected to channel 2.
      If u are getting Ic_VAl2 as 0, use channel 3 in indirect mode. However i will advice not to use this. There are 2 more posts on how to measure pulse width and frequency. Use them.. i was supposed to delete this post but forgot.

  • Sir, In your code IC_Val2, is not getting updated. Hence duty cycle is zero. Also How falling is detected?

    • Did you followed the steps correctly? As shown above it detects falling edge. I wouldn’t prefer this method though. Look for another tutorial “measure pulse width using input capture”. That is more effective


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.