01 Why GPIO on the L4R5 works this way
Every pin on the STM32L4R5 is a mux. Each I/O can be driven as a general-purpose input, a general-purpose output, one of up to sixteen alternate functions (USART, SPI, I2C, timers, …), or an analog input. Getting a peripheral onto real copper is always the same two-step: pick the pin's mode, then — for a peripheral — pick which alternate function number that pin should route.
GPIO ports live on the AHB2 bus. The port base is 0x48000000 and each port is a 1 KB register block, so port addresses are simply spaced 0x400 apart. HCLK on the L4R5 runs up to 120 MHz, and because GPIO sits directly on AHB2 (not behind an APB bridge), single-cycle register access and very fast toggling are possible.
| Port | Base address | RCC_AHB2ENR bit | Notes |
|---|---|---|---|
| GPIOA | 0x48000000 | GPIOAEN (bit 0) | PA13/PA14/PA15 reset to SWD/JTAG |
| GPIOB | 0x48000400 | GPIOBEN (bit 1) | PB3/PB4 reset to JTAG (TDO/NJTRST) |
| GPIOC | 0x48000800 | GPIOCEN (bit 2) | PC13 = user button B1 on Nucleo |
| GPIOD | 0x48000C00 | GPIODEN (bit 3) | |
| GPIOE | 0x48001000 | GPIOEEN (bit 4) | |
| GPIOF | 0x48001400 | GPIOFEN (bit 5) | |
| GPIOG | 0x48001800 | GPIOGEN (bit 6) | PG[15:2] need VDDIO2 (PWR_CR2.IOSV) |
| GPIOH | 0x48001C00 | GPIOHEN (bit 7) | PH0/PH1 = OSC in/out |
| GPIOI | 0x48002000 | GPIOIEN (bit 8) | Only on large BGA packages |
A GPIO port is dead until its clock is enabled in RCC_AHB2ENR. Before it, register reads return 0 and writes are dropped — the classic "my LED won't light" bug. RCC_AHB2ENR is at RCC offset 0x4C (absolute 0x4002104C).
Unlike the original STM32L47x/L48x devices, the STM32L4R5 (an L4+ part) has no GPIOx_ASCR analog-switch register. Its GPIO_TypeDef ends at BRR (offset 0x28). For an analog input you only set MODER = 11 — there is no analog switch bit to toggle. Code ported from an L476 that writes ASCR will not compile against stm32l4r5xx.h.
02 The GPIO register block
Eleven 32-bit registers per port. Two-bits-per-pin registers (MODER, OSPEEDR, PUPDR) use field 2n..2n+1 for pin n; one-bit-per-pin registers (OTYPER, IDR, ODR) use bit n; the AF registers use a 4-bit nibble per pin split across two words.
| Offset | Register | Width/pin | Access | Purpose |
|---|---|---|---|---|
| 0x00 | MODER | 2 bits | rw | 00 input · 01 output · 10 alternate · 11 analog |
| 0x04 | OTYPER | 1 bit | rw | 0 push-pull · 1 open-drain |
| 0x08 | OSPEEDR | 2 bits | rw | 00 low · 01 medium · 10 high · 11 very-high |
| 0x0C | PUPDR | 2 bits | rw | 00 none · 01 pull-up · 10 pull-down · 11 reserved |
| 0x10 | IDR | 1 bit | r | Live input level of each pin (read-only) |
| 0x14 | ODR | 1 bit | rw | Output latch; prefer BSRR for atomic writes |
| 0x18 | BSRR | 1 bit ×2 | w | [15:0] set-to-1 · [31:16] reset-to-0 (atomic) |
| 0x1C | LCKR | 1 bit | rw | Config lock (needs magic sequence); frozen until reset |
| 0x20 | AFRL / AFR[0] | 4 bits | rw | Alternate function select for pins 0–7 |
| 0x24 | AFRH / AFR[1] | 4 bits | rw | Alternate function select for pins 8–15 |
| 0x28 | BRR | 1 bit | w | Reset-only shortcut: write 1 to clear a pin |
This is exactly the layout in the official CMSIS device header, which you can rely on for pointer arithmetic:
typedef struct {
volatile uint32_t MODER; /* 0x00 mode (2 bits/pin) */
volatile uint32_t OTYPER; /* 0x04 output type (1 bit/pin) */
volatile uint32_t OSPEEDR; /* 0x08 speed (2 bits/pin) */
volatile uint32_t PUPDR; /* 0x0C pull up/dn (2 bits/pin) */
volatile uint32_t IDR; /* 0x10 input data (read-only) */
volatile uint32_t ODR; /* 0x14 output data */
volatile uint32_t BSRR; /* 0x18 bit set/reset (atomic) */
volatile uint32_t LCKR; /* 0x1C config lock */
volatile uint32_t AFR[2]; /* 0x20 AFRL (0-7), 0x24 AFRH (8-15) */
volatile uint32_t BRR; /* 0x28 bit reset */
/* NOTE: no ASCR on L4R5 (L4+) — struct ends here */
} GPIO_TypeDef;
Field math you will use constantly. For pin n:
/* 2-bit fields (MODER / OSPEEDR / PUPDR): shift = 2*n, mask = 0x3 */
#define MODE_MASK(n) (0x3UL << ((n) * 2))
#define MODE_VAL(n,v) (((uint32_t)(v)) << ((n) * 2))
/* 1-bit fields (OTYPER / IDR / ODR): shift = n */
#define BIT(n) (1UL << (n))
/* BSRR: set pin n -> bit n ; reset pin n -> bit n+16 */
#define BSRR_SET(n) (1UL << (n))
#define BSRR_RST(n) (1UL << ((n) + 16))
/* AFR nibble: pins 0-7 use AFR[0], pins 8-15 use AFR[1] */
#define AFR_IDX(n) ((n) >> 3) /* 0 or 1 */
#define AFR_SH(n) (((n) & 7) * 4) /* nibble position */
#define AFR_MASK(n) (0xFUL << AFR_SH(n))
#define AFR_VAL(n,af) (((uint32_t)(af)) << AFR_SH(n))
To save power most pins reset to analog mode. Documented MODER reset values: GPIOA = 0xABFFFFFF (PA13/14/15 = SWD AF), GPIOB = 0xFFFFFEBF (PB3 = TDO AF, PB4 = NJTRST input), all other ports = 0xFFFFFFFF (fully analog). You must therefore explicitly program output/AF mode — never assume input.
03 Configuring a pin: input / output / AF / analog
Four modes, four recipes. Each is: clock the port, clear the 2-bit MODER field, write the new mode, then set the mode-specific extras.
| Mode | MODER | Also set | Ignored |
|---|---|---|---|
| Digital input | 00 | PUPDR (opt.) | OTYPER, OSPEEDR |
| Digital output | 01 | OTYPER, OSPEEDR, PUPDR | AFR |
| Alternate function | 10 | AFRL/AFRH nibble, OTYPER, OSPEEDR, PUPDR | — |
| Analog | 11 | nothing (no ASCR on L4R5) | OTYPER, OSPEEDR, PUPDR |
Input — read a button (PC13, active-low)
RCC->AHB2ENR |= (1UL << 2); /* GPIOCEN */
(void)RCC->AHB2ENR; /* sync read after clock enable */
GPIOC->MODER &= ~(0x3UL << (13*2)); /* 00 = input */
GPIOC->PUPDR &= ~(0x3UL << (13*2));
GPIOC->PUPDR |= (0x1UL << (13*2)); /* 01 = pull-up */
int pressed = (GPIOC->IDR & (1UL << 13)) == 0; /* B1 is active-low */
Output — push-pull LED (PB7)
RCC->AHB2ENR |= (1UL << 1); /* GPIOBEN */
(void)RCC->AHB2ENR;
GPIOB->MODER &= ~(0x3UL << (7*2));
GPIOB->MODER |= (0x1UL << (7*2)); /* 01 = output */
GPIOB->OTYPER &= ~(1UL << 7); /* 0 = push-pull */
GPIOB->OSPEEDR &= ~(0x3UL << (7*2)); /* 00 = low speed (LED needs nothing more) */
GPIOB->PUPDR &= ~(0x3UL << (7*2)); /* no pull */
GPIOB->BSRR = (1UL << 7); /* drive high */
Alternate function — route a peripheral (PA5 = SPI1_SCK, AF5)
RCC->AHB2ENR |= (1UL << 0); /* GPIOAEN */
(void)RCC->AHB2ENR;
GPIOA->MODER &= ~(0x3UL << (5*2));
GPIOA->MODER |= (0x2UL << (5*2)); /* 10 = alternate function */
GPIOA->OTYPER &= ~(1UL << 5); /* push-pull (SPI clock) */
GPIOA->OSPEEDR |= (0x3UL << (5*2)); /* 11 = very-high for clean SPI edges */
/* AF5 into AFRL nibble for pin 5 (pins 0-7 -> AFR[0]) */
GPIOA->AFR[0] &= ~(0xFUL << (5*4));
GPIOA->AFR[0] |= (5UL << (5*4)); /* 5 = AF5 = SPI1 */
Analog — feed the ADC (PA0 = ADC1_IN5)
RCC->AHB2ENR |= (1UL << 0); /* GPIOAEN */
(void)RCC->AHB2ENR;
GPIOA->MODER |= (0x3UL << (0*2)); /* 11 = analog. That is all — */
/* the L4R5 has no ASCR to set. */
GPIOA->PUPDR &= ~(0x3UL << (0*2)); /* disable pulls on an analog pin */
I2C lines must be open-drain: set the pin to AF mode and OTYPER = 1, then rely on external pull-ups (typ. 4.7 kΩ). The internal PUPDR pull-up (~30–50 kΩ) is far too weak for standard/fast-mode bus edges.
04 Alternate-function & peripheral pin maps
The AF number you write into AFRL/AFRH decides which internal peripheral a pin connects to. The 16 AF slots are grouped by peripheral family and are consistent across the STM32L4 line. The authoritative per-pin table is "Alternate function mapping" in datasheet DS12023 — the maps below cover the pins you use daily.
AF number → peripheral family
| AF | Peripherals | AF | Peripherals |
|---|---|---|---|
| AF0 | SYS (SWD/JTAG, MCO, LSCO, RTC_REFIN) | AF8 | UART4, UART5, LPUART1 |
| AF1 | TIM1, TIM2, TIM5, TIM8_BKIN, LPTIM1, IR | AF9 | CAN1, TSC |
| AF2 | TIM1, TIM2, TIM3, TIM4, TIM5 | AF10 | OCTOSPI1, OTG_FS |
| AF3 | SAI1, TIM8 | AF11 | Device-specific (see DS12023) |
| AF4 | I2C1, I2C2, I2C3, I2C4 | AF12 | SDMMC1, COMP1/2, FMC, SWPMI1 |
| AF5 | SPI1, SPI2, DFSDM1 | AF13 | SAI1, SAI2 |
| AF6 | SPI3, DFSDM1 | AF14 | TIM2, TIM15, TIM16, TIM17, LPTIM2 |
| AF7 | USART1, USART2, USART3 | AF15 | EVENTOUT |
USART / UART pins
| Signal | Pins | AF |
|---|---|---|
| USART1_TX / _RX | PA9 / PA10 · PB6 / PB7 | AF7 |
| USART1_CTS / _RTS-DE / _CK | PA11 / PA12 / PA8 | AF7 |
| USART2_TX / _RX | PA2 / PA3 · PD5 / PD6 | AF7 |
| USART3_TX / _RX | PB10 / PB11 · PC4 / PC5 · PD8 / PD9 | AF7 |
| UART4_TX / _RX | PA0 / PA1 · PC10 / PC11 | AF8 |
| LPUART1_TX / _RX | PB11 / PB10 · PC1 / PC0 · PG7 / PG8 | AF8 |
SPI pins
| Bus | NSS | SCK | MISO | MOSI | AF |
|---|---|---|---|---|---|
| SPI1 | PA4 / PA15 | PA5 / PB3 | PA6 / PB4 | PA7 / PB5 | AF5 |
| SPI2 | PB12 / PB9 | PB13 / PB10 | PB14 / PC2 | PB15 / PC3 | AF5 |
| SPI3 | PA4 / PA15 | PB3 / PC10 | PB4 / PC11 | PB5 / PC12 | AF6 |
I2C pins (open-drain, need external pull-ups)
| Bus | SCL | SDA | AF |
|---|---|---|---|
| I2C1 | PB6 / PB8 | PB7 / PB9 | AF4 |
| I2C2 | PB10 / PF1 | PB11 / PF0 | AF4 |
| I2C3 | PC0 / PG7 | PC1 / PG8 | AF4 |
Timer channel pins
| Timer | CH1 / CH2 / CH3 / CH4 | AF |
|---|---|---|
| TIM1 (adv.) | PA8 / PA9 / PA10 / PA11 (CHxN: PA7,PB13 / PB0,PB14 / PB1,PB15) | AF1 |
| TIM2 | PA0,PA5,PA15 / PA1,PB3 / PA2,PB10 / PA3,PB11 | AF1 |
| TIM3 | PA6,PB4,PC6 / PA7,PB5,PC7 / PB0,PC8 / PB1,PC9 | AF2 |
| TIM4 | PB6,PD12 / PB7,PD13 / PB8,PD14 / PB9,PD15 | AF2 |
| TIM15 / 16 / 17 | TIM15: PA2/PA3 · TIM16: PA6/PB8 · TIM17: PA7/PB9 | AF14 |
ADC external channels (ADC1 & ADC2 share these pins)
Set the pin to analog (MODER = 11); no AF nibble applies to analog. Channel numbering matches ADC1/ADC2 on the L4R5:
| Pin | Channel | Pin | Channel | Pin | Channel |
|---|---|---|---|---|---|
| PC0 | IN1 | PA2 | IN7 | PA7 | IN12 |
| PC1 | IN2 | PA3 | IN8 | PC4 | IN13 |
| PC2 | IN3 | PA4 | IN9 | PC5 | IN14 |
| PC3 | IN4 | PA5 | IN10 | PB0 | IN15 |
| PA0 | IN5 | PA6 | IN11 | PB1 | IN16 |
| PA1 | IN6 |
Most pins list a function on several AF numbers. PB7, for example, is USART1_RX on AF7 and I2C1_SDA on AF4 — you choose by the nibble you write. Always confirm the exact package/pin against the DS12023 alternate-function table before committing a board layout; a few signals move between L4 variants.
05 Register-level example: blink + USART2 AF
A complete, self-contained main.c — no CMSIS/HAL headers required, only raw addresses — that blinks the Nucleo-L4R5ZI blue LED (PB7) and brings up USART2 on PA2/PA3 (AF7). Runs on the reset default MSI clock (4 MHz).
#include <stdint.h>
/* ---- minimal register model (RM0432 addresses) ---- */
typedef struct {
volatile uint32_t MODER, OTYPER, OSPEEDR, PUPDR;
volatile uint32_t IDR, ODR, BSRR, LCKR;
volatile uint32_t AFR[2]; /* AFRL @0x20, AFRH @0x24 */
volatile uint32_t BRR; /* @0x28 (no ASCR on L4R5) */
} GPIO_TypeDef;
typedef struct {
volatile uint32_t CR1, CR2, CR3, BRR, GTPR, RTOR, RQR, ISR, ICR, RDR, TDR;
} USART_TypeDef;
#define GPIOA ((GPIO_TypeDef *)0x48000000UL)
#define GPIOB ((GPIO_TypeDef *)0x48000400UL)
#define USART2 ((USART_TypeDef *)0x40004400UL)
#define RCC_AHB2ENR (*(volatile uint32_t *)0x4002104CUL)
#define RCC_APB1ENR1 (*(volatile uint32_t *)0x40021058UL)
#define GPIOAEN (1UL << 0)
#define GPIOBEN (1UL << 1)
#define USART2EN (1UL << 17) /* RCC_APB1ENR1 bit 17 */
#define USART_CR1_UE (1UL << 0)
#define USART_CR1_RE (1UL << 2)
#define USART_CR1_TE (1UL << 3)
#define USART_ISR_TXE (1UL << 7)
#define LED_PIN 7 /* PB7 = blue LED (LD2) */
static void delay(volatile uint32_t n) { while (n--) __asm__ volatile("nop"); }
static void led_init(void)
{
RCC_AHB2ENR |= GPIOBEN;
(void)RCC_AHB2ENR; /* sync after clock enable */
GPIOB->MODER &= ~(0x3UL << (LED_PIN * 2));
GPIOB->MODER |= (0x1UL << (LED_PIN * 2)); /* output */
GPIOB->OTYPER &= ~(1UL << LED_PIN); /* push-pull */
GPIOB->OSPEEDR &= ~(0x3UL << (LED_PIN * 2)); /* low speed */
GPIOB->PUPDR &= ~(0x3UL << (LED_PIN * 2)); /* no pull */
}
static void usart2_init(void)
{
RCC_AHB2ENR |= GPIOAEN;
RCC_APB1ENR1 |= USART2EN;
(void)RCC_APB1ENR1;
/* PA2 (TX) and PA3 (RX) -> AF mode */
GPIOA->MODER &= ~((0x3UL << (2*2)) | (0x3UL << (3*2)));
GPIOA->MODER |= ((0x2UL << (2*2)) | (0x2UL << (3*2))); /* 10 = AF */
/* AF7 for pins 2 and 3, both in AFRL (AFR[0]) */
GPIOA->AFR[0] &= ~((0xFUL << (2*4)) | (0xFUL << (3*4)));
GPIOA->AFR[0] |= ((7UL << (2*4)) | (7UL << (3*4)));
/* 9600 8N1, PCLK1 = 4 MHz (MSI reset default), OVER8=0 */
USART2->BRR = 4000000UL / 9600UL; /* ~416 */
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
static void usart2_putc(char c)
{
while (!(USART2->ISR & USART_ISR_TXE)) { }
USART2->TDR = (uint8_t)c;
}
int main(void)
{
led_init();
usart2_init();
for (;;) {
GPIOB->BSRR = (1UL << LED_PIN); /* LED on (atomic) */
usart2_putc('*');
delay(400000);
GPIOB->BSRR = (1UL << (LED_PIN + 16)); /* LED off (atomic) */
delay(400000);
}
}
Compile with arm-none-eabi-gcc -mcpu=cortex-m4 -mthumb -mfloat-abi=hard -mfpu=fpv4-sp-d16 -nostdlib -T link.ld startup.s main.c -o fw.elf. You still need a vector table / startup and a linker script placing .text at 0x08000000 and stack at top of SRAM. The USART VCP routing on the Nucleo-L4R5ZI is board-specific (solder bridges, see UM2179) — for a guaranteed link, wire a USB-serial adapter to PA2/PA3.
06 Fast toggle (BSRR/BRR), LL & HAL variants
Never read-modify-write ODR from more than one context. BSRR is a write-only "command" register: the lower 16 bits set pins, the upper 16 reset them, in a single atomic bus write with no lock and no interference between pins on the same port.
/* Atomic, ISR-safe — preferred */
GPIOB->BSRR = (1UL << 7); /* PB7 = 1 (lower half = SET) */
GPIOB->BSRR = (1UL << (7 + 16)); /* PB7 = 0 (upper half = RESET) */
GPIOB->BRR = (1UL << 7); /* PB7 = 0 (BRR is reset-only) */
/* Set several pins, clear others, in ONE write: */
GPIOB->BSRR = (1UL << 0) | (1UL << 1) | (1UL << (2 + 16));
/* -> PB0=1, PB1=1, PB2=0 simultaneously */
/* Software toggle needs a read of ODR — NOT atomic vs. an ISR
touching another pin on the same port: */
GPIOB->ODR ^= (1UL << 7);
/* Maximum square-wave: back-to-back BSRR stores.
GPIO is on AHB2, so the rate is limited by store latency at HCLK. */
for (;;) {
GPIOB->BSRR = (1UL << 7);
GPIOB->BSRR = (1UL << (7 + 16));
}
If, in the same BSRR write, you set both the set-bit (n) and reset-bit (n+16) for one pin, the set action wins (RM0432). Use BRR when you only ever clear a pin.
Same thing with ST LL (stm32l4xx_ll_gpio.h)
#include "stm32l4xx_ll_bus.h"
#include "stm32l4xx_ll_gpio.h"
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOB);
LL_GPIO_SetPinMode(GPIOB, LL_GPIO_PIN_7, LL_GPIO_MODE_OUTPUT);
LL_GPIO_SetPinOutputType(GPIOB, LL_GPIO_PIN_7, LL_GPIO_OUTPUT_PUSHPULL);
LL_GPIO_SetPinSpeed(GPIOB, LL_GPIO_PIN_7, LL_GPIO_SPEED_FREQ_LOW);
LL_GPIO_SetPinPull(GPIOB, LL_GPIO_PIN_7, LL_GPIO_PULL_NO);
LL_GPIO_SetOutputPin(GPIOB, LL_GPIO_PIN_7); /* -> BSRR set */
LL_GPIO_ResetOutputPin(GPIOB, LL_GPIO_PIN_7); /* -> BRR / BSRR */
LL_GPIO_TogglePin(GPIOB, LL_GPIO_PIN_7); /* reads ODR, writes BSRR */
/* AF pin: PA2 = USART2_TX (AF7). AFRL helper for pins 0-7: */
LL_AHB2_GRP1_EnableClock(LL_AHB2_GRP1_PERIPH_GPIOA);
LL_GPIO_SetPinMode(GPIOA, LL_GPIO_PIN_2, LL_GPIO_MODE_ALTERNATE);
LL_GPIO_SetAFPin_0_7(GPIOA, LL_GPIO_PIN_2, LL_GPIO_AF_7);
/* pins 8-15 would use LL_GPIO_SetAFPin_8_15() for AFRH */
Same thing with the HAL (stm32l4xx_hal_gpio.h)
#include "stm32l4xx_hal.h"
__HAL_RCC_GPIOB_CLK_ENABLE();
GPIO_InitTypeDef led = {0};
led.Pin = GPIO_PIN_7;
led.Mode = GPIO_MODE_OUTPUT_PP;
led.Pull = GPIO_NOPULL;
led.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &led);
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, GPIO_PIN_SET); /* uses BSRR */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_7);
/* Alternate function: USART2 TX/RX on PA2/PA3 */
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitTypeDef af = {0};
af.Pin = GPIO_PIN_2 | GPIO_PIN_3;
af.Mode = GPIO_MODE_AF_PP;
af.Pull = GPIO_PULLUP;
af.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
af.Alternate = GPIO_AF7_USART2; /* the AF7 nibble, chosen by macro */
HAL_GPIO_Init(GPIOA, &af);
Register code is smallest and clearest about what the silicon does; LL gives you named helpers that still map 1:1 to registers (great for performance-sensitive drivers); the HAL validates parameters and handles clock/AF bookkeeping (fastest to bring up, larger footprint). All three ultimately write the same MODER/AFR/BSRR bits shown above.
07 Gotchas & common mistakes
Almost every "GPIO doesn't work" ticket is one of these. Scan this list before you reach for a scope.
| Symptom | Cause | Fix |
|---|---|---|
| Writes to a port do nothing; reads return 0 | Port clock not enabled | Set the bit in RCC_AHB2ENR first, then a dummy read to let the clock domain sync |
| Other pins on the port stopped working | Used = instead of |=/&= on MODER/AFR | Read-modify-write the field: clear with &= ~mask, then |= the value |
| Pin picks up the wrong mode value | Forgot to clear the old 2-bit field | Always MODER &= ~(0x3<<2n) before OR-ing |
| Peripheral wired but silent | MODER left as output, or wrong AF nibble | MODER = 10 and correct AF in AFRL (0-7) / AFRH (8-15) |
| PG2–PG15 dead | VDDIO2 domain not powered | Enable PWR clock, then PWR->CR2 |= PWR_CR2_IOSV (bit 9) |
| Debugger drops after configuring PA13/14/15 or PB3/PB4 | Reprogrammed the SWD/JTAG pins | Leave PA13 (SWDIO) / PA14 (SWCLK) alone; free PB3/PB4/PA15 only if you don't need JTAG |
| I2C bus stuck / no ACK | Push-pull instead of open-drain, or no pull-ups | OTYPER = 1 on SCL/SDA + external ~4.7 kΩ pull-ups |
| SPI/OCTOSPI edges too slow, data corrupt | OSPEEDR left low | Set high or very-high speed on clock/data lines |
| Overheating / EMI / extra current | Very-high speed on pins that don't need it | Use the lowest OSPEEDR that meets the rise-time budget |
| ADC reads garbage | Pin not in analog mode, or pull still enabled | MODER = 11 and PUPDR = 00 (L4R5 needs no ASCR) |
| ISR and main both toggle a pin, glitches | Read-modify-write on ODR races | Use BSRR/BRR — single atomic write, no read |
| 3.3V logic damaged by 5V input | Drove 5V into a non-FT pin | Check the pin's I/O type in DS12023; only FT pins tolerate 5V |
1. No ASCR. Do not copy analog-input code from an STM32L476 project — the L4R5 has no GPIOx_ASCR; setting MODER = 11 is sufficient and ASCR will not even exist in the header.
2. VDDIO2 gate. Port G bits 2–15 are on the independent VDDIO2 supply. Even with the GPIOG clock on, those pins stay Hi-Z until you enable the PWR peripheral clock and set PWR_CR2.IOSV. This bites anyone using PG7/PG8 for LPUART1 or I2C3.
LCKR freezes a pin's MODER/OTYPER/OSPEEDR/PUPDR/AFR until the next reset. The lock only takes effect after the documented write sequence (write 1+bits, write 0+bits, write 1+bits, then read twice). Use it to protect a critical pin from a runaway write — but remember it is one-way until reset.