Modulacja szerokości impulsu (PWM) to jedna z najważniejszych technik sterowania urządzeniami elektronicznymi w systemach z mikrokontrolerami. W STM32 generowanie PWM opiera się na wbudowanych timerach, które pozwalają tworzyć sygnały o zmiennym wypełnieniu przy stałej częstotliwości. Poniższy przewodnik pokazuje, jak konfigurować i wykorzystywać PWM w warstwie HAL (Hardware Abstraction Layer) dla rodziny STM32.

Czym jest PWM i dlaczego jest ważny?

PWM to technika generowania sygnału cyfrowego, który przełącza się między stanem HIGH i LOW z kontrolowanym wypełnieniem (duty cycle). Wypełnienie to stosunek czasu trwania stanu wysokiego do całego okresu sygnału, wyrażony w procentach. Zmieniając wypełnienie, kontrolujesz średnią moc dostarczaną do elementu wykonawczego, zachowując stałą częstotliwość.

Zastosowania PWM obejmują między innymi:

  • sterowanie jasnością LED – płynna regulacja światła poprzez zmianę wypełnienia PWM;
  • kontrolę prędkości silników DC – modyfikacja prędkości obrotowej przez zmianę wartości PWM;
  • sterowanie serwomechanizmami – precyzyjne pozycjonowanie poprzez odpowiednią długość impulsu;
  • sterowanie diodami RGB – niezależna kontrola kanałów R, G i B dla mieszania kolorów.

Architektura timerów w STM32

Mikrokontrolery STM32 wyposażono w rozbudowane timery, które działają w wielu trybach, w tym w PWM. Na przykład STM32L053R8 udostępnia 3 z 4 timerów obsługujących PWM (łącznie 8 kanałów), a STM32L476RG oferuje wiele wyjść PWM dostępnych na różnych pinach GPIO.

W uproszczeniu, każdy timer zawiera następujące elementy:

  • licznik główny (Counter) – zlicza z zadaną częstotliwością źródła zegarowego;
  • rejestry porównawcze (CCR) – definiują moment zmiany stanu wyjścia w ramach okresu;
  • kanały wyjściowe – zwykle do 4 kanałów PWM na timer (w zależności od serii STM32);
  • rejestry konfiguracyjne – ustawiają tryb pracy, polaryzację i parametry czasowe.

Konfiguracja PWM w STM32CubeMX

Krok 1 – wybór timera i kanału

W narzędziu STM32CubeMX wybierz odpowiedni timer i kanał PWM. Przykładowo: aby sterować silnikiem DC na pinie PB10, wybierz TIM2_CH3 (Timer 2, Kanał 3).

Krok 2 – konfiguracja trybu PWM

W zakładce Timers → TIM2 (lub dla wybranego timera) ustaw tryb PWM Generation CH3 dla odpowiedniego kanału. Najważniejsze parametry to:

  • tryb licznika – ustaw Up (liczenie w górę);
  • Prescaler – dzielnik częstotliwości zegara timera, wpływa na rozdzielczość PWM;
  • Okres licznika (ARR) – definiuje okres sygnału, a więc i częstotliwość PWM;
  • Impuls (CCR) – wartość początkowa wypełnienia (liczba taktów stanu HIGH);
  • Polaryzacja – zwykle High (stan wysoki podczas impulsu).

Częstotliwość PWM obliczysz ze wzoru:

f_PWM = f_CLK / ((Prescaler + 1) × (ARR + 1))

gdzie f_CLK to częstotliwość zegarowa timera (po ewentualnych preskalerach magistrali).

Krok 3 – konfiguracja pinów GPIO

Każdy kanał PWM musi być przypisany do właściwego pinu GPIO. STM32CubeMX automatycznie skonfiguruje funkcję alternatywną pinu po wybraniu trybu PWM dla danego kanału.

Programowanie PWM w kodzie HAL

Inicjalizacja timera

Po wygenerowaniu projektu w STM32CubeMX konfiguracja timera trafia do funkcji MX_TIM2_Init() w pliku main.c. Aby faktycznie wystawić sygnał na pinie, uruchom kanał PWM następującą funkcją:

HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);

Bez jawnego wywołania HAL_TIM_PWM_Start() sygnał PWM nie zostanie wygenerowany.

Modyfikacja wypełnienia PWM

W trakcie pracy programu zmieniaj wypełnienie PWM makrem:

__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_3, value);

Parametr value powinien mieścić się w zakresie 0…ARR (maksimum to aktualna wartość Auto-Reload).

Praktyczne przykłady zastosowań

Sterowanie jasnością LED

Dla prostej regulacji jasności LED (0–100%) użyj poniższego przeliczenia na wartość CCR:

int32_t brightness = 50; // 0–100%
int32_t value = (brightness * ARR) / 100;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, value);

Lepszą percepcję zmian jasności uzyskasz przez korekcję gamma:

int32_t value = powf(brightness, 2.2f) * 49999;
__HAL_TIM_SET_COMPARE(timer, channel, value);

Korekcja gamma (np. potęga 2.2) sprawia, że subiektywnie skoki jasności są równomierne dla oka.

Sterowanie serwomechanizmami

Możesz niezależnie sterować kilkoma serwami, korzystając z osobnych kanałów tego samego timera:

Servo_Init(&servo1, &htim2, TIM_CHANNEL_1);
Servo_Init(&servo2, &htim2, TIM_CHANNEL_2);

Kanały PWM oparte na jednym timerze mają wspólny punkt startu impulsu, ale ich wypełnienia (pozycje serw) są od siebie niezależne.

Sterowanie silnikiem DC

Dla sterownika w trybie PHASE/ENABLE potrzebujesz dwóch wyjść cyfrowych (np. MODE, PHASE) oraz wyjścia PWM dla prędkości. Przykładowa inicjalizacja:

void drv8835_init() {
drv8835_mode_control(Phase_Enable_Mode);
drv8835_set_motorA_direction(CCW);
drv8835_set_motorA_speed(0);
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_3);
}

W pętli głównej płynnie reguluj prędkość:

while (1) {
if ((HAL_GetTick() - time_tick) > 20) {
time_tick = HAL_GetTick();
pwm += inc_dec;
drv8835_set_motorA_speed(pwm);
if (pwm >= 100) inc_dec = -1;
}
}

Zaawansowane techniki – układ PCA9685

Gdy wyczerpiesz liczbę sprzętowych kanałów PWM w STM32, rozważ dedykowany kontroler PCA9685 sterowany przez I²C. Jego kluczowe cechy to:

  • 16 kanałów PWM – znacznie więcej niż w pojedynczym mikrokontrolerze;
  • zasilanie 2,3–5,5 V – kompatybilne logicznie z wieloma rodzinami STM32;
  • obciążalność – do 25 mA na wyjście przy 5,5 V;
  • programowalna częstotliwość – od 24 do 1526 Hz;
  • rozdzielczość 12 bitów – 4096 kroków wypełnienia na kanał;
  • wspólna częstotliwość – wszystkie kanały dzielą tę samą f_PWM, ale mają niezależne punkty startu/końca impulsu.

Interfejs I²C pozwala sterować wieloma kanałami PWM, wykorzystując jedynie dwa piny mikrokontrolera.

Typowe problemy i rozwiązania

Brak sygnału PWM na wyjściu

Najczęstszy błąd to brak wywołania funkcji HAL_TIM_PWM_Start(). Samo ustawienie rejestru CCR nie uruchamia generatora – timer i kanał muszą zostać jawnie włączone.

Nieoczekiwana częstotliwość

Sprawdź wartości Prescaler i ARR. Jeśli częstotliwość odbiega od oczekiwanej, skoryguj parametry według zależności:

f_PWM = f_CLK / ((Prescaler + 1) × (ARR + 1))

Przerwy w sygnale PWM

Przy dynamicznej zmianie wypełnienia mogą pojawiać się artefakty czasowe. Modyfikuj CCR w odpowiednim momencie cyklu lub skorzystaj z przerwań (interrupts), gdy wymagana jest deterministyczna czasowo aktualizacja.

Przerwania w timerach PWM

Timery generują przerwania m.in. przy przepełnieniu (overflow) lub zakończeniu impulsu. W HAL użyj callbacku:

void HAL_TIM_PWM_PulseFinishedCallback(TIM_HandleTypeDef *htim) {
// Kod wykonywany na końcu impulsu PWM
}

Callback ułatwia precyzyjne, czasowo spójne aktualizacje parametrów PWM w aplikacjach krytycznych.

Optymalizacja energetyczna

Dynamiczna zmiana Prescaler/ARR i wyłączanie nieużywanych timerów pozwala ograniczyć pobór prądu. W aplikacjach bateryjnych obniż częstotliwość PWM lub czasowo zatrzymaj timer, gdy napęd/LED są nieaktywne.