Książki o elektronice i programowaniu w promocji 2 za 1 (tańsza za 0 zł)! Sprawdź listę tytułów »

Kurs STM32 – #7 – Liczniki (timery) w praktyce, PWM

Kurs STM32 – #7 – Liczniki (timery) w praktyce, PWM

Przed nami kolejna część kursu programowania STM32. W tej części poznamy podstawy modułów sprzętowych liczników (timerów).

Po odrobinie niezbędnych podstaw teoretycznych wykorzystamy PWM do płynnej regulacji jasności diod świecących (w tym RGB).

Mikrokontrolery z rodziny STM32 znane są z rozbudowanych możliwości timerów. Jest to niewątpliwie ich zaletą, jednak dla początkujących liczba dostępnych opcji może wydawać się nieco przytłaczająca. W ramach tego kursu nie będziemy omawiać wszystkich możliwości  jakie dają układy licznikowe, skupimy się na tym co najważniejsze na początek - czyli głównie PWM.

Efekt działania jednego z programów testowych.

Efekt działania jednego z programów testowych.

Kiedy mówimy o timerach, najczęściej mamy na myśli odmierzanie czasu. Zanim więc zaczniemy na dobre poznawać liczniki musimy przypomnieć sobie podstaw oraz wykonać trochę obliczeń.

Liczniki w STM32 - podstawy teoretyczne

W przypadku sygnału okresowego s(t), czas T > 0 mierzony w sekundach jest okresem, jeśli dla każdego momentu t: s(t) = s(t + T), czyli co T sekund sygnał się powtarza. W elektronice często sekunda to bardzo długi czas, więc popularne jest używanie takich jednostek jak:

1 ms = 10-3 s
1 us = 10-6 s
1 ns = 10-9 s

Częstotliwość jest odwrotnością okresu: f = 1/T, jednostką jest Herz (Hz). Dla przypomnienia:

1 kHz = 103 Hz
1 MHz = 106 Hz
1 GHz = 109 Hz

Pracując z zegarami często będziemy przechodzić, albo zamiennie używać okresu i częstotliwości, warto zapamiętać następującą zależność:

CzęstotliwośćOkres
1Hz1s
1kHz1ms
1MHz1us

Dlaczego to takie ważne? Po prostu w pewnych momentach znana jest jedna z tych wartości, a wygodniej jest używać drugiej. Przykład: mikrokontroler na płytce Nucleo pracuje z częstotliwością 64MHz. Jaki należy zastosować dzielnik, aby uzyskać okres 1ms?

No właśnie - parametrem jest częstotliwość, a my chcemy uzyskać przerwanie co 1ms, jak to przeliczyć? Możemy okres, czyli 1ms zamienić na częstotliwość - 1kHz, a następnie podzielić. 64MHz  przez 1kHz. Wynik to 64000 i taki dzielnik będzie nam potrzebny (w przykładach).

Zestaw elementów do kursu

 999+ pozytywnych opinii  Gwarancja pomocy  Wysyłka w 24h

Zestaw ponad 120 elementów do przeprowadzenia wszystkich ćwiczeń z kursu można nabyć u naszych dystrybutorów! Dostępne są wersje z płytką Nucleo lub bez niej!

Zamów w Botland.com.pl »

Podstawa czasu w STM32

Na początek upewnimy się, czy nasze obliczenia są poprawne. Ponieważ nie każdy ma pod ręką oscyloskop, spróbujemy skonfigurować licznik tak, aby dokładnie co 1s otrzymywać przerwanie. Za pomocą zegarka będziemy mogli się przekonać, czy nasz układ działa poprawnie.

Jak zwykle pierwszy krok to podłączenie zegara do naszego timera:

Układ STM32F103 posiada 4 moduły timerów, my wykorzystamy TIM2 jako przykład. Kolejny krok to jak zwykle zdefiniowanie zmiennej z konfiguracją. Na początek ustawiamy podstawę czasu:

Tryb pracy to zliczanie do góry, czyli wewnętrzny licznik będzie liczył od 0 do wartości TIM_Period. Licznik zlicza od 0, więc gdybyśmy ustawili ten parametr na 1000, okres wynosiłby 1001.

Ponieważ zegar systemowy działa z częstotliwością 64 MHz, to do 1000 doliczy za szybko (otrzymamy częstotliwość 64 kHz, a chcieliśmy 1 Hz). Wykorzystamy więc preskaler, czyli dzielnik zegara wejściowego. Podzielimy 64 MHz przez 64000, dzięki czemu nasz timer będzie pracował z częstotliwością 1 kHz, więc do 999 doliczy dokładnie po 1s.

Jak sprawdzimy, czy mamy rację? Uruchomimy przerwanie wywoływane po przepełnieniu licznika:

