Concept Q&A
10 questions
Popular

GPIO — Interview Questions & Answers

Common GPIO interview questions covering output modes, pull resistors, interrupts, debouncing, atomic operations, and alternate functions.

Study the fundamentals first

Read the GPIO topic page for in-depth concepts before practicing Q&A

Output Modes

QWhat is the difference between push-pull and open-drain GPIO output modes?

In push-pull mode, the GPIO pin actively drives both high and low using a complementary pair of transistors — a PMOS transistor pulls the output to VDD when the output register bit is 1, and an NMOS transistor pulls it to GND when the bit is 0. This gives fast, strong transitions in both directions with well-defined voltage levels. Push-pull is the default output mode for most applications: driving LEDs, chip-select lines, reset pins, and general-purpose control signals. The pin always has a defined state, so no external components are needed.

In open-drain mode, only the NMOS transistor is present. The pin can actively pull the line low, but when the output is "high," the transistor simply turns off, leaving the pin floating (high-impedance). An external pull-up resistor is required to bring the line to a defined high level. This mode is essential for shared buses like I2C, where multiple devices connect to the same SDA/SCL lines. If two push-pull outputs drove the same wire — one high and one low — they would create a bus contention short circuit from VDD to GND through both active transistors, potentially damaging the drivers or causing excessive current draw. Open-drain eliminates this: any device can pull the line low, and the pull-up passively holds it high when no device is pulling down. This also enables wired-AND logic — the line is high only when all devices release it.

A secondary advantage of open-drain is voltage-level translation. The pull-up resistor can be tied to a different voltage rail than the MCU supply. For example, a 3.3V MCU driving an open-drain output with a 5V pull-up can interface directly with 5V logic, provided the MCU's GPIO is 5V-tolerant on that pin (check the datasheet's "FT" marking on STM32).

QWhy are pull-up and pull-down resistors needed, and how do you choose their values?

Pull resistors ensure a GPIO input rests at a known logic level when nothing is actively driving it. Without one, a floating input picks up electromagnetic noise and oscillates unpredictably between high and low. This causes three problems: (1) the CMOS input buffer's PMOS and NMOS transistors both partially conduct when the input voltage sits near the switching threshold, creating a shoot-through current path from VDD to GND that wastes power — potentially several microamps per pin; (2) the pin reads random values, producing unpredictable software behavior; (3) if the pin is configured as an interrupt source, noise coupling generates spurious interrupts that flood the CPU.

A pull-up resistor ties the line to VDD (idle-high), used for active-low buttons and open-drain buses. A pull-down ties it to GND (idle-low), used for active-high signals. Most STM32 MCUs include internal pull resistors of approximately 30-50 kohm. These are convenient for prototyping but too weak for noisy environments or fast edges — the high resistance means slow RC time constants and poor noise rejection. External pull resistors of 1k-10k ohm are used when stronger pull strength is needed.

Choosing the value is a trade-off between noise immunity and power consumption. Lower resistance gives stronger pull strength and faster edge transitions but increases static current when the line is actively driven to the opposite state (I = V / R). For a button pulled up to 3.3V with a 4.7 kohm resistor, pressing the button sinks 0.7 mA — acceptable for battery applications. For I2C, the pull-up value must satisfy rise-time requirements: 4.7 kohm works at 100 kHz standard mode, but 400 kHz fast mode often needs 2.2 kohm because the bus capacitance and the higher clock rate demand faster edges. Always calculate t_rise = 0.7 * R * C_bus and verify it meets the spec.

Input Configuration

QWhat problems can a floating (unconnected) GPIO input cause, and how do you prevent them?

A floating input has no defined voltage — it hovers near the switching threshold of the CMOS input buffer, typically around VDD/2. The consequences are more severe than many engineers realize. The PMOS and NMOS transistors in the Schmitt-trigger input buffer both partially conduct, creating a direct current path from VDD to GND. This shoot-through current can reach several microamps per pin. Across 20-30 unused pins on a microcontroller, the cumulative leakage can add hundreds of microamps to the system's quiescent current — a significant penalty in battery-powered designs where the sleep current budget might be only a few microamps total.

