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 buy me a coffee Sensor by clicking DONATE OR Just click DOWNLOAD to download the code


Notify of
Oldest Most Voted
Inline Feedbacks
View all comments