External Interrupts on the STM32F0

One of my major gripes with the STM32 family is the documentation. The reference manual is pretty comprehensive but is not well organized. A simple task of figuring out how to use external interrupts is utterly unclear. The availability of the peripheral libraries which again are not well documented exacerbates this problem. Programmers are encouraged to recycle peripheral library based code from the available examples without really understanding the hardware…they simply use the code because they know it works. This approach might seem reasonable but when trying to implement a complicated peripheral setup it can cause much angst.

Now that I’ve got my rant out of the way, let’s take a look at interrupts on the STM32F0 chip and specifically external Interrupts.

Figure 1. STM32F0 Interrupt vector Table (Table 27 in the Reference Manual)

The STM32F0 Microcontroller is based on the Cortex-M0 core and possesses an NVIC (nested vector interrupt control ) peripheral which can keep track of up to 32 interrupt vectors shown in Figure 1 including exceptions. Each one of these interrupt vectors can be mapped to a set of related interrupt sources. For example all USART1 interrupt events such as: Transmission Complete, Clear to Send, Transmit Data Register empty or Framing error (in Smartcard mode),Overrun error, Receive data register not empty, Character match e.t.c are mapped to the USART1 interrupt vector no# 27.

Similarly external interrupt lines 0 & 1 are mapped to interrupt vector 5, external interrupt lines 2 & 3 are mapped to interrupt vector 6 and external interrupt lines 4 through 15 are mapped to interrupt vector 7.

Figure 2. Mapping Pins to External interrupt lines

The GPIO pins are mapped to the various external interrupt lines by way of multiplexers as shown in Figure 2. For each external interrupt line, up to 6 pins can be mapped. The mapping is accomplished by the EXTIXX bits in the  SYSCFG_EXTICRX registers.

 

To configure an external interrupt one must configure the external interrupt (EXTI) peripheral as well as the NVIC peripheral. The general procedure is as follows:

  1. Configure the EXTIXX bits in the SYSCFG_EXTICRX registers to map the GPIO pin(s) of interest to the appropriate external interrupt lines (EXTI0-EXTI15).
  2. For the external interrupt lines (EXTIXX) of interest choose a signal change that will trigger the external interrupt.The signal change can be a rising edge, a falling edge or both. These can be set via the EXTI_RTSR (rising) and the EXTI_FTSR (falling) registers.
  3. Unmask the external interrupt line(s)  of interest. by setting the bit corresponding to the EXTI line of interest in the EXT_IMR register.
  4. Set the priority for the interrupt vector in question in the NVIC either via the CMSIS based “NVIC_SetPriority()” function or through the IPR0-IPR7 registers.
  5. Enable the interrupt in the NVIC either via the CMSIS based “NVIC_EnableIRQ()” function or via the ISER register.
  6. Write your interrupt service routine (ISR).
  7. Inside your interrupt service routine, check the source of the interrupt…either the GPIO pin directly or the external interrupt line. Once you figure out which one triggered the interrupt, perform the interrupt processing scheme associated with it. Make sure that you clear the corresponding pending bit of the external interrupt lines of interest in the EXT_PR (external interrupt pending register) register by writing a ’1′ to it.

To provide you with  example, consider the gpiotogglep3project  described in the previous blog entry Understanding the STM32F0′s GPIO part 2. In that example we waited (polling) for a button press on pin PA0 to advance a state machine that controlled the color of an RGB LED. The state machine was programmed to light up the RGB LED in a specific sequence of colors. The state machine advanced from one color to the other only when the button was pressed.

We would like to implement the same example but instead of utilizing a polling approach we will utilize and interrupt approach.i.e. each time the button is pressed code execution jumps to an interrupt service routine/handler (ISR). In this handler a flag variable is checked. The main code will then check this flag variable. If it is set then the RGB LED state machine will advance and the RGB will light up with the next color in the sequence.

