STM32 interrupts and programming with GCC

Probably one of the key features of any microcontroller is the interrupt system. ARM Cortex-M3 microcontrollers may have up to 256 interrupted sources. The first 15 interrupt sources are called system exceptions. These exceptions arise 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 the starting point of stack pointer):

ARM Cortex-M3 interrupts

Each exception vector holds the four-byte address of the service routine that is called when an 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 the .vectors memory section described in the linker script and usually resides at the very beginning of flash memory.

Inside NVIC of Cortex-M3

Nested Vectored Interrupt Controller (NVIC) is an essential part of the Cortex processor. It is a pretty complex module that takes care of interrupt processing logic. Additionally, it holds control registers for the SysTick timer and debugs control. Generally speaking, NVIC can support up to 240 external interrupts. Speaking of the STM32F100RB microcontroller that resides in the discovery board, NVIC supports 41 maskable interrupt channels and 16 priority levels. As NVIC is tightly coupled with the 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 reaching low latency and a more robust response. These features enable avoiding stack overhead when processing multiple interrupts that arrive pretty much at the same time. For instance, the tail chaining mechanism allows skipping stack pop if there is another pending interrupt once the current is completed. Refer to the Cortex-M3 programming manual for more info.

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

Handling external interrupts

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

external interrupt controller (EXTI)

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

NVIC

It is hardly possible to cover all exception handling functionality. More should come during practice, as each case has its scent. When pushing projects to RTOS based applications, these things start to matter. But for now, let us make another code example where we will implement interrupt-based routines.

Writing interrupt-based code example

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

First of all, we need to initialize EXTI module that would map the button pin to the EXTI0 line and then configure this line on the 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 the button as EXTI, we need an external interrupt to a port pin.

GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);

This sets pin input for EXTI in the AFIO_EXTCR register. As we map pin0 of GPIOA port, this command sets the least bit of AFIO_EXTICR1 register. Once the mapping is done, we need to configure the 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 choose the rising edge triggering source as our button pin is going high when pressed. And lastly, we only enable this line to output signals on the EXTI line that goes into NVIC.

NVIC also has to be set 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 the same situation: we select which interrupt source to configure (EXT0_IRQn), then set priority and sub priority – both to 0, and lastly, we enable that interrupt. After NVIC is initialized, it can start processing incoming interrupts from the EXTI0 line mapped to the button pin.

Our template uses a 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 the LED every time we press a button. One important thing about EXTI lines!. Once the line has been set, it won’t reset automatically, so after a single button press, it stays high constantly, and this leads to chained interrupts as NVIC “thinks” that there are new interrupt requests incoming. To avoid this, we need to reset the EXTI line manually after we process the interrupt. For this, we use the 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 a 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 initialize the button, LEDs, and start the SysTick timer. Interrupt routines do other work. This saves processing power significantly. In battery-operated devices, the processor is sent to one of power safe modes between interrupts or other tasks while interrupting automatically work in the background. Download the project.

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
    https://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.

Comments are closed