W poprzednim artykule poświęconym tej tematyce przedstawiłem najprostszą metodę dekodowania sygnału PPM dostępną w Arduino, dziś natomiast pokażę bardziej zaawansowany sposób z użyciem przerwania.

Dla mniej obeznanych z tematem kilka słów odnośnie samych przerwań. W technologii mikrokontrolerów jest to podstawowa metoda do interakcji procesora ze światem zewnętrznym. Mówiąc w pewnym uproszczeniu - jeden procesor jest w stanie w danym momencie zajmować się wykonaniem tylko jednej czynności (jednego fragmentu kodu). Kiedy zależy nam na tym, aby nie tracić czasu na cykliczne sprawdzanie przez podstawowy program stanu jakiegoś zewnętrznego sygnału, wtedy należy użyć tzw. przerwania - czyli obsługiwać wejście w taki sposób, że kiedy pojawi się na nim badany sygnał, to procesor zawiesza aktualnie wykonywany program i przechodzi do wykonania kodu związanego z przerwaniem, a po jego zakończeniu powraca do wykonania głównego programu. W Arduino można przy tym zdefiniować jakie zdarzenie związane z sygnałem wejściowym ma uruchomić mechanizm przerwania - poziom sygnału (wysoki/niski) czy zbocze sygnału (narastające/opadające). W modułach typu UNO i podobnych, opartych o procesor 324, użytkownik ma do dyspozycji 2 przerwania - numer 0 mapowane na Pin2 i numer 1 mapowane na Pin3. Warto też wiedzieć, że inne przerwania są wykorzystywane w wielu kluczowych procesach, nad którymi się zwykle nie zastanawiamy - na przykład funkcje związane z odliczaniem czasu czy transmisją szeregową. To oczywiście generuje pewne niuanse, jako że przy jednym procesorze i wielu przerwaniach obsługiwane w danym momencie jakieś przerwanie może być zawieszone przez przerwanie o wyższym priorytecie, a z kolei przerwanie o niższym priorytecie musi czekać na zakończenie obsługi przerwania o wyższym priorytecie. Są to ogólnie niebanalne tematy, z których warto sobie zdawać sprawę, bo te właśnie niuanse mogą powodować, że programy nie zawsze działają zgodnie z oczekiwaniem, chociaż na naszą logikę wydaje się, że zostały napisane poprawnie.

Schemat powyżej ilustruje sposób pomiaru długości impulsu PPM. Po starcie programu należy zainicjalizować przerwanie 0 poleceniem attachInterrupt(0, procedura, RISING). Kiedy na wejściu pojawi się narastające zbocze sygnału, procedura obsługi przerwania na zboczu narastającym zapamiętuje bieżący czas do odpowiedniej zmiennej i aktywuje przerwanie - ale na zbocze opadające. Kiedy pojawi się zbocze opadające, procedura obsługi przerwania (dla zbocza opadającego) zapamiętuje bieżący czas i aktywuje ponownie przerwanie na zbocze narastające. Różnica czasu pomiędzy wystąpieniem zbocza narastającego i opadającego daje nam czas trwania impulsu, a cały cykl powtarza się dla kolejnych impulsów, ok. 50 razy na sekundę, zgodnie ze standardem PPM.

Program ppm_test4 (pobierz) realizuje pomiar sygnału z jednego kanału odbiornika, podłączonego do Pin2 płytki UNO, połączonej kablem USB z komputerem. Wynik pomiaru jest wysyłany do programu terminalowego, a odbiornik jest zasilany ze złącz +5V, GND modułu UNO.

Mając do dyspozycji 2 przerwania, można dekodować 2 kanały, tak jak zrobiłem to w programie ppm_test5 (pobierz). Drugi kanał odbiornika jest podłączony do Pin3 UNO.

Tak wyglądają wyniki przesyłane na ekran przez oba programy, wartości liczbowe podane są w mikrosekundach. Dla przypomnienia standard sygnału PPM (min-max) to 1-2ms, czyli 1000-2000 mikrosekund z neutrum 1500 mikrosekund. Liczby pokazane na ekranie obrazują sytuację, kiedy porusza się drążkiem. Wydruk następował co 0,5 sekundy, co oznacza, że drukowany jest przeciętnie co 10-ty wynik pomiaru.

Obserwując ekran (kiedy nie porusza się drążkami) można zauważyć, że odczyt nie jest idealnie stabilny, zmierzona wartość raz na kilka odczytów „pływa” o kilka mikrosekund. Jaka może być tego przyczyna? Albo pomiar czasu przy użyciu standardowej funkcji micros() nie jest zbyt dokładny, albo sygnał na wyjściu odbiornika nie jest stabilny, zwykle jednak aparatury RC nie powodują takich problemów. Można więc podejrzewać, że to pomiar czasu nie jest zbyt dokładny. Standardowa funkcja micros() zapewnia pomiar z dokładnością do 4 mikrosekund, można to zauważyć nawet na podstawie odczytów w naszych prostych przykładach. Minimalna zmiana wartości to 4us (a nie 1, 2, 3us), a jeśli sygnał „pływa” to również o 4us. Rozwiązaniem są „zewnętrzne” biblioteki do pomiaru czasu, jedna z nich to <eRCaGuy_Timer2_Counter.h> autorstwa Gabriela Staplesa. Po pobraniu, rozpakowaniu i podegraniu folderu biblioteki do systemowego podfolderu ...\libraries środowiska IDE (wymagany restart), będziemy mieć do dyspozycji dwie nowe funkcje pomiaru czasu:

timer2.get_count() - podaje wartość pomiaru w jednostkach 0,5us (zmiennoprzecinkowa)

