Większość urządzeń uzależnia swoją pracę od czasu. W celu skutecznej i optymalnej realizacji zadań wykorzystuje się timery (liczniki).
W tym artykule zostaną opisane zagadnienia takie jak generowanie PWM, dekodowanie sygnału kwadraturowego z enkoderów, a nawet analiza sygnału PWM, pochodzącego np. z aparatury RC.
Uwaga! Ten kurs został zarchiwizowany. Sprawdź najnowszy kurs STM32 »
Modulacja Szerokości Impulsu - PWM
Zapewne większość z Was wie czym jest PWM. Jeśli nie, to odsyłam do 6 części kursu elektroniki, w której zagadnienie to zostało szerzej opisane. W roli krótkiego przypomnienia posłużę się jedynie cytatem z Wikipedii:
PWM (ang. Pulse-Width Modulation) – metoda regulacji sygnału prądowego lub napięciowego, o stałej amplitudzie i częstotliwości, polegająca na zmianie wypełnienia sygnału, używana w zasilaczach impulsowych, wzmacniaczach impulsowych i układach sterujących pracą silników. Wikipedia, 14.06.2016
Ze względu na prostotę działania i bardzo łatwą realizację, zarówno sprzętową jak i programową, sygnał PWM używany jest w bardzo wielu zastosowaniach. Niektóre z nich to:
sterowanie prędkością silnika,
sterowanie jasnością diod świecących (LED),
przesyłanie informacji.
Gotowe zestawy do kursów Forbota
Komplet elementów Gwarancja pomocy Wysyłka w 24h
Zestaw elementów do przeprowadzenia wszystkich ćwiczeń z kursu STM32 F4 można nabyć u naszego dystrybutora! Zestaw zawiera m.in. płytkę Discovery, wyświetlacz OLED, joystick oraz enkoder.
Masz już zestaw? Zarejestruj go wykorzystując dołączony do niego kod. Szczegóły »
Jak wygląda sygnał PWM?
W swojej najprostszej postaci, sygnał PWM jest sygnałem prostokątnym o zmiennej szerokości wypełnienia impulsu (duty). Zobrazujmy to na prostych przykładach. Sygnał PWM o wypełnieniu 50% to taki sygnał, gdzie w jednym okresie (T) stan wysoki trwa tyle samo co stan niski.
Przykład sygnału PWM.
Ponieważ sygnał ten zazwyczaj zmienia się dosyć szybko (kilkadziesiąt - kilka tysięcy razy na sekundę), często traktuje się go jak sygnał o wartości napięcia proporcjonalnej do wypełnienia impulsu. Znaczy to tyle, że w teorii sygnał PWM o wypełnieniu równym 50% ma średnią wartość napięcia (Vavg) równą połowie wartości napięcia stanu wysokiego.
Inne przykłady sygnału PWM przedstawiono na rysunku poniżej:
Różne wypełnienie sygnału PWM.
Warto zauważyć, że sygnał PWM o wypełnieniu 100% to po prostu ciągły stan wysoki. Sygnał PWM o wypełnieniu równym 0% będzie natomiast stanem niskim.
Generowanie sygnału PWM w praktyce na STM32F4
Skoro wiemy już jak wygląda i jakimi parametrami cechuje się sygnał PWM, czas wykorzystać możliwości naszego mikrokontrolera i wygenerować taki sygnał w praktyce. Za realizację takich zadań jak generowanie sygnału PWM odpowiedzialne są timery. Skonfigurujemy teraz jeden z nich, tak aby móc regulować jasność świecenia diody LED na płytce Discovery.
Konfiguracja w Cube
Krok 1. Tworzymy nowy projekt i konfigurujemy główny zegar mikrokontrolera na maksymalną wartość (100MHz), tak jak robiliśmy to w jednym z poprzednich artykułów.
Krok 2. Klikamy w wyprowadzenie, na którym znajduje się czerwona dioda (PD14) i sprawdzamy, czy da się na tym pinie wyprowadzić kanał jednego z timerów.
Konf. wyprowadzenia.
Okazuje się, że tak. Funkcja TIM4_CH3 oznacza kanał 3 timera 4. Po wybraniu odpowiedniej funkcji zauważamy, że konfigurowane wyprowadzenie zamiast na zielono, podświetliło się na żółto.
Efekt wybrania opcji.
Jest to spowodowane tym, że kanał ten timera może pełnić wiele funkcji i użytkownik musi sprecyzować, o którą konkretnie mu chodzi.
Krok 3. Rozwijamy panel timera 4 z lewej strony i konfigurujemy kanał 3 tak, aby generował sygnał PWM. W tym miejscu musimy również sprecyzować, że timer będzie taktowany z wewnętrznej magistrali (Clock Source: Internal Clock).
Odpowiednia konfiguracja licznika.
Po wybraniu funkcji timera wyprowadzenie natychmiast zmieni kolor na zielony.
Krok 4. Przechodzimy do panelu konfiguracyjnego timera 4.
Konfiguracja 4 timera (licznika).
Pierwsze 4 parametry sekcji Counter Settings poznaliśmy już w artykule o zegarach, w którym konfigurowaliśmy prosty timer generujący przerwania cykliczne. Zarówno funkcja prescalera jak i rejestru CKD (Internal Clock Division) nie zmienia się w porównaniu do tamtego zastosowania.
Rejestr ARR będzie natomiast w tym wypadku oznaczał rozdzielczość,
z jaką będzie potem można ustawiać wypełnienie PWM.
Wypełnienie impulsu ustawiamy w rejestrze CCR. Rejestr ten może przyjąć minimalną wartość 0, a maksymalną ARR+1 (w rzeczywistości możemy tam wpisać dużo większe wartości, ale będzie to działać tak samo jak przy wpisaniu wartość ARR+1).
Przykład:
Wartość rejestru ARR wynosi 3. Oznacza to, że mamy dostępnych tylko 5 poziomów wypełnienia impulsu. Trzy poziomy pośrednie plus dwa dodatkowe - zerowy i maksymalny:
CCR = 0, duty = 0%
CCR = 1, duty = 25%
CCR = 2, duty = 50%
CCR = 3, duty = 75%
CCR = ARR+1= 4, duty = 100%
W praktyce oznaczałoby to, że sterując silnikiem moglibyśmy zmieniać jego prędkość obrotową tylko "co 25%" wartości maksymalnej. W robotyce zazwyczaj interesuje nas dużo większa dokładność, będąca na poziomie co najmniej 1% (ARR = 99) lub 0.1% (ARR = 999).
Spróbujmy zatem ustawić timer w taki sposób, aby generował sygnał PWM z częstotliwością 100Hz i z rozdzielczością 1%, czyli w zakresie 0-100. Przypominamy sobie wzór na częstotliwość generowania przerwania, który jest taki sam jak wzór na częstotliwość sygnału PWM (czyli na ilość okresów w jednej sekundzie):
FREQ = TIM_CLK/(ARR+1)(PSC+1)(CKD+1)
Znamy naszą częstotliwość (FREQ) oraz rozdzielczość (ARR+1), z jaką chcemy zadawać wypełnienie PWM. Zaglądając na początek artykułu o zegarachdowiemy się, że timer 4 znajduje się na magistrali APB1, a więc będzie taktowany częstotliwością 50MHz (TIM_CLK). Do ustalenia zostają nam więc tylko dwa rejestry (PSC i CKD).
Pamiętajmy, że w rejestrach timera można zapisać maksymalną wartość 65535.
Rozdzielczość: ARR+1 = 100, czyli: ARR = 99, co daje 99 poziomów pośrednich (1-99). Poza tym dostępne będą dla nas jeszcze poziom zerowy (0) i maksymalny (100).
Prescaler i CKD:
FREQ = TIM_CLK/(ARR+1)(PSC+1)(CKD+1)
100 = 50000000/(100)(PSC+1)(CKD+1)
(PSC+1)(CKD+1) = 5000
Przyjmujemy że CKD = 0 (No division):
PSC+1 = 5000
PSC = 4999
Tak wyliczonymi wartościami parametryzujemy nasz timer:
Parametry timera generującego sygnał PWM.
Drugą interesującą dla nas sekcją będzie ta dotycząca konkretnego kanału:
Parametry kanału timera generującego sygnał PWM.
Mode - w trybie PWM Mode 1 pin wyjściowy timera utrzymywany jest w stanie wysokim, aż wartość licznika timera nie osiągnie wartości wpisanej do rejestru CCR, który przechowuje wpisaną przez nas wartość wypełnienia. Pin jest ustawiony w stan niski aż do momentu, gdy licznik się zresetuje (przekroczy wartość rejestru ARR+1). W tym trybie im większa wartość rejestru CCR, tym większe wypełnienie sygnału. Tryb PWM Mode 2 działa dokładnie na odwrót. Pin zaczyna ze stanu niskiego i dopiero przy zrównaniu się wartości licznika z wartością w rejestrze CCR, ustawiany jest w stan wysoki. W tym trybie zwiększanie wartości rejestru CCR zmniejsza wypełnienie sygnału. Zostawiamy domyślny PWM Mode 1.
Pulse - początkowa wartość wypełnienia wpisana do rejestru CCR. Zostawiamy domyślne 0.
Fast Mode - przydatny tylko w trybie timera One Pulse Mode, z którego nie będziemy tu korzystać. Zostawiamy domyślne Disable.
CH Polarity - zmiana polaryzacji pinu wyjściowego. Efekt działania jest dokładnie taki sam, jak zmiana z PWM Mode 1 na PWM Mode 2. Zostawiamy domyślne High.
Krok 5. Generujemy projekt pod nazwą 05_PWM.
Uruchomienie timera generującego sygnał PWM
Krok 1. Jak zwykle, odszukujemy funkcji, która pozwoli nam uruchomić timer w odpowiednim trybie. Domyślamy się, że musimy zacząć szukać od HAL_TIM_PWM. Ponieważ na razie nie będziemy korzystać z przerwań ani z DMA, uruchamiamy timer:
Pierwszym parametrem jest oczywiście wskaźnik na strukturę konfiguracyjną, wskazującą który timer chcemy uruchomić. Drugi parametr precyzuje kanał, który ma zostać włączony.
Aby dowiedzieć się jakie mamy możliwości, wystarczy najechać kursorem myszki nad nazwę funkcji żeby wyświetlić krótką dokumentację.
Podgląd skróconej dokumentacji funkcji.
Finalnie nasze wywołanie będzie miało następującą postać.
C
85
86
87
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim4, TIM_CHANNEL_3);// Uruchamia generowanie PWM przez timer 4 na kanale 3
/* USER CODE END 2 */
Krok 2. Następnie musimy wstawić żądaną wartość wypełnienia w odpowiednie miejsce. Takim miejscem jest wspomniany wcześniej TIM Capture/Compare register, czyli rejestr CCR. Rejestr ten jest składową głównej struktury timera, więc dostęp do niego będzie wyglądał następująco.
Krok 3. Aby zaobserwować zmiany w jasności świecenia diody, uzależnimy zmiany wypełnienia sygnału PWM od czasu.
Zadanie: Chcielibyśmy, aby jasność diody narastała przez 4 sekundy w zakresie 0 - ARR+1 (wartość maksymalna). Następnie jasność ma maleć przez kolejne 4 sekundy i tak w kółko.
Jedna z możliwości implementacji:
Wykorzystamy do tego domyślne przerwanie generowane przez SysTick, które wywoływane jest z częstotliwością 1KHz. Wiemy zatem, że w ciągu 4 sekund przerwanie wywoła się 4000 razy. Ponieważ w tym czasie musimy zinkrementować wartość wypełnienia 100 razy, oznacza to że inkrementacja powinna nastąpić co 4000/100 = 40 przerwań.
Kod realizujący to zadanie przedstawia poniższy listing.
C
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
TIM4->CCR3=Duty;// Wstawienie wyliczonej wartosci wypelnienia do
// rejestru timera odpowiedzialnego za wypelnienie generowanego sygnalu PWM
}
Po wgraniu powyższego programu czerwona dioda na płytce Discovery powinna przez 4 sekundy świecić coraz mocniej, a następnie przez 4 sekundy coraz słabiej.
Efekt działania programu (powolne przygaszanie diody).
Generowanie sygnału PWM z wykorzystaniem DMA
Aby nie musieć ręcznie wpisywać wartości wypełnienia do rejestru, możemy wykorzystać DMA do przesyłania danych z naszej zmiennej. Nie jest to może jakoś bardzo przydatne, bo tak na prawdę możemy obliczać nowe wypełnienia bezpośrednio w rejestrze. Jest to jednak bardzo proste do zrobienia, więc pozwolę sobie na opisanie tego przykładu.
Konfiguracja Cube
W panelu konfiguracyjnym timera 4 dodajemy transmisję DMA do obsługi kanału 3. Domyślnie kierunek przesyłanych danych będzie ustawiony na Peripheral to Memory. My natomiast chcemy przesyłać dane z pamięci do timera, dlatego musimy zmienić ten parametr na Memory to Peripheral.
Mode zmieniamy na circular, ponieważ chcemy żeby transmisja danych odbywała się cały czas. Resztę opcji zostawiamy domyślnie.
Konfiguracja kanału DMA dla timera generującego sygnał PWM.
Uruchomienia timera generującego sygnał PWM z wykorzystaniem DMA
Po wygenerowaniu kodu musimy dokonać kilku drobnych modyfikacji. Zmienna przechowująca zadaną wartość wypełnienia musi zostać wyciągnięta na zewnątrz przerwania, ponieważ musimy mieć do niej dostęp z funkcji main.
Poza usunięciem zmiennej Duty, w obsłudze przerwania zmieni się tylko tyle, że teraz przy każdym jego wywołaniu nie będzięmy odwoływać się jawnie do rejestru CCR3 timera 4.
C
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
Dochodzą tu 2 znane już z poprzednich odcinków parametry. Adres obszaru pamięci, z którego mają być pobierane dane oraz ich liczba danych do przesłania. Po wgraniu nowego programu na mikrokontroler wszystko powinno działać bez zmian.
Rozwiązanie to jest o tyle wygodne, że nie musimy wiedzieć, w którym rejestrze przechowywana jest wartość wypełnienia. Wystarczy zdefiniowana zmienna.
Odczyt sygnału kwadraturowego z enkodera
Kolejnym bardzo często wykorzystywanym elementem w robotyce i nie tylko jest enkoder obrotowy. Jest to urządzenie generujące impulsy odpowiadające ruchowi obrotowemu. Enkodery stosuje się najczęściej, aby zmierzyć położenie kątowe wału silnika. Innym zastosowaniem może być np. pokrętło we wzmacniaczach audio.
Zasada działania enkodera
Standardowy enkoder obrotowy ma dwa wyjścia (kanały), na których generowany jest sygnał prostokątny. Sygnały są przesunięte względem siebie w fazie, dzięki czemu jest możliwe wykrycie kierunku obrotu. Przykład takiego sygnału przedstawiono na poniższym rysunku:
Przykład sygnału z enkodera obrotowego.
W pierwszej sytuacji sygnał na linii A wyprzedza w fazie sygnał na linii B, co jest tu interpretowane jako obrót zgodnie z ruchem wskazówek zegara (CW-Clockwise). W przeciwnej sytuacji, przedstawionej w niższej części rysunku, to sygnał B wyprzedza sygnał A, co oznacza obrót w drugą stronę (CCW-Counterclockwise).
Podstawowym parametrem każdego enkodera jest liczba impulsów na obrót. Jeżeli enkoder ma 10 impulsów na obrót, oznacza to, że jesteśmy w stanie zmierzyć położenie kątowe z dokładnością 360°/10 = 36° (więc rozdzielczość enkodera wynosi 36°)
W robotyce bardzo często poszukujemy jak największej dokładności, dlatego używane w takich zastosowaniach enkodery mają zazwyczaj więcej niż 1000 impulsów na obrotów. Przykładem bardzo popularnego enkodera jest AS5040. Jest to enkoder o 10 bitowej rozdzielczości, co daje 1024 impulsy na obrót (dokładność do ~0.35°). Oczywiście istnieją enkodery o dużo większych rozdzielczościach.
Timer zlicza impulsy w rejestrze CNT i w zależności od kierunku obrotu, inkrementuje lub dekrementuje wartość tego rejestru. Spróbujmy zaimplementować taki odczyt w praktyce.
Konfiguracja Cube
Krok 1. Tworzymy nowy projekt w Cube. Krok 2. Konfigurujemy timer 1 w trybie Encoder Mode.
Konfiguracja timera w trybie do dekodowania sygnałów z enkodera obrotowego.
Zauważmy, że tryb Encoder Mode wykorzystuje dwa kanały timera. Ze względu na swoją specyfikę działania, blokuje również możliwość konfiguracji niektórych parametrów.
Krok 3. Przechodzimy do panelu konfiguracyjnego timera 1.
Panel konfiguracyjny timera w trybie Encoder Mode.
W tym zastosowaniu timera nie generujemy żadnego sygnału, ani nie korzystamy w żaden sposób z taktowania podawanego na ten timer. Z tego powodu jedyny parametr jaki będzie nas interesował w sekcji Counter settings, to Counter Period (rejestr ARR).
Wpisana tam liczba będzie maksymalną wartością, którą może osiągnąć rejestr zliczający impulsy (CNT). Po przekroczeniu tej wartości rejestr zachowa się jak zwyczajna zmienna bez znaku, czyli zostanie zresetowany i zacznie liczyć od zera.
Wartość tę ustawiamy na 403. Dlaczego? Zaraz się okaże.
Encoder Mode - określa na jakiej podstawie zliczane są impulsy w liczniku:
Encoder Mode TI1 - przy każdej zmianie poziomu sygnału na kanale drugim licznik jest inkrementowany lub dekrementowany w zależności od stanu na kanale pierwszym.
Encoder Mode TI1 - przy każdej zmianie poziomu sygnału na kanale pierwszym licznik jest inkrementowany lub dekrementowany, w zależności od stanu na kanale drugim.
Encoder Mode TI1 and TI2 - przy zmianie stanu dowolnego z kanałów następuje inkrementacja lub dekrementacja licznika. Ten tryb daje dwukrotnie więcej informacji niż poprzednie tryby. Ustawiamy ten tryb.
Polarity - definiuje na które zbocze będzie reagować timer. Pozostawiamy domyślnie Rising Edge.
IC Selection - Parametr który zupełnie nas nie interesuje, i którego nie możemy zmienić.
Prescaler Division Ration - Jak wyżej, ten parametr nie ma wpływu na działanie timera w trybie Encoder. Pozostawiamy domyślnie No division.
Input Filter - określa czas trwania próbkowania sygnału wejściowego podczas jego zmiany w cyklach zegara. Ustawiamy wartość maksymalną, a więc 15.
Na koniec konfiguracja naszego timera powinna wyglądać następująco.
Konfiguracja timera w trybie Encoder Mode.
Podłączenie enkodera do płytki Discovery
Enkoder znajdujący się na wyposażeniu zestawu posiada 5 wyprowadzeń:
Moduł enkodera z dedykowanym pokrętłem.
SIA i SIB to kanały enkodera, które generują sygnał kwadraturowy. Należy je podłączyć pod wejścia naszego timera. SW, to wyprowadzenie przycisku znajdującego się w wale obrotowym enkodera. Ostatnie dwa wyprowadzenia to oczywiście zasilanie, które może być z zakresu 3-5.3V
Finalnie podłączenie będzie wyglądać następująco:
SIA - PE9
SIB - PE11
SW - nie podłączony
GND - GND
VCC - 3V
Podłączenie enkodera do zestawu Discovery.
Uruchomienie timera dekodującego sygnał z enkoderów
Po wygenerowaniu kodu musimy uruchomić timer i odczytywać wartość licznika
Krok 1. Pierwszą rzeczą, której będziemy potrzebować jest zmienna do wpisywania aktualnego stanu licznika.
volatileuint16_t positions;// Licznik przekreconych pozycji
/* USER CODE END PV */
Zmienne są globalne, ponieważ będziemy je podglądać w STMStudio. Dlaczego są zadeklarowane jako volatile, okaże się już niedługo.
Krok 2. Następnie musimy uruchomić timer w trybie Encoder.
C
80
81
82
/* USER CODE BEGIN 2 */
HAL_TIM_Encoder_Start(&htim1, TIM_CHANNEL_ALL);
/* USER CODE END 2 */
Uruchamiamy timer z parametrem TIM_CHANNEL_ALL,
ponieważ ten tryb wykorzystuje dwa kanały jednocześnie.
Krok 3. Ostatnią rzeczą, którą musimy zrobić jest przepisanie wartości licznika timera do zmiennej. Jak już wcześniej wspomniałem, timer zlicza impulsy w rejestrze CNT. Przepisanie będzie miało w takim razie następującą postać.
C
84
85
86
87
88
89
90
91
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1){
pulse_count=TIM1->CNT;// przepisanie wartosci z rejestru timera
positions=pulse_count/4;// zeskalowanie impulsow do liczby stabilnych pozycji walu enkodera
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
W tym właśnie miejscu pojawi się problem, jeżeli zmienna positions nie będzie zadeklarowana ze słowem kluczowym volatile.
Zmienna positions nie jest tak na prawdę do niczego używana, kompilator z włączoną optymalizacją stwierdzi, że nie jest potrzebna i usuwa to przepisanie.
Rozwiązania tej sytuacji są co najmniej dwa:
Użyć słowa kluczowego volatile, które zapobiega wprowadzaniu optymalizacji.
Wyłączyć optymalizację kodu.
Ze względu na drastyczność drugiego rozwiązania zalecam pozostanie przy pierwszym. Jeżeli ktoś jednak chciałby to przetestować, parametry optymalizacji można znaleźć w opcjach projektu, jak na poniższym obrazku.
Opcje optymalizacji kompilatora.
Aby zupełnie wyłączyć optymalizację, trzeba zmienić Optimization Level na None.
Jeżeli coś nam nie działa w projekcie, jakieś zmienne nie zawierają tych wartości, które powinny, warto wyłączyć optymalizację i sprawdzić czy to coś zmienia. Jest to jeden ze sposobów znajdywania ciężkich do wykrycia błędów. Po wykryciu błędu warto jednak włączyć optymalizację z powrotem!
Krok 4. Kompilujemy projekt, wgrywamy go na płytkę, otwieramy STMStudio i importujemy zmienne pulse_count i positions do podglądu. Jeżeli nie pamiętasz jak się to robiło, warto wrócić do artykułu o STMStudio.
STMStudio z dodanymi zmiennymi pulse_count i positions
Po połączeniu się przez STMStudio możemy zacząć przeprowadzać testy. Zauważmy, że enkoder ma swoje "stabilne pozycje", które znajdują się średnio co 18° stopni (20 "pozycji" na obrót).
Przekręcając enkoder widzimy, że zmienna pulse_count zmienia swoją wartość o 4 co każdą stabilną pozycję.
Dlaczego 4, a nie 1? Ponieważ podczas przejścia z jednej "stabilnej" pozycji do drugiej generowany jest pełen cykl sygnału kwadraturowego na kanałach SIA oraz SIB. W jednym takim cyklu występują cztery zbocza, a nasz timer skonfigurowany jest na reagowanie na każde z nich.
Cykl sygnału kwadraturowego z zaznaczonymi zboczami.
Można to sprawdzić przechodząc bardzo wolno z jednej stabilnej pozycji do drugiej, cały czas obserwując co się dzieje ze zmienną pulse_count w STMStudio.
Skąd natomiast wzięła się wartość 403 w rejestrze ARR? Jak już powiedzieliśmy, w naszym rozwiązaniu impulsy między pozycjami zliczane są co 4. W takim razie przekręcając enkoder o 100 pozycji (1 obrót), otrzymamy dokładnie 400 impulsów.
Chcielibyśmy, żeby sto pierwsze przekręcenie wyzerowało licznik. Aby to osiągnąć, należy ustawić wartość ARR właśnie na 403, ponieważ ostatni impuls nowej pozycji, a więc 404, przekroczy wartość ARR i wyzeruje licznik. Zmienna positions zawiera, dzięki temu, liczbę stabilnych pozycji, o które przekręciliśmy enkoder.
Podgląd zmiennych w STMStudio.
Enkoder vs. potencjometr
Część z was pewnie zastanawia się: czy nie łatwiej użyć zamiast tego użyć potencjometru?
Ponieważ potencjometr działa na zupełnie innej zasadzie niż enkoder, występuje między nimi kilka fundamentalnych różnic, które decydują o ich przydatności w danym zastosowaniu.
Najważniejsze z nich to:
Standardowe potencjometry obracają się tylko w pewnym zakresie (np. 0-270°). Enkoder nie ma takiego ograniczenia.
Sygnał z potencjometru jest analogowy, a z enkodera cyfrowy. Sygnał analogowy może zostać łatwo zakłócony i obciążony błędem pomiarowym.
Przy ponownym uruchomieniu systemu, potencjometr daje bezwzględną wartość pozycji. Enkoder natomiast nie posiada pamięci, więc wysyła informacje tylko o następującej rotacji, bez informacji o całkowitym "przekręceniu".
Z powyższych właściwości wynika, że potencjometry kiepsko nadają się na mierzenie pozycji wału silnika. Tak właśnie robione jest to w analogowych serwomechanizmach, stąd często występujące ograniczenie ruchu w pewnym zakresie kątowym.
Z drugiej strony, operując ramieniem robota bez jakichkolwiek innych czujników, bezpieczniejszym rozwiązaniem jest potencjometr. Używając tylko enkodera, przy ewentualnej utracie zasilania nie jesteśmy w stanie stwierdzić w jakiej pozycji znajduje się obecnie nasze ramie.
Finalnie wybór zawsze podyktowany będzie specyfiką danego zastosowania.
Warto jednak znać wady i zalety możliwych (nie jedynych!) rozwiązań.
Odczytywanie sygnału PWM
Ostatnią przydatną funkcją timera, którą zajmiemy się w dzisiejszym artykule będzie odczytanie parametrów sygnału PWM. Znajduje to zastosowanie między innymi przy współpracy z modelarską aparaturą radiową, gdzie informacje przesyłane są właśnie za pomocą sygnału PWM.
W tym przykładzie posłużymy się całą zdobytą do tej pory w artykule wiedzą.
Krok 1. Tworzymy nowy projekt w Cube. Krok 2. Konfigurujemy timer 4 aby generował sygnał PWM tak jak na początku tego artykułu. Krok 3. Konfigurujemy timer 1 aby odczytywał sygnał kwadraturowy z enkodera. Krok 4. Konfigurujemy timer 2 w trybie PWM Input na kanale 1.
Konfiguracja timera w trybie PWM Input.
Zauważmy, że w sekcji konfiguracji zajęte zostały dwa kanały, podczas gdy wyprowadzenie tylko jedno (PA0). Jest to spowodowane tym, że ten tryb korzysta z dwóch kanałów, ale są one wewnętrznie połączone, stąd wystarczy tylko jedno wyprowadzenie.
Tak na prawdę tryb PWM Input to zsynchronizowane
dwa kanały ustawione w trybie Input Capture.
Krok 5. Konfigurujemy zegar główny na 100MHz. Krok 6. Przechodzimy do panelu konfiguracyjnego timera 2.
Panel konfiguracyjny timera 2 skonfigurowanego w trybie PWM Input.
Musimy ustawić nasz zegar w taki sposób, aby jego okres był co najmniej takiej samej długości (najlepiej dłuższy) jak okres sygnału, który zamierzamy odczytywać. W naszym wypadku nie ma tego problemu, ponieważ znamy dokładnie częstotliwość odczytywanego sygnału PWM, która wynosi 100Hz.
Z prostej zależności łączącej częstotliwość sygnału z okresem (T=1/f) wiemy, że jeden okres tego sygnału trwa 10 milisekund.
Przyjmijmy w takim razie, że chcemy, aby okres naszego timera był dwukrotnie dłuższy, a więc żeby trwał 20 milisekund. Ponownie używając przytoczonego przed chwilą wzoru wiemy, że daje nam to częstotliwość 50Hz.
Wynika z tego prosta zasada: częstotliwość zegara odczytującego sygnał PWM należy ustawić mniejszą, niż częstotliwość samego sygnału. W przeciwnym timer doliczy do końca, zanim skończy się okres sygnału.
Rejestr ARR po raz kolejny oznacza rozdzielczość, z jaką będzie mierzony czas w odczytywanym sygnale. Przyjmijmy tę wartość na 1999 (rozdzielczość 2000). Dlaczego akurat tyle? Spodziewamy się, że okres sygnału PWM będzie zajmował dokładnie połowę czasu zliczania, dlatego na jego mierzenie pozostanie połowa ustawionej rozdzielczości. 1000 cykli (dokładność 0.1%) wydaje się wystarczające na mierzenie sygnału PWM, który po stronie nadawczej ustawiany jest z dokładnością do 1%.
Z takim zestawem danych możemy przystąpić do wyliczenia odpowiednich wartości. Przypomnijmy najważniejszy wzór występujący w timerach, a więc:
FREQ = TIM_CLK/(ARR+1)(PSC+1)(CKD+1)
Z noty katalogowej, bądź z artykułu o zegarachwiemy, że timer 2 może być taktowany zegarem 50MHz, a więc TIM_CLK = 50MHz.
Określiliśmy, że ARR = 1999, a FREQ = 50. Podstawiając wartości do wzoru otrzymujemy:
50 = 50000000/(1999+1)(PSC+1)(CKD+1)
(PSC+1)(CKD+1) = 500
Jak zwykle przyjmujemy, że CKD = 0.
PSC = 499
Konfigurujemy timer wyliczonymi wartościami.
Timer 2 skonfigurowany w trybie Input PWM na częstotliwość 50Hz.
Całą resztę parametrów pozostawiamy bez zmian.
Krok 7. Konfigurujemy timer generujący sygnał PWM tak jak w przykładzie na początku artykułu. Timer odczytujący sygnał z enkodera konfigurujemy tak samo jak w przykładzie z enkoderem. Generujemy projekt pod nazwą 05_PWM_IN.
Uruchomienie timera w trybie Input PWM
Naszym zadaniem będzie napisanie krótkiego programu, który będzie generował sygnał PWM o wypełnieniu ustawianym za pomocą enkodera kwadraturowego. Następnie za pomocą timera skonfigurowanego w trybie PWM Input trzeba będzie odczytać parametry sygnału PWM.
Krok 1. Będziemy potrzebować zmiennych do operowania na odczytanych danych.
constuint16_t InputTimerPeriodDuration_miliseconds=20;// Czas trwania jednego okresu timera odczytujacego sygnal PWM [w milisekundach]
// Zmienne do obslugi enkodera
volatileuint16_t pulse_count;// liczba zliczonych impulsow
Krok 2. Następnie musimy uruchomić wszystkie timery. W przypadku tego odpowiedzialnego za odczytywanie sygnału PWM, będziemy korzystać z funkcji trybu Input Capture.
Musimy uruchomić odczytywanie sygnału PWM na dwóch kanałach, ponieważ jeden z nich odpowiedzialny będzie za okres sygnału, a drugi za jego wypełnienie.
Krok 3. Po uruchomieniu timerów musimy odczytać wartości wejściowego sygnału PWM. Służy do tego funkcja z poniższego listingu.
Wiedząc, że odczyt kanału pierwszego da nam informację o długości trwania całego okresu, a kanału drugiego o długości trwania sygnału wysokiego, pętla nieskończona będzie wyglądać następująco.
C
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while(1){
// Odczytanie wartosci enkodera i zeskalowanie do liczby "stablinych pozycji"
pulse_count=(TIM1->CNT)/4;
// Ustawienie wypelnienia rownego odczytanej liczbie impulsow
Duty=pulse_count;
// odczytanie dlugosci trwania okresu sygnalu (w cyklach zegara)
Wadą tego rozwiązania jest to, że tryb Input PWM nie da nam informacji o wypełnieniu równym 0 lub 100%. Wynika to z tego, że timer reaguje na zmiany sygnału. Przy sygnale stałym, jakim jest PWM z wypełnieniem 0 lub 100, zmiany nie występują, więc timer ich nie "łapie" i nie wpisuje nic nowego do rejestru. Oczywiście da się to ominąć, ale wymaga to dodania kilku elementów do systemu.
Zapewne zastanawiacie się również dlaczego dodałem liczbę 1 do każego z odczytów. Można potraktować to jako zadanie domowe, żeby poczytać nieco o tym jak działają timery!
Podsumowanie
W tym odcinku nauczyliśmy się korzystać z bardziej zaawansowanych funkcjonalności timerów, które często przydają się w zastosowaniach robotycznych. Wiemy jak sterować jasnością diody, czy prędkością silnika za pomocą sygnału PWM. Umiemy już odczytać położenie kątowe wału silnika lub gałki enkodera obrotowego. Nauczyliśmy się także odczytywać parametry sygnału PWM, pochodzącego np. z modelarskiej aparatury radiowej.
W następnym odcinku poznamy interfejs I2C służący do komunikacji pomiędzy układami scalonymi. Przy okazji nauczymy się korzystać z umieszczonego na płytce akcelerometru.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY na bazie Arduino i Raspberry Pi.
Dołącz do 20 tysięcy osób, które otrzymują powiadomienia o nowych artykułach! Zapisz się, a otrzymasz PDF-y ze ściągami (m.in. na temat mocy, tranzystorów, diod i schematów) oraz listę inspirujących DIY z Arduino i RPi.
Trwa ładowanie komentarzy...