0 Abstract
This unit will cover the basic usage of timers and related interrupts, a crucial topic in the world of microcontrollers. A simple 1-second-timer will be demonstrated.
1 Introduction on interrupts
Usually a program in a microprocessor is worked by executing instructions that are in a consecutive order. But as certain situations might require a breakout from this routine, interrupts have been invented. An interrupt occurs when the controller comes to a certain condition and special procedure is needed in that case. This will cause (or “fire”) an interrupt.
Interrupts are “deviations” int the “normal” run of a program. They can be programmed to occur when certain conditions are present to react to them. Some examples:
- A timer reaches a defined value,
- a pin changes its value,
- a signal is present at UART
- etc.
Usually an event is anticipated and a routine is defined to cope with such a situation.
2 Defining interrupt procedure
Dealing with interrupts requires several steps. First you have to tell the MCU which event should cause an interrupt and then write a portion of code to deal (“handle”) that event. There are handler functions for the various interrupts. These handler functions have names that are predefined by CMSIS in the case of “bare metal” programming. For GPIO pins (external interrupts) they are in table below:
Irq | Handler | Description |
---|---|---|
EXTI0_IRQn | EXTI0_IRQHandler | Handler for pins connected to line 0 |
EXTI1_IRQn | EXTI1_IRQHandler | Handler for pins connected to line 1 |
EXTI2_IRQn | EXTI2_IRQHandler | Handler for pins connected to line 2 |
EXTI3_IRQn | EXTI3_IRQHandler | Handler for pins connected to line 3 |
EXTI4_IRQn | EXTI4_IRQHandler | Handler for pins connected to line 4 |
EXTI9_5_IRQn | EXTI9_5_IRQHandler | Handler for pins connected to line 5 to 9 |
EXTI15_10_IRQn | EXTI15_10_IRQHandler | Handler for pins connected to line 10 to 15 |
For internal interrupts (like timers “fire”) they have got different names. We will go into the details with a practical example to make understanding easier.
3 Introduction on timers
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.
4 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)
5 Handling interrupt condition
As mentioend before when using interrupts, a function must be defined that handles the interrupt. As for all functions there is declaration (1st step) and definition (2nd step):
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.
Thus a PINx can only be connected to a certain handler routine. From EXTI9 to EXTI15 a respective common handler routine must be used, thus distiguishin interrupt source must be done by reading the interrupt handler flag:
if(EXTI->PR == interrupt_number) {}
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!
All rights reserved by Peter Baier, Bad Bergzabern, Germany (https://micromaker.de).