Skip to main content

Welcome to a Grasp lesson

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

Lesson 16

Configuring Timers for Periodic Interrupts

Recommended resources

Hello! Welcome to the first lesson in our third module on Advanced Timers.

In the previous module, we laid a solid groundwork with core system components. You learned to configure system clocks, handle user input with debounced external interrupts, and you became familiar with the SysTick timer, using it to create a reliable time base for your debouncing logic. This interrupt-driven approach is key to writing efficient firmware.

Today, we'll expand on that knowledge. While SysTick is excellent as a simple system-wide time base, STM32 microcontrollers are equipped with much more powerful and flexible general-purpose (GP) timers. Our goal for this lesson is to master the learning outcome: Configure a general-purpose timer to generate periodic update interrupts for timed tasks. Think of this as creating a highly precise, configurable "heartbeat" for your application, allowing you to schedule tasks without ever blocking the CPU in a while loop.

1. From SysTick to General-Purpose Timers

SysTick is a part of the ARM Cortex-M core itself, designed to be a simple timer primarily for providing a "tick" for a Real-Time Operating System (RTOS). General-purpose timers, on the other hand, are peripherals designed by the chip manufacturer (STMicroelectronics, in our case) and offer a much wider range of features, which we'll explore in this module.

Today's focus is on the most fundamental function of a GP timer: generating a periodic interrupt. The core principle involves three key components working together:

  1. Prescaler (PSC): A divider that slows down the timer's input clock.
  2. Counter (CNT): A register that increments at the rate of the prescaled clock.
  3. Auto-Reload Register (ARR): The value that the counter counts up to. When the counter reaches this value, it "rolls over" or "resets" to zero.

This "rollover" event is called an Update Event, and it's what we use to trigger an interrupt.

Timer Interrupts in STM32 Block Diagram
This diagram shows the fundamental data flow for a timer interrupt. The input clock is divided by the Prescaler, the Counter increments with this new clock, and when the Counter value matches the Auto-Reload Register (ARR), an Update Event is generated, which triggers the interrupt.

2. The Math Behind the Heartbeat

To get a precise time interval, we need to calculate the correct values for the Prescaler (PSC) and the Auto-Reload Register (ARR). The formula is straightforward:

Let's break this down with an example. Assume your STM32F103 is running with a system clock of 72 MHz. The general-purpose timers on the APB1 bus (like TIM2, TIM3, TIM4) are also clocked at 72 MHz if the APB1 prescaler is 1.

Goal: Generate an interrupt every 500ms (a frequency of 2 Hz).

  1. Choose a Prescaler (PSC): It's common practice to choose a PSC value that brings the counter clock to an easy-to-manage frequency, like 10 kHz.

    • Counter Clock = Timer Clock / (PSC + 1)
    • 10,000 Hz = 72,000,000 Hz / (PSC + 1)
    • PSC + 1 = 72,000,000 / 10,000 = 7200
    • PSC = 7199
  2. Calculate the Auto-Reload (ARR): Now that our counter increments 10,000 times per second, how many increments do we need to wait for 500ms?

    • Number of Ticks = Desired Period × Counter Clock
    • Number of Ticks = 0.5 s × 10,000 Hz = 5000
    • The ARR value is this number of ticks minus one, because the counter starts at 0.
    • ARR = 5000 - 1 = 4999

By setting PSC = 7199 and ARR = 4999, the timer will trigger an update interrupt exactly every 500 milliseconds.

To solidify this theory, the following article from ST provides another clear example of this calculation.

How to generate a one second interrupt using an STM32 timer

This ST Community article, 'How to generate a one second interrupt using an STM32 timer,' explains the theory and calculations for setting up a periodic interrupt. It reinforces the concepts of the timer clock source, prescaler, and auto-reload register.

Please read Section 2, 'Theory'. It walks through the same calculation process we just discussed, but for a 1-second interval on a different STM32 chip. The principles are identical and seeing it applied in a slightly different context will deepen your understanding.


3. Implementing a Timer Interrupt

Now that we have the theory, let's see how it's done in practice. We will use the STM32CubeIDE and the HAL library to configure the timer and write the interrupt handler code.

The process involves:

  1. Configuring the system clock.
  2. Selecting a timer (e.g., TIM2) and setting its clock source to Internal Clock.
  3. Entering the calculated Prescaler and Counter Period (ARR) values.
  4. Enabling the timer's global interrupt in the NVIC (Nested Vectored Interrupt Controller) settings.
  5. Generating the code.

In the C code, two things are essential:

  1. Starting the timer in interrupt mode: HAL_TIM_Base_Start_IT(&htimX);
  2. Implementing the callback function that executes when the interrupt occurs: HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim).

