Skip to main content

Welcome to a Grasp lesson

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

Lesson 22

PWM LED Control & Sensor Pulse Measurement Project

Recommended resources

Hello! Welcome to the final lesson of our module on Advanced Timers, PWM, and Signal Measurement.

In the previous lessons, you mastered two fundamental timer applications. First, you generated Pulse-Width Modulation (PWM) signals to control the brightness of an LED. Then, you configured a timer in Input Capture mode to measure the precise pulse width of a signal from the HC-SR04 ultrasonic sensor, converting it into a distance measurement.

Today, we will unite these two powerful techniques. Our learning outcome is to build a project that controls an LED's brightness via PWM based on the distance measured by the ultrasonic sensor. This is a classic embedded systems application that demonstrates a complete sense-process-actuate loop, forming the basis of many real-world control systems.

1. Project Architecture: The Proximity-Controlled LED

Let's design our system. The goal is to create a device where an LED's brightness reflects the distance to an object.

  • Sensing: An HC-SR04 ultrasonic sensor will measure the distance to an object.
  • Processing: The STM32 microcontroller will read the sensor's pulse, calculate the distance, and then map this distance to a corresponding brightness level. For instance, a short distance could mean a dim light, and a long distance could mean a bright light.
  • Actuating: A PWM signal will drive an LED, with its duty cycle being updated based on the processed sensor data.

To implement this, we'll need to use two separate hardware timers to avoid conflicts and ensure robust operation:

  1. A timer for Input Capture (IC): To measure the HC-SR04 ECHO pulse. We can use TIM1, as in our last lesson.
  2. A timer for PWM Output: To control the LED. We can use TIM2 for this task.

The logic will flow as follows:

  • The main loop will periodically trigger the sensor to start a new distance measurement.
  • The Input Capture interrupt service routine (ISR) will asynchronously measure the ECHO pulse and update a global Distance variable.
  • The main loop will then read this Distance variable, perform a calculation to map it to a PWM duty cycle, and update the CCR register of the PWM timer to change the LED's brightness.

2. PWM Generation Review

First, let's briefly review setting up a timer for PWM. We need to configure a timer channel to generate a PWM signal on an output pin.

For a detailed walkthrough of the calculations and STM32CubeIDE setup, you can refer back to the video we used previously.

STM32 Timers (PART 1): How to Generate PWM Output with/without DMA

As a refresher on PWM configuration, this video, 'STM32 Timers (PART 1): How to Generate PWM Output' from ControllersTech, is an excellent resource. Pay attention to how the prescaler and ARR values are calculated to achieve a specific frequency.

You can quickly review the sections from 02:33 to 05:55. This part covers selecting the timer channel and, most importantly, the formulas and logic for setting the Prescaler (PSC) and Auto-Reload Register (ARR) values.


3. Input Capture Measurement Review

Similarly, let's recall the process for measuring the HC-SR04 sensor's ECHO pulse using Input Capture. The core logic involves capturing the timer value on the rising edge, reconfiguring the channel to capture the falling edge, capturing the second value, and calculating the difference.

The article from DeepBlueEmbedded provides a great overview of the different methods for reading the sensor, confirming that our Input Capture Unit (ICU) approach is efficient and precise.

STM32 Ultrasonic Sensor HC-SR04 Library With Timer Input Capture

The article 'STM32 Ultrasonic Sensor HC-SR04 Library' offers a fantastic theoretical comparison of different methods for reading the sensor's echo pulse. It helps solidify why the Input Capture method is a strong choice.

Read the section titled 'Different Techniques & Procedures To Read HC-SR04 Ultrasonic Sensor'. This will give you a professional perspective on the trade-offs between polling, external interrupts, and various timer-based input capture methods.

HC-SR04 Ultrasonic Sensor with STM32 and Timer Input Capture Timing Diagram
This diagram illustrates the core principle of our sensor project. A trigger pulse is sent to the HC-SR04, which then emits an echo pulse. Our STM32 uses the timer's Input Capture feature to measure the width of this echo pulse (`T`), which is directly proportional to the distance of the object.


4. Hands-On Project: Proximity LED Dimmer

Now, let's integrate these two functionalities into our final project for this module.

Hardware Setup:

  1. HC-SR04 Sensor: Connect it exactly as in the previous lesson.
    • VCC -> 5V
    • GND -> GND
    • TRIG -> PA9
    • ECHO -> 1kΩ Resistor -> PA8. Also connect a 2kΩ Resistor from PA8 to GND.
  2. LED:
    • Connect the anode (longer leg) of an LED to a 220Ω resistor.
    • Connect the other end of the resistor to pin PA0.
    • Connect the cathode (shorter leg) of the LED to GND.