Beyond power waste, a floating pin reads as random values. If any software path reads that pin, the behavior is unpredictable. If the pin is configured as an EXTI interrupt source, noise coupling generates spurious interrupts that waste CPU cycles and can cause subtle timing bugs in the application. In extreme cases, a floating pin connected to a peripheral mux (like an alternate function input) can inject garbage data into a communication peripheral.

Prevention strategies, in order of preference: (1) configure unused pins as analog mode — this disconnects the digital input buffer entirely, eliminating shoot-through current. STM32 reference manuals explicitly recommend this for unused pins; (2) configure as output driven low (but be careful about inadvertent connections on the PCB); (3) enable internal pull-up or pull-down resistors to clamp the pin to a defined level. Analog mode is the lowest-power option and should be the default in your board initialization code for every pin not explicitly used by the application.

QWhy is analog mode recommended for unused GPIO pins?

When a pin is configured in any digital mode (input, output, or alternate function), the input Schmitt-trigger buffer is active. The Schmitt trigger consumes power even when the pin is stable, and consumes significantly more power when the input voltage floats near the switching threshold. In analog mode, the digital input buffer is completely disconnected from the pin — there is no Schmitt trigger, no shoot-through current, and no digital readback path. The pin is electrically isolated from the digital domain.

This makes analog mode the absolute lowest-power configuration for any GPIO pin. On STM32, the power difference is measurable: a single floating digital input can draw 1-5 uA of shoot-through current, while an analog-mode pin draws essentially zero. In a system with 50+ unused pins, the aggregate savings can be the difference between meeting a 10 uA sleep-current specification and failing it by an order of magnitude. This is why STM32CubeMX defaults unused pins to analog mode in its code generator, and why the reference manual dedicates a note to it in every GPIO chapter.

A secondary benefit is safety: analog-mode pins cannot generate spurious digital interrupts, cannot be accidentally read by software as valid digital data, and cannot inject noise into peripheral muxes. It is the "do nothing and cause no harm" configuration.

QWhat are GPIO alternate functions, and how are they configured?

Each GPIO pin on an STM32 can serve multiple roles beyond simple digital I/O. Alternate functions map internal peripheral signals — UART TX/RX, SPI MOSI/MISO/SCK, I2C SDA/SCL, timer channels, DAC outputs — to physical pins. Each pin typically supports up to 16 alternate function mappings (AF0 through AF15), defined in the datasheet's alternate function mapping table. The purpose is silicon flexibility: a single MCU package can support many different board layouts by allowing peripheral signals to be routed to different pins.

To configure a pin as a peripheral signal, you set three things: (1) the GPIO mode to alternate function (MODER = 0b10), (2) the correct AF number via the AFR registers (AFRL for pins 0-7, AFRH for pins 8-15), and (3) the appropriate output type, speed, and pull configuration. For example, I2C SDA requires open-drain with a pull-up; SPI SCK requires push-pull at high speed; UART TX requires push-pull at medium speed. Getting any of these wrong produces subtle bugs — a push-pull I2C SDA will appear to work with a single device but cause bus contention with two devices.

A common source of "peripheral not working" bugs is selecting the wrong AF number. The mapping is not intuitive and varies between STM32 families and even between pins on the same MCU. PA9 might be USART1_TX on AF7, but PA2 might be USART2_TX on AF7 on one chip and AF1 on another. Always verify against the datasheet's alternate function table, not from memory. STM32CubeMX prevents this class of errors by showing valid AF mappings graphically and generating the correct initialization code.

Interrupts and Debouncing

QHow do GPIO interrupts work on STM32, and what are the limitations of the EXTI system?

