STM32 Programming· Live

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
  1. PC13 as an interrupt pin
  2. Turn the interrupt on in the NVIC
  3. The callback
  4. Empty the loop
  5. Build and run

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.

Polling checks all the time; an interrupt runs only when the event happens

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.

Interrupt sources reach the CPU through the NVIC; the CPU returns to main() after the ISR

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.

Each EXTI line takes the pins with the same number; PC13 goes to EXTI13

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.

Rising edge: low to high. Falling edge: high to low

1PC13 as an interrupt pin

  1. We stay in STM32_GPIO_PROGRAMMING. Double-click its .ioc file. Click PC13 → GPIO_EXTI13
  2. Video note: in the video the project is first copied as STM32_EXTI_PROGRAMMING. Staying in one project works the same.
  3. 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

  1. System Core → NVIC: tick EXTI line[15:10] interrupts
  2. Generate Code, then Refresh (F5) the project in CubeIDE
Why?
Lines 10 to 15 share one interrupt in the NVIC, and line 13 is one of them. If it is not ticked, the signal never reaches the CPU.

3The callback

main.c · USER CODE BEGIN 0

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 */
Why?
HAL already contains an empty placeholder 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

  1. Delete the button code from lesson 1.2 inside while (1). The loop is now empty
Why?
The CPU no longer checks the button. The hardware calls our function only when the button changes.

5Build and run

  1. 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.