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:
- A timer for Input Capture (IC): To measure the HC-SR04
ECHOpulse. We can useTIM1, as in our last lesson. - A timer for PWM Output: To control the LED. We can use
TIM2for 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
ECHOpulse and update a globalDistancevariable. - The main loop will then read this
Distancevariable, perform a calculation to map it to a PWM duty cycle, and update theCCRregister 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.

4. Hands-On Project: Proximity LED Dimmer
Now, let's integrate these two functionalities into our final project for this module.
Hardware Setup:
- HC-SR04 Sensor: Connect it exactly as in the previous lesson.
VCC->5VGND->GNDTRIG->PA9ECHO-> 1kΩ Resistor ->PA8. Also connect a 2kΩ Resistor fromPA8toGND.
- 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):
-
Create a New Project: Start a new STM32CubeIDE project for your
STM32F103C8board. -
Clock Configuration: Set the system clock to 72 MHz.
-
Peripheral Configuration:
- PWM Timer (
TIM2):- Navigate to
Timers->TIM2. - Set
Channel 1toPWM Generation CH1. This will assign pinPA0. - 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).
- Set Prescaler (PSC) to
- Navigate to
- Input Capture Timer (
TIM1):- Navigate to
Timers->TIM1. - Set
Channel 1toInput Capture direct mode. This will assign pinPA8. - In the Parameter Settings tab:
- Set Prescaler (PSC) to
71. (Each timer tick = 1µs). - Set Counter Period (ARR) to
65535(maximum value).
- Set Prescaler (PSC) to
- Go to the NVIC Settings tab and enable the
TIM1 Capture Compare interrupt.
- Navigate to
- Trigger Pin (
PA9):- Click on pin
PA9and configure it asGPIO_Output.
- Click on pin
- PWM Timer (
-
Generate Code: Save the
.iocfile to generate the project code. -
main.cImplementation:
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 */
- 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 Comparefor PWM,Input Capturefor 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-actuateloop 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.