On STM32, external interrupts are managed by the EXTI (External Interrupt/Event Controller). Each of the 16 EXTI lines (EXTI0 through EXTI15) corresponds to a pin number — EXTI0 handles pin 0 from any port, EXTI1 handles pin 1, and so on. A SYSCFG multiplexer selects which GPIO port (A, B, C, etc.) is connected to each EXTI line. Each line can be configured to trigger on a rising edge, falling edge, or both. When the configured edge is detected, a pending flag is set and, if the corresponding interrupt mask bit is enabled, an interrupt request is generated.

The critical limitation is that only one port can be mapped to each EXTI line number at a time. PA0 and PB0 both map to EXTI0, so you cannot use both as interrupt sources simultaneously. This constraint applies across all ports — if you select PA0 for EXTI0, then PB0, PC0, PD0 are all excluded. This means a maximum of 16 external interrupts total, one per pin number. Hardware designers must account for this during schematic design by placing critical interrupt signals on pin numbers that do not conflict.

A second limitation is interrupt vector sharing. On most STM32 families, EXTI lines 5 through 9 share a single NVIC vector (EXTI9_5_IRQHandler), and lines 10 through 15 share another (EXTI15_10_IRQHandler). Inside these shared ISRs, firmware must check the EXTI pending register (PR) to determine which specific line triggered, then clear the correct pending bit. Lines 0 through 4 each have dedicated vectors. This sharing adds a small overhead but is rarely a practical problem — just remember to check and clear the flags correctly, or you will miss interrupts or create infinite ISR re-entry.

QWhen would you use GPIO polling vs. external interrupts? What are the trade-offs?

Polling means the CPU periodically reads the pin state in a loop or timed task. It is simple to implement, deterministic in timing, and appropriate when the main loop already runs at a frequency that exceeds the event rate — for example, checking a button state every 10 ms in a super-loop. Polling also naturally supports debouncing, since the periodic sampling acts as a time filter. The downsides are wasted CPU cycles checking a pin that rarely changes, inability to wake the MCU from sleep modes, and the risk of missing very short pulses that occur between polling intervals.

External interrupts (EXTI) trigger immediately on a pin edge, so the CPU can sleep or perform useful work between events. This is essential for low-power designs where the MCU must wake from Stop or Standby mode on a pin change, and for capturing fast or infrequent signals like encoder pulses or zero-crossing detections. However, interrupts add complexity: the ISR must be kept short, shared state between ISR and main context must be managed carefully (volatile variables, critical sections), and mechanical switches generate dozens of bounce edges that can flood the interrupt system.

The best practice for mechanical inputs is a hybrid approach: configure an EXTI interrupt to detect the first edge (this allows the MCU to sleep between presses), then in the ISR, disable the EXTI interrupt and start a software timer for 20-50 ms. When the timer expires, read the pin state to get the stable value, process the event, and re-enable the EXTI interrupt. This gives you both low-power wake-up capability and clean debouncing without the complexity of handling every bounce edge.

QHow do you debounce a mechanical switch in firmware? Describe at least two approaches.

Mechanical switches bounce for 1-20 ms after actuation, producing multiple rapid transitions as the metal contacts physically settle. Without debouncing, a single button press can register as dozens of events — a menu advances 15 steps, a counter increments 20 times, or a state machine enters an undefined state.

Timer-based debounce (recommended): On the first detected edge (via polling or interrupt), record the event and start a one-shot software timer for 20-50 ms. During this debounce window, ignore all subsequent edges on that pin. When the timer expires, read the pin's current state — this stable reading is the true state. If using interrupts, disable the EXTI for that pin during the debounce period to prevent ISR re-entry, and re-enable it when the timer fires. This approach is non-blocking, robust, and works well in both bare-metal and RTOS environments.

c
// Timer-based debounce in EXTI ISR + software timer callback
void EXTI0_IRQHandler(void) {
EXTI->PR = EXTI_PR_PR0; // Clear pending
NVIC_DisableIRQ(EXTI0_IRQn); // Ignore bounces
start_debounce_timer(20); // 20 ms one-shot
}
void debounce_timer_callback(void) {
bool stable = (GPIOA->IDR & 1); // Read settled state
process_button(stable);
NVIC_EnableIRQ(EXTI0_IRQn); // Re-arm interrupt
}

