Conslabs
Embedded Firmware Basics
intermediateMarch 2, 2025 · 1 min read

GPIO & Memory-Mapped I/O

How microcontrollers expose digital pins as ordinary memory addresses, and why the volatile qualifier matters.

GPIO is the first peripheral you touch in nearly every piece of embedded firmware. It builds directly on the circuit fundamentals from Circuit Analysis Fundamentals — a GPIO pin is, electrically, just a controllable voltage source with current limits.

GPIO as memory-mapped registers

A microcontroller doesn't expose pins through a special instruction set — it exposes them as ordinary memory addresses. Writing to a register sets pin state; reading a register reads pin state. A simplified example, configuring and toggling a pin on an STM32-style part:

#define GPIOA_MODER   (*(volatile uint32_t *)0x40020000)
#define GPIOA_ODR     (*(volatile uint32_t *)0x40020014)
 
void led_init(void) {
    // Set pin 5 to general-purpose output mode (01)
    GPIOA_MODER &= ~(0b11 << (5 * 2));
    GPIOA_MODER |=  (0b01 << (5 * 2));
}
 
void led_toggle(void) {
    GPIOA_ODR ^= (1 << 5);
}

The volatile qualifier is not optional here: it tells the compiler this memory can change outside the normal flow of the program (or that writes have side effects), preventing it from optimizing away what looks like a "redundant" read or write.

GPIO alone only gets you simple, single-bit I/O. The next sub-lesson, Communication Protocols, covers how firmware talks to other chips over UART, SPI, and I2C.