Input Capture in STM32

This is yet another tutorial in the Timer Series in STM32, and today we will see how to use Input Capture in STM32.
We will use the input capture to measure the Frequency and width of the input signal.

Let’s see the Cube MX setup first.

CubeMX Setup

CLOCK CFG
  • Here I am going to use two timers, i.e Timer 1 for the PWM output and Timer 2 for the Input Capture
  • Timer 1 is connected to the APB2 clock, which is running at 180 MHz
  • Timer 2 is connected to the APB1 clock, which is running at 90 MHz

This part is very important. The minimum frequency, which the device can read will depend on it. Below is the configuration for the TIMER 2

TIM2 CFG
  • I have enabled the Input capture Direct Mode for channel 1
  • The Prescalar is set o 90, which would divide the APB2 clock by 90, making the Timer 2 clock = 1 MHz
  • I am leaving the ARR to 0xffffffff (Max for 32 bit Timer)
  • The minimum frequency that the Timer can read is equal to (TIMx CLOCK/ARR). In our case it will be (1MHz/0xffffffff) Hz.

The TIMER1 is configures to give a PWM output signal. If you want to know more about PWM output, check out the tutorial PWM (Pulse Width Modulation) in STM32 – Controllerstech.com




Measuring Frequency

In order to measure the Frequency of the input signal, we need to measure the time between the 2 rising edges, or between 2 falling edges.

Below is the figure explaining the same

  • When the first Rising edge occurs, the counter value is recorded.
  • Another counter value is recorded after the second rising edge occurs.
  • Now the difference between these 2 counter values is calculated.
  • The Difference in the counter values will give us the frequency.
  • This entire process is shown below
#define TIMCLOCK   90000000
#define PRESCALAR  90

uint32_t IC_Val1 = 0;
uint32_t IC_Val2 = 0;
uint32_t Difference = 0;
int Is_First_Captured = 0;

/* Measure Frequency */
float frequency = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
	{
		if (Is_First_Captured==0) // if the first rising edge is not captured
		{
			IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // read the first value
			Is_First_Captured = 1;  // set the first captured as true
		}

		else   // If the first rising edge is captured, now we will capture the second edge
		{
			IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);  // read second value

			if (IC_Val2 > IC_Val1)
			{
				Difference = IC_Val2-IC_Val1;
			}

			else if (IC_Val1 > IC_Val2)
			{
				Difference = (0xffffffff - IC_Val1) + IC_Val2;
			}

			float refClock = TIMCLOCK/(PRESCALAR);

			frequency = refClock/Difference;

			__HAL_TIM_SET_COUNTER(htim, 0);  // reset the counter
			Is_First_Captured = 0; // set it back to false
		}
	}
}
  • The above callback function is called whenever the rising edge is detected.
  • When called first time, Is_First_Captured was 0 so the hence the IC_Val1 will be recorded.
  • When called after the second rising edge, the Is_First_Captured is 1 now so IC_Val2 will be recorded.
  • We will then calculate the Difference between the 2 values.
  • Reference clock is calculated based on the setup we have done for our timer.
  • Frequency is equal to the (Reference clock / Difference).

In the main function, we have to start the TIMER in the Input capture interrupt mode.
I am also starting the Timer 1 in the PWM mode, so to provide the signal for the Timer2.

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

  HAL_TIM_IC_Start_IT(&htim2, TIM_CHANNEL_1);
Frequency measured using input capture


Measuring Pulse Width

In order to measure the Pulse width, we want the interrupt to trigger on both the edges of the incoming signal.
To do so, we need to modify the CubeMX as shown below.

As you can see in the figure above, I have switched the Polarity to both the edges.
This will make the interrupt to trigger on both the edges of the incoming signal


  • When the first rising edge occurs, The counter value is stored in the ICVal 1
  • The next interrupt will occur at the falling edge, and the counter value is stored in the IC val 2
  • The Pulse width can be calculated using this counter value.
  • The process for the same is shown below
