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)

  1. Otwórz STM32CubeMX i utwórz nowy projekt – wybierz MCU (np. STM32L432KC);
  2. Włącz USART2 w trybie asynchronicznym – zakładka Connectivity → USART2;
  3. Ustaw parametry – 115200 8N1 (baud 115200, 8 bitów, parzystość none, 1 bit stopu);
  4. Przypisz piny – automatycznie PA2 (TX), PA3 (RX) (AF7);
  5. Skonfiguruj zegary – np. HCLK = 80 MHz, źródło zegara UART na HCLK lub APB1;
  6. 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)

  1. Utwórz nowy projekt w STM32CubeMX – wybierz MCU STM32F103C8T6;
  2. Włącz USART1 w trybie asynchronicznym – zakładka Connectivity → USART1;
  3. Ustaw parametry jak wyżej – piny PA9 (TX), PA10 (RX);
  4. 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

  1. Skompiluj i uruchom projekt w STM32CubeIDE;
  2. Sprawdź port COM – Menedżer urządzeń → Porty (COM i LPT) → ST-LINK Virtual COM Port (np. COM3);
  3. Otwórz terminal szeregowy – Widok → Terminal → Nowy → Port szeregowy → 115200, UTF‑8;
  4. 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.