External interrupt: react to the button
Instead of checking the button in the loop, we let the hardware tell the CPU when the button changes. PC13 becomes an external interrupt (EXTI line 13), a short function switches the LED, and the main loop stays empty.
In this lesson · 5 steps
Why the loop is not enough
In lesson 1.2 the loop read the button on every pass, even when nothing changed. In a real program the loop has other work too, so a slow loop can react late or miss a press. It is better when the hardware tells the CPU that something happened: that is an interrupt.
How an interrupt works
The CPU runs main(). When an event happens (a button press, a timer, new serial data), the hardware raises an interrupt. The CPU pauses main(), runs a short function called an Interrupt Service Routine (ISR), then goes back to exactly where it stopped.
The NVIC (Nested Vectored Interrupt Controller) is the part of the core that decides which interrupts are on and sends each one to its ISR. Before the ISR runs, the CPU saves where it was in main(), so it can go back.
Who calls your code
You don't write the ISR yourself. CubeMX generates it: for our button it is EXTI15_10_IRQHandler, in stm32f4xx_it.c. It calls a HAL function, and HAL then calls your function, HAL_GPIO_EXTI_Callback. So the chain is: button → EXTI → NVIC → ISR → HAL → your callback.
EXTI lines
External interrupts go through the EXTI peripheral. It has 16 lines, EXTI0 to EXTI15. Each line takes the pins with the same number: PA13, PB13 and PC13 all share EXTI line 13, and only one of them can use it at a time. Our button is on PC13, so we use line 13.
Rising and falling edges
An interrupt can fire on a rising edge (low to high), a falling edge (high to low), or both. With the pull-up, pressing the button gives a falling edge and letting go gives a rising edge. We pick both, so we hear about the press and the release.
1PC13 as an interrupt pin
- We stay in
STM32_GPIO_PROGRAMMING. Double-click its.iocfile. Click PC13 →GPIO_EXTI13 - Video note: in the video the project is first copied as
STM32_EXTI_PROGRAMMING. Staying in one project works the same. - System Core → GPIO → PC13: GPIO mode = External Interrupt Mode with Rising/Falling edge trigger detection, Pull-up stays on, label stays
BUTTON
2Turn the interrupt on in the NVIC
- System Core → NVIC: tick EXTI line[15:10] interrupts
- Generate Code, then Refresh (F5) the project in CubeIDE
3The callback
HAL calls this function by itself when an EXTI line fires. GPIO_Pin tells you which pin it was.Grey lines are already in the file: add only the coloured ones. Copy takes just those.
/* USER CODE BEGIN 0 */ void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { if (GPIO_Pin == BUTTON_Pin) { if (HAL_GPIO_ReadPin(BUTTON_GPIO_Port, BUTTON_Pin) == GPIO_PIN_RESET) { /* button pressed: with the pull-up, the pin reads RESET when pressed */ HAL_GPIO_WritePin(LED_CONTROL_GPIO_Port, LED_CONTROL_Pin, GPIO_PIN_SET); } else { HAL_GPIO_WritePin(LED_CONTROL_GPIO_Port, LED_CONTROL_Pin, GPIO_PIN_RESET); } } } /* USER CODE END 0 */
HAL_GPIO_EXTI_Callback, marked __weak. Weak means: if you write a function with the same name, yours replaces the placeholder. One callback serves every EXTI pin, which is why we check GPIO_Pin.4Empty the loop
- Delete the button code from lesson 1.2 inside
while (1). The loop is now empty
5Build and run
- Terminate the last session if needed, then Debug and Resume
Check & troubleshoot
What you should see
- Hold the button: the LED is on. Let go: the LED is off
- The while loop is empty, and the LED still follows the button
If it doesn't work
- Nothing happens: tick EXTI line[15:10] interrupts in the NVIC and generate the code again
- Callback never runs: the name must be exactly
HAL_GPIO_EXTI_Callback, with a capital C - LED only turns on, never off: the trigger is set to falling edge only; pick Rising/Falling
Good to know
- A button contact bounces for a few milliseconds when you press it, so the callback can run several times per press. Here that does no harm: the LED simply follows the pin.