/* Measure Width */
uint32_t usWidth = 0;

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)  // if the interrupt source is channel1
	{
		if (Is_First_Captured==0) // if the first value is not captured
		{
			IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // read the first value
			Is_First_Captured = 1;  // set the first captured as true
		}

		else   // if the first is already captured
		{
			IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);  // read second value

			if (IC_Val2 > IC_Val1)
			{
				Difference = IC_Val2-IC_Val1;
			}

			else if (IC_Val1 > IC_Val2)
			{
				Difference = (0xffffffff - IC_Val1) + IC_Val2;
			}

			float refClock = TIMCLOCK/(PRESCALAR);
			float mFactor = 1000000/refClock;

			usWidth = Difference*mFactor;

			__HAL_TIM_SET_COUNTER(htim, 0);  // reset the counter
			Is_First_Captured = 0; // set it back to false
		}
	}
}
  • The above callback function is called when either the Rising or Falling edge is detected.
  • When called for the rising edge, Is_First_Captured was 0 so the hence the IC_Val1 will be recorded.
  • When called for the falling edge, the Is_First_Captured is 1 now so IC_Val2 will be recorded.
  • We will then calculate the Difference between the 2 values.
  • This difference is the time for which the Pulse is high, and this time will depend on the timer configuration.
  • To measure this time in microseconds, we have to use the reference clock.
  • Reference clock is calculated based on the setup we have done for our timer.
  • And finally, the usWidth will be calculated.


Check out the Video Below









Info

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.

