GPIO, Timers & Interrupts

The first peripherals every embedded engineer masters. Learn GPIO modes (push-pull, open-drain, pull-ups, alternate function), drive strength and input thresholds, button debouncing, external interrupts (EXTI) with edge selection, timers for delays, periodic ticks, input capture (frequency/pulse measurement) and output compare.

Blinking an LED, reading a button and measuring a sensor's pulse are the 'hello world' of embedded systems — but doing them robustly teaches electrical limits, interrupts and timer hardware used in every serious design.

An analogy

A GPIO pin is a door that can be set to open outward (output) or inward (input); timers are the building's clocks and stopwatches that time everything precisely.

GPIO modes

Output push-pull (drives high and low), open-drain (only pulls low — for I²C and wired-OR, needs pull-up), input floating/pull-up/pull-down, analog, and alternate function (peripheral controls the pin). Respect per-pin current limits (≈ 8–25 mA).

Buttons and EXTI

Mechanical contacts bounce for 1–10 ms. Debounce with RC + Schmitt input, or in software (time filter or state sampling). External interrupt lines trigger on rising, falling or both edges.

Timer functions

Time base (periodic interrupts), input capture (measure frequency, pulse width, encoder speed), output compare (precise edges), PWM, one-pulse mode, encoder interface mode for quadrature encoders.

Robustness

ESD and over-voltage protection on external pins, 5 V-tolerant pins, slew-rate settings to reduce EMI, glitch filters on inputs, and handling timer overflow when measuring long periods.

Formulas

Worked example: Frequency by input capture

Given: Timer at 1 MHz; Captured difference 2500 counts

Find: Frequency.

Answer: 400 Hz (resolution ±1 count = ±0.04 %)

Worked example: Pull-up current

Given: 3.3 V, 40 kΩ internal pull-up, button pressed

Find: Current.

Answer: 82.5 µA while pressed

Button interrupt with debounce + input capture (STM32 HAL)

#include "main.h"

volatile uint32_t last_press_ms = 0;
volatile uint32_t period_counts = 0, last_capture = 0;

void HAL_GPIO_EXTI_Callback(uint16_t pin) {          /* falling edge on button */
    uint32_t now = HAL_GetTick();
    if (pin == BUTTON_Pin && (now - last_press_ms) > 30) {   /* 30 ms debounce */
        last_press_ms = now;
        HAL_GPIO_TogglePin(LED_GPIO_Port, LED_Pin);
    }
}

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {   /* timer at 1 MHz */
    uint32_t cap = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    period_counts = (cap - last_capture) & 0xFFFF;            /* handles wrap-around */
    last_capture = cap;
}

float signal_hz(void) { return period_counts ? 1e6f / period_counts : 0.0f; }

EXTI gives an interrupt on the button edge; time-based debounce ignores bounces within 30 ms. Input capture latches the timer count on each signal edge, so the difference gives the period with hardware precision.

Common mistakes

Applications

Key takeaways

Going further