Embedded HandbookApps
All sections › II. Core and system mechanisms
STM32F103, STM32F407, STM32F401, STM32F072 · level basic · 20 min read
Open in the interactive handbook

8. Input/output ports (GPIO)

GPIO (General-Purpose Input/Output) is how the microcontroller touches the outside world. Everything else — timers, UART, SPI — ultimately goes out through the same pins. That is why GPIO is studied first: without understanding it you cannot even light an LED.

Ports are named by letters: GPIOA, GPIOB, GPIOC… Each port has up to 16 pins, and each pin is configured independently of its neighbours.

How many are there? The number of pins depends on the package (LQFP48, BGA176…), the family (F0, F1, F4…) and on whether pins are taken by the HSE/LSE crystals.


8.1 How it works

The structure of one pin

Any STM32 pin is not a "leg" but a small circuit: a pair of protection diodes, pull-up and pull-down resistors, a Schmitt trigger at the input, a pair of transistors at the output and multiplexers that decide who controls the pin — your code or a peripheral.

Basic structure of an I/O port pin Basic structure of an I/O port pin

5 V tolerance. In the diagram the protection diode is marked VDD_FT. It means that the pin tolerates a 5 V input signal with the MCU powered from 3.3 V. If the datasheet has no such marking for a pin, you must not apply more than the supply (the typical margin is only ~0.3 V). Pins routed to the ADC are usually not tolerant. And a tolerant pin has two caveats: it tolerates 5 V only with the supply present — the limit is VDD + 4 V, so 5 V must not be applied to an unpowered chip; and above VDD + 0.3 V its internal pull resistors must be off. On F4 the tolerance also disappears in analog mode.

How much current a pin handles. The absolute limit on F1 and F4 is 25 mA per pin, sunk or sourced, but the datasheet guarantees the 0 and 1 levels only up to 8 mA (up to 20 mA — with degraded levels). The sum of the currents of all pins together with the consumption of the chip itself is no more than 150 mA on F103 and 240 mA on F407, separately through VDD and through VSS. PC13–PC15 — no more than 3 mA and not as a current source (section 2.6). An LED is connected through a resistor: (3.3 − 2.0) V / 330 Ω ≈ 4 mA; a relay, a motor and a long LED strip — only through a transistor.

Eight pin modes

Terms used in the documentation

Term Meaning
port bit configuration the settings of one port pin
output push-pull an output that drives both high and low
output open-drain an output that only pulls low
floating input an input with no pull resistor
input with pull-up / pull-down an input pulled to the supply / to ground
Schmitt trigger an input stage with hysteresis
output driving current the current an output can supply
maximum output speed the edge rate setting of an output
five-volt tolerant (FT) a pin that accepts 5 V
alternate function a pin driven by a peripheral
atomic bit set/reset setting and clearing a bit in one bus write
locking mechanism freezing the configuration until reset
Mode What it does
Input floating input, pull resistors off (the state after reset)
Input pull-up input, pulled to the supply
Input pull-down input, pulled to ground
Analog ADC input/DAC output; the digital circuitry is off
Output push-pull a "push-pull" output: 1 → VDD, 0 → GND
Output open-drain an open-drain output: 0 → GND, 1 → Hi-Z
AF push-pull the pin is driven by a peripheral, push-pull output
AF open-drain the pin is driven by a peripheral, open-drain output

What happens to the hardware in each mode is shown in the figures below:

Input — the output buffer is off, the Schmitt trigger is on, pull resistors as configured, the pin state is latched into IDR on every bus cycle.

Input configuration Input: floating, pulled up/down

Output — the output buffer works; reading IDR gives the real level on the pin, reading ODR gives the last value written. The difference matters: in open-drain mode an external circuit can "overpower" it, and the pin will not have what you wrote.

Output configuration Output

Alternate function — the buffer is controlled by a signal from a peripheral (USART, SPI, I²C). If you configure a pin as AF but do not enable the peripheral itself, the output state is undefined.

Alternate function configuration Alternate function

Analog — the output buffer is off, the Schmitt trigger is off (otherwise it would draw current at intermediate levels), pull resistors are off, reading IDR gives 0.

Analog input High-impedance analog input

Atomic access

The classic mistake is to change the pin state through ODR:

GPIOC->ODR |= (1 << 13);   // читать → изменить → записать: ТРИ операции

An interrupt can come or an RTOS task can switch between the read and the write — and a neighbouring bit changed there will be lost. That is why GPIO has separate registers BSRR and BRR: a write to them changes the selected ODR bits in a single bus operation, leaving the others untouched.