23 Comments. Leave new

  • Hii Good afternoon,
    I am using STM32F401re nucleo board, for measure the input capture frequency
    but in that i am seeing frequency rising edge is not detect by

    “IC_Val1 = HAL_TIM_ReadCapturedValue(htim1, TIM_CHANNEL_1);”
    this function so value remain 0 as per this code.
    please suggest what i should do?
    Or can you suggest any other function to read the rising edge from register in Input Capture

    my function is
    void CaptureCallback(TIM_HandleTypeDef *htim1)
    {
    if (htim1->Channel == HAL_TIM_ACTIVE_CHANNEL_CLEARED)
    {
    if (Is_First_Captured==0) // if the first rising edge is not captured
    {
    IC_Val1 = HAL_TIM_ReadCapturedValue(htim1, TIM_CHANNEL_1); // read the first value
    Is_First_Captured = 1; // set the first captured as true
    }

    else // If the first rising edge is captured, now we will capture the second edge
    {
    IC_Val2 = HAL_TIM_ReadCapturedValue(htim1, TIM_CHANNEL_1); // read second value

    if (IC_Val2 > IC_Val1)
    {
    Difference = IC_Val2-IC_Val1;
    }

    else if (IC_Val1 > IC_Val2)
    {
    Difference = (0xffffffff – IC_Val1) + IC_Val2;
    }
    else if (IC_Val1 == IC_Val2)
    {
    Difference = IC_Val2;
    }

    float refClock = TIMCLOCK/(PRESCALAR);

    frequency = refClock/Difference;

    __HAL_TIM_SET_COUNTER(htim1, 0); // reset the counter to start
    Is_First_Captured = 0; // set it back to false
    }
    }
    }

    uint32_t HAL_TIM_ReadCapturedValue(TIM_HandleTypeDef *htim, uint32_t Channel)
    {
    uint32_t tmpreg = 0U;

    switch (Channel)
    {
    case TIM_CHANNEL_1:
    {
    /* Check the parameters */
    assert_param(IS_TIM_CC1_INSTANCE(htim->Instance));

    /* Return the capture 1 value */
    tmpreg = htim->Instance->CCR1;

    break;
    }

    case TIM_CHANNEL_2:
    {
    /* Check the parameters */
    assert_param(IS_TIM_CC2_INSTANCE(htim->Instance));

    /* Return the capture 2 value */
    tmpreg = htim->Instance->CCR2;

    break;
    }

    case TIM_CHANNEL_3:
    {
    /* Check the parameters */
    assert_param(IS_TIM_CC3_INSTANCE(htim->Instance));

    /* Return the capture 3 value */
    tmpreg = htim->Instance->CCR3;

    break;
    }

    case TIM_CHANNEL_4:
    {
    /* Check the parameters */
    assert_param(IS_TIM_CC4_INSTANCE(htim->Instance));

    /* Return the capture 4 value */
    tmpreg = htim->Instance->CCR4;

    break;
    }

    default:
    break;
    }

    return tmpreg;
    }
    Here are both functions for your reference, Please suggest what changes are need.
    Thank you.

    Reply
  • Hi
    When you measure the width- because trigger is both edges, how do you know that the first interrupt is the rising? if duty cycle is NOT 50% as here , if the interrupt is will be due the falling you will measure the low width od the pulse.You have to check the cause to the interrrupt is falling or rising and calculate the 2 width of the signal – the high part and the low part.Is it? Can I know what was the edge while enter the interrupt?

    Reply
  • lucas mallmann
    April 25, 2023 5:49 PM

    incrivel artigo sobre stm32

    Reply
  • Shivam Kumar
    May 24, 2022 7:28 PM

    can i have your tutorials in just register level based , using HAL and all makes it very convoluted and i guess its not the best practise to do so .

    Reply
  • Thank you so much.
    I have learned from you.
    It is very clear and understandable even for the beginners.

    Reply
  • i initially started with this technique, but found that with this model that the interrupts are proportional to the frequency being measured, and you quickly saturate the MPU with interrupts (as you noticed) leaving you with nothing but an interrupt generation machine as the freq of the signal to be measured goes up, with no cycles left for anything else. In my case I am trying to measure the freq of an external input. what i did was use one timer to provide/manage the “gate” for another timer, and CLOCK the second timer with the external freq to be measured. now the interrupt rate is independent of my “unknown” frequency, and i am readily able to measure frequencies over 30mhz, with a gate frequency of 250ms (created by the first timer by linking the second to the first timer). connect the input being measured to the external clock source of the second timer (note- not all STM32 timers allow external clock source). In my case, T3 gates T2, T3 is setup with MasterslaveOutTrig = TRG0_OC1REF and MasterSlaveMode enabled, T2 is setup with CLKSRC=ETRMODE2 and SLAVEMODE=SLAVEMODE_GATED ITR2. in the T3 PeriodElapsed callback, read T2’s counter directly as the frequency

    Reply
    • I think I met your issue that jam the interrupts… and agree that alternative is best to measure some high speed tachometer PWM inputs.
      unfortunately, I cant fully get your solution in your message.

      Reply
  • Hi man, please how do I measure the speed of a bldc motor by capturing the hall sensor signals (H1,H2,H3) on an STM32. Do you mind refering me to a video or link if you have one?

    Reply
  • Hi man, please how do I measure the speed of a bldc motor by capturing the hall sensor signals (H1,H2,H3)

    Reply
  • Coming from Measure input frequency using stm32 article where the main issue was code getting stuck in the frequency counter loop.

    I just wanted to share how I configured my project to get this working in STM32CubeIDE v1.4.0 and STM32F103C8T6 (aka blue pill) board.

    I set my clock configuration as follows:

    HCLK (Mhz) set to “72”
    APB1 Prescaler set to “/4”
    resulting in APB1 Timer clocks of 36 Mhz
    

    By the same token I set my parameters as:

    Prescaler (PSC – 16 bits value) = 0
    Counter Period (AutoReload Register – 16 bits value) = 65535
    

    Code wise on the frequncy calculation changed:

    Frequency = 2 * HAL_RCC_GetPCLK1Freq() / Difference;
    

    According to the calculation lowest measureable frequnechy is 549 Hz.

    36 * 1000 * 1000 / 65536 = 549.316 Hz
    

    What options do I have if I want to measure signals at 200 Hz? Adjust APB1 Prescaler from “/4” to “/16” seem to do the trick but is the right way to do it?

    9 * 1000 * 1000 / 65536 = 137.329 Hz
    
    Reply
    • yes you did it right. As per with this code, you need to reduce the sysclock inorder to measure the lower frequencies

      Reply
  • Helo
    I tried this code and working fine . But when frequency increase , after about 36kHz the main loop stops to work . When decrease the frequency , main loop begins to work again . But frequency measurement still fine even main loop stop. Do you have any advice ?

    Reply
    • what you mean main loop stops ?

      Reply
      • Inan want to say …. what ever he writes in while loop thats not working when frequency increases …. it is because when frequency increase callback function interrupts increases with it and cpu do not get extra time to perform while loop code out of interrupt … when decrease frequency cpu get out of interrupt for some time and get some extra time to run while loop code also …. if there is any way to fix it im also interested to know

        Reply
        • i don’t think it will work. One way you could try is instead of putting the entire code in the callback, just use some flag, and put the code in the while loop.
          maybe that could work

          Reply
          • I’m facing a similar problem, I can read up to 115Khz, but anything high causes my stm32 to stop work properly, I’m also using serial com and the board stop to receive RX but still send TX.

          • Well that may be because the uart baud rate could be 115200.

        • i saw that if i use internal crystal and low working frequency(16 Mhz) at the cubemx , main loop stop to work at high frequency measures like 36 khz . I used external crystal and set max speed(168 Mhz) . now it is working perfect. thanks

          Reply
          • thats great 😀😀 … always remember to put top gear 🚀🚀of ur hardware before exicute anything 😁😁😁

    • aswdf

      Reply
  • Thanks for this example, works fine.

    Reply
  • Thanks, this tutorial was super useful to get the capture inputs working. Showing both the code and cube mx configuration did the trick.

    Reply
  • Hello! Thank you for your tutorial! Could I ask what to do if the rising edge will come in two channels at the same time?

    Reply

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.

keyboard_arrow_up

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 controllerstech.com to your ad blocking whitelist or disable your adblocking software.

×