HomeSTM32 TutorialsSTM32 TIMERS #2. How to Measure PWM Input

How to measure PWM signal in STM32

PWM (Pulse Width Modulation) signals are commonly used in embedded systems to represent information such as speed, position, or sensor data. In many real-world applications, it is not enough to generate a PWM signal — we also need to measure it. This includes finding the signal’s frequency and duty cycle accurately using the microcontroller.

In this second tutorial in the STM32 Timer Series, we will learn how to read a PWM input signal using an STM32 microcontroller. We will configure the timer in PWM input mode using STM32CubeMX and then use HAL functions to capture the signal’s high time and period. By the end of this tutorial, you will understand how to calculate the duty cycle and frequency of an incoming PWM signal and use this technique in applications such as motor feedback, RC receiver input, and signal monitoring.

PWM Signal Basics and PWM Input Working in STM32

What Is a PWM Signal?

PWM (Pulse Width Modulation) is a digital signal that alternates between HIGH and LOW states, but instead of varying voltage, it varies time.

A PWM signal is defined by three main parameters:

  • Period – Total time taken for one complete cycle
  • Frequency – How often the signal repeats (Frequency = 1 / Period)
  • Duty Cycle – Percentage of time the signal stays HIGH in one period

Why these matter

  • Duty cycle often represents power, speed, or position
  • Frequency defines how fast the control or data updates
  • Many peripherals (ESCs, motors, RC receivers) encode information using PWM

This makes PWM a very common signal type that needs to be measured, not just generated.


How PWM Input Works in STM32

STM32 microcontrollers measure PWM signals using timers, which are high-resolution hardware counters.

Role of Timers

  • Timers continuously count at a known clock frequency
  • The counter value represents elapsed time
  • This allows precise time measurement between signal edges

What Is Input Capture?

  • Input Capture is a timer mode where the counter value is latched automatically
  • The capture happens when a selected signal edge occurs:
    • Rising edge
    • Falling edge

Measuring Duty Cycle and Frequency

STM32 uses both edges of the PWM signal:

  • Rising edge → Falling edge
    • Measures HIGH time
  • Rising edge → Next rising edge
    • Measures total period

Using these two values:

  • Duty Cycle = (High Time / Period) × 100
  • Frequency = Timer Clock / Period

This method is fully hardware-assisted, accurate, and efficient, making it ideal for reading PWM signals in real-time applications before moving on to the firmware implementation.

STM32 CubeMX Configuration

Clock Configuration

The image below shows the STM32 clock configuration.

image showing the clock configuration to measure PWM input.

The Nucleo-F446 has 8MHz crystal shared between ST-Link and the main MCU. We will use this 8MHz crystal along with PLL to generate the system clock.

  • 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 Configuration (PWM out for Testing)

To measure the input PWM signal, we need a PWM signal. I am going to use the TIM1 to generate one. This will be useful as we will already know the pulse width and frequency, making it easier for us to debug any issue with the measured pulse.

image shows the Timer 1 configuration to generate the PWM output signal.
  • We already know that the APB2 Timer Clock is running at 180 MHz.
  • Using the Auto Reload Value (ARR) of 1800, along with the prescaler of 1 (0 in the configuration) 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.

I have already covered how to generate the PWM with STM32. If you have any issues understanding these parameters, check out the linked tutorial.


Timer 2 Configuration (PWM Input)

Timer 2 will be used to measure the incoming signal. The image below shows the Timer 2 configuration for PWM input mode.

Image shows the STM32 Timer 2 configuration for PWM input mode
  • 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 1 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)

Internal clock division in STM32 Timer.

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


Prescaler Division Ratio

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

Prescaler division ratio in STM32 timer.

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.

Input filter configuration in STM32 timer.

The IC Filter configures the Sampling 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.

STM32 Code to measure PWM Input

Generate PWM signal

Before we capture the PWM input signal, we need to generate it using Timer 1. Inside the main function, we will simply start the Timer 1.

  HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
  TIM1->CCR1 = 900;

Here I am starting Timer1 in the PWM mode. The Capture Compare Value is set to 900. With the ARR of 1800, this will generate a pulse with the duty cycle of 50% [(900/1800)*100].

We will measure this signal using the Timer 2 PWM Input Mode.


Start Timer 2

Inside the main function, we will start the TIM2 in the input capture mode. We need to start both the channels, even though the signal pin is connected to only one of them.

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.


Input Capture Callback

We will do our entire calculation inside the HAL_TIM_IC_CaptureCallback function.

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 and store it in the variable ICValue.
  • 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.

image explaining how the PWM inout mode is used to calculate the duty and frequency of the incoming PWM signal.

As I mentioned above, the CubeMX did not let me configure the IC Prescaler. 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.

This allows us to capture the signal once after every 8 events. This is used to slow down the interrupt generation rate is the signal frequency is very high.

  sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_RISING;
  sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
  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)
  {
    Error_Handler();
  }

Result of the PWM input Capture

The image below shows the result captured on the STM32CubeIDE Debugger console.

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.

VIDEO Tutorial

STM32 PWM Input Tutorial Video

This tutorial explains how to read and measure a PWM input signal using STM32 microcontrollers. Along with the written guide, this video walks through the timer configuration in CubeMX, input capture setup, and the HAL code used to calculate the duty cycle and frequency of an incoming PWM signal. Watch the video to clearly understand how PWM input works at both the hardware and code level.

Watch the Video

Conclusion

In this tutorial, we covered how to read a PWM input signal using an STM32 microcontroller by configuring the timer in input capture mode. We discussed the basics of PWM signals, explained how STM32 timers use rising and falling edges to measure time, and walked through the CubeMX configuration and HAL-based code needed to capture the signal accurately. By measuring the high time and total period, we were able to calculate both the duty cycle and frequency of the incoming PWM signal.

Understanding PWM input is extremely useful in many real-world embedded applications where external devices communicate using PWM. This technique can be applied to read signals from RC receivers, motor controllers, sensors, and feedback systems without additional hardware. Since the measurement is handled by the timer peripheral, it is both precise and efficient, making it suitable for real-time applications and forming a strong foundation for more advanced timer-based features in STM32 projects.

Checkout More STM32 Timer Tutorials

1 2

STM32 PWM Input Project Download

Info

You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.

STM32 PWM Input FAQs

Subscribe
Notify of

11 Comments
Newest
Oldest Most Voted
Inline Feedbacks
View all comments
Kris
11 months ago
  • In Timer 2 section: “The prescalar is 0, so the timer clock will be same as APB 2 Timer clock, that is 90 MHz” – should be … will be same as APB 1, …

As I understand timers 1, 8, 9, 10, 11 are connected to APB 2 and 2, 3, 4, 5, 6, 7, 12, 13, 14 to APB 1

James Chou
1 year ago

When I use the ADC value to dynamically change the pulse value of Timer 1 Channel 1 (__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, adc_value );), why doesn’t the duty cycle change? That is, HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_2) does not change along with the ADC value.
I observed the PWM generated by Timer 1 Channel 1 using a logic analyzer, and it indeed changes according to the ADC value.

damar
Reply to  James Chou
11 months ago

chibba chibba

Enver
4 years ago

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

Denko
4 years ago

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

Victor
5 years ago

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

Miguel
5 years ago
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?
JISHNU
6 years ago

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