GPIO Modes & Configuration
Embedded → Digital I/O & Timing (HAL)
Every pin starts as a blank slate
The mode you pick decides which internal circuit a pin connects to. On reset, every STM32 pin defaults to floating input — nothing drives it, nothing reads it usefully until configured.
The four pin modes
| Mode | What it does |
|---|---|
| Input | voltage level read into a status register; pin drives nothing |
| Output | code drives the pin HIGH/LOW via a data register |
| Alternate function | pin handed to a peripheral (UART TX, PWM channel, I2C SCL) — one owner at a time |
| Analog | digital buffers disconnected so an ADC/DAC can use the pin without loading it |
Push-pull vs open-drain — the output stage detail that trips people up
Push-pull is the default — both a pull-up and pull-down transistor, so writing 1/0 actively drives the pin to VDD/GND. Open-drainremoves the pull-up transistor — writing 0 pulls LOW, writing 1 just releases the pin to float (reads HIGH only via an external/internal pull-up). This lets multiple devices share one wire without fighting: I2C's SDA/SCL are open-drain so any device can pull LOW, and the shared pull-up brings it back HIGH once everyone releases. Two push-pull outputs driving opposite levels would short VDD to GND; two open-drain outputs never can.
Pull-up / pull-down resistors, and why floating inputs are dangerous
An undriven input floats, picking up noise and reading random glitches — a button wired with no resistor is exactly this trap. The fix: a weak resistor (tens of kΩ) tying the pin to a known rail. A pull-up ties to VDD (reads HIGH by default, button press reads clean LOW); pull-downis the mirror. Modern MCUs build a selectable weak pull-up/pull-down into every pin's config register, so an external resistor is rarely needed.
One pin, one register field, four settings
On an STM32: MODER (input/output/AF/analog), OTYPER (push-pull/open-drain), and PUPDR(none/pull-up/pull-down). Configuring PA5 as a push-pull output with no pull resistor — the classic "blink an LED" setup:
#define GPIOA_MODER (*(volatile uint32_t *)0x40020000) #define GPIOA_OTYPER (*(volatile uint32_t *)0x40020004) #define GPIOA_PUPDR (*(volatile uint32_t *)0x4002000C) GPIOA_MODER &= ~(0x3u << (5 * 2)); // clear PA5's mode field GPIOA_MODER |= (0x1u << (5 * 2)); // 01 = general-purpose output GPIOA_OTYPER &= ~(0x1u << 5); // 0 = push-pull (this is also the reset value) GPIOA_PUPDR &= ~(0x3u << (5 * 2)); // 00 = no pull-up/pull-down
Try it — pick a mode, see the register fields
Select a mode below and watch which MODER/OTYPER/PUPDR field values it maps to:
Practice
Completion saves automatically after you read through to the end.
Next lesson →