Mikrokontrolery STM32 oferują zaawansowane porty GPIO (General Purpose Input/Output), które umożliwiają łatwą konfigurację pinów jako wejść cyfrowych, wyjść lub funkcji specjalistycznych.

GPIO to podstawa większości projektów w robotyce i elektronice, pozwalająca sterować diodami LED, silnikami, sensorami czy przyciskami.

W tym artykule omówimy krok po kroku konfigurację GPIO na przykładzie popularnych płytek Nucleo i Black Pill, korzystając zarówno z programowania na rejestrach, jak i narzędzi STM32CubeMX z HAL/LL.

Skupimy się na praktycznych przykładach dla wyjść (sterowanie diodą) i wejść (odczyt przycisku), z kodem źródłowym gotowym do kopiowania.

Budowa portu GPIO w STM32

Każdy port GPIO (np. GPIOA, GPIOB) składa się z 16 pinów i jest kontrolowany przez dedykowane rejestry. Pin może pracować w jednym z czterech trybów: wejście, wyjście, analogowy lub funkcja alternatywna (np. UART, PWM).

Kluczowe rejestry GPIOx (gdzie x to A, B itp.):

  • MODER – ustawia tryb pinu (00=wejście, 01=wyjście, 10=alternatywna, 11=analogowy);
  • OTYPER – typ wyjścia (0=push-pull, 1=open-drain);
  • OSPEEDR – prędkość przełączania (00=niska, 01=średnia, 10=wysoka, 11=bardzo wysoka);
  • PUPDR – podciągnięcia (00=brak, 01=pull-up, 10=pull-down, 11=rezerwowane);
  • IDR – odczyt stanu wejścia;
  • ODR – stan wyjścia (do odczytu/zapisu);
  • BSRR – atomowe ustawianie (dolne 16 bitów=set=1, górne 16=reset=0).

Ważne: przed konfiguracją zawsze włącz taktowanie portu w bloku RCC (Reset and Clock Control), np. RCC->IOPENR |= RCC_IOPENR_GPIOAEN; dla GPIOA w STM32C0.

Blok wejściowy jest zawsze aktywny, nawet w trybie wyjścia – pozwala to na odczyt stanu pinu (np. w I2C).

Konfiguracja wyjścia GPIO – miganie diodą LED

Najprostszy przykład: sterowanie diodą LED na płytce NUCLEO-C031C6 (pin PA5) lub Black Pill (pin PC13).

Krok 1 – włączenie zegara RCC

// Włączenie taktowania GPIOA (dostosuj do portu!)
RCC->IOPENR |= RCC_IOPENR_GPIOAEN; // Dla STM32C0

Uwaga: inne serie STM32 (np. F4) używają RCC->AHB1ENR. Zawsze sprawdź Reference Manual dla swojego mikrokontrolera!

Krok 2 – konfiguracja pinu (w rejestrach)

Pełna funkcja dla PA5 jako wyjścia push-pull, niska prędkość, bez pull-up:

void ConfigureLD4(void) {
// Włączenie zegara
RCC->IOPENR |= RCC_IOPENR_GPIOAEN;

// MODER: PA5 jako wyjście (01)
GPIOA->MODER &= ~(GPIO_MODER_MODE5); // Czyść bity
GPIOA->MODER |= GPIO_MODER_MODE5_0; // Ustaw bit 0

// OTYPER: Push-pull (0, domyślne)
GPIOA->OTYPER &= ~(GPIO_OTYPER_OT5);

// OSPEEDR: Niska prędkość (00, domyślne)
GPIOA->OSPEEDR &= ~(GPIO_OSPEEDR_OSPEED5);

// PUPDR: Bez podciągnięcia (00, domyślne)
GPIOA->PUPDR &= ~(GPIO_PUPDR_PUPD5);
}

Stan domyślny po resecie to wejście analogowe – zawsze jawnie konfiguruj piny!

Krok 3 – sterowanie wyjściem

Najważniejsze sposoby ustawiania stanu pinu to:

  • ustaw 1GPIOA->BSRR = GPIO_BSRR_BS_5; (atomowe, nie wpływa na inne piny);
  • ustaw 0GPIOA->BSRR = GPIO_BSRR_BR_5;;
  • bezpośrednio w ODRGPIOA->ODR |= GPIO_ODR_OD5; (1) oraz GPIOA->ODR &= ~GPIO_ODR_OD5; (0).

Pełny kod migania z SysTick (timer programowy):

volatile uint32_t tick = 0;

void SysTick_Handler(void) {
tick++;
}

int main(void) {
ConfigureLD4();

SysTick_Load = 8000000 / 2; // 0.5s przy 8MHz
SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_TICKINT_Msk | SysTick_CTRL_ENABLE_Msk;

while(1) {
if (tick % 2 == 0) {
GPIOA->BSRR = GPIO_BSRR_BS_5; // LED on
} else {
GPIOA->BSRR = GPIO_BSRR_BR_5; // LED off
}
}
}

To działa wyłącznie na rejestrach – bez warstwy HAL!

Konfiguracja wejścia GPIO – odczyt przycisku

Dla wejścia (np. przycisk na PC13) ustaw: MODER=00 (wejście) oraz PUPDR=01 (pull-up, aby zapobiec dryftowi).

Przykładowa konfiguracja i odczyt:

void ConfigureButton(void) {
RCC->IOPENR |= RCC_IOPENR_GPIOCEN;
GPIOC->MODER &= ~(GPIO_MODER_MODE13); // Wejście (00)
GPIOC->PUPDR |= GPIO_PUPDR_PUPD13_0; // Pull-up (01)
}

uint8_t ReadButton(void) {
return (GPIOC->IDR & GPIO_IDR_ID13) ? 1 : 0; // 1=przyciśnięty (jeśli podłączony do GND)
}

W trybie wejścia pin odczytuje stan logiczny z rejestru IDR.

Pływające (floating) wejście to brak podciągnięcia i większa wrażliwość na zakłócenia. Pull-up/pull-down stabilizuje poziom logiczny pinu, ograniczając błędne odczyty.

Używanie STM32CubeMX i HAL/LL – łatwiejsza metoda

Dla początkujących warto wybrać STM32CubeIDE z graficzną konfiguracją:

  1. Utwórz projekt w CubeMX, wybierz mikrokontroler (np. STM32C031C6).
  2. Kliknij pin (np. PC13), ustaw GPIO_Output > Push-Pull > Low Speed > No Pull-up.
  3. Wygeneruj kod – automatycznie tworzy MX_GPIO_Init() z ustawieniami.
  4. Dodaj kod w main():

/* USER CODE BEGIN PFP */
extern void SystemClock_Config(void);
extern void MX_GPIO_Init(void);
/* USER CODE END PFP */

int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init(); // Konfiguracja z CubeMX

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

CubeMX domyślnie ustawia nieużywane piny jako analogowe, co ogranicza pobór energii.

LL (Low Layer) działa bliżej rejestrów niż HAL i oferuje funkcje, np. LL_GPIO_SetPinMode(), zapewniając mniejsze narzuty i wyższą wydajność.

Porównanie trybów wyjścia i wejścia

Poniższa tabela zestawia najważniejsze ustawienia trybów GPIO i ich typowe zastosowania:

Tryb Rejestr MODER OTYPER (wyjście) PUPDR Zastosowanie
Wyjście push-pull 01 0 00 (brak) Dioda LED, przekaźnik
Wyjście open-drain 01 1 01/10 I2C (z pull-up)
Wejście 00 01 (pull-up) Przycisk, sensor cyfrowy
Analog 11 ADC (potencjometr)

Open-drain oznacza „otwarty kolektor/otwarty dren” – aby uzyskać logiczną jedynkę, konieczny jest zewnętrzny lub wewnętrzny rezystor pull-up.

Praktyczne wskazówki w robotyce

Aby uniknąć typowych błędów i poprawić niezawodność układu, pamiętaj o poniższych zasadach:

  • szybkość – ustaw wysoką (11 w OSPEEDR) dla szybkich sygnałów (np. PWM), niską dla LED;
  • zużycie prądu – nieużywane piny ustaw jako analogowe, aby zminimalizować pobór;
  • dokumentacja – zawsze korzystaj z Reference Manual (RM) dla swojego MCU, ponieważ rejestry różnią się między seriami;
  • błędy – najczęstszy problem to zapomniane włączenie zegara RCC, co skutkuje brakiem reakcji pinu;
  • zastosowania – w robotach GPIO służy do sterowania mostkami H-bridge, odczytu enkoderów i komunikacji z modułami (np. HC‑SR04);
  • prototypowanie – testuj na płytkach Nucleo/Black Pill z wbudowaną diodą i przyciskiem.