Understanding STM32 external interrupt EXTI setup is essential for any embedded developer working with STM32 microcontrollers. External interrupts allow your STM32 to respond to hardware events — button presses, sensor triggers, encoder pulses — without wasting CPU cycles in polling loops. This tutorial covers rising edge, falling edge, and both-edge EXTI configuration using STM32CubeIDE and HAL library.
Table of Contents
- What is EXTI in STM32?
- Understanding EXTI Lines
- Setting Up EXTI in STM32CubeMX
- HAL Code for External Interrupt
- Rising vs Falling vs Both Edge
- Software Debouncing in ISR
- Advanced EXTI Techniques
- Frequently Asked Questions
What is EXTI in STM32?
EXTI (External Interrupt/Event Controller) is a peripheral in STM32 microcontrollers that detects signal transitions on GPIO pins and triggers CPU interrupts. When a configured edge (rising, falling, or both) is detected on the EXTI pin, the corresponding interrupt handler (ISR) is called — allowing immediate response without polling.
Key advantages of using STM32 external interrupt EXTI over polling:
- CPU efficiency: CPU can sleep in low-power mode and wake only on interrupt
- Precise timing: Interrupt latency is typically 4–6 CPU clock cycles
- Non-blocking: Main loop continues uninterrupted; ISR handles the event asynchronously
- Multiple sources: Up to 16 EXTI lines available on most STM32 variants
Understanding EXTI Lines
STM32 has 16 external interrupt lines (EXTI0–EXTI15), each corresponding to the pin number across all GPIO ports. This is a crucial concept to understand:
- EXTI0 can be connected to PA0, PB0, PC0, PD0 — but only ONE at a time
- EXTI1 handles pin 1 of any port (PA1, PB1, etc.) — again, only one at a time
- This means you cannot use PA0 and PB0 as external interrupts simultaneously
EXTI lines are grouped for ISR handlers:
- EXTI0–EXTI4: Each has its own ISR (EXTI0_IRQHandler through EXTI4_IRQHandler)
- EXTI5–EXTI9: Share EXTI9_5_IRQHandler
- EXTI10–EXTI15: Share EXTI15_10_IRQHandler
Setting Up EXTI in STM32CubeMX
STM32CubeMX (included in STM32CubeIDE) makes EXTI configuration visual and straightforward:
- Open your project in STM32CubeIDE, navigate to the .ioc file
- In the Pinout view, click on your desired GPIO pin (e.g., PA0)
- Select GPIO_EXTI0 from the dropdown
- Go to System Core → GPIO → PA0
- Set GPIO Mode to: External Interrupt Mode with Rising/Falling/Both Edge trigger
- Enable Pull-up or Pull-down as needed (for button: usually Pull-up)
- Go to System Core → NVIC
- Enable the corresponding EXTI interrupt (EXTI line 0 interrupt for PA0)
- Set the interrupt priority (0–15, lower = higher priority)
- Click Generate Code
HAL Code for External Interrupt
CubeMX generates the NVIC configuration automatically. You only need to implement the callback function:
/* In main.c or your application file */
/* This callback is called from HAL_GPIO_EXTI_IRQHandler() */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0) /* PA0 triggered */
{
/* Handle button press or rising/falling edge event */
/* IMPORTANT: Keep ISR code SHORT and FAST */
/* Set a flag, toggle LED, increment counter */
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13); /* Toggle LED */
}
if (GPIO_Pin == GPIO_PIN_5) /* PB5 triggered */
{
/* Handle second interrupt source */
button_pressed_flag = 1; /* Set flag for main loop processing */
}
}
/* In the IRQHandler (auto-generated by CubeMX) */
void EXTI0_IRQHandler(void)
{
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0);
/* HAL clears the pending flag and calls HAL_GPIO_EXTI_Callback */
}
Rising vs Falling vs Both Edge
Choosing the correct edge type is critical for reliable operation:
Rising Edge (GPIO_MODE_IT_RISING)
Triggers when pin transitions from LOW (0V) to HIGH (3.3V). Use for:
- Detecting when a button is released (if active-low with pull-up)
- Counting rising edges from encoders or frequency signals
- Detecting when a sensor output goes high (e.g., PIR motion sensor output)
Falling Edge (GPIO_MODE_IT_FALLING)
Triggers when pin transitions from HIGH to LOW. Use for:
- Button press detection with pull-up resistor (most common for tactile buttons)
- Detecting active-low chip select or interrupt signals from external ICs
Both Edges (GPIO_MODE_IT_RISING_FALLING)
Triggers on any transition. Use for:
- Quadrature encoders where both edges carry position information
- UART bit-banging or protocol decoding
- Measuring pulse width (record timestamp on both edges)
/* Example: Measure pulse width using both edges */
uint32_t pulse_start = 0;
uint32_t pulse_width_us = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0)
{
if (HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) == GPIO_PIN_SET)
{
/* Rising edge - start timing */
pulse_start = __HAL_TIM_GET_COUNTER(&htim1);
}
else
{
/* Falling edge - calculate pulse width */
pulse_width_us = __HAL_TIM_GET_COUNTER(&htim1) - pulse_start;
}
}
}
Software Debouncing in ISR
Mechanical buttons produce multiple transitions (bounce) when pressed. Without debouncing, a single button press can trigger dozens of interrupts. Simple software debouncing in the EXTI callback:
#include "stm32f4xx_hal.h"
#define DEBOUNCE_TIME_MS 50
volatile uint8_t button_state = 0;
uint32_t last_interrupt_time = 0;
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_0)
{
uint32_t current_time = HAL_GetTick();
/* Ignore interrupts within debounce window */
if ((current_time - last_interrupt_time) > DEBOUNCE_TIME_MS)
{
button_state = 1; /* Signal main loop */
last_interrupt_time = current_time;
}
}
}
Advanced EXTI Techniques
EXTI with Low-Power Mode (Stop Mode Wake-up)
/* Configure EXTI for wakeup from STOP mode */
/* PA0 must be configured as EXTI with wake-up capability */
void enter_stop_mode_with_wakeup(void)
{
/* Configure EXTI line 0 as wake-up source */
/* GPIO and EXTI already configured via CubeMX */
HAL_SuspendTick();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
/* CPU wakes here when EXTI0 fires */
HAL_ResumeTick();
/* Reconfigure system clock after wakeup (STOP mode disables PLL) */
SystemClock_Config();
}
Changing EXTI Edge at Runtime
/* Dynamically switch between rising and falling detection */
void switch_to_rising_edge(void)
{
/* Get EXTI line config */
EXTI->FTSR &= ~GPIO_PIN_0; /* Disable falling */
EXTI->RTSR |= GPIO_PIN_0; /* Enable rising */
}
void switch_to_falling_edge(void)
{
EXTI->RTSR &= ~GPIO_PIN_0; /* Disable rising */
EXTI->FTSR |= GPIO_PIN_0; /* Enable falling */
}
Frequently Asked Questions
How many EXTI lines can be active simultaneously on STM32?
All 16 EXTI lines (EXTI0–EXTI15) can be active simultaneously, but remember each line can only be connected to one port. So you can have PA0, PB1, PC2, PD3, etc., all as interrupts simultaneously, but not PA0 and PB0 together.
What is the maximum safe interrupt frequency for STM32 EXTI?
Theoretically, EXTI can respond to signals up to half the CPU clock frequency. Practically, if your ISR takes N cycles, you should not receive interrupts faster than N cycles. For a 168 MHz STM32F4 with a 10-cycle ISR, you could handle interrupts up to ~16 MHz — far more than any button press.
Can I use EXTI with DMA on STM32?
EXTI itself doesn’t support DMA. However, timers triggered by EXTI can DMA-capture timestamps, and peripherals like SPI/UART use DMA independently of EXTI. Use TIM input capture with DMA for high-frequency signal capture instead.
Why does my STM32 EXTI interrupt keep firing continuously?
Likely causes: button bounce (add debouncing), missing pull-up/pull-down resistor causing floating pin, incorrect edge selection, or forgetting to clear the EXTI pending flag (HAL_GPIO_EXTI_IRQHandler does this automatically when using HAL).
What is the interrupt latency of STM32 EXTI?
STM32 Cortex-M interrupt latency from the edge event to the first instruction of the ISR is approximately 12 CPU clock cycles in the best case (no higher-priority ISR running, no flash wait states). At 168 MHz, this is about 71 nanoseconds.
Add comment