STM32 GPIO interrupt configured but ISR never fires — what am I missing?
Asked by stale_biscuit_03 ·
Trying to use a button on PA0 to trigger an interrupt on an STM32F411 Nucleo board.
Using HAL, generated the init code with CubeMX. The GPIO pin is configured, and if I
just poll HAL_GPIO_ReadPin(GPIOA, GPIO_PIN_0) in the main loop I can see it changing
state when I press the button — so the hardware side is fine.
But HAL_GPIO_EXTI_Callback never gets called. I've got the override in my main.c,
it just sits there doing nothing. I've stared at this for an hour and can't see what's
different from the tutorials I've been following.
Relevant init code from CubeMX:
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_0;
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
And in main.c:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
if (GPIO_Pin == GPIO_PIN_0) {
HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
}
}
LED on PC13 never toggles. What have I missed?
3 Replies
The GPIO init looks correct. What's missing is almost certainly the NVIC configuration.
Setting up the GPIO in interrupt mode (GPIO_MODE_IT_RISING) arms the EXTI hardware,
but the ARM Cortex-M NVIC is a separate controller that has to be told to route that
interrupt to the CPU. Without it, the EXTI flag sets when the edge arrives — the
hardware saw the event — but the processor never gets the interrupt request.
Add this right after your GPIO init:
HAL_NVIC_SetPriority(EXTI0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(EXTI0_IRQn);
EXTI0_IRQn is for lines 0–0 (PA0, PB0, PC0 — all share the same EXTI line 0).
Lines 5–9 share EXTI9_5_IRQn; lines 10–15 share EXTI15_10_IRQn. Get the wrong
IRQn and you'll enable an interrupt that never fires.
If you're generating with CubeMX: in the NVIC configuration tab there's an explicit
checkbox for "EXTI line0 interrupt" (or whichever line your pin is on). It's easy to
miss because the GPIO tab doesn't surface it — you have to go to NVIC separately and
tick it. That generates the HAL_NVIC_SetPriority / HAL_NVIC_EnableIRQ call in
the init code for you.
Two other things to verify while you're in there:
-
EXTI0_IRQHandlermust exist and callHAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0). CubeMX puts this instm32f4xx_it.c. If it's missing, the CPU has nowhere to vector. If you've accidentally deleted or renamed it, the weak default in the startup file runs — which does nothing. -
HAL_GPIO_EXTI_Callbackis declared__weak. If there's a typo in your override — wrong return type, wrong argument type — the linker silently keeps the weak empty version. Double-check the signature:void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin).
The interrupts explainer covers
how the NVIC priority model works if the SetPriority arguments are new territory.
Once NVIC is enabled and the callback starts firing, one more thing to get right before you trust the behaviour: the EXTI pending bit.
HAL's HAL_GPIO_EXTI_IRQHandler() clears it for you via __HAL_GPIO_EXTI_CLEAR_IT(GPIO_Pin)
before calling the callback. As long as you're calling through to that function (which
CubeMX-generated EXTI0_IRQHandler does), you're fine.
If you ever go bare-metal and write the handler yourself without clearing the pending
bit in EXTI->PR, the interrupt immediately re-enters after it returns. The ISR fires
at full CPU speed in an infinite loop. Not dangerous, but the board effectively hangs
and it's genuinely confusing the first time you see it — looks like a lockup rather than
an interrupt storm.
Separately: keep the callback short. HAL_GPIO_TogglePin is fine. Anything involving
HAL_Delay, printf, or blocking I2C/SPI calls inside that callback will cause
problems — either missed interrupts, priority inversions, or outright lockups depending
on your NVIC priority setup. Set a volatile flag in the callback, handle the work in
the main loop. The bare-metal vs RTOS article
has a section on interrupt-to-task handoff patterns if the flag-and-poll approach starts
feeling limiting as the project grows.
The NVIC enable was it. Found the checkbox in CubeMX's NVIC tab — it was unchecked and
wasn't generating the HAL_NVIC_EnableIRQ call at all. Added it, regenerated, and the
callback fires immediately now.
Good call on the pending bit thing too — I had actually tried writing the handler myself first (before switching to CubeMX) and hit exactly that problem. Assumed the board was crashing. Took a while to figure out what was actually happening.
Related Discussions
Can't decide between FreeRTOS and bare-metal for a simple sensor node — what's the tipping point?
Working on a temperature and humidity monitoring node — STM32F103 target, BME280 over I2C, reports data every 60 seconds over UART to a Rasp
STM32F401 UART printing garbage after switching to 84 MHz PLL — same 115200 baud in CubeMX and PuTTY
Got a WeAct Black Pill (STM32F401CCU6) project that's been running happily on the default HSI clock at 16 MHz. Using USART1 on PA9/PA10 thro
STM32H743 HAL_UART_Receive_DMA fires error callback immediately — TEIF1 set, RxCplt never fires
Upgrading a project from STM32F4 to STM32H743. UART DMA receive worked on the F4 without any issues — standard CubeMX setup, call HAL_UART_R
STM32 USB not detected by Windows after jumping to bootloader mode
Working on a custom STM32F411 board, trying to jump into the built-in USB DFU bootloader from application code instead of holding BOOT0 on p