So lets apply the general procedure posted above to this particular problem

  1. The STM32F0Discovery button is connected to the PA0 pin. According to Figure 2 the PA0 pin is connected to the EXTI0 external interrupt line. So to map the PA0 pin to the EXTI0 line we need to set the EXTI0[3:0] bits in the SYSCFG_EXTICR1 register to “x000″. since the most significant bit is a don’t care, we could set these bits to “0000″. Note that the SYSCFG_EXTICR1 register is all zeroes on reset. So for this particular scenario this action is strictly not necessary but would not hurt
    SYSCFG->EXTICR1  &= ~(0x000F) ; //clear bits 3:0 in the SYSCFG_EXTICR1 reg
  2. The PA0 button is connectedto a pulldown resistor by default. When the button is pressed, the voltage on the pin change from GND to VDD i.e. the pin exhibits a rising edge. So we will configure PAo to trigger an interrupt event on the EXTIo  line on a rising edge by setting the ’0′th bit in the EXTI_RTSR register.
    EXTI->RTSR |= EXTI_RTSR_TR0;
  3. Unmask the external interrupt line EXTI0. by setting the bit corresponding to the EXTI0 “bit 0″ in the EXT_IMR register.
    EXTI->RTSR |= EXTI_IMR_MR0;
  4. The next step is to set the priority for the interrupt. Each interrupt in Figure 1 can be set a priority between 0(highest priority)-3(lowest priority). These priority settings help manage the nesting of interrupts and is important in improving the responsiveness of an embedded system. For example If an interrupt subroutine for a low priority interrupt is running when a higher priority interrupt event occurs, code execution will switch from the lower priority ISR to the higher priority ISR. Once the higher priority ISR completes, code execution will then return to the lower priority ISR. Once that ISR completes, code execution returns to the “main” function.  Well what if two interrupts of the same priority happen at the same time? In that case the one with the smaller position/vector number (placed higher up in Figure 1) will run first and the other will be put in a pending state. Once the ISR for the first interrupt completes, code execution switches to the ISR of the second interrupt. This is sometimes referred to as a “natural priority scheme”. The NVIC contains 8 32-bit registers NVIC_IPR0-NVIC_IPR7. The value of the 2 most significant bits of each byte in each of these registers control determines the priority for a specific interrupt in the table in Figure 1. The CMSIS library contains a function that makes this job much easier. It’s called “NVIC_SetPriority()”. This function takes two parameters; the first one is the position/vector number of the interrupt of interest. In our case the position number of the EXTI0 interrupt is 5. The second one is the priority for that interrupt source which can be set from 0(highest) to 3(lowest). We will choose to set it to one just for fun. If you look inside your “stm32f0xx.h” file you’ll find that the label “EXTI0_1_IRQn” is defined as 5.
    NVIC_SetPriority(EXTI0_1_IRQn,1); // alternatively NVIC_SetPriority(5,1)
  5. The next step is to enable the interrupt in the NVIC (Note that its very important to set the interrupt priority before you enable the interrupt). One can do this by setting bit 5 of the NVIC_ISER register. Or alternatively use the CMSIS function “NVIC_EnableIRQ()” which takes the interrupt source position in the table in Figure 1 as a parameter. In our case that’s “EXTI0_1_IRQn”  or 5.
    NVIC_EnableIRQ(EXTI0_1_IRQn);
  6. The next step is to write the interrupt service routine (ISR):
    void EXTI0_1_IRQHandler(void)
    {
	if( (EXTI->IMR & EXTI_IMR_MR0) && (EXTI->PR & EXTI_PR_PR0))
        {
             EXTI0Flag = 1;
	     delay(50000);
	     while(GPIOA->IDR & GPIO_IDR_0){}
	     EXTI->PR |= EXTI_PR_PR0 ;
        }
}

Note that I’m following ST’s convention of putting all of the ISRs in the stm32f0xx_it.c files. Protoypes of the ISRs need to be declared in the stm32f0xx_it.h. The ISR function names for each interrupt source are already defined in the “startup_stm32f0xx.S” startup file so if in doubt, you can always check there to verify the ISR name. Once in the ISR, we check to see if this Interrupt was indeed caused by the EXTI0 interrupt line and not by the EXTI1 line; since both of these lines can cause this ISR handler/function (associated with the EXTI0_1 vector in Table 1) to be called. If it is indeed caused by EXTI0 line i.e. EXTI0 (’0′th ) bit is set in the EXT_IMR register,  and EXTI0(’0′th)  bit is set in the EXT_PR register, then handle the interrupt for the EXTI0 interrupt line. In this example we are simply setting a flag variable called “EXTI0Flag”. Note that instead of verifying the interrupt line that caused the interrupt we could also check which pin triggered the interrupt i.e.

