Controlling STM32 Hardware Timers using HAL

This tutorial shows how to use the STM32 hardware timers via the STM32 HAL API. If you want to use them with the legacy StdPeriph library, follow this tutorial instead.

Before you begin, ensure that you can build and debug embedded projects for your STM32 board by following one of our basic STM32 tutorials.

  1. Before we can start configuring the timer, we’ll give a brief overview how the timers work. Hardware timers keep counting up or down depending on the module until the timer period is reached. Then the timer is reset:
  2. We will use the timer to keep our LED on 80% of the time by setting a period of 500, turning it on when the counter reaches 400 and turning it off when it reaches 500:
  3. Create a basic HAL-based LEDBlink project for your board if you have not done that already. Then we will begin with configuring the timer. This is done by calling __TIMx_CLK_ENABLE(),  filling the fields of the Init member in the TIM_HandleTypeDef structure and calling HAL_TIM_Base_Init() and HAL_TIM_Base_Start():
        __TIM2_CLK_ENABLE();
        s_TimerInstance.Init.Prescaler = 40000;
        s_TimerInstance.Init.CounterMode = TIM_COUNTERMODE_UP;
        s_TimerInstance.Init.Period = 500;
        s_TimerInstance.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        s_TimerInstance.Init.RepetitionCounter = 0;
        HAL_TIM_Base_Init(&s_TimerInstance);
        HAL_TIM_Base_Start(&s_TimerInstance);
  4. From the main() function we can get the current timer value using the __HAL_TIM_GET_COUNTER() macro and update the LED accordingly. The main source file will look the following way:
    #include <stm32f4xx_hal.h>
     
    #ifdef __cplusplus
    extern "C"
    #endif
    void SysTick_Handler(void)
    {
        HAL_IncTick();
        HAL_SYSTICK_IRQHandler();
    }
     
    static TIM_HandleTypeDef s_TimerInstance = { 
        .Instance = TIM2
    };
     
    void InitializeTimer()
    {
        __TIM2_CLK_ENABLE();
        s_TimerInstance.Init.Prescaler = 40000;
        s_TimerInstance.Init.CounterMode = TIM_COUNTERMODE_UP;
        s_TimerInstance.Init.Period = 500;
        s_TimerInstance.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
        s_TimerInstance.Init.RepetitionCounter = 0;
        HAL_TIM_Base_Init(&s_TimerInstance);
        HAL_TIM_Base_Start(&s_TimerInstance);
    }
     
    void InitializeLED()
    {
        __GPIOD_CLK_ENABLE();
        GPIO_InitTypeDef GPIO_InitStructure;
     
        GPIO_InitStructure.Pin = GPIO_PIN_12;
     
        GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
        GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
        GPIO_InitStructure.Pull = GPIO_NOPULL;
        HAL_GPIO_Init(GPIOD, &GPIO_InitStructure);
    }
     
    int main(void)
    {
        HAL_Init();
        InitializeLED();
        InitializeTimer();
     
        for (;;)
        {
            int timerValue = __HAL_TIM_GET_COUNTER(&s_TimerInstance);
            if (timerValue == 400)
                HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_SET);
            if (timerValue == 500)
                HAL_GPIO_WritePin(GPIOD, GPIO_PIN_12, GPIO_PIN_RESET);
        }
    }
  5. Run the program and notice how the LED starts blinking. You can use the Preprocess Selected Lines command to see that the __HAL_TIM_GET_COUNTER() simply reads the CNT register of the timer:timer
  6. If you change timerValue to be a static variable, you can use the Live Variables to plot how the value is changing over time following the TIM2->CNT value:live
  7. You can also configure the timer to automatically generate the ‘update’ events once the value reaches the period value. This can be done by replacing a call to HAL_TIM_Base_Start() with HAL_TIM_Base_Start_IT():
        HAL_TIM_Base_Start_IT(&s_TimerInstance);
  8. From the main() function you can check the status of the ‘Update’ interrupt and toggle the LED state when the update happens:
        for (;;)
        {
            if (__HAL_TIM_GET_FLAG(&s_TimerInstance, TIM_FLAG_UPDATE) != RESET)
            {
                __HAL_TIM_CLEAR_IT(&s_TimerInstance, TIM_IT_UPDATE);
                HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
            }
        }

    If you run the program now, you will see how the LED changes its state every 500 timer cycles.

  9. The good thing about the timer interrupts is that you actually don’t need to check their status manually. As long as you enable the interrupt by calling HAL_NVIC_EnableIRQ(), the timer will automatically trigger an interrupt handler function when the event occurs. You can look up the interrupt function name in the startup_stm32xxxx.c file:handler
  10. The easiest way to write the interrupt handler is to simply call HAL_TIM_IRQHandler() from it. The handler provided by HAL will automatically check which event caused the interrupt and invoke one of the specialized handlers like HAL_TIM_PeriodElapsedCallback(). Add the following code to your main file to try that out:
    extern "C" void TIM2_IRQHandler()
    {
        HAL_TIM_IRQHandler(&s_TimerInstance);
    }
     
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
    }
     
    int main(void)
    {
        HAL_Init();
        InitializeLED();
        InitializeTimer();
        
        HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
        HAL_NVIC_EnableIRQ(TIM2_IRQn);
     
        for (;;)
        {
        }
    }

    Note that the main() function does not need to do anything with the timer anymore and can simply handle the rest of your application’s logic.

  11. You can visualize what happens when the interrupt is triggered by adding an indicator variable and toggling it from the callback function (a value of 250 is chosen to scale good with the timer counter range):
    int g_Indicator;
     
    void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
    {
        HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_12);
        g_Indicator = g_Indicator ? 0 : 250;
    }
  12. Simply add both timer counter and the g_Indicator to live variables and observe how the indicator variable is changed each time the timer gets reset:ind
  13. If you set a breakpoint at the HAL_TIM_PeriodElapsedCallback() function, you can see how it’s called by the HAL_TIM_IRQHandler() as a result of the timer 2 interrupt handler being invoked:stack
  14. You can use the Code Map to quickly explore what other event handlers the HAL_TIM_IRQHandler() method invokes:cb