Software Implementation (STM32CubeIDE):

  1. Create a New Project: Start a new STM32CubeIDE project for your STM32F103C8 board.

  2. Clock Configuration: Set the system clock to 72 MHz.

  3. Peripheral Configuration:

    • PWM Timer (TIM2):
      • Navigate to Timers -> TIM2.
      • Set Channel 1 to PWM Generation CH1. This will assign pin PA0.
      • In the Parameter Settings tab under Counter Settings:
        • Set Prescaler (PSC) to 71. (Timer clock = 72MHz / (71+1) = 1MHz).
        • Set Counter Period (ARR) to 999. (PWM Frequency = 1MHz / (999+1) = 1kHz). This gives us 1000 steps of brightness control (0-999).
    • Input Capture Timer (TIM1):
      • Navigate to Timers -> TIM1.
      • Set Channel 1 to Input Capture direct mode. This will assign pin PA8.
      • In the Parameter Settings tab:
        • Set Prescaler (PSC) to 71. (Each timer tick = 1µs).
        • Set Counter Period (ARR) to 65535 (maximum value).
      • Go to the NVIC Settings tab and enable the TIM1 Capture Compare interrupt.
    • Trigger Pin (PA9):
      • Click on pin PA9 and configure it as GPIO_Output.
  4. Generate Code: Save the .ioc file to generate the project code.

  5. main.c Implementation:

Now, open main.c and add the logic to tie everything together. We will use the HAL_TIM_IC_CaptureCallback from the previous lesson and add new logic in the main loop to map the distance to a PWM duty cycle.

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



#define TRIG_PORT GPIOA
#define TRIG_PIN GPIO_PIN_9

// Variables for Input Capture (from previous lesson)
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;

// New variable for PWM duty cycle
uint16_t pwm_duty_cycle = 0;
/* USER CODE END PV */

/* main() function */
int main(void)
{
  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_TIM2_Init();
  /* USER CODE BEGIN 2 */
  // Start the PWM timer
  HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    // 1. Trigger the HC-SR04 Sensor
    HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_SET);
    HAL_Delay(1); // Small delay
    HAL_GPIO_WritePin(TRIG_PORT, TRIG_PIN, GPIO_PIN_RESET);

    // 2. Start Input Capture interrupt
    HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);

    // 3. Wait for measurement to complete (can be improved with other methods)
    HAL_Delay(100);

    // 4. Map the distance to a PWM duty cycle (0-999)
    // Let's map a distance from 5cm to 40cm.
    if (Distance < 5) {
        pwm_duty_cycle = 0; // Object is too close, LED off
    } else if (Distance > 40) {
        pwm_duty_cycle = 999; // Object is far, LED fully bright
    } else {
        // Linear mapping
        pwm_duty_cycle = (uint16_t)(((Distance - 5.0) / (40.0 - 5.0)) * 999.0);
    }
    
    // 5. Update the PWM duty cycle
    __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_duty_cycle);
  }
  /* USER CODE END 3 */
}


/* USER CODE BEGIN 4 */
// This is the exact same callback function from the previous lesson.
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
    if (htim->Instance == TIM1 && htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
    {
        if (Is_First_Captured == 0) // First capture (rising edge)
        {
            IC_Val1 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            Is_First_Captured = 1;
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_FALLING);
        }
        else if (Is_First_Captured == 1)   // Second capture (falling edge)
        {
            IC_Val2 = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
            __HAL_TIM_SET_COUNTER(htim, 0);

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

            // Speed of sound is ~343 m/s or 0.0343 cm/µs.
            // Time = Difference in µs (since our timer clock is 1MHz).
            // Distance = (Time * Speed of Sound) / 2 (for there and back)
            // Distance (cm) = (Difference * 0.0343) / 2 = Difference / 58.3
            Distance = (float)Difference / 58.3f;
            Is_First_Captured = 0;

            __HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING);
            HAL_TIM_IC_Stop_IT(&htim1, TIM_CHANNEL_1);
        }
    }
}
/* USER CODE END 4 */
  1. Compile, Flash, and Test:
    • Build and flash the code to your Blue Pill.
    • Place your hand in front of the sensor. As you move your hand closer (less than 40cm), the LED should dim. As you move it away, the LED should get brighter. If your hand is very close (less than 5cm), the LED should turn off completely.

Conclusion

Congratulations on completing this module! You have successfully built a complete embedded system that senses its environment, processes the data, and actuates a response. This project integrates multiple timer modes (PWM and Input Capture) and demonstrates a fundamental pattern in firmware engineering.

Key Takeaways:

  • You can effectively combine different timer modes (Output Compare for PWM, Input Capture for measurement) by using separate hardware timer peripherals (TIM2, TIM1).
  • Mapping sensor data to an actuator's output range is a common and critical task in control systems. Linear mapping is a straightforward and effective technique.
  • The sense-process-actuate loop is a core concept that you will encounter in countless embedded applications, from simple IoT devices to complex automotive control units.

Preview of the Next Module:
So far, our primary way of "seeing" what the MCU is doing has been through the debugger's live expressions or by observing an LED. This has its limits. In the next module, Serial Communication with UART, we will learn how to make our device communicate with a computer. You will be able to send sensor readings, status messages, and debug information over a serial port, which is an indispensable skill for professional development and debugging.