Understanding GPIO Interrupts in Embedded Systems

Suyash KoriSuyash Kori
6 min read

🔃Quick Recap of Interrupts and ISRs

In embedded systems, Interrupts Service Routines(ISRs) allow microcontrollers to respond to external or internal events immediately, without constantly polling for changes. This makes your code efficient and responsive - a must for real-time systems.

______________________________________________________________________________________________

📌What are GPIO Interrupts?

GPIO(General Purpose Input/Output) pins can be configured to detect events like a button press or a sensor signal. Instead of constantly checking the pin status(polling), you can configure it to trigger an interrupt whenever the input changes.

GPIO interrupts help in:

  • Reacting to external stimuli

  • Saving CPU cycles

  • Improving power efficiency

______________________________________________________________________________________________

Types of GPIO Interrupt Triggers

A GPIO interrupt can be triggered by:

  • Rising Edge - when the signal goes from LOW to HIGH

  • Falling Edge - when the signal goes from HIGH to LOW

  • Both Edges - useful for toggle detection

  • (Optional) Level Triggered - stays active as long as the pin remains HIGH or LOW

    ✅Tip: Edge-triggered interrupts are generally preferred for avoiding repeated ISR execution.

______________________________________________________________________________________________

💡The Flow of a GPIO Interrupt

External Event → EXTI Line Detection → NVIC Interrupt Request → ISR Execution

This block diagram is specific to STM32 microcontrollers (from STMicroelectronics).

Here’s what happens step-by-step:

  1. An external signal(e.g., button press) causes a change on a GPIO pin.

  2. The EXTI (External Interrupt/Event Controller) detects this change.

  3. NVIC (Nested Vectored Interrupt Controller) receives an interrupt request.

  4. NVIC passes control to the relevant ISR (Interrupt Service Routine).

  5. ISR runs and clears the interrupt flag before exiting.

Now, let’s go through each block one by one to understand how it contributes to interrupt generation. We'll also dive a bit deeper into the register level — not too much — just enough to connect the theoretical concepts with their real-world application. This hands-on approach with actual registers will definitely make the learning more interesting and practical.

______________________________________________________________________________________________

🔎What is EXTI (External Interrupt) ?

EXTI allows GPIO pins to be linked to interrupt lines. You can:

  • Choose which pin triggers which EXTI line

  • Set the trigger type: rising/falling/both.

  • Map EXTI lines to GPIO pins using SYSCFG (in STM32)

As mentioned earlier, before deciding which pin should trigger which EXTI line, we first need to configure the trigger type — whether it's a rising edge, falling edge, or both. To manage this, a peripheral register definition structure named EXTI_RegDef_t is created. This structure contains various members such as RTSR, FTSR, IMR, and others related to EXTI configuration.

______________________________________________________________________________________________

As I said before, we won’t go too deep into the details, since that might cause confusion for you. The goal here is to give you a clear overview of the concepts and the logic behind how they are implemented.

______________________________________________________________________________________________

  1. So first configure the EXTI type for respective pin numbers.
    Below is the logic used to set the falling edge bit:

     EXTI → FTSR | = ( 1 « pGPIOHandle → GPIO_PInConfig.GPIO_PinNumber);
    

    In other words, we’re setting the bit at that position so that the corresponding pin is configured to detect a falling edge.
    Additionally, we need to clear the rising edge bit. This is important because if it was previously set by some other function or part of the code, it could lead to unexpected behavior.
    Below is the logic used to clear the rising edge bit:

     EXTI → RTSR & = ~ ( 1 « pGPIOHandle → GPIO_PInConfig.GPIO_PinNumber);
    

    Don’t worry about the bit operations, we will cover those topics in the upcoming blogs someday.

    ____________________________________________________________________________________

  2. Now we will set SYSCFG_EXTICR.
    As you can see in the block diagram above, if you want to select EXTI0, EXTI1, EXTI2, and so on, for a specific GPIOx pin, you’ll need to configure it accordingly.
    To do this, you make use of the SYSCFG_EXTICR register. There are four such registers available — SYSCFG_EXTICR1 to SYSCFG_EXTICR4 — each responsible for configuring a specific range of EXTI lines.

    Below are the register’s overview :-

     i) SYSCFG external interrupt configuration register 1 ( SYSCFG_EXTICR0 ): -
    
              | EXTI3 | EXTI2 | EXTI1 | EXTI0 | ( each block of 4 bits)
    
     ii) SYSCFG external interrupt configuration register 2 ( SYSCFG_EXTICR1 ): -
    
              | EXTI7 | EXTI6 | EXTI5 | EXTI4 | ( each block of 4 bits)
    
     iii) SYSCFG external interrupt configuration register 3 ( SYSCFG_EXTICR2 ): -
    
              | EXTI11 | EXTI10 | EXTI9 | EXTI8 | ( each block of 4 bits)
    
     iv) SYSCFG external interrupt configuration register 4 ( SYSCFG_EXTICR3 ): -
    
              | EXTI15 | EXTI14 | EXTI13 | EXTI12 | ( each block of 4 bits)
    
     ```
     Where:
         0000 → PA[x] pin (Port A)
         0001 → PB[x] pin (Port B)
         0010 → PC[x] pin (Port C)
         ...
         0101 → PF[x] pin (Port F)
     ```
    

    ⭐ Let’s take a real-world example to understand how EXTI is configured for a given input pin.

    Consider a case where you press a button connected to PC13 — that is, Port C, Pin 13.
    To configure this, we need to set the value 0010 (which represents Port C) at EXTI13, located in SYSCFG_EXTICR3.

    To implement this logic, we’ll apply some simple math to determine the correct register and position.

     ```
     - Divide pin number by 4 and store it in temp1 i.e.
                     temp1 = 13 / 4 = 3 ( SYSCFG_EXTICR3 )
        so basically here the register is getting selected, Great ;
    
     - Now use the modulus operator i.e. and store it in temp2 i.e.
                     temp2 = 13 % 4 = 1 ( 1st block as you can see above in the register diagram)
     ```
    

    Now, let’s write the code for this logic.
    It’s fairly straightforward based on what we’ve discussed above — so go through it quickly and try to connect it with the explanation.

     uint8_t temp1 = pGPIOHandle → GPIO_PinConfig.GPIO_PinNumber / 4;
     uint8_t temp2 = pGPIOHandle → GPIO_PinConfig.GPIO_PinNumber % 4;
     uint8_t portcode = GPIO_BASEADDR_TO_CODE ( pGPIOHandle → pGPIOx );
     SYSCFG_PCLK_EN();
     SYSCFG → EXTICR [ temp1 ] = portcode « ( temp * 4 );
    

    ____________________________________________________________________________________

    Alright, we’ll wrap up this blog here — I know it’s been quite a bit to digest today.
    The reason we’re stopping at this point is because everything we’ve covered so far falls under the MCU peripheral side of GPIO interrupt delivery to the processor.

    In the next blog, we’ll shift our focus to the processor side — where we’ll cover topics like enabling/disabling IRQs, understanding the Interrupt Priority Register, and handling GPIO interrupts with a real example.

    So get ready — the journey ahead is going to be even more interesting (and a bit more challenging). But hey, we’re engineers — tackling complexity is what we do best!

    See you in the next blog — bubye for now! 🚀

3
Subscribe to my newsletter

Read articles from Suyash Kori directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Suyash Kori
Suyash Kori