void EXTI0_1_IRQHandler(void)
{
	if( (PINA->IDR & GPIO_IDR_0)
        {
             EXTI0Flag = 1;
	     delay(50000);
	     while(GPIOA->IDR & GPIO_IDR_0){}
	     EXTI->PR |= EXTI_PR_PR0 ;
        }
}

The while loop will wait until the PIN PA0 is not 1 i.e. until the button is released. Along with the delay, this ensures that the interrupt is not re-triggered due to switch de-bounce or anything else other than a valid button press. The ISR will exit only when the push button is released. For this sort of application doing this is valid. It may not be valid for other applications.

7.  Finally we clear the bit in the EXTI->PR register that corresponds to the EXTI0   interrupt. We do this by writing a ’1′ to that bit. I know this is confusing….just accept it.

And thats it!!!!

 

The complete main.c source file is listed below:

/******************************************************************************
  * @file    IO_Toggle/main.c 
  * @author  MCD Application Team
  * @version V1.0.0
  * @date    23-March-2012
  * @brief   Main program body
  ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "stm32f0xx.h"

int EXTI0Flag = 0 ;
enum color {RED,GREEN,YELLOW,BLUE,MAGENTA,CYAN,WHITE};
enum color state = RED;

void delay (int a);
enum color runRGBStateMachine(enum color a);

/**
  * @brief  Main program.
  * @param  None
  * @retval None
  */

int main(void)
{
  /*!< At this stage the microcontroller clock setting is already configured, 
       this is done through SystemInit() function which is called from startup
       file (startup_stm32f0xx.s) before to branch to application main.
       To reconfigure the default setting of SystemInit() function, refer to
       system_stm32f0xx.c file
     */

  /* GPIOC GPIOA Periph clock enable */
	RCC->AHBENR |= RCC_AHBENR_GPIOCEN; 
	RCC->AHBENR |= RCC_AHBENR_GPIOAEN;

	GPIOC->MODER |= (GPIO_MODER_MODER1_0 | GPIO_MODER_MODER2_0|GPIO_MODER_MODER3_0) ;
	/* Configure PC1 PC2 and PC3 in output  mode and PA0 in input mode  */
	GPIOA->MODER &= ~(GPIO_MODER_MODER0) ;

	GPIOC->OTYPER &= ~(GPIO_OTYPER_OT_1 | GPIO_OTYPER_OT_2 | GPIO_OTYPER_OT_3) ;
	// Ensure push pull mode selected for output pins--default

	GPIOC->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR1|GPIO_OSPEEDER_OSPEEDR2|GPIO_OSPEEDER_OSPEEDR3);
	GPIOA->OSPEEDR |= (GPIO_OSPEEDER_OSPEEDR0);
	//Ensure maximum speed setting (even though it is unnecessary)

	GPIOC->PUPDR &= ~(GPIO_PUPDR_PUPDR1|GPIO_PUPDR_PUPDR2|GPIO_PUPDR_PUPDR3);
	GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPDR0);
	//Ensure all pull up pull down resistors are disabled PA0 is connected to external pulldown on STM32F0Discovery baord

        SYSCFG->EXTICR1  &= (0x000F) ; 
       //1. clear bits 3:0 in the SYSCFG_EXTICR1 reg to amp EXTI Line to NVIC

        EXTI->RTSR = EXTI_RTSR_TR0;
       // 2.Set interrupt trigger to rising edge

       EXTI->IMR = EXTI_IMR_MR0; // 3. unmask EXTI0 line
	NVIC_SetPriority(EXTI0_1_IRQn, 1); //4. Set Priority to 1 
	NVIC_EnableIRQ(EXTI0_1_IRQn);  // 5. Enable EXTI0_1 interrupt in NVIC (do 4 first)

	while (1)
	{
		if(EXTI0Flag)
		{
		    EXTI0Flag = 0;
			state = runRGBStateMachine(state);
		}
	}
	return 0;
}

void delay (int a)
{
	volatile int i,j;

	for (i=0 ; i < a ; i++)
	{
		j++;
	}

	return;
}

enum color runRGBStateMachine(enum color a)
{
	//GPIOC->BSRR |= ( (1<<3) |(1<<2) | (1<<1) );
	switch(a)
	{
			case RED:
				GPIOC->ODR = ( (1<<2) | (1<<3) );
				a = GREEN;
				break;

			case GREEN:
				GPIOC->ODR = ( (1<<1) | (1<<3));
				a = BLUE;
				break;

			case BLUE:
				GPIOC->ODR = ( (1<<1) |(1<<2) );
				a = YELLOW;
				break;

			case YELLOW :
				GPIOC->ODR = (1<<3);
				a = MAGENTA;
				break;

			case MAGENTA:
				GPIOC->ODR = (1<<2);
				a = CYAN;
				break;

			case CYAN:
				GPIOC->ODR = (1<<1);
				a = WHITE;
				break;

			case WHITE:
				GPIOC->ODR = 0;
				a = RED;
				break;

			default:
				GPIOC->ODR = ( (1<<3) |(1<<2) | (1<<1) );
				a = WHITE;
				break;
	}
	return a;
}

The ISR handler code in the stm32f0xx_it.c is:

void EXTI0_1_IRQHandler(void)
{
    if( (EXTI->IMR & EXTI_IMR_MR0) && (EXTI->PR & EXTI_PR_PR0))
        {
             EXTI0Flag = 1;
         delay(50000);
         while(GPIOA->IDR & GPIO_IDR_0){}
         EXTI->PR |= EXTI_PR_PR0 ;
        }
}

