GPIO: every setting on a single pin
Input and output, push-pull and open-drain, pull-ups, speed and alternate functions. What each STM32 GPIO register corresponds to in the circuit.
Configuration registers (STM32F4)
| Register | Setting |
|---|---|
| MODER | Input / output / alternate function (AF) / analog |
| OTYPER | Push-pull / open-drain |
| OSPEEDR | Output edge speed |
| PUPDR | Pull-up / pull-down / none |
| IDR | Read the input value |
| ODR | Output value |
| BSRR | Set / clear bits atomically |
| AFR | Alternate function number (UART, SPI, timer …) |
How to choose the settings
- Button input: input + internal pull-up, button to ground
- LED, MOSFET gate: push-pull output
- I2C: alternate function + open-drain (+ external pull-ups)
- ADC input: analog mode (disconnects the digital input stage, reducing leakage and noise)
- Unused pins: analog mode or output LOW
Output speed
Raising OSPEEDR sharpens the edges, which helps fast communication but increases noise and ringing. For LEDs and relays, leave it at the lowest setting.
Atomic writes
ODR |= x is a read-modify-write, so it can race with an interrupt. Writing to BSRR changes only the chosen bits in a single write, which is safe.
The settings on one pin
| Setting | Choices | When |
|---|---|---|
| Mode | Input, output, alternate function, analog | The pin's role |
| Output type | Push-pull, open-drain | Open-drain when several devices share one line |
| Pull-up / pull-down | None, pull-up, pull-down | Inputs that could float |
| Speed | Low to very high | Only as much as needed. Faster means more noise |
Turning on an LED with registers (STM32 example)
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN; // 1) turn on the port clock first
GPIOA->MODER &= ~(0x3u << (5 * 2)); // 2) clear the PA5 mode field
GPIOA->MODER |= (0x1u << (5 * 2)); // 01 = output
GPIOA->BSRR = (1u << 5); // 3) on (set)
GPIOA->BSRR = (1u << (5 + 16)); // off (reset)
The most common mistake is leaving out step 1. A peripheral whose clock is off ignores writes to its registers. If the code looks fine but the pin does not move at all, check the clock first.
The reason to use BSRR instead of ODR |= ... is that there is no read-modify-write sequence, so an interrupt touching another pin on the same port cannot overwrite your change, or you its.
Push-pull and open-drain
- Push-pull: drives both HIGH and LOW directly. Used for most outputs, such as LEDs and chip-select signals.
- Open-drain: only pulls LOW and leaves HIGH to an external pull-up. Used for buses where several devices share a line, such as I2C, and for connections between different voltages.
If you tie two push-pull outputs to one line and one drives HIGH while the other drives LOW, a large current flows from supply to ground and damages the pins.
Inputs must not float
An input pin with nothing connected wanders between 0 and 1 with the surrounding noise. For an input that is connected in only one state, such as a button, turn on the internal pull-up or pull-down. Leaving unused pins in analog mode or as output LOW reduces current consumption.
Reading a button with debounce
bool button_pressed(void) {
static uint8_t hist = 0xFF;
hist = (uint8_t)((hist << 1) | ((GPIOC->IDR >> 13) & 1u)); // call every 1 ms
return hist == 0x00; // LOW 8 times in a row
}
The value read every 1 ms is shifted into 8 bits, and it only counts when the same value was seen 8 times in a row. The few milliseconds of contact bounce are filtered out naturally.
Pin current limits
There is a limit on the current one pin can supply, and separate limits on the total for a port and for the whole chip. When driving several LEDs directly, check the total. Loads such as relays and motors must always go through a transistor.