Counter-based (integrating) debounce: In a periodic task running every 5-10 ms, read the pin state. Increment a counter when the reading matches the proposed new state; reset it when the reading differs. Accept the new state only after N consecutive identical readings (e.g., 3-4 samples = 15-40 ms of stability). This approach fits naturally into RTOS tick handlers or super-loop architectures and requires no additional timers. The trade-off is slightly higher latency and the requirement for a periodic polling context.

Atomic Operations

QWhy should you use the BSRR register instead of directly writing to ODR for atomic pin manipulation?

The Output Data Register (ODR) represents the current output state of all 16 pins in a GPIO port as a 16-bit value. To change a single pin via ODR, firmware must perform a read-modify-write (RMW) sequence: read the entire register, modify the target bit using AND/OR masking, and write the result back. This three-step operation is not atomic — if an interrupt fires between the read and the write, and the ISR modifies a different pin in the same port through ODR, the ISR's change is silently overwritten when the interrupted code completes its write. This is a classic race condition that produces intermittent, hard-to-reproduce bugs.

The Bit Set/Reset Register (BSRR) eliminates this problem entirely. It is a 32-bit write-only register: the lower 16 bits set individual pins (writing 1 to bit N drives pin N high), and the upper 16 bits reset individual pins (writing 1 to bit N+16 drives pin N low). Writing 0 to any bit has no effect. Because a single write to BSRR atomically modifies only the specified pins without reading or affecting any other pins, no read-modify-write is needed, and there is no window for a race condition. The operation completes in a single bus cycle.

This makes BSRR the correct choice whenever GPIO pins are manipulated from multiple execution contexts — main loop, ISRs, or RTOS tasks. Even if you believe only one context touches a port, using BSRR is a defensive habit that costs nothing and prevents future bugs when code evolves. The HAL functions HAL_GPIO_WritePin() and HAL_GPIO_TogglePin() use BSRR internally for this reason. Note that toggling via BSRR still requires a read of ODR to determine the current state, so HAL_GPIO_TogglePin() is not fully atomic — if atomicity is critical for a toggle, disable interrupts briefly or use the dedicated toggle register (available on some newer STM32 families).

GPIO Speed and Slew Rate

QWhat does the GPIO speed (slew rate) setting control, and how do you choose it?

The GPIO speed setting (Low, Medium, High, Very High on STM32) controls the slew rate of the output driver — how fast the pin voltage transitions between logic levels. Internally, this adjusts the drive strength of the output transistors. A higher speed setting turns on the output transistors more aggressively, producing steeper voltage edges (faster rise and fall times). The speed setting does not affect the maximum toggling frequency directly, but slower edges may violate setup/hold timing at high switching rates.

Choosing the right speed is a trade-off between signal integrity and electromagnetic interference (EMI). Low speed (typically 2-8 MHz edge rate) produces gentle transitions that generate minimal high-frequency energy — ideal for LEDs, relays, chip-select lines, and any slow control signal. High or Very High speed (50-100+ MHz edge rate) is necessary for fast communication peripherals: SPI at 10+ MHz, SDIO, FSMC/FMC external memory interfaces, or high-speed timer outputs where slow edges would violate timing constraints. Using unnecessarily high speed settings on low-frequency signals creates problems: faster edges generate more EMI (radiated emissions that may fail compliance testing), cause signal ringing on unterminated PCB traces, and increase dynamic power consumption due to higher transient currents during switching.

The rule of thumb: use the lowest speed setting that meets your signal's timing requirements. For a 1 MHz SPI clock, Medium speed is sufficient. For a 42 MHz SPI clock, Very High is required. For an LED toggle, Low speed is perfectly adequate. If you see ringing or overshoot on an oscilloscope, reducing the GPIO speed setting is often a cheaper fix than adding series termination resistors. During EMC pre-compliance testing, one of the first things to try for excessive radiated emissions is reducing GPIO speed on high-frequency outputs.