STM32 interrupts and programming with GCC

Probably one of the key features in any microcontroller is interrupt system. ARM Cortex-M3 microcontrollers may have up to 256 interrupts sources. First 15 interrupt sources are called system exceptions. These exceptions rise within Cortex core like reset, NMI, hard fault and error, debug and SystTick timer interrupt. In the exception table they start from address 0x00000004 and are numbered from 1 to 15. There is no 0 number exception (FYI – the very top of exception table address is used to store starting point of stack pointer):

Each exception vector holds four byte wide address of service routine that is called when exception occurs. Exception table usually is located in startup code like this:

__attribute__ ((section(".vectors"), used))

void (* const gVectors[])(void) =

{

(void (*)(void))((unsigned long)&_estack),

ResetHandler,

NMI_Handler,

HardFault_Handler,

MemManage_Handler,

BusFault_Handler,

UsageFault_Handler,

….........................

 

It is located in .vectors memory section which is described in linker script and usually resides at very beginning of flash memory.

Inside NVIC of Cortex-M3

Nested Vectored Interrupt Controller (NVIC) is an essential part of Cortex processor. It is pretty complex module that takes care of interrupt processing logic. Additionally it holds control registers for SysTick timer and debug control. Generally speaking NVIC can support up to 240 external interrupts. Speaking of STM32F100RB microcontroller that resides in discovery board NVIC supports 41 maskable interrupt channel and 16 priority levels. As NVIC are closely coupled with processor core it is assured that interrupts are processed with low latency. NVIC supports some advanced interrupt handling modes including Interrupt preemption, tail chaining, late arrival. These features allow to reach low latency and more robust response. These features allow to avoid stack overhead when processing multiple interrupts that arrive pretty much at same time. For instance tail chaining mechanism allows skipping stack pop if there is another pending interrupt once current is completed. Refer to Cortex-M3 programming manual for more info.

Fe words about priority. Generally speaking every interrupt has associated an 8-bit priority level register. But not all bits are used to set priorities. STM32F100RB microcontroller has 16 priority levels what means that 4 MSB bits are used to set priorities. The lower priority number the higher priority. If needed these bits can be split in to two groups where you can create sub-priority levels for each preemptive priority. Subpriority levels are handy when two or more same priority level interrupts occur. Then one with higher subpriority will be handled first:

Handling external interrupts

External interrupts are connected to NVIC through special external interrupt controller (EXTI). It allows to map multiple GPIO lines to NVIC external interrupt inputs. STM32F100RB interrupt controller consists of 18 edge detector lines. Each line can be configured to trigger on different event like rising edge, falling edge or both. As ports are 16-bit wide then 16 lines are dedicated to map port pins. For instance EXTI0 line can be mapped to pins 0 of all ports or any other combination.

Additional 3 EXTI lines are dedicated to RTC alarm interrupt, power voltage detect (PVD). EXTI lines then connects to NVIC controller. EXTI lines 0..4 connects individually to NVIC while lines 5 to 16 are grouped in to two lines that connect to NVIC. RTC alarm interrupt, power voltage detect lines also connect to NVIC individually.

It is hardly possible to cover all exception handling functionality. More should come during practice as each individual case has it’s own scent. When pushing projects to RTOS based applications these things start really matter. But for now lets make another code example where we will implement interrupt based routines.

Writing interrupt based code example

We are going to improve our previous code example where button were checked and LEDs lit in while(1) super-loop. This time we are going to configure so that button press would generate interrupt. Within interrupt service routine we will process button function and then return to loop. Also we are going to implement SysTick timer which would blink LED.

First of all we need to initialize EXTI module that would map button pin to EXTI0 line and then configure this line on NVIC controller where we will set up a priority and enable interrupt. To do so in button.c source file we create:

void ButtonInit(void)
{
  //EXTI structure to init EXT
  EXTI_InitTypeDef EXTI_InitStructure;
  //NVIC structure to set up NVIC controller
  NVIC_InitTypeDef NVIC_InitStructure;
  //Connect EXTI Line to Button Pin
  GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
  //Configure Button EXTI line
  EXTI_InitStructure.EXTI_Line = EXTI_Line0;
  //select interrupt mode
  EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
  //generate interrupt on rising edge
  EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
  //enable EXTI line
  EXTI_InitStructure.EXTI_LineCmd = ENABLE;
  //send values to registers
  EXTI_Init(&EXTI_InitStructure);
  //configure NVIC
  //select NVIC channel to configure
  NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
  //set priority to lowest
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;
  //set subpriority to lowest
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;
  //enable IRQ channel
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  //update NVIC registers
  NVIC_Init(&NVIC_InitStructure);
}

To use button as EXTI we need external interrupt to port pin.

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);


This simply sets pin input for EXTI in AFIO_EXTCR register. As we map pin0 of GPIOA port this command sets least bit of AFIO_EXTICR1 register. Once mapping is done we need to configure EXTI module itself:

//Configure EXTI line 0

EXTI_InitStructure.EXTI_Line = EXTI_Line0;

//select interrupt mode

EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;

//generate interrupt on rising edge

EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;

//enable EXTI line

EXTI_InitStructure.EXTI_LineCmd = ENABLE;

//send values to registers

EXTI_Init(&EXTI_InitStructure);


Here we select which EXTI line we need to configure. Then select Interrupt mode for this line (also EXTI lines can be configured to generate software interrupts). Then we select rising edge triggering source as our button pin is going high when pressed. And lastly we simply enable this line so it could output signals on EXTI line that goes in to NVIC.

NVIC also has to be set in order to process upcoming interrupts:

//select NVIC channel to configure

NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;

//set priority to lowest

NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x0F;

//set subpriority to lowest

NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0F;

//enable IRQ channel

NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

//update NVIC registers

NVIC_Init(&NVIC_InitStructure);


Here is same situation: we select which interrupt source to configure (EXT0_IRQn), then set priority and subpriority – both to 0 and lastly we enable that interrupt. After NVIC is initialized it can start processing incoming interrupts from EXTI0 line that is mapped to button pin.

In our template we are using separate source file (stm32f10x_it.c) where interrupt service routines are implemented. Here we write our EXTI0 handler:

void EXTI0_IRQHandler(void)
{
	//Check if EXTI_Line0 is asserted
	if(EXTI_GetITStatus(EXTI_Line0) != RESET)
	{
	LEDToggle(LEDG);
	}
	//we need to clear line pending bit manually
    EXTI_ClearITPendingBit(EXTI_Line0);
}


It toggles LED every time we press a button. One important thing about EXTI lines!. Once line have been set it won’t reset automatically so after single button press it stays high constantly and this leads chained interrupts as NVIC “thinks” that there are new interrupt requests incoming. To avoid this we need to reset EXTI line manually after we process interrupt. For this we use function:

EXTI_ClearITPendingBit(EXTI_Line0);


Additionally we wanted to run SysTickTimer which would blink a led every 1s. CMSIS core_cm3.c source has a nice function that initializes and starts Systick timer with single line:

SysTick_Config(15000000);


We only need to pass how many ticks to count between SysTick interrupts. Also we need to write our handler in stm32f10x_it.c file:

void SysTick_Handler(void)
{
	LEDToggle(LEDB);
}


We just toggle led on every SysTick interrupt.

After everything is set up we can write our main program:

// Includes ------------------------------------------------------------------*/
#include "stm32f10x.h"
#include "leds.h"
#include "button.h"
int main(void)
{
  ButtonInit();
  LEDsInit();
  SysTick_Config(15000000);
  //__enable_irq ();
while (1)
  {
	//interrupts does the job
  }
}


All we do is just initialize button, leds and start SysTick timer. Other work is done by interrupt routines. This saves processing power significantly. In battery operated devices processor is sent to one of power safe modes between interrupts or simply do other tasks while interrupts automatically work in background. Download project.

Bookmark the permalink.

8 Comments

  1. Thanks, mate. Really interesting.

  2. Hi I would like to ask something . What is the use of NVIC priority groups? I read some code which had several
    NVIC_priority_groups definitions like NVIC_priority_group0, NVIC_priority_group1, NVIC_priority_group2 etc. What do they mean ? Thank you.
    Yusuf Bakir

  3. As I mentioned Priorities can have 16 levels. But those levels can be abstracted differently. Like in above image – four priority bits were split in to two sections priority and sub-priority. In our case 2 bits for each of them. Overall there are still 16 total priority levels. If you desire you can choose to have 1 bit for priority and 3 bits for sub-priority. So each of those priority+subpriority compositions are called priority groups. These can be set in Application Interrupt and Reset Control Register (AIRCR) [10:8] bits to be specific.
    For instance NVIC_priority_group0 would mean that all four bits are set to priority and the is no subriorities.
    If you need to select different priority group you can call
    NVIC_PriorityGroupConfig(NVIC_priority_group2);
    Using grouping can help manage large number of interrupts.
    Hope this answers your question.

  4. Hi,
    what if I want to return a value from the interrupt handler?
    lets say a variable is incremented every time the handler is invoked and this variable should be returned to the main thread.
    how do I do this?

  5. Simplest way of doing this is to use global volatile variable. Follow
    http://www.embedds.com/using-volatile-keyword-in-embedded-code/
    for more info.

  6. How can you manage key debounce? Like to add 100ms or so. Each debounce is a new interrupt. I REALLY don’t want to add delay to Interrupt routine (not sure it would work anyway as the interrupt routine, would interrupt the routine if key is down. I have tried using this to set a flag (that the key state has changed, then call an external routine to read the port to determine status. Maybe it is just me, but every switch I have is always NOISY.. (perhaps it is merely the OPERATOR)..

  7. I’ve long suggested that people seeking to gett a good understanding of this speciific topic spread their research acrooss many blogs kbbkbedbafaeaegk

  8. is STM32F107 Controller provide Level trigger interrupt?
    if yes, please provide the reference. i would be very grateful to you.

Leave a Reply

Your email address will not be published. Required fields are marked *