Don’t forget to include a protoype for this handler in the “stm32f0xx_it.h” header file. Also EXTI0Flag is an external variable declared in main.c . To enable its use in the “stm32f0xx_it.c”, you need to declare in that file with the extern keyword i.e. “extern int EXTI0Flag;”. The complete project is provided here :gpiotogglep4.zip

Note that when debugging code with interrupts, code execution will not switch to the ISR in the debugger if you’re stepping through the code. You need to have the CPU in a run state. The best way to debug code with interrupts is to insert breakpoints in the ISR. Then run the code with the “Continue”  (play) button. When an interrupt occurs, code execution will switch to the ISR and will halt at the breakpoint setup there.

This entry was posted in STM32F0 Peripherals. Bookmark the permalink.

13 Responses to External Interrupts on the STM32F0

  1. Luca says:

    Hi! I’ve a question. If I want to set PB2 and PB3 as interrupt, what should I do?
    Thanks

    • halherta says:

      For PB2 you will need to map the PB2 to the EXTI2 Line via the EXTI[3:0] bits in the SYSCFG_EXTICR2 register. You can then follow the instructions in the post.

      For PB2 you will need to map the PB3 pin to the EXTI3 Line via the EXTI[3:0] bits in the SYSCFG_EXTICR3 register. You can then follow the instructions in the post.

      I highly suggest that you take a look at the User Manual as well Good luck

  2. Bilut says:

    Hi! I’d like to ask a question about EXTI GPIO interrupts here, or rather confirm my suspicion. Do i correctly understand, that each EXTI line can be assigned to be triggered by only one GPIO pin at a time? For example EXTI0 can be triggered by PA0 but not PB0. Is there any possible way to work around it?

    • halherta says:

      I do not think that its possible to MAP PA0 and PB0 to the same EXTI line and thus ISR. Perhaps you ought to post this question on the STM32 forum on the ST site.

    • crispus says:

      You can’t. I tried that, and doesn’t work. Last configred line will work…

  3. Radosław says:

    I want to count the pulses of +5V in input PA5 and save it to a variable $result. How do I do that? Can you write a program for me? I would be very grateful for your help ;)
    Darken

    • Radosław says:

      Until now, I did support for 2×16 LCD. Now I’m trying to display a variable $result on second line of my LCD, but I don’t know how to count the input pulses in PA5 input.
      My code:

      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
      #include
      #include “stm32f0xx_rcc.h”
      #include “lcd_hd44780_lib.h”

      int main(void)
      {

      lcd_init();
      while (1)
      {
      lcd_locate(0,0);
      lcd_str(“first line”);
      lcd_locate(0,1);
      lcd_str(“second line”);
      return 0;
      }
      }
      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

      • halherta says:

        Radoslaw, in the ISR toggle a global variable. This way the variable will keep track of how many rising edges happened.

  4. sejong says:

    hi,
    i want to use PA1 as an interrupt. what will be the name of ISR that will be given
    in that case ?

    void EXTI1_1_IRQHandler(void) ??
    as in this case it is
    void EXTI0_1_IRQHandler(void)

    help me… as i have written the code , but on generating interrupt on PA1
    it does not run the ISR present

    • halherta says:

      sejong, I believe that it should be “void EXTI0_1_IRQHandler(void)” since there is only one interrupt vector for both EXTI0 & 1. Take a look at my code and user manual to help you set the PA1 interrupt. good luck

  5. sejong says:

    Thankyou halherta ,
    need some more help as i am generating external interrupts at PA0, PA1 on rising edge of signal.
    PA0 is working accurately , as when push button at PA0 is pressed an interrupt is generated
    and ISR is called. but in case of PA1, it is generating an interrupt even when a finger is moved towards button (even if button is not pressed). Why is this so happening. help me please

  6. cezar says:

    Hello,

    I spent many hours to figure out which is the current interrupt to be served.

    This is the scenario:

    I have configured port B, pins 8-11, each pin/interrupt source with different priority.

    It happens that 2 or more interrupts occurs in the same time ( EXTI->PR has more than 1 bit set).

    Now, in interrupt routine EXTI4_15_IRQHandler, I want to figure out which one is the current interrupt because in the EXTI->PR register I have more than one bit set. It will be nice to know which one of the possible interrupt (from 4 to 15) I am supposed to serve in order to respect the priority settings.

    Because if I do something like this:

    if(is_exti_line_8)
    do_irq8();
    else if(is_exti_line_9)
    do_irq9();
    … etc

    In this way, “IRQ8″ has higher priority in handling if even I configured the line exti_8 with lower priority.

    The only thing I found that says what is the current exception is the register SCB->ICSR[5..0], but the value is always 23 (and I substract 16 as says in doc) it will be 7, which is 7th entry from vector table which is EXTI4_15 which I already knew, so this information is useless for me.

    Does anybody knows what external interrupt I supposed to serve?

    Many thanks,
    Cezar

Leave a Reply

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

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>