An introduction to ‘Bare Metal’-programming the Arm-Cortex-M4(R) MCUs – Lesson 5: Timers and Interrupts

by Peter Baier (DK7IH)

Abstract

This unit will cover the basic usage of timers and related interrupts, a crucial topic in the world of microcontrollers.

Introduction

The STM32/Arm Cortex(R)-M4 MCUs contain a large number of timers:

  • Basic timers
  • General-purpose timers
  • Advanced-control timers

There are up to 14 timers in an STM32-MCU that are fully independent and “do not share any system resources” (according to reference manual), thus there are quite a lot timers to chose from.

In this unit we will cover the general aspects of usage of a general purpose timer, detailed description and enhanced possibilities will be subject for later discussion. As timers in most cases go along with usage of interrupts, we will take a look to them as well.

Setting up a timer

We want to set up a timer to count seconds in our application. Because usage of timers depends on the correct setting of the system clock we have to set this up correctly before we go on. Please refer to lesson #3 to learn about system clock settings.

First we set up timer #2 (TIM2), a 32-bit timer, that can be used as a up, down, up/down auto-reload
counter and is labeled as general purpose timer. Information in reference manual can be taken beginning from p. 589.

First step is to power up TIM2, which is defined by bit 0 of APB1ENR register:

//Enable TIM2 clock (Bit 0)
RCC->APB1ENR |= (1 << 0);

Next we do some calculations to set TIM2 adequately:

//Timer calculation
TIM2->PSC = 100000; //Divide system clock (f=100MHz) by 100000 -> update frequency = 1000/s
TIM2->ARR = 500; //Define timer overrun based on auto-reload-register to happen after 500ms

“PSC” is the prescaler used to divide system clock rate by a given factor to make the timer count. It will increase the timer by 1 every number of clock ticks defined in “PSC”.

ARR is the register that contains the upper (or lower, if you are downcounting) margin of the counter. When this limit is exceeded an interrupt is fired, if activated.

Thus the interrupt has to be addressed and enabled before firing it:

//Interrupt definition
TIM2->DIER |= (1 << 0); //Update of Interrupt enable
NVIC_SetPriority(TIM2_IRQn, 2); //Priority level 2 for this event
NVIC_EnableIRQ(TIM2_IRQn); //Enable TIM2 IRQ from NVIC

To end the sequence correctly TIM2 must be switched on:

 TIM2->CR1 |= (1 << 0); //Enable Timer 2 (CEN, bit0)

Handling interrupt condition

As known from AVRs, a function must be defined that handles the interrupt.

Declaration (1st) and definition (2nd):

extern "C" void TIM2_IRQHandler(void); //IRQ-Handler timer2
...
extern "C" void TIM2_IRQHandler(void)//IRQ-Handler for TIM2
{
    if (TIM2->SR & TIM_SR_UIF) //Toggle LED on update event every 500ms
    {
        GPIOD->ODR ^= (1 << 15); //Blue LED
    }
    TIM2->SR = 0x0; //Reset status register
}

Note that this handler function must be declared AND defined as ‘extern “C”‘. When your compiler is set to C++ code generation mode the name of the handler function otherwise will be mangled according to C++ rules and get a different spelling, thus won’t be recognized when the code is executed and therefore the function call will end up anywhere in nowhere-land.

Additional information: If you don’t want to use an interrupt, you can also poll TIM2->CNT register while your program is in the infinite loop structure:

TIM2->ARR = 0xFFFF;
...
while(1)
{
    if(TIM2->CNT >= 500)
    {
        GPIOD->ODR ^= (1 << 15); //Blue LED
        TIM2->CNT = 0;
    } 
}


TIMx-ARR must be preset to a value that is able to be reached, because if the counter gets above the value of TIM2->ARR register TIMx->CNT will be reset to 0 and your if(..)-request never will trigger. If this method makes sense in all is up to the reader to decide!

The full code for this example can be downloaded here.

Hope, this unit provided some benefit and say CU later!

73 de Peter