timer2.get_micros() - podaje wartość pomiaru w mikrosekundach

Program ppm_test6 (pobierz) robi pomiar długości impulsu metodą micros() i get_micros() oraz drukuje wyniki na ekran (wraz z różnicą pomiaru wg obu metod), efekt można zobaczyć poniżej:

Sygnał testowy pochodził z testera serw, a pomiary zostały wykonane dla 3 wartości sygnału PPM - maksimum, neutrum i minimum. Na pierwszy rzut wydaje się, że druga metoda rzeczywiście sprawia wrażenie dokładniejszej, przy czym dla minimum sygnału odczyty na zrzucie mają tę samą wartość, mimo że pomiar pierwszą metodą nie daje w tym samym czasie takich samych wyników. Nie można jednak tej podstawie jednoznacznie stwierdzić, że druga metoda jest idealna. Po pierwsze pomiary nie są wykonywane dokładnie w tym samym czasie, po drugie wydruk jest co 0.5 sek, czyli w przybliżeniu oglądamy tylko co 10-tą próbkę.

Żeby przekonać się jak to wygląda w praktyce, przeprowadziłem kolejny test. W programie ppm_test7 (pobierz) dołączyłem bibliotekę 'servo', podłączyłem serwomechanizm na Pin8 i podawałem na to wyjście do serwa wartość zmierzoną tak przy pomocy pierwszej jak i drugiej metody. No i tu pewne zaskoczenie. Otóż serwo obserwowane przez dłuższą chwilę od czasu do czasu wpadało w minimalne drgania (bez dotykania pokrętła testera) – efekt bardziej akustyczny niż wizualny. Drgania pojawiały się zarówno przy podawaniu wartości mierzonej pierwszą jak i drugą metodą. Przy czym w drugim przypadku te drgania wydawały się mniejsze i występowały rzadziej. Co ciekawe, kiedy wyłączyłem wydruk danych na ekran, to efekt drgań był mniejszy. Przeprowadziłem więc kolejny eksperyment, polegający na podłączeniu do testera drugiego serwa i porównanie zachowania obu serw (jedno na wejściu drugie na wyjściu UNO). Płytka UNO nie była podłączona do komputera, pomiar czasu wg metody get_micros(). W tym przypadku efekt był lepszy, wizualnie orczyki serw poruszały się synchronicznie, zatrzymując w tym samym położeniu. Miałem jednak wrażenie, że w momencie zatrzymywania orczyka w serwie obsługiwanym przez UNO występowała czasem bardzo krótka i niewielka oscylacja.

Jakie stąd wyciągnąłem wnioski? Otóż dokładność pomiaru a stabilność sygnału, jaką na wyjście podaje biblioteka ' servo', to dwie różne sprawy. Przyznam, że zauważyłem już wcześniej podczas wielu innych testów podobny efekt. Pewnie stąd funkcjonują w Internecie alternatywne biblioteki „RC”, służące do przetwarzania sygnału do zdalnego sterowania. W każdym razie nawet lepsza metoda pomiaru długości impulsu wraz z lepszą metodą pomiaru czasu nie gwarantuje bardzo dobrego efektu, jeśli wynikiem działania programu (przy użyciu standardowych mechanizmów Arduino) ma być sygnał podawany na serwo. Pewnym rozwiązaniem (robiłem już takie próby) jest stosowanie czegoś w rodzaju prostych filtrów cyfrowych, poprawia to dość skutecznie stabilność ruchu orczyka, ale zawsze się wiąże z utratą czułości reakcji serwa na drążek.

Przedstawione wyżej problemy nie są tylko 'winą' samej biblioteki ‘servo’, a raczej złożoności całego procesu obsługi rzeczywistych zdarzeń, niezbyt dużej efektywności języka wysokiego poziomu i (co oczywiste) braku wiedzy, jak te problemy obsługiwać lepiej. Natomiast z pozytywów wydaje się, że zarówno tę metodę pomiaru długości impulsu wraz z alternatywną metodą pomiaru czasu można z powodzeniem zastosować w innych aplikacjach DIY - takich jak np. obrotomierze. Trzeba przy tym pamiętać, że biblioteka <eRCaGuy_Timer2_Counter.h> koliduje z niektórymi standardowymi funkcjami Arduino, szczegółowe informacje można znaleźć w dokumentacji autora.

 

Aktualizacja 2017-06-20

Przy okazji prac nad projektem monitora LiPo SGM-TGuard byłem zmuszony zrobić kolejne podejście do tematu dekodowania sygnału PPM/PWM z użyciem przerwania i biblioteki <eRCaGuy_Timer2_Counter.h>. Tym razem, dzięki nieco innemu sposobowi wykorzystania tej biblioteki udało mi się uzyskać bardzo stabilny odczyt szerokości impulsu, nadający się do praktycznego zastosowania (dotyczy dekodowania tylko jednego kanału). Testowy program ppm_test8 (pobierz) czyta pojedynczy kanał z odbiornika RC i powtarza ten impuls na wyjściu D8 Arduino, do którego zostało podłączone testowe serwo. Tym razem, w przeciwieństwie do wcześniejszych prób, orczyk serwa zachowuje się bardzo stabilnie, powtarza gładko ruchy drążka, a w stanie spoczynkowym stoi w miejscu stabilnie, bez żadnych drgań.

 

Nie masz uprawnień aby komentować.

Publikowane tutaj materiały i zdjęcia stanowią własność ich autorów, nie mogą być kopiowane oraz wykorzystywane bez ich zgody.
Strona niekomercyjna.