Zdarzenie TIM_IT_Update jest generowane, gdy licznik modułu osiągnie wartość TIM_Period i zaczyna ponownie liczyć od zera. Nasz timer jeszcze nie pracuje, uruchomimy go komendą:

Potrzebujemy teraz skonfigurować moduł NVIC do obsługi przerwania:

Wcześniej już używaliśmy przerwań, jedyna różnica to kanał przerwania – teraz jest to TIM2_IRQn. Czas napisać procedurę jego obsługi. Ważna jest nazwa TIM2_IRQHandler - jeśli podamy inną, program nie zadziała:

W kodzie ważne jest sprawdzenie przyczyny przerwania za pomocą funkcji TIM_GetITStatus. W tej chwili interesuje nas tylko zdarzenie TIM_IT_Update, ale później dodamy kolejne.

Gdy obsłużymy przerwanie, powiadamiamy o tym układ zerując odpowiednią flagę za pomocą wywołania TIM_ClearITPendingBit. Dalsza część, to nasza funkcja - sprawdzamy, czy dioda świeci, jeśli tak to ją wyłączamy, a w przeciwnym przypadku włączamy.

Pełny kod programu:

Po uruchomieniu możemy sprawdzić, czy wszystko się zgadza. Dioda powinna świecić przez dokładnie jedną sekundę, po czym gasnąć na dokładnie taki sam czas. Jako ćwiczenie warto sprawdzić co stanie się, gdy zmienimy wartości parametrów TIM_Period i TIM_Prescaler.

Zadanie 7.1

Zmień program tak, aby dioda świeciła dokładnie 5 sekund, a następnie gasła na 5 sekund.

Zadanie 7.2

Wykonaj podobne ćwiczenie jak 7.1, ale ustaw czas na 100 ms.

Zadanie 7.3

Podczas sterowania silnikami, częstotliwość sterowania ma duże znaczenie. Zbyt wysoka powoduje straty mocy na mostku podczas przełączania. Zbyt mała może być bardzo uciążliwa dla użytkownika - jeśli częstotliwość przełączania jest w zakresie akustycznym, silnik może dość dokuczliwie piszczeć.

Dlatego najczęściej wykorzystuje się sterowanie z częstotliwością niesłyszalną dla człowieka. Przygotuj program, w którym przerwanie będzie wywoływane z częstotliwością 20 kHz. Sprawdź jaki jest zakres słyszalnych częstotliwości, czy to wystarczająca wartość? Jak sprawdzić, czy układ faktycznie pracuje z tą częstotliwością?

Liczniki w STM32 - kanały

Układy licznikowe STM32 posiadają jeszcze dodatkowe 4 rejestry kanałów. Gdy główny licznik zrówna się z wartością rejestru danego kanału, można np.: wygenerować przerwanie (można też zmienić stan wyjścia).

Dzięki temu możemy otrzymać przerwanie co okres zegara oraz cztery dodatkowe przerwania po zadanym czasie. W poprzednim przykładzie ustawiliśmy okres zegara na 1s. Teraz spróbujemy za pomocą kanałów wygenerować przerwania po 100ms, 200ms, 500ms i 900ms.

Do czego to nam się może przydać? Na początku okresu zapalimy 4 diody. Po nadejściu każdego z przerwań będziemy gasić jedną z nich. Powinniśmy otrzymać 4 synchronicznie pracujące diody, świecące. Jest to duży krok w stronę sterowania silnikami poprzez PWM. Jednak dzięki diodom i długiemu okresowi pracy, możemy zobaczyć jak układ działa.

Na początek podłączamy diody podobnie jak w części kursu omawiającej GPIO, czyli do pinów PC0, PC1, PC2, PC3.

tim-leds_bb

Większość konfiguracji timera przebiega jak poprzednio, trzeba tylko ustawić wartość rejestrów kanałów:

Następnie musimy włączyć przerwanie od zdarzenia TIM_IT_Update (jak poprzednio) oraz od zdarzenia zrównania licznika z rejestrem kanału (Compare Channel) - TIM_IT_CCx:

Najwięcej zmian musimy dokonać w procedurze obsługi przerwania. Sterowanie diodą na płytce Nucleo zostawimy jak poprzednio. Dodamy do tego sterowanie właśnie podłączonymi diodami:

Przy zdarzeniu Update zapalamy wszystkie 4 diody:

Natomiast po wystąpieniu zdarzenia TIM_IT_CCx, gasimy odpowiednią diodę:

Funkcja TIM_GetITStatus sprawdza, które zdarzenie jest przyczyną przerwania, następnie zerujemy flagę przerwania i gasimy diodę. To samo robimy dla pozostałych kanałów.

Kod całego programu prezentuje się tak, jak poniżej:

Po uruchomieniu zobaczymy, że pierwsza dioda zapala się na bardzo krótko (100ms), kolejna nieco dłużej (200ms), itd.

Teraz spróbujmy zmienić preskaler, w tej chwili wynosi 64 000. Zmieńmy go przykładowo na 64:

Przy częstotliwości 1kHz nie widzimy już migania diod. Za to świecą one z różną jasnością – pierwsza najsłabiej, ostatnia najjaśniej (jeśli różnica jest mało widoczna, najlepiej zmniejszyć współczynniki wypełnienia).

Poprawiamy program korzystający z kanałów licznika

Poprzedni program działał poprawnie, jednak w rzeczywistości trochę niechcący. Mianowicie konfiguracja kanałów naszego licznika była w dużym stopniu domyślna. Na nasze szczęście domyślne wartości w pełni nam odpowiadają, jednak dodajmy dla pewności do programu pełną konfigurację kanałów.

W tym celu potrzebujemy zmiennej typu TIM_OCInitTypeDef. Dotychczas poznaliśmy typ TIM_TimBaseInitTypeDef, który służył do konfiguracji podstawy czasu. Nowy typ pozwoli nam skonfigurować kanały. Funkcję musimy wywołać cztery razy (dla każdego kanału osobno):

Jak zwykle najpierw wywołujemy konstruktor TIM_OCStructInit. Następnie ustawiamy tryb pracy kanału, na domyślną wartość TIM_OCMode_Timing. Pole TIM_Pulse, to wartość licznika naszego kanału – poprzednio jego wartość ustawialiśmy wywołaniem TIM_SetCompare, teraz ustawiamy ją podczas inicjalizacji.

Gdy uruchomimy program, będzie on działał identycznie jak poprzednio. Możemy jednak mieć wpływ na konfigurację kanałów. Co wykorzystamy w kolejnym przykładzie.

Kod całego programu:

Zadanie 7.4

Wykonanie procedury obsługi przerwania w obecnej postaci zajmuje mikrokontrolerowi ok. 7.7us (zmierzona wartość). Przyjmując, że częstotliwość sterowania wynosi 20 kHz, oblicz ile procent czasu procesora zajmuje obsługa przerwań (dla 1 i dla 4 kanałów).

Generowanie PWM na STM32

Poprzednio do sterowania wyjściami procesora wykorzystywaliśmy przerwania. Takie rozwiązanie jest dość nieekonomiczne – procesor ma pełne ręce roboty obsługując przerwania i w kółko zapalając i gasząc diody.

Na początek musimy wybrać wyprowadzenia, na których dostępny jest sprzętowy PWM. Pełny opis wyprowadzeń procesora jest dostępny w dokumentacji, czyli datasheet-cie. Okazuje się, że wyprowadzenia TIM2, który używaliśmy wcześniej kolidują z konwerterem UART-USB. Można użyć remapowania i innych wyprowadzeń, ale my wykorzystamy łatwiejszą opcję i zmienimy timer.

Okazuje się, że timer TIM4 udostępnia wyjścia PWM na pinach PB6, PB7, PB8 oraz PB8, które są bez problemu dostępne. Przełączamy układ zgodnie z rysunkiem:

tim-pwm_bb

Jak zawsze pierwszym etapem jest podłączenie zegara, tym razem do TIM4.

Musimy również dodać konfigurację pinów PB6-9. Wykonamy to podobnie jak dotychczas, jednak użyjemy 2 nowych opcji. Ustawimy prędkość działania pinów na 50 MHz – domyślnie pracują z maksymalną prędkością 2MHz, jednak sprzętowy PWM może wymagać szybkich pinów.

Druga zmiana, to ustawienie trybu wyjścia jako GPIO_Mode_AF_PP, czyli funkcja alternatywna (PWM), w trybie push-pull. Kod wygląda następująco:

Konfiguracja podstawy czasu pozostaje praktycznie bez zmian (poza innym timerem):

Natomiast, to co nowe, to ustawienia kanałów. Tym razem mają pracować w trybie PWM:

Początkowe wartości PWM to 100, 200 i 500. Ponieważ pełny okres PWM wynosi 1000, więc odpowiada to wypełnieniu 10%, 20% i 50%.

Pozostaje już tylko uruchomić timer:

Nasz program poza konfiguracją modułu już właściwie nic nie musi robić. Nie ma obsługi przerwań, program główny, to pusta pętla.

Cały kod programu:

Zadanie 7.5

Przygotuj program, który będzie płynnie zapalał i gasił kolejne diody.

Wykorzystanie PWM do sterowania diody RGB

Na zakończenie podłączymy diodę RGB i za pomocą PWM będziemy płynnie zmieniali kolor jej świecenia. Wykorzystamy TIM4, jak w poprzednim przykładzie, ale tym razem wystarczą nam 3 kanały. Podłączamy układ zgodnie ze schematem montażowym:

