Embedded Systems · Microcontrollers
GPIO Configuration Registers and the Reset States That Drive Pins
How a general-purpose pin is routed, why the reset state of every pin matters at power-on, and which configuration mistakes produce a pin that looks correct and is not.
A pin on a microcontroller package is a pad connected to a chain of analogue and digital blocks: an input buffer, an output driver, a pull resistor, possibly a Schmitt trigger, and a multiplexer that selects which peripheral’s signal reaches the pad. The general-purpose I/O block’s registers configure that chain, and the configuration is per-pin rather than per-port in everything except the data register.
The reason to care about the details is that the reset state of the chain is not neutral. Every pin has a defined state before any firmware runs, and that state determines what the connected circuit sees during the interval between power-on and the first configuration write.
What the chain contains
The path from pad to peripheral is a series of switches, and each register field controls one of them.
The mode field selects among input, output, alternate function, and analogue. Input mode disables the output driver entirely and connects the pad to the input buffer. Output mode enables the driver and drives whatever the output data register holds. Alternate-function mode enables the driver and connects the pad to a peripheral’s signal instead of to the data register. Analogue mode disconnects both buffers, which is required for an ADC channel because a digital input buffer presents a load and a leakage path that would corrupt a high-impedance analogue measurement.
The output type field selects push-pull or open-drain. Push-pull drives the pad both high and low. Open-drain drives only low and leaves the high state to an external pull-up, which is what a shared bus such as I2C requires: two open-drain drivers on one line can each pull low without either damaging the other, and the wired-AND result is the bus’s logic level.
The pull field connects an internal resistor of roughly 30 to 50 kilohms to the pad, up or down or neither. The resistor is weak enough that an external signal overrides it, which is the point: it defines the level of an undriven input rather than driving a load.
The speed or slew field controls the output driver’s edge rate. A faster setting produces sharper edges and more radiated energy; a slower setting reduces ringing on a long trace at the cost of a longer propagation delay. The field is a compromise between signal integrity and timing, and the highest setting is not automatically the right one.
The alternate function field selects which of the peripheral signals reaches the pad. On parts with more than eight alternate functions per pin, the selection is split across two registers, and the number of the function is an index into the part’s pinout table rather than a fixed mapping.
The reset state and why it is a design input
After reset, most pins come up as inputs with the output driver disabled and the pull resistor disconnected. That state is chosen because an input with no pull is electrically inert: it neither drives the connected circuit nor draws current through it. A pin that came up as a push-pull output driving high would fight any external device holding the line low, and the resulting current could damage either side.
Inert is not the same as defined. A pin configured as an input with no pull reads an indeterminate level when nothing drives it, and the input buffer’s output oscillates if the pad voltage sits near the threshold. The oscillation consumes current in the input buffer and, worse, propagates into the peripheral if the pin is later selected as an alternate function. A pin that is not connected to anything should have its pull enabled or its mode set to analogue, so the buffer is not left floating.
The interval between power-on and the first configuration write is the vulnerable window. Firmware that takes milliseconds to reach its GPIO initialisation leaves every pin in its reset state for that whole time, and the external circuit sees it. A motor driver whose enable pin floats during that window may conduct; a relay driver may chatter. The usual fix is an external pull-down on every signal that must be low at power-on, sized so the internal pull cannot override it, and firmware that configures those pins early in the boot sequence.
The second vulnerable window is a reset caused by a watchdog or a brown-out while the system is running. The pins return to their reset state, which may be the opposite of the state the application had them in, and any output that was driving a load changes state. A system that must hold an output through a reset needs the external circuit to hold it, not the firmware.
Reading a pin whose mode is output
The input buffer remains connected in output mode on most parts, so the input data register reports the pad’s actual voltage rather than the value that was written. When the pin drives a load that pulls the line to an unexpected level, the input register shows the contention; the output data register shows the intent. The difference between the two is a short circuit, and it is one of the few ways to detect an output conflict without a current measurement.
The read also captures the pin’s state at the moment of the read, not continuously. A pulse shorter than the time between two reads is invisible, and a peripheral’s alternate function is the correct way to capture it. This is why input capture exists: the timer samples the pin in hardware at the timer’s clock rate, independent of what the core is doing.
There is a third state worth naming: a pin in alternate-function mode whose peripheral is not enabled. The pad is driven by the peripheral’s output, which for a disabled peripheral is typically its reset value, and the input path still reaches the peripheral’s input. The pin looks configured and behaves as a constant. The clock-enable ordering described in the peripheral bring-up article applies here as much as anywhere: the peripheral’s clock must be on before its alternate function means anything.
The read-modify-write hazard and the registers that avoid it
The output data register is a plain read-write register, and writing one bit requires reading the whole port, modifying the bit, and writing the port back. That sequence has two failure modes.
The first is that another context modifies a different bit between the read and the write. If an interrupt handler sets a pin while the main loop is doing a read-modify-write on the same port, the handler’s change is lost when the main loop writes back its stale copy. The window is a few cycles, and the corruption is rare enough to be hard to reproduce.
The second is that the read returns the input state rather than the output state on some parts, so a pin driven low by an external load reads as zero and the write-back clears the bit that was meant to stay set. The result is a pin that turns off when the code meant to turn a different pin on.
The fix is the bit-set and bit-reset registers, which exist precisely to avoid the read. Writing a one to a bit in the set register drives that pin high; writing a one to the reset register drives it low; writing a zero to either leaves the pin unchanged. Because the operation is a write-only mask, there is no read to be stale, and the sequence is atomic with respect to every other context. A single write that sets both the set bit and the reset bit for the same pin is resolved by a priority rule that the part’s documentation defines, so it should not be relied on as a way to produce a defined level. The older alternative, an alias address region that maps each bit to a word, is described in the bit-banding article.
The set and reset registers also change what a toggle costs. Toggling through the output register requires a read of that register, an exclusive-or, and a write back, with the read-modify-write hazard that implies; toggling through the set and reset registers still requires knowing the current state, which the input register provides, but the write that follows is a single store to whichever of the two registers applies and cannot disturb any other pin. A store to the reset register followed by a store to the set register produces a low pulse whose width is the time between the two stores — a technique that works but whose width depends on the core’s execution, which makes it unsuitable for anything with a timing requirement.
Alternate functions, remapping and the pins that are shared
The number of peripheral signals exceeds the number of pads, so most alternate functions are available on more than one pin and most pins carry more than one function. The pinout table in the data sheet is the authority, and it lists for each pin the functions it can carry and the alternate function number that selects each.
Two consequences follow. First, a peripheral’s pins are not necessarily contiguous or on one port, so a design that assumes a peripheral’s signals are all on port A will fail on a part where one of them is on port B. Second, a function that is available on two pins is usually usable on only one at a time, because the peripheral has one output and the pad selection is a multiplexer, not a fan-out.
Some parts add a remap register that moves a peripheral’s signals to a different set of pins as a group. The remap is a debug and layout convenience, and it interacts badly with the per-pin alternate-function field when both are used: the remap decides which pins the peripheral drives, and the per-pin field decides which function each pin carries, and a pin must satisfy both. The failure is a peripheral whose pins are correctly configured per-pin and still produce no signal, because the remap selected a different set.
Debug pins deserve a mention because they are the ones that break last. The two-wire debug port occupies pins that are otherwise general-purpose, and the part’s reset state usually assigns them to the debug function so that a debugger can attach to a running target. Firmware that reconfigures those pins to a peripheral function loses the ability to attach after the reconfiguration, and recovering requires a reset with the debugger holding the target in reset. Leaving the debug pins alone unless the design needs them is worth the two pads.
Where the electrical model decides the configuration
The register fields are digital, but the choices they make are analogue. A push-pull output driving a capacitive load produces a current spike at each edge proportional to the capacitance and inversely proportional to the edge time, and the supply’s transient response determines whether that spike disturbs neighbouring circuitry. A slow slew setting reduces the spike and is often the right choice for a signal that is not fast, which is most signals on a microcontroller.
An input with a pull-up connected to a switch to ground draws current continuously while the switch is closed. The resistor is tens of kilohms, so the current is tens to hundreds of microamps, which is negligible for a mains-powered design and significant for one running from a coin cell for years. The alternative is to leave the pin as an input with no pull and enable the pull only while scanning the switch, at the cost of the scan timing depending on the pull’s settling.
A pin configured as an output that is driven against an external source dissipates power in the driver’s output transistors. The driver has a finite on-resistance, typically tens of ohms, so a pin driven high into a line held low sinks a current limited by that resistance and the external impedance. The part’s absolute maximum ratings state the per-pin and total current limits, and a design that stays inside them still has to justify the heat: a few tens of milliamps per pin across many pins is a real thermal load in a small package.
References
- STM32F4 Series Reference Manual (RM0090) — STMicroelectronics
- Cortex-M3 Devices Generic User Guide (ARM DUI 0552) — Arm Limited
- The Art of Electronics — Horowitz & Hill, Cambridge University Press