Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 21

Measuring Pulse Width with Timer Input Capture

Recommended resources

Hello! Welcome back to our module on Advanced Timers, PWM, and Signal Measurement.

In the last lesson, you successfully configured a timer in Input Capture mode to detect the rising edge of a signal and trigger an interrupt. You confirmed that the hardware can precisely notify the CPU when an event occurs. Today, we will build directly on that foundation.

Our goal is to use timer Input Capture to measure the pulse width of an external signal. Instead of just detecting one edge, we will measure the time between a signal's rising edge and its subsequent falling edge. This is a fundamental technique for interfacing with a wide variety of sensors, and we'll apply it directly to the HC-SR04 ultrasonic distance sensor from your kit.

1. The Theory: From One Edge to Two

Measuring a pulse width is a two-step process. In the previous lesson, you captured the timer's value on a single event. To measure duration, we need two timestamps:

  1. Capture T1: When the pulse begins (e.g., a rising edge), we capture the timer's counter value.
  2. Capture T2: When the pulse ends (e.g., a falling edge), we capture the counter value again.
  3. Calculate Duration: The pulse width is simply the difference between these two values: Duration = T2 - T1.

Since a single input capture channel can only be configured to detect one type of edge at a time (rising or falling), we must dynamically reconfigure it within our interrupt service routine.

The logic flow looks like this:

  • Configure the timer channel to capture on the rising edge.
  • When the interrupt fires, read the first captured value (T1) from the CCR register.
  • Immediately reconfigure the channel polarity to capture on the falling edge.
  • When the interrupt fires a second time, read the second captured value (T2).
  • Calculate the difference, T2 - T1, to get the pulse width in timer ticks.
  • Reconfigure the channel polarity back to rising edge to prepare for the next pulse.

This video demonstrates exactly how to modify an input capture setup from measuring frequency (rising edge to rising edge) to measuring pulse width (rising edge to falling edge).

STM32 Timers (Part 4): How to use Input Capture Mode to Measure Frequency & Pulse Width

The video 'STM32 Timers (Part 4): How to use Input Capture Mode to Measure Frequency & Pulse Width' by ControllersTech provides a clear demonstration of measuring pulse width. We will focus on the part where the code is adapted to capture both edges.

Watch from 09:58 to 13:17. Pay close attention to two key changes: first, in the CubeMX configuration, changing the polarity to 'Both Edges', and second, in the code, using a state variable (like a flag) to distinguish between the first capture (rising edge) and the second capture (falling edge).


2. The Application: HC-SR04 Ultrasonic Sensor

The HC-SR04 is a perfect real-world example for this technique. It measures distance and communicates the result by varying the width of an output pulse.

How it Works

  1. Trigger: You send a short (at least 10µs) high pulse to the sensor's TRIG pin.
  2. Transmit: The sensor responds by emitting a burst of ultrasonic sound waves.
  3. Echo: The sound travels, hits an object, and reflects back to the sensor.
  4. Output: The sensor drives its ECHO pin high for the exact amount of time the sound wave was traveling.

Our job is to measure the width of this ECHO pulse. The distance can then be calculated using the speed of sound.

HC-SR04 Ultrasonic Sensor with STM32

The article 'HC-SR04 Ultrasonic Sensor with STM32' from ControllersTech provides a concise explanation of the sensor's operation and the formula to convert pulse width to distance.

Read the section titled 'How the HC-SR04 Ultrasonic Sensor Works'. Focus on the step-by-step process and the formula: Distance (cm) = Pulse Duration (µs) / 58.

A Critical Hardware Detail: Logic Level Mismatch

Your STM32F103 operates at 3.3V. The HC-SR04 sensor, however, operates at 5V and outputs a 5V signal on its ECHO pin. While some STM32 pins are "5V tolerant," many are not. Applying 5V to a non-tolerant 3.3V pin can permanently damage the microcontroller.

The professional and safe approach is to use a voltage divider to step down the 5V ECHO signal to a safe 3.3V level.

STM32 HC-SR04 Ultrasonic Sensor Interfacing Schematic
This diagram shows how to safely connect the 5V HC-SR04 to a 3.3V STM32. A voltage divider made of a 1kΩ and 2kΩ resistor on the ECHO line reduces the 5V signal to approximately 3.3V (`5V * 2kΩ / (1kΩ + 2kΩ) = 3.33V`), protecting the MCU's input pin.

3. Hands-On Lab: Measuring Distance

Now, let's put it all together. We will configure a timer for input capture to measure the ECHO pulse from the HC-SR04 sensor.

The following video provides a complete walkthrough of this process using STM32CubeIDE and HAL functions, which directly correspond to the register-level concepts we've been studying.

Interface HC-SR04 Ultrasonic Sensor with STM32 | Input Capture Tutorial

This tutorial, 'Interface HC-SR04 Ultrasonic Sensor with STM32' by ControllersTech, is a step-by-step guide for our exact task. It covers the project setup, coding, and final result.

Watch the video from 00:26 to 06:42. You don't need to follow along and code just yet. First, get a high-level view of the entire process: CubeMX configuration, the functions created in the C code, and how they work together in the main loop.

Now, let's implement this yourself.

