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 1 –
GPIOA->BSRR = GPIO_BSRR_BS_5;(atomowe, nie wpływa na inne piny); - ustaw 0 –
GPIOA->BSRR = GPIO_BSRR_BR_5;; - bezpośrednio w ODR –
GPIOA->ODR |= GPIO_ODR_OD5;(1) orazGPIOA->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ą:
- Utwórz projekt w CubeMX, wybierz mikrokontroler (np. STM32C031C6).
- Kliknij pin (np. PC13), ustaw GPIO_Output > Push-Pull > Low Speed > No Pull-up.
- Wygeneruj kod – automatycznie tworzy
MX_GPIO_Init()z ustawieniami. - 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.