Electronics Design AU
Embedded SystemsSTM32Solved

STM32 GPIO interrupt configured but ISR never fires — what am I missing?

3 min read3 replies
Original Question

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?

From the knowledge baseWhat Are Interrupts in Embedded Systems and How Do They Work?

3 Replies

soggy_waffle42
Accepted Answer

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:

  1. EXTI0_IRQHandler must exist and call HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0). CubeMX puts this in stm32f4xx_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.

  2. HAL_GPIO_EXTI_Callback is 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.

isr_overload

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.

stale_biscuit_03

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