Hardware Setup:

  1. Power the HC-SR04 sensor: Connect its VCC to 5V and GND to GND on your Blue Pill.
  2. Connect the TRIG pin: Connect the sensor's TRIG pin to pin PA9 of your STM32.
  3. Connect the ECHO pin with the voltage divider:
    • Connect the sensor's ECHO pin to a 1kΩ resistor.
    • Connect the other end of the 1kΩ resistor to pin PA8 of your STM32.
    • Connect a 2kΩ resistor from pin PA8 to GND.

Software Implementation:

Create a new project in STM32CubeIDE.

1. CubeMX Configuration:

  • System Clock: Configure it to 72 MHz as usual.
  • TIM1 Configuration:
    • Set Channel 1 to Input Capture direct mode.
    • In the Parameter Settings tab:
      • Set Prescaler to 71. This makes the timer clock 1 MHz (72MHz / (71+1)), so each timer tick is exactly 1 microsecond.
      • Set Counter Period (ARR) to 65535 (the maximum).
  • PA9 Configuration: Set pin PA9 as GPIO_Output.
  • NVIC Settings: Enable the TIM1 Capture Compare interrupt.

Generate the code.

2. main.c Code:

Add the following code to your main.c file. Global variables should go in the user code section /* USER CODE BEGIN PV */, functions in /* USER CODE BEGIN 4 */, and the main loop logic in /* USER CODE BEGIN WHILE */.

/* USER CODE BEGIN PV */
// For the TRIG pin



#define TRIG_PORT GPIOA
#define TRIG_PIN GPIO_PIN_9

// Variables for Input Capture
uint32_t IC_Val1 = 0;
uint32_t IC_Val2 = 0;
uint32_t Difference = 0;
uint8_t Is_First_Captured = 0;  // 0 = no, 1 = yes
float Distance  = 0;
/* USER CODE END PV */

/* USER CODE BEGIN 4 */
// Function to generate a microsecond delay
void us_delay (uint16_t us)
{
	__HAL_TIM_SET_COUNTER(&htim1,0);  // Set the counter value to 0
	while (__HAL_TIM_GET_COUNTER(&htim1) < us);  // Wait for the counter to reach the us input value
}

// Function to trigger the sensor
void HCSR04_Read (void)
{
	HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET);  // Pull the TRIG pin HIGH
	us_delay(10);  // Wait for 10 us
	HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET);  // Pull the TRIG pin LOW

	// Start the TIM1 IC in interrupt mode
	HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
}

// Input Capture Callback Function
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
	{
		if (Is_First_Captured==0) // If this is the first captured value (rising edge)
		{
			IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1); // Read the rising edge value
			Is_First_Captured = 1;  // Set the flag to indicate the first value has been captured
			// Now change the polarity to falling edge
			__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
		}
		else if (Is_First_Captured==1)   // If the first value has been captured, this is the falling edge
		{
			IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);  // Read the falling edge value
			__HAL_TIM_SET_COUNTER(htim, 0);  // Reset the counter

			if (IC_Val2 > IC_Val1)
			{
				Difference = IC_Val2-IC_Val1;
			}
			else if (IC_Val1 > IC_Val2) // Handle timer overflow
			{
				Difference = (0xffff - IC_Val1) + IC_Val2;
			}

			Distance = (float)Difference * 0.0343 / 2.0; // Speed of sound is 343 m/s or 0.0343 cm/us
			Is_First_Captured = 0; // Reset the flag for the next reading

			// Change polarity back to rising edge
			__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
			HAL_TIM_IC_Stop_IT(&htim1, TIM_CHANNEL_1); // Stop the interrupt until the next trigger
		}
	}
}
/* USER CODE END 4 */

/* In the main function, inside the while(1) loop */
/* USER CODE BEGIN 3 */
  /* This is the main application loop */
  HCSR04_Read();      // Trigger the sensor
  HAL_Delay(100);     // Wait 100ms between readings
}
/* USER CODE END 3 */

3. Compile, Flash, and Debug:

  • Compile and flash the code to your Blue Pill board.
  • Start a debug session.
  • Place the Distance variable in the "Live Expressions" window.
  • Place an object (like your hand or a book) in front of the sensor.
  • Run the code. You should see the Distance variable update with the measured distance in centimeters! Move the object closer and farther away to see the value change.

Conclusion

Excellent work. You have successfully implemented one of the most common and useful timer functionalities in embedded systems. You've not only measured a signal's pulse width but also translated that measurement into meaningful, real-world data from a sensor.

Key Takeaways:

  • Pulse width is measured by capturing the timer's value on both the rising and falling edges of a signal.
  • A state variable (flag) is necessary inside the ISR to track whether you're capturing the first or second edge.
  • The timer's capture polarity must be dynamically changed within the ISR to switch between detecting rising and falling edges.
  • The timer's clock frequency determines the resolution of your measurement. A 1 MHz clock gives you 1µs resolution.
  • Always be mindful of logic level differences between your MCU and peripherals. Using a voltage divider is a simple and essential technique for safe interfacing.

Preview of the Next Lesson:
You have now mastered PWM signal generation and input signal measurement. In the final lesson of this module, we will combine these skills into a small project: you will build a system that uses the ultrasonic sensor to measure distance and controls the brightness of an LED using PWM based on that distance.