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.