This video from Digi-Key offers a professional and clear explanation of timer interrupts and the corresponding HAL implementation.

Getting Started with STM32 and Nucleo Part 6: Timers and Timer Interrupts | Digi-Key Electronics

This video, 'Getting Started with STM32 and Nucleo Part 6: Timers and Timer Interrupts,' provides an excellent conceptual overview and practical demonstration of setting up a timer interrupt.

Please watch from 10:40 to 14:29. This section covers: The concept of a timer update interrupt. How to calculate the auto-reload value (Counter Period) for a desired interval. The specific HAL functions to start the timer (HAL_TIM_Base_Start_IT) and handle the callback (HAL_TIM_PeriodElapsedCallback). Pay close attention to how the callback function is implemented to toggle an LED.


4. Your Turn: Create a Periodic LED Toggle

It's time for you to apply these concepts. Your task is to configure a general-purpose timer to toggle the onboard LED (PC13) on your Blue Pill board at a frequency of 1 Hz (meaning it should toggle every 500ms).

Step 1: Calculation

First, perform the calculations for a 500ms interrupt period with a 72 MHz timer clock. We already did this above, so you should have:

  • Prescaler (PSC): 7199
  • Counter Period (ARR): 4999

Step 2: Project Setup in STM32CubeIDE

  1. Create a new STM32 project for your STM32F103C8T6.
  2. RCC: Set High Speed Clock (HSE) to Crystal/Ceramic Resonator.
  3. SYS: Set Debug to Serial Wire.
  4. Clock Configuration: Go to the tab, select HSE, and set HCLK to 72 MHz.
  5. Pinout & Configuration -> Timers:
    • Select a timer, for example, TIM2.
    • Under Clock Source, select Internal Clock.
  6. Parameter Settings for TIM2:
    • Set Prescaler (PSC) to 7199.
    • Set Counter Period (Auto-Reload Register) to 4999.
  7. NVIC Settings Tab:
    • Find the TIM2 global interrupt row and check the Enabled box.
  8. GPIO Configuration:
    • Click pin PC13 and configure it as GPIO_Output.
  9. Generate Code: Name your project and generate the initialization code.

Step 3: Writing the Application Code

Open main.c in your newly generated project.

  1. Start the timer. In your main() function, after the MX_..._Init() calls but before the while(1) loop, add the following line to start TIM2 in interrupt mode.

    /* USER CODE BEGIN 2 */
    HAL_TIM_Base_Start_IT(&htim2);
    /* USER CODE END 2 */
    
    /* Infinite loop */
    while (1)
    {
      /* USER CODE END WHILE */
      /* USER CODE BEGIN 3 */
    }
    
  2. Implement the callback. At the bottom of main.c, in the USER CODE BEGIN 4 section, add the implementation for the period elapsed callback. This function will be called automatically every 500ms.

    /* USER CODE BEGIN 4 */
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
      // Check if the interrupt came from TIM2
      if (htim->Instance == TIM2)
      {
        HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
      }
    }
    /* USER CODE END 4 */
    

    The if statement is good practice to ensure you're handling the correct timer, which becomes essential when you use multiple timers in a project.

Step 4: Build, Flash, and Test

Compile your project, flash it to your Blue Pill board, and reset it. The onboard LED on PC13 should now be blinking steadily, turning on for 500ms and off for 500ms. Your while(1) loop is completely empty, yet your program is performing a periodic task. This is the power of interrupt-driven design.


Conclusion

Excellent work. You have successfully configured a general-purpose timer to act as a precise, periodic scheduler for a task. This is a cornerstone skill in embedded systems, forming the basis for everything from reading sensors at a fixed interval to running control loops in automotive systems.

Key Takeaways:

  • General-purpose timers offer more flexibility and features than the core SysTick timer.
  • The Prescaler (PSC) and Auto-Reload Register (ARR) are used to configure the exact period of a timer interrupt.
  • The periodic interrupt is triggered by the timer's Update Event.
  • In HAL, you start the timer with HAL_TIM_Base_Start_IT() and handle the event in the HAL_TIM_PeriodElapsedCallback() function.
  • This interrupt-driven approach allows for periodic tasks to run efficiently without blocking the main CPU loop.

Preview of the Next Lesson:

Now that you can create a periodic interrupt, we will explore another powerful mode of the timer peripheral. In the next lesson, we will configure a timer channel in Output Compare mode to generate a Pulse Width Modulation (PWM) signal. This will allow you to control the brightness of an LED or the speed of a motor, moving from simple on/off control to precise analog-like behavior.