8.2 Registers

⚠️ The main fork of the whole section. STM32F1 and all the other families (F0, F3, F4, F7, L0, L4, G0, G4, H7…) have different GPIO register layouts. Code with MODER will not build for F1, code with CRL/CRH will not build for F4. When you meet examples that contradict each other, look not at "correctness" but at which generation of peripherals they were written for.

What we configure STM32F1 Other families
pin mode CRL / CRH — 4 bits each (MODE[1:0]+CNF[1:0]) MODER — 2 bits each
push-pull / open-drain in the same place, in CNF OTYPER
edge rate in the same place, in MODE OSPEEDR
pull resistors CNF=10 + a bit in ODR PUPDR
alternate function number none (only a coarse remap through AFIO_MAPR) AFRL / AFRH
read / write IDR / ODR the same
atomic set BSRR / BRR BSRR (BRR is not everywhere)

STM32F1 registers

Register Offset After reset Purpose
GPIOx_CRL 0x00 0x4444 4444 configuration of pins 0–7
GPIOx_CRH 0x04 0x4444 4444 configuration of pins 8–15
GPIOx_IDR 0x08 0x0000 XXXX input data, read-only
GPIOx_ODR 0x0C 0x0000 0000 output data
GPIOx_BSRR 0x10 0x0000 0000 atomic: set (BS) / reset (BR)
GPIOx_BRR 0x14 0x0000 0000 atomic: reset only
GPIOx_LCKR 0x18 0x0000 0000 configuration lock until MCU reset

The reset value 0x44444444 means CNF=01, MODE=00 for every pin — that is, after reset all pins are floating inputs.

GPIOx_CRL register bits Bits of the GPIOx_CRL register, ports x=A..G

4 bits per pin. For pin y: bits [4y+1:4y] are MODE, bits [4y+3:4y+2] are CNF.

MODE[1:0] — input or output, and at what speed:

MODE Value
00 input (after reset)
01 output, maximum 10 MHz
10 output, maximum 2 MHz
11 output, maximum 50 MHz

CNF[1:0] is read differently depending on MODE:

CNF if MODE=00 (input) if MODE≠00 (output)
00 analog push-pull
01 floating input (after reset) open-drain
10 input with pull AF push-pull
11 reserved AF open-drain

⚠️ A little-known detail. In input-with-pull mode (CNF=10) the direction of the pull is set not by a separate register but by a bit in ODR: 1 — up, 0 — down. If you configured CNF=10 and forgot about ODR, you get a pull-down, although the code says not a word about it. F4 and the others have an honest PUPDR for this.

Speed is not "the faster the better". The setting limits the edge steepness. Setting 50 MHz where an LED blinks means needlessly raising the electromagnetic interference from the board. For slow signals take 2 MHz.

BSRR and BRR

BSRR is 32 bits: the lower 16 (BS0..BS15) set ODR bits, the upper 16 (BR0..BR15) reset them. If both BS and BR are set for the same pin, BS wins.

BRR — reset only, the lower 16 bits.

Both registers are write-only, reading returns zeros.

GPIO register map GPIO register map and reset values

Configuration lock (LCKR)

Lets you "freeze" the pin configuration until the next MCU reset — protection against someone else's code accidentally reconfiguring a critical pin. It requires a strict sequence of writes of the LCKK key (bit 16): write 1 → write 0 → write 1 → read 0 → read 1. Any deviation cancels the lock.

What is taken right after reset

The pins of the JTAG/SWD debug interface come up not floating but in their own modes: PA15 (JTDI, pull-up), PA14 (JTCK, pull-down), PA13 (JTMS, pull-up), PB4 (JNTRST, pull-up).

⚠️ By taking PA13/PA14 for your own needs, you lose SWD — and with it the ability to reflash the board the usual way. Hence the commented-out lines with AFIO_MAPR_SWJ_CFG_JTAGDISABLE in the button example below: they are enabled deliberately and only when you are really short of pins.

STM32F4 registers

On F4 the same peripheral is designed in a fundamentally different way: instead of two registers with 4-bit fields — five separate registers, each with its own meaning. This is more convenient: change the speed — you touch only OSPEEDR, without risking the mode.

Register Offset Bits per pin Purpose
MODER 0x00 2 mode: input / output / alternate function / analog
OTYPER 0x04 1 0 — push-pull, 1 — open-drain
OSPEEDR 0x08 2 edge rate: low / medium / high / very high
PUPDR 0x0C 2 00 — none, 01 — up, 10 — down
IDR 0x10 1 input data, read-only
ODR 0x14 1 output data
BSRR 0x18 1 atomic set (BS) and reset (BR)
LCKR 0x1C 1 configuration lock
AFR[0], AFR[1] 0x20, 0x24 4 alternate function number AF0…AF15

MODER, two-bit fields:

MODER Mode
00 input (after reset, except the SWD pins)
01 general-purpose output
10 alternate function
11 analog

Three differences that keep F1 code from porting to F4:

  1. The ports are on a different bus. On F1 the clock is enabled in RCC->APB2ENR (IOPxEN), on F4 in RCC->AHB1ENR (GPIOxEN). Copied the line with APB2ENR — the port is silent.
  2. Different base addresses. GPIOA on F1 is 0x4001 0800, on F4 0x4002 0000, the step between ports is 0x400. The ODR offset on F1 is 0x0C, on F4 0x14.
  3. Alternate functions for real. On F4 each pin can get any of the sixteen alternate functions through AFR[0] (pins 0–7) and AFR[1] (8–15), 4 bits per pin. F1 has no such register at all — there is only a coarse "remap" with ready-made combinations through AFIO_MAPR. This is perhaps the most noticeable improvement of F4 for board layout: a peripheral can be put on almost any pins.
/* Назначить PA9 функцию AF7 (USART1_TX) — так это делается только у F4 */
GPIOA->MODER  &= ~GPIO_MODER_MODER9;
GPIOA->MODER  |=  GPIO_MODER_MODER9_1;          /* 10 -> альтернативная функция */
GPIOA->AFR[1] &= ~GPIO_AFRH_AFSEL9;             /* вывод 9 -> AFR[1], биты 7:4 */
GPIOA->AFR[1] |=  (7u << GPIO_AFRH_AFSEL9_Pos); /* AF7 */

⚠️ The alternate function number is not universal: AF7 is USART1/2/3 on F407, but on another chip the same number may carry a different peripheral. Look up the alternate function table in the datasheet of the specific MCU (not in the reference manual — it is not there).


8.3 HAL, LL and CubeMX

HAL hides the differences between families behind one structure.

typedef struct {
  uint32_t Pin;        // маска выводов: GPIO_PIN_5, или GPIO_PIN_1|GPIO_PIN_5
  uint32_t Mode;       // GPIO_MODE_OUTPUT_PP, GPIO_MODE_INPUT, ...
  uint32_t Pull;       // GPIO_NOPULL | GPIO_PULLUP | GPIO_PULLDOWN
  uint32_t Speed;      // GPIO_SPEED_FREQ_LOW ... _VERY_HIGH
  uint32_t Alternate;  // номер альтернативной функции
} GPIO_InitTypeDef;

Pin is a mask, not a number: GPIO_PIN_5 equals 0x0020, not 5. The Alternate field does not exist in CubeF1 — F1 has no AFRL/AFRH registers, the remap is done through AFIO_MAPR.

The Mode values, besides the obvious ones, include interrupt and event modes: GPIO_MODE_IT_RISING, GPIO_MODE_IT_FALLING, GPIO_MODE_IT_RISING_FALLING (covered in section 7 on EXTI) and GPIO_MODE_EVT_* — waking the core by the WFE instruction without calling a handler.

Five functions for all work with a pin:

GPIO_PinState HAL_GPIO_ReadPin (GPIO_TypeDef *GPIOx, uint16_t Pin);
void          HAL_GPIO_WritePin(GPIO_TypeDef *GPIOx, uint16_t Pin, GPIO_PinState St);
void          HAL_GPIO_TogglePin(GPIO_TypeDef *GPIOx, uint16_t Pin);
HAL_StatusTypeDef HAL_GPIO_LockPin(GPIO_TypeDef *GPIOx, uint16_t Pin);
void          HAL_GPIO_DeInit  (GPIO_TypeDef *GPIOx, uint32_t Pin);

ReadPin returns GPIO_PIN_RESET or GPIO_PIN_SET. DeInit returns the pin to its reset state (floating input) — useful before going to sleep, so as not to waste current.

The same thing in three ways — turn on PA5:

/* регистры, F1 */          GPIOA->BSRR = GPIO_BSRR_BS5;
/* LL */                    LL_GPIO_SetOutputPin(GPIOA, LL_GPIO_PIN_5);
/* HAL */                   HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);

All three compile into a write to BSRR. HAL adds a function call, LL an inline wrapper. The choice is a matter of readability and portability, not speed.


8.4 🔧 Recipe: blinking an LED

A BluePill board (STM32F103C8T6), an LED on PC13.

The LED on PC13 is connected with its cathode to the microcontroller pin

#include "stm32f10x.h"

void PORTC_13_INIT (void)
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPCEN;      // такт на порт C — без него порт мёртв

    GPIOC->CRH &= ~GPIO_CRH_MODE13;          // сброс MODE13
    GPIOC->CRH |=  GPIO_CRH_MODE13_1;        // MODE13 = 10 -> выход 2 МГц
    GPIOC->CRH &= ~GPIO_CRH_CNF13;           // CNF13  = 00 -> push-pull
}

int main (void)
{
    PORTC_13_INIT();

    while (1)
    {
        GPIOC->BSRR = GPIO_BSRR_BR13;        // зажечь: светодиод включён катодом

        for (volatile int i = 0; i < 1000000; i++) { }

        GPIOC->BSRR = GPIO_BSRR_BS13;        // погасить

        for (volatile int i = 0; i < 1000000; i++) { }
    }
}

Walkthrough:

⚠️ The BluePill LED lights on zero. PC13 is connected to the cathode: BR13 (a zero at the output) turns it on, BS13 turns it off. And BSRR is a write-only register, so it is written with =, not |=: reading gives 0, and |= only adds an unnecessary bus access.

The same with HAL:

__HAL_RCC_GPIOC_CLK_ENABLE();

GPIO_InitTypeDef g = {0};
g.Pin   = GPIO_PIN_13;
g.Mode  = GPIO_MODE_OUTPUT_PP;
g.Pull  = GPIO_NOPULL;
g.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOC, &g);

while (1) {
    HAL_GPIO_TogglePin(GPIOC, GPIO_PIN_13);
    HAL_Delay(500);
}

The same on STM32F4 — Discovery F407, the green LED LD4 on PD12:

#include "stm32f4xx.h"

void PD12_INIT(void)
{
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN;        /* AHB1, а не APB2 */

    GPIOD->MODER   &= ~GPIO_MODER_MODER12;
    GPIOD->MODER   |=  GPIO_MODER_MODER12_0;    /* 01 -> выход */
    GPIOD->OTYPER  &= ~GPIO_OTYPER_OT12;        /* push-pull */
    GPIOD->OSPEEDR &= ~GPIO_OSPEEDR_OSPEED12;   /* низкая скорость */
    GPIOD->PUPDR   &= ~GPIO_PUPDR_PUPD12;       /* без подтяжек */
}

int main(void)
{
    PD12_INIT();

    while (1)
    {
        GPIOD->BSRR = GPIO_BSRR_BS12;           /* только запись, без |= */
        for (volatile int i = 0; i < 1000000; i++) { }
        GPIOD->BSRR = GPIO_BSRR_BR12;
        for (volatile int i = 0; i < 1000000; i++) { }
    }
}

On Nucleo-F401RE/F411RE the LED LD2 sits on PA5 — only the port and the pin number change, the structure of the code is the same.

Compare with the F1 version above: five separate registers instead of CRH, AHB1ENR instead of APB2ENR. The logic is identical — enable the clock, set the mode, write to BSRR. This is the practical meaning of the "core versus peripherals" split from section 1.


8.5 🔧 Recipe: polling a button

A button on PA0, an LED on PB2.

A button with a pull-up resistor to the supply

uint32_t Button_State = 0;

#define LED_PB2_ON()   (GPIOB->BSRR = GPIO_BSRR_BS2)
#define LED_PB2_OFF()  (GPIOB->BSRR = GPIO_BSRR_BR2)

void PINA_0_INIT (void)                      // кнопка PA0
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;

    GPIOA->CRL &= ~GPIO_CRL_MODE0_0;         // MODE0 = 00 -> вход
    GPIOA->CRL &= ~GPIO_CRL_MODE0_1;

    GPIOA->CRL |=  GPIO_CRL_CNF0_0;          // CNF0 = 01 -> плавающий вход:
    GPIOA->CRL &= ~GPIO_CRL_CNF0_1;          // подтяжка стоит на плате
}

void PINB_2_INIT (void)                      // светодиод PB2
{
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

    GPIOB->CRL &= ~GPIO_CRL_MODE2_0;         // MODE2 = 10 -> выход 2 МГц
    GPIOB->CRL |=  GPIO_CRL_MODE2_1;

    GPIOB->CRL &= ~GPIO_CRL_CNF2_0;          // CNF2 = 00 -> push-pull
    GPIOB->CRL &= ~GPIO_CRL_CNF2_1;
}

int main (void)
{
    PINB_2_INIT();
    PINA_0_INIT();

//  SET_BIT(RCC->APB2ENR, RCC_APB2ENR_AFIOEN);
//  CLEAR_BIT(AFIO->MAPR, AFIO_MAPR_SWJ_CFG);
//  SET_BIT(AFIO->MAPR, AFIO_MAPR_SWJ_CFG_JTAGDISABLE);

    while (1)
    {
        Button_State = READ_BIT(GPIOA->IDR, GPIO_IDR_IDR0);

        if (Button_State == 1)
            {
             LED_PB2_ON();
            }
        else
            {
               LED_PB2_OFF();
            }
    }
}

⚠️ READ_BIT returns a mask, not a one. Here the comparison with 1 works only because GPIO_IDR_IDR0 equals 0x0001. Move the button to PA5 — and READ_BIT will return 0x0020, the condition == 1 will never be met, and the code will look correct. And do not put the mask into a uint8_t: for pins 8–15 it will be truncated to zero, and even != 0 will not work — that is why Button_State is 32-bit here. This is more reliable:

c if (READ_BIT(GPIOA->IDR, GPIO_IDR_IDR5) != 0) { ... }

⚠️ A floating input under a button holds an undefined level while the button is not pressed — the pin picks up noise. A pull is needed: an external resistor or CNF0 = 10 plus the corresponding bit in ODR (see the pitfall in 8.2).

Bounce. A mechanical contact gives a burst of pulses on press and release lasting from one to tens of milliseconds. In a polling loop this shows up as multiple triggering. The cure is a software delay after the first edge, latching the state on two matching readings 10–20 ms apart, or an RC network on the board:

Debouncing with an RC network

Handling a button on an interrupt — section 7 (EXTI).


8.6 F0 and G0: registers as on F4, plus BRR

The F0 and G0 ports are built like F4's: MODER, OTYPER, OSPEEDR, PUPDR, AFR, and BSRR at the same offset 0x18. On top there is a BRR register at 0x28 — resetting a pin by writing a one, separately from BSRR. There are three differences, and each breaks code porting:

F0 G0
port clock RCC->AHBENR RCC->IOPENR
port A MODER after reset 0x28000000 — inputs 0xEBFFFFFF — analog
bit names GPIO_MODER_MODER5, GPIO_BSRR_BS_5, GPIO_IDR_0 GPIO_MODER_MODE5, GPIO_BSRR_BS5, GPIO_IDR_ID0

The reset values come from the peripheral description files (SVD) in the packs. On port B of G0 MODER after reset is 0xFFFFFFFF: all pins are analog.

On G0 an input must be enabled explicitly. Analog mode (MODER = 11) saves current, but the digital input is off in it — IDR reads zero whatever is on the pin. F4 code that relied on "after reset it is an input" sees the button always pressed on G0:

RCC->IOPENR |= RCC_IOPENR_GPIOAEN;                          /* порты у G0 — регистр IOPENR */
GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODE5) | GPIO_MODER_MODE5_0;     /* 01 — выход */
GPIOA->MODER &= ~GPIO_MODER_MODE0;                          /* 00 — вход: после сброса там 11 */
GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_0;

The bit names are different. The G0 header names the fields in a new way, and code for F0 does not build with it: "use of undeclared identifier 'GPIO_MODER_MODER5'". The same LED on F0:

RCC->AHBENR |= RCC_AHBENR_GPIOAEN;                          /* порты у F0 — на шине AHB */
GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER5) | GPIO_MODER_MODER5_0;   /* 01 — выход */
GPIOA->BSRR = GPIO_BSRR_BS_5;
GPIOA->BRR = GPIO_BRR_BR_5;                                 /* то же, что BSRR = BR_5 */

The portable way is not the names of individual bits but masks by pin number (3u << (2u * pin) for MODER) or the LL library: its functions are named the same on both series.


8.7 The same pin on 1986VE9x

The same tasks on the Russian Cortex-M3 from Milandr are solved identically in meaning: enable the port clock, set the mode, write to the data register. What differs is the register names and the fact that a pin is configured not by a MODE/CNF pair but by separate registers OE, FUNC, ANALOG, PULL, PWR.

The practical value here is not in the API differences but in the fact that the line of reasoning carries over between different Cortex-M cores unchanged.


8.8 Exercises

Eight LEDs LED1..LED8 on the pins of one BluePill port. Every step of any exercise is one write to ODR or BSRR, without reading the port.

  1. "Running light": one LED, from left to right.
  2. A "running light" of three LEDs in a row.
  3. "Accumulation" from left to right: they light up one by one and stay on.
  4. "Collapse": pairs from the edges converge to the middle in turn (LED1+LED8, LED2+LED7, …), then go out in the reverse order.

One more — on logical expressions: implement an output whose state equals a logical function of two buttons (AND, OR, exclusive OR), without using branches — only bit operations on IDR and BSRR.


8.9 Pitfalls

An example with MODER does not build for F1, an example with CRL does not build for F4. GPIO on F1 and on the other families are built differently (8.2).

An input with pull pulls down, although not a word is said about it. On F1 the pull direction is set by a bit in ODR (8.2).

After "freeing" PA13/PA14 the board cannot be flashed. The SWD pins were given to something else — disable JTAG/SWD only deliberately (8.2).

The wrong alternate function. The AF number means different things on different chips; the table is in the datasheet of the specific chip (8.2).

The BluePill LED lights the other way round. It is connected with its cathode to PC13: BS13 turns it off, BR13 turns it on (8.4).

A button on another pin stopped being read. READ_BIT returned a mask and the code compared it with 1 — or the mask was put into a uint8_t, and for pins 8–15 it was truncated to zero (8.5).

The button "presses" itself. A floating input without a pull picks up noise (8.5).


8.10 Summary

Full sources

gpio_f0.c — LED PA5 and button PA0 on STM32F072.

/* Раздел 8.6: светодиод PA5 и кнопка PA0 на STM32F072.
 * Регистры — как у F4 (MODER, OTYPER, OSPEEDR, PUPDR) плюс BRR; такт портов — на AHB. */
#include <stddef.h>
#include "stm32f0xx.h"

_Static_assert(offsetof(GPIO_TypeDef, MODER) == 0x00u && offsetof(GPIO_TypeDef, BSRR) == 0x18u, "как у F4");
_Static_assert(offsetof(GPIO_TypeDef, BRR) == 0x28u, "BRR — сброс вывода записью единицы, у F4 его нет");

void led_init_f0(void)
{
    RCC->AHBENR |= RCC_AHBENR_GPIOAEN;                          /* порты у F0 — на шине AHB */
    GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER5) | GPIO_MODER_MODER5_0;   /* 01 — выход */
    GPIOA->PUPDR |= GPIO_PUPDR_PUPDR0_0;                        /* PA0 — вход с подтяжкой вверх */
}

void led_on_f0(void)
{
    GPIOA->BSRR = GPIO_BSRR_BS_5;
}

void led_off_f0(void)
{
    GPIOA->BRR = GPIO_BRR_BR_5;                                 /* то же, что BSRR = BR_5 */
}

uint32_t button_f0(void)
{
    return (GPIOA->IDR & GPIO_IDR_0) == 0u;                     /* нажата — вывод на земле */
}

gpio_g0.c — the same on STM32G071: after reset the input is analog, it is enabled explicitly.

/* Раздел 8.6: светодиод PA5 и кнопка PA0 на STM32G071.
 * После сброса выводы G0 — аналоговые (MODER = 11): вход надо включить явно. */
#include <stddef.h>
#include "stm32g0xx.h"

_Static_assert(offsetof(GPIO_TypeDef, BRR) == 0x28u, "BRR на том же месте, что у F0");

void led_init_g0(void)
{
    RCC->IOPENR |= RCC_IOPENR_GPIOAEN;                          /* порты у G0 — регистр IOPENR */
    GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODE5) | GPIO_MODER_MODE5_0;     /* 01 — выход */
    GPIOA->MODER &= ~GPIO_MODER_MODE0;                          /* 00 — вход: после сброса там 11 */
    GPIOA->PUPDR |= GPIO_PUPDR_PUPD0_0;
}

void led_on_g0(void)
{
    GPIOA->BSRR = GPIO_BSRR_BS5;
}

void led_off_g0(void)
{
    GPIOA->BRR = GPIO_BRR_BR5;
}

uint32_t button_g0(void)
{
    return (GPIOA->IDR & GPIO_IDR_ID0) == 0u;
}

Next: section 9 "Direct memory access (DMA)".

Open in the interactive handbook