The puzzle
You want a UART on your board. The datasheet’s pin table lists its transmit signal in three places, the I²C you also need shares one of those pins, and the pin you soldered the LED to can only be a PWM output on one particular channel. Who decides which peripheral owns a pin, and how do you tell the chip?
STEP 1
A pin is a multiplexer and a pad
Inside the chip, each package pin connects to a pad: the output driver, the input buffer, the pull resistors and the protection diodes. In front of the pad sits a multiplexer that chooses which internal signal drives it: the software-controlled GPIO block, or one of several peripherals (UART, SPI, I²C, PWM, timers). Configuring a pin means choosing both: who owns it and how the pad behaves.
↑ This step uses the figure at the top of the page.
On the RP2040 the choice is one field per pin, FUNCSEL in IO_BANK0 GPIOn_CTRL: 1 for SPI, 2 for UART, 3 for I²C, 5 for software GPIO (SIO) and so on. The table differs pin by pin: GPIO0 with FUNCSEL 2 is UART0 TX, GPIO4 with the same value is UART1 TX. After reset FUNCSEL is 0x1F, “no function”, and the SDK’s gpio_set_function() writes the chosen value and turns the pad’s input on.
STEP 2
The pad’s own settings
The pad has controls that apply whatever the owner:
| control | what it does | RP2040 reset value |
|---|---|---|
| input enable | connects the input buffer | on |
| pull-up / pull-down | weak resistor to a rail (unit 1, lesson 3); both = bus keeper on the RP2040 | pull-down on |
| Schmitt trigger | hysteresis on the input (unit 1, lesson 2) | on |
| drive strength | how hard the output drives: 2, 4, 8 or 12 mA settings | 4 mA |
| slew rate | slow or fast edges | slow |
| output disable | forces the driver off, whatever the owner wants | off |
That last row is a trap: the RP2040 bit called OD means output disable, not open-drain. Names are not portable; read the register description.
STEP 3
The STM32F4 way: several registers per port
An STM32F4 GPIO port splits the same decisions across registers, with a field per pin:
An STM32F4 GPIO port spreads the choice over several registers: MODER picks input, output, alternate function or analog; OTYPER push-pull or open-drain; PUPDR the pulls; AFRL or AFRH which of 16 alternate functions (AF0–AF15). Register layout from ST’s stm32f407xx.h and HAL; which AF number reaches which peripheral on which pin is in the chip’s datasheet.
MODER (2 bits per pin) chooses input, output, alternate function or analog; OTYPER (1 bit) push-pull or open-drain; PUPDR (2 bits) the pulls; OSPEEDR the slew rate; and AFRL/AFRH (4 bits per pin) which alternate function, AF0 to AF15, the pin uses when MODER says “alternate”. The HAL names AF numbers after peripherals (GPIO_AF7_USART2); which pin offers which AF is in the chip’s datasheet, not the register header.
Because each field is a few bits of a shared word, changing one pin is a read-modify-write (unit 2, lesson 6): clear the pin’s field with a mask, OR in the new value, and do it where no interrupt handler changes the same register.
STEP 4
Worked example: USART2 on pin 2 of a port
Suppose the chip’s datasheet lists USART2 TX on pin PA2 as alternate function 7 (the HAL names USART2’s function AF7; the pin assignment itself must come from the datasheet of your exact part). The fields for pin 2:
The shifts come straight from the pin number: 2 bits per pin puts pin 2 at bits 5:4; 4 bits per pin in AFRL puts it at bits 11:8. In C:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; /* clock the port first */
GPIOA->AFR[0] = (GPIOA->AFR[0] & ~(0xFu << 8)) | (7u << 8); /* AF7 = USART2 */
GPIOA->MODER = (GPIOA->MODER & ~(3u << 4)) | (2u << 4); /* then alternate mode */
The order matters: writes to an unclocked port are ignored, and switching MODER before AFR holds 7 would briefly give the pin to AF0. ST’s HAL also writes AFR before MODER.
On the RP2040 the equivalent is one call, gpio_set_function(0, GPIO_FUNC_UART), which puts UART0 TX on GPIO0.
STEP 5
Planning pins
Assign pins on paper before the board is drawn. List every peripheral signal, the pins that offer it, and pick a combination with no overlaps; check the pins the board already uses (the debug port, the crystal, the RP2040’s dedicated QSPI flash pins) and the pins whose reset state matters for what they drive (lesson 5).
MYTHS AND FACTS
Common misconceptions
Writing the GPIO output register drives the pin
Only while software GPIO owns the pin; once a peripheral does, the software output register is ignored.
Any peripheral can use any pin
Each pin offers a fixed subset, listed in the datasheet or the FUNCSEL table.
OD means open-drain
On the RP2040 it means output disable. Check each register’s description.
AF7 is the UART on every chip
AF numbers are family-specific, and whether a peripheral appears on a given pin at all is in that part’s datasheet.
Check yourself
Answer in your head, then open the card.
On the RP2040, which pin carries UART1 TX according to the table in the figure, and with which FUNCSEL value?
GPIO4, FUNCSEL 2.
On an STM32F4, which bits of MODER and AFRH belong to pin 10?
MODER bits 21:20 (2 × 10 and 2 × 10 + 1); pin 10 is in AFRH (pins 8–15) at bits 4 × (10 − 8) = 8 to 11.
The code writes GPIOA->MODER = 2u << 4; to put pin 2 in alternate-function mode. What else happened?
Every other pin of port A was set to input (00), including any that were outputs or alternate functions, the debug pins among them. Use a masked read-modify-write.
Why does gpio_set_function() on the RP2040 also turn on the pad's input enable?
Many peripherals read the pin as well as drive it (UART RX, I²C, SPI), and the input enable may have been turned off: the boot ROM and the SDK's runtime clear it on GPIO26–29, and user code can clear it on any pin. Setting it makes sure the chosen function can see the pin.
Sources (3)
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2040/hardware_regs/include/hardware/regs/io_bank0.h and pads_bank0.h — GPIOn_CTRL FUNCSEL values per pin (GPIO0: 0 JTAG_TCK, 1 SPI0_RX, 2 UART0_TX, 3 I2C0_SDA, 4 PWM_A_0, 5 SIO_0, 6 PIO0_0, 7 PIO1_0, 9 USB_MUXING_OVERCURR_DETECT; GPIO4: 1 SPI0_RX, 2 UART1_TX …), FUNCSEL reset 0x1F; pads_bank0.h: GPIOn reset 0x56 (IE 1, DRIVE 4 mA, PDE 1, SCHMITT 1, SLEWFAST 0, PUE 0), DRIVE values 2/4/8/12 mA, OD = “Output disable. Has priority over output enable from peripherals”
- Raspberry Pi Ltd, pico-sdk 1.5.1, src/rp2_common/hardware_gpio/gpio.h and gpio.c — enum gpio_function (SPI 1, UART 2, I2C 3, PWM 4, SIO 5, PIO0 6, PIO1 7, GPCK 8, USB 9, NULL 0x1F); gpio_set_function sets IE, clears OD and writes FUNCSEL; gpio_init = input, output value 0, function SIO; “setting both pulls enables a bus keep function”
- STMicroelectronics, cmsis-device-f4 stm32f407xx.h, and stm32f4xx-hal-driver stm32f4xx_hal_gpio.h / _gpio_ex.h — GPIO_TypeDef: MODER 0x00, OTYPER 0x04, OSPEEDR 0x08, PUPDR 0x0C, IDR 0x10, ODR 0x14, BSRR 0x18, LCKR 0x1C, AFR[2] 0x20–0x24; MODE_INPUT 0, MODE_OUTPUT 1, MODE_AF 2, MODE_ANALOG 3; OUTPUT_PP 0, OUTPUT_OD 1; GPIO_NOPULL 0, PULLUP 1, PULLDOWN 2; AF numbers per peripheral, e.g. GPIO_AF7_USART2, GPIO_AF4_I2C1