Komunikacja UART na mikrokontrolerach STM32 to jedno z najpotężniejszych narzędzi do debugowania i monitorowania kodu w czasie rzeczywistym. Dzięki niej możesz wysyłać dane z mikrokontrolera do komputera, wyświetlać zmienne, komunikaty diagnostyczne czy nawet strumieniować telemetryczne informacje z Twojego robota bez użycia pełnego debugera SWD. W tym rozbudowanym artykule omówimy krok po kroku konfigurację UART w STM32CubeMX i STM32CubeIDE, implementację serial print dla debugowania, różnice między płytkami Nucleo a Blue Pill, obsługę przerwań oraz praktyczne przykłady kodu.
UART (Universal Asynchronous Receiver-Transmitter) działa w trybie asynchronicznym, co oznacza brak wspólnego zegara – transmisja opiera się na ustalonych parametrach jak baud rate (np. 115200), liczba bitów danych (zwykle 8), bity stopu (1 lub 2) i parzystość (brak/parzysta/nieparzysta). To idealne rozwiązanie do komunikacji z PC poprzez terminale, takie jak wbudowany Serial Monitor w STM32CubeIDE, PuTTY czy Tera Term.
Dlaczego UART do debugowania STM32?
UART umożliwia debugowanie w stylu printf, działa szybko i bez programatora po wgraniu kodu – idealnie sprawdza się w telemetryce i testach w terenie.
Zalety UART w robotyce i elektronice
Najważniejsze atuty to:
- podgląd w czasie rzeczywistym – bez zatrzymywania programu możesz śledzić wartości sensorów, flagi błędów i stany algorytmów;
- niski koszt – na płytkach Nucleo USART2 łączy się z wirtualnym portem COM przez ST-LINK bez dodatkowych przewodów;
- uniwersalność – pełna kompatybilność z popularnymi konwerterami USB–TTL dla tanich płytek (np. Blue Pill);
- obsługa przerwań i DMA – wygodna, dwukierunkowa komunikacja dla komend i strumieniowania danych.
Wady
Brak inspekcji rejestrów jak w SWD; wymaga zgodnej konfiguracji po obu stronach (baud rate, format ramek) oraz odpowiedniego przypisania pinów.
Wymagane narzędzia i sprzęt
Do szybkiego startu przydadzą się:
- STM32CubeIDE – darmowe środowisko z terminalem szeregowym i debugerem;
- STM32CubeMX – graficzna konfiguracja peryferiów i generator kodu;
- płytki testowe – Nucleo-32 L432KC (ze ST-LINK) lub Blue Pill (STM32F103C8, wymaga USB–TTL);
- konwerter USB–TTL 3.3 V – np. CP2102 lub CH340 dla Blue Pill;
- terminale – wbudowany Terminal w STM32CubeIDE, PuTTY, Tera Term.
STM32L432KC posiada 3 moduły USART (1, 2, 3); USART2 łączy się z ST-LINK bez dodatkowego okablowania. STM32F103C8 na Blue Pill używa UART1 (piny PA9–TX, PA10–RX).
Konfiguracja UART w STM32CubeMX – krok po kroku
Dla płytek Nucleo (USART2 z ST-LINK)
- Otwórz STM32CubeMX i utwórz nowy projekt – wybierz MCU (np. STM32L432KC);
- Włącz USART2 w trybie asynchronicznym – zakładka Connectivity → USART2;
- Ustaw parametry – 115200 8N1 (baud 115200, 8 bitów, parzystość none, 1 bit stopu);
- Przypisz piny – automatycznie PA2 (TX), PA3 (RX) (AF7);
- Skonfiguruj zegary – np. HCLK = 80 MHz, źródło zegara UART na HCLK lub APB1;
- Wygeneruj kod – Project Manager → STM32CubeIDE.
Schemat połączenia
Brak dodatkowych przewodów – ST-LINK tworzy wirtualny port COM (sprawdź numer w Menedżerze urządzeń).
Dla Blue Pill (UART1 z USB–TTL)
- Utwórz nowy projekt w STM32CubeMX – wybierz MCU STM32F103C8T6;
- Włącz USART1 w trybie asynchronicznym – zakładka Connectivity → USART1;
- Ustaw parametry jak wyżej – piny PA9 (TX), PA10 (RX);
- Wygeneruj kod – Project Manager → STM32CubeIDE.
Połączenia fizyczne
| Pin STM32 | Pin USB–TTL | Opis |
|---|---|---|
| PA9 (TX) | RX | Wysyłanie z MCU |
| GND | GND | Masa |
| PA10 (RX) – opcjonalnie | TX | Odbiór do MCU |
Uwaga – piny STM32F103 nie tolerują 5 V. Używaj wyłącznie konwertera USB–TTL 3.3 V.
Implementacja kodu – podstawowa transmisja
Po wygenerowaniu projektu w STM32CubeIDE dodaj wysyłanie danych w pętli głównej. HAL (Hardware Abstraction Layer) upraszcza konfigurację i zwiększa przenośność kodu.
Przykład 1 – prosty licznik (Nucleo USART2, HAL)
#include "main.h"
#include <stdio.h>
#include <string.h>
extern UART_HandleTypeDef huart2;
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
uint32_t x = 0;
char msg[64];
while (1) {
int n = snprintf(msg, sizeof(msg), "Wartosc X = %lu\r\n", (unsigned long)x);
if (n > 0) {
HAL_UART_Transmit(&huart2, (uint8_t*)msg, (uint16_t)n, 100);
}
HAL_Delay(500);
x++;
}
}
Kroki uruchomienia
- Skompiluj i uruchom projekt w STM32CubeIDE;
- Sprawdź port COM – Menedżer urządzeń → Porty (COM i LPT) → ST-LINK Virtual COM Port (np. COM3);
- Otwórz terminal szeregowy – Widok → Terminal → Nowy → Port szeregowy → 115200, UTF‑8;
- Efekt – na konsoli pojawiają się kolejne linie: „Wartosc X = 0”, „Wartosc X = 1” co 0.5 s.
Przykład 2 – transmisja na rejestrach (bez HAL, STM32L4xx)
Poniższy przykład ilustruje podstawy konfiguracji USART2 rejestrowo na STM32L4xx (PA2=TX, PA3=RX, założony PCLK1=80 MHz). Dostosuj rejestry i piny do swojej rodziny MCU.
#include "stm32l4xx.h"
static void UART2_Init_Registers(void) {
// Zegary: GPIOA i USART2
RCC->AHB2ENR |= RCC_AHB2ENR_GPIOAEN;
RCC->APB1ENR1 |= RCC_APB1ENR1_USART2EN;
// PA2, PA3 jako AF7 (USART2)
// MODER: 10 = Alternate
GPIOA->MODER &= ~((0x3u << (2*2)) | (0x3u << (3*2)));
GPIOA->MODER |= (0x2u << (2*2)) | (0x2u << (3*2));
// AFRL: AF7 dla PA2 i PA3
GPIOA->AFR[0] &= ~((0xFu << (4*2)) | (0xFu << (4*3)));
GPIOA->AFR[0] |= (0x7u << (4*2)) | (0x7u << (4*3));
// Prędkość wysoka (opcjonalnie)
GPIOA->OSPEEDR |= (0x3u << (2*2)) | (0x3u << (3*2));
// Baud rate: BRR = PCLK1 / 115200 = ~694 (dla oversampling 16)
USART2->BRR = 694u;
// Włącz: nadajnik, odbiornik, UART
USART2->CR1 = USART_CR1_TE | USART_CR1_RE | USART_CR1_UE;
}
int main(void) {
UART2_Init_Registers();
for (;;) {
// Czekaj aż bufor nadawczy pusty
while (!(USART2->ISR & USART_ISR_TXE)) {}
USART2->TDR = 'A';
// Czekaj na zakończenie transmisji
while (!(USART2->ISR & USART_ISR_TC)) {}
}
}
Podłącz konwerter USB–TTL do PA2/PA3 i otwórz terminal (np. PuTTY, 115200 8N1).
Debugowanie z przerwaniami (dwukierunkowa komunikacja)
Przerwania RX pozwalają reagować na komendy z PC bez blokowania pętli głównej (np. „START” do uruchamiania silników).
#include "main.h"
#include <string.h>
extern UART_HandleTypeDef huart2;
static uint8_t rx_byte;
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART2_UART_Init();
// Start odbioru jednego bajtu w przerwaniu
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
while (1) {
// Główna pętla – logika sterowania robotem itp.
}
}
// Callback wywoływany po odebraniu 1 bajtu
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
// Echa back: odesłanie odebranego znaku
HAL_UART_Transmit(&huart2, &rx_byte, 1, 50);
// Restart odbioru kolejnego bajtu
HAL_UART_Receive_IT(&huart2, &rx_byte, 1);
}
}
// Opcjonalnie: obsługa błędów (np. ORE) w HAL_UART_ErrorCallback
Najczęstsza przyczyna „działa tylko w debug” – inicjalizacja odbioru IT (HAL_UART_Receive_IT) umieszczona w sekcjach warunkowych, opóźniona lub wykonywana przed inicjalizacją UART. Przenieś wywołanie do main() po MX_USARTx_UART_Init().
Przekierowanie printf do UART (zaawansowany debug)
Nadpisanie funkcji _write() pozwala używać printf bez dodatkowego kodu nadawania.
#include "usart.h" // uchwyt huart2
#include <unistd.h>
int _write(int file, char *ptr, int len) {
(void)file;
HAL_UART_Transmit(&huart2, (uint8_t*)ptr, (uint16_t)len, HAL_MAX_DELAY);
return len;
}
// Przykład:
// printf("Czujnik: %d C, Silnik: %d RPM\r\n", temp, rpm);
W trybie produkcyjnym połącz się przez ST-LINK Virtual COM (Nucleo) lub konwerter USB–TTL (Blue Pill) i otwórz terminal – dane będą płynąć w zadanym interwale.
Popularne problemy i rozwiązania
| Problem | Przyczyna | Rozwiązanie |
|---|---|---|
| Brak danych na terminalu | Zły port COM lub parametry ramek | Sprawdź numer COM w systemie; ustaw 115200 8N1 i poprawny konwerter/sterowniki |
| UART działa tylko w trybie debug | Nieprawidłowa kolejność inicjalizacji lub warunek kompilacji | Wywołaj HAL_UART_Receive_IT po inicjalizacji UART; wyłącz semihosting, sprawdź sekcje USER CODE |
| Szumy/krzaki na Blue Pill | Poziomy 5 V lub niezgodny baud rate | Użyj konwertera 3.3 V; ujednolić baud po obu stronach; skróć przewody, zadbaj o masę |
| Buffer overflow | Zbyt wolna obróbka RX lub brak bufora | Zastosuj DMA, bufor cykliczny (ring buffer) i parsowanie w tle (np. w przerwaniu/RTOS) |
Wskazówka dla robotyki – integruj UART z FreeRTOS, wysyłając komunikaty przez kolejki (queue) do zadań silników i sensorów, co stabilizuje przepływ danych.
Zastosowania w robotyce i elektronice
Oto praktyczne scenariusze wykorzystania UART w projektach embedded:
- telemetria w autonomicznych robotach – wysyłanie pozycji GPS, prędkości kół, temperatur i stanu baterii;
- zdalna konfiguracja – odbiór i zapis parametrów regulatorów (np. PID) bez rekompilacji;
- rejestrowanie błędów – czytelne komunikaty („ERROR: Czujnik IR out of range”) zamiast migania diodami;
- łączność z modułami – komunikacja z Bluetooth / Wi‑Fi przez mostki UART.
W porównaniu do SWD, UART jest lżejszy i działa bez zewnętrznego programatora po wdrożeniu, dlatego świetnie nadaje się do stałej diagnostyki w urządzeniach embedded.