A bare‑metal firmware that counts button presses using a hardware interrupt instead of polling, and reports the running total over UART.
Every time the onboard button is pressed, the EXTI13 interrupt fires, the ISR toggles the LED and increments a counter, and the main loop prints the new value to the serial terminal.
This project marks the jump from polling (used in earlier projects) to event‑driven programming — the way real embedded systems respond to the outside world.
Earlier projects did this:
while (1) {
if (button_pressed()) do_something(); // CPU asks 1,000,000 times/sec
}
The CPU wastes almost all its time just checking.
This project does this:
while (1) {
// CPU is free to do other work (or sleep)
}
// Hardware calls the ISR the instant the button is pressed
The button press is detected by hardware, not by software looping. This is the foundation of responsive, low‑power firmware.
system_init()configures GPIO, USART2, the EXTI line for PC13, and enables the NVIC interrupt.- The main loop prints
Press count:once. - When the button is pressed (falling edge on PC13):
- The EXTI13 line triggers.
- The NVIC vectors to
EXTI15_10_IRQHandler(). - The ISR clears the pending flag, increments
button_press_count, and toggles the LED.
- Back in
main(), the loop notices the count changed and prints the new number over UART.
- STM32 NUCLEO‑F446RE
- USB cable
No external components required.
| Function | Pin | Role |
|---|---|---|
| LED | PA5 | Toggled inside the ISR |
| Button | PC13 | Mapped to EXTI line 13, falling edge |
| USART2_TX | PA2 | Prints the press count (AF7) |
| USART2_RX | PA3 | Configured (AF7), not used for input here |
- EXTI (External Interrupt) configuration:
EXTI_IMR,EXTI_FTSR,EXTI_PR - SYSCFG_EXTICR — mapping a GPIO port (Port C) onto an EXTI line (13)
- NVIC — enabling the
EXTI15_10IRQ viaNVIC_ISER1 - ISR (
EXTI15_10_IRQHandler) — the function the CPU jumps to on the event - Clearing the pending flag first inside the ISR (correct, safe practice)
volatilekeyword —button_press_countis shared between the ISR andmain(), so the compiler must never optimize away the reads- Event‑driven design —
main()only reacts to changes, not by constant polling of the pin - Falling‑edge trigger — the button is active LOW, so the press is the 1→0 transition
- UART number printing — a hand‑written
uart_send_number()for multi‑digit values - Register‑level RCC / GPIO / USART configuration (consistent with earlier projects)
Button press (PC13 falling edge)
|
v
EXTI line 13 --(EXTI_FTSR / EXTI_IMR)--> pending flag (EXTI_PR)
|
v
NVIC (NVIC_ISER1 bit 8) --> vectors to EXTI15_10_IRQHandler()
|
v
ISR: clear EXTI_PR -> button_press_count++ -> LED_TOGGLE()
|
v
main() loop sees count changed -> uart_send_number() over USART2_TX (PA2)
stm32-interrupt-button-counter/
│
├── README.md
├── .gitignore
├── docs/
│ └── wiring.md
└── src/
└── main.c
- Open STM32CubeIDE
- Create a project for NUCLEO‑F446RE
- Replace
Core/Src/main.cwith the code fromsrc/main.c - Build (expect 0 errors)
- Flash to the board
- Open your terminal at 9600 baud and press the blue button
You should see the count climb on the terminal, and the LED toggle with every press.
- EXTI line → port mapping:
SYSCFG_EXTICR4bits[7:4] = 0x2selects Port C for line 13. (SYSCFG clock on APB2 must be enabled — it is.) - IRQ vector name:
EXTI15_10_IRQHandleris the shared vector for lines 10–15, so the ISR checksEXTI_PR & (1 << 13)to confirm it was line 13. - Flag clearing order: the pending bit is cleared before the action, which avoids missing a press that arrives during the ISR.
volatile: without it, the compiler could assumebutton_press_countnever changes insidemain()(sincemain()never writes it) and print0forever.volatileforces a fresh read each loop.- Baud rate: 9600 (
USART2_BRR = 0x683at 16 MHz APB1).
- Add software debouncing inside or around the ISR (a press can bounce and fire the interrupt several times)
- Use a hardware timer to gate re‑triggering for a few ms after each press
- Move UART printing fully out of the critical path with a small ring buffer
- Add a second button on a different EXTI line to demonstrate multiple sources on the shared vector
- Put the CPU into a low‑power sleep mode in
main()and let the interrupt wake it (real low‑power design)