tim-rgb_bb

Dioda RGB dostarczana z zestawem ma wspólną anodę - więc nasz układ nie ma wspólnej masy, co jest typowym rozwiązaniem. Zamiast tego mamy wspólne zasilanie 3.3V, a sterowanie jest odwrócone.

Moglibyśmy wykorzystać poprzedni program do sterowania, ale wtedy wypełnienie sygnały byłoby również odwrócone. Wykorzystamy więc tryb PWM2, który działa dokładnie tak jak potrzebujemy - na początku okresu wystawia logiczne 0, a po zrównaniu licznika z rejestrem kanału wystawia 1. Działa więc jak odwrócony tryb PWM1.

Ponieważ będziemy chcieli płynnie zmieniać kolory, przyda nam się funkcja opóźniająca delay_ms, którą napisaliśmy w części o GPIO. Dodamy również obsługę przerwania SysTick. Do ustawiania jasności świecenia każdej składowej wykorzystamy następujące funkcje:

TIM_SetCompare1TIM_SetCompare2TIM_SetCompare3.

Okazuje się, że pozornie proste zadanie, jak sterowanie diodą RGB zawiera kilka niespodzianek. Pierwsza, to sama budowa diody. Po podłączeniu wszystkich 3 składowych zamiast białej barwy uzyskamy widoczne trzy punkty świecące odpowiednio na czerwono, zielono i niebiesko.

Dioda w rzeczywistości zawiera trzy struktury, z których każda świeci innym kolorem. Niestety są one widoczne jako oddzielne źródła światła i nie mieszają się zgodnie z oczekiwaniami.

Najprościej poszukać odpowiedniego elementu z częściowo przezroczystego plastiku, można wydrukować coś ładnego w 3D, a jeśli nic nie znajdziemy, po prostu zasłonić diodę chusteczką.

Tymczasowe osłonięcie diody chusteczką (dla lepszego efektu).

Tymczasowe osłonięcie diody chusteczką (dla lepszego efektu).

Drugi problem, to znaczna nieliniowość jasności świecenia diody. Okazuje się, że zwiększając liniowo wypełnienie PWM, otrzymujemy bardzo nieliniową jasność świecenia diody. Poza bardzo małymi współczynnikami, dioda świeci właściwie z pełną jasnością.

Chcąc zlinearyzować jasność świecenia diody, musimy odpowiednio przeliczyć jasność na wypełnienie PWM. Możemy w tym celu wykorzystać funkcję:

Więcej na ten temat znaleźć można na Wikipedii.

Przyjmując oczekiwaną jasność wyrażoną w procentach (0-100%), możemy napisać następującą funkcję:

Współczynniki 300, k oraz x0 są dobrane eksperymentalnie, zachęcam do ich zmiany i obserwacji wyników. Mając względnie liniową charakterystykę świecenia diody, możemy przegotować interesujący efekt wizualny. Z pomocą przychodzą nam funkcje trygonometryczne, np. sin().

Możemy napisać następujący program:

W efekcie dostaniemy całkiem ładnie wyglądającą lampkę RGB:

Kod całego programu:

Zadanie 7.6

Sprawdź, jak wygląda sterowanie jasnością świecenia diody bez funkcji calc_pwm. Najlepiej użyć jednego koloru i liniowo zmieniać wypełnienie - czy jasność świecenia diody też zmienia się liniowo? Wstaw w komentarzu film pokazujący rezultat.

Zadanie 7.7

Przygotuj inny sposób zmiany kolorów, wstaw w komentarzu filmik z rezultatem, który uzyskałeś.

Podsumowanie

Omówiliśmy tylko podstawowe możliwości układów licznikowych, w które wyposażony jest STM32F103RB. Warto chociaż wspomnieć o pozostałych funkcjach.

Korzystając ze sprzętowych liczników mamy możliwość pomiaru szerokości impulsu. Pozwala to przykładowy na dokładny pomiar odległości popularnym czujnikiem HC-SR04. Co ważne dla osób budujących roboty - wśród dostępnych opcji znajdziemy również sprzętowy interfejs do enkoderów. oraz do obsługi czujników hall-a.

Dodatkowo pierwszy licznik ma dodatkowe możliwości, m.in. generowania PWM z wyjściem komplementarnym i programowanym czasem martwym (deadtime). Daje to możliwość bezpośredniego sterowania zarówno górnymi jak i dolnymi tranzystorami mostka H.

Nawigacja kursu

Autor kursu: Piotr (Elvis) Bugalski
Redakcja: Damian (Treker) Szymański

kursSTM32, liczniki, programowanie, PWM, stm32, timer

Trwa ładowanie komentarzy...