KursyPoradnikiInspirujące DIYForum

Kurs STM32L4 – #14 – czujnik odległości, wyświetlacz 7-seg.

Kurs STM32L4 – #14 – czujnik odległości, wyświetlacz 7-seg.

Wracamy do tematu liczników w STM32L4. Tym razem wykorzystamy je do obsługi wyświetlaczy 7-segmentowych oraz do mierzenia odległości za pomocą czujnika ultradźwiękowego HC-SR04.

Przy okazji w ramach ciekawostki użyjemy też jednego ze wzmacniaczy, który wbudowany jest wewnątrz mikrokontrolera STM32L476RG.

Czego dowiesz się z tej części kursu STM32L4?

Zaczniemy od wykorzystania liczników do mierzenia czasu trwania impulsów. Przykładem będzie tu HC-SR04, czyli popularny ultradźwiękowy czujnik odległości. Następnie skorzystamy z liczników do reprezentowania cyfr na wyświetlaczach 7-segmentowych, dzięki czemu stworzymy prosty dalmierz, który w całości będzie działał w przerwaniach. Na koniec dodamy do projektu obsługę analogowego czujnika temperatury, którego sygnał przepuścimy przez wzmacniacz, aby docelowo wykorzystać go do poprawy wskazań czujnika odległości (temperatura powietrza wpływa na prędkość dźwięku).

HC-SR04 – ultradźwiękowy czujnik odległości

Czujniki ultradźwiękowe spotkać można w wielu przemysłowych zastosowaniach (np. w roli czujników parkowania). Sensory takie są również popularne w hobbystycznych projektach – głównie ze względu na stosunkowo niską cenę gotowych modułów.

Niezależnie od ceny i zastosowania sposób obsługi tego typu czujników przeważnie jest taki sam, więc umiejętność opisana w tej części będzie bardzo uniwersalna. Na potrzeby tego kursu zdecydowaliśmy się wybrać moduł HC-SR04 – głównie ze względu na jego popularność wśród hobbystów i studentów.

Przykładowy moduł HC-SR04 (niektóre wersje modułu mogą wyglądać trochę inaczej)

Przykładowy moduł HC-SR04 (niektóre wersje modułu mogą wyglądać trochę inaczej)

Czujnik posiada cztery wyprowadzenia: masę, zasilanie (5 V), wejście wyzwalające pomiar (opisywane jako Trig, od angielskiego trigger) oraz pin, z którego odczytamy wynik (opisywany jako Echo). Działanie czujnika najłatwiej zrozumieć za pomocą poniższej ilustracji.

Przebiegi związane z pomiarem odległości za pomocą czujnika HC-SR04

Przebiegi związane z pomiarem odległości za pomocą czujnika HC-SR04

Pomiar rozpoczynamy od wygenerowania na linii Trig impulsu o długości 10 μs. Czujnik wyśle wówczas paczkę kilku impulsów ultradźwiękowych i wystawi stan wysoki na wyjściu Echo. Następnie będzie on oczekiwał na odbiór sygnału, który odbił się od przeszkody. Po jego odebraniu stan wyjścia Echo znów zostanie zmieniony (z wysokiego na niski).

Przyjmując, że prędkość dźwięku w powietrzu to 340 m/s, możemy policzyć, że w ciągu 1 µs dźwięk przebywa 0,034 cm, czyli około 1/29 cm. W związku z tym, że w tym przypadku dźwięk musi przebyć drogę dwa razy (od nadajnika do przeszkody i z powrotem), to aby „przeliczyć czas na odległość”, wystarczy podzielić go przez 58 (2 * 29) – uzyskamy wtedy odległość od przeszkody w centymetrach. Tym samym uda nam się precyzyjnie wykrywać otaczające nas obiekty – zupełnie tak samo, jak robią to nietoperze, korzystające z echolokacji.

Ultradźwiękowe czujniki odległości wykorzystują ten sam efekt co nietoperze

Ultradźwiękowe czujniki odległości wykorzystują ten sam efekt co nietoperze

Podłączenie HC-SR04 do STM32L4

Krótki wstęp teoretyczny za nami, więc możemy przejść do praktyki. Zaczynamy od podłączenia czujnika do mikrokontrolera. Tym razem wejście wyzwalające pomiar, czyli Trig, łączymy z pinem PB10, a wyjście czujnika, czyli Echo – z pinem PA0. Oprócz tego podłączamy oczywiście czujnik do zasilania – tym razem wyjątkowo do 5 V.

Schemat ideowy i montażowy podłączenia HC-SR04 do STM32L4

Schemat ideowy i montażowy podłączenia HC-SR04 do STM32L4

Układ w obecnej wersji można podłączyć bezpośrednio do Nucleo, można również wykorzystać płytkę stykową – wtedy warto skorzystać z możliwości podłączenia dodatkowego kondensatora 100 nF w celu filtrowania zasilania (nie jest to jednak konieczne).

Gotowe zestawy do kursów Forbota

 Komplet elementów  Gwarancja pomocy  Wysyłka w 24h

Zamów zestaw elementów i wykonaj ćwiczenia z tego kursu! W komplecie płytka NUCLEO-L476RG oraz m.in. wyświetlacz graficzny, joystick, enkoder, czujniki (światła, temperatury, wysokości, odległości), pilot IR i wiele innych.

Zamów w Botland.com.pl »

Współpraca STM32L4 z układami zasilanymi z 5 V

Nasz mikrokontroler jest zasilany 3,3 V. Niestety dość często okazuje się, że układy peryferyjne, które chcemy do niego podłączyć, wymagają zasilania z 5 V – tak właśnie jest w tym przypadku. Pojawiają się wówczas dwa problemy:

  1. Na wyjściach naszego mikrokontrolera maksymalne napięcie wynosi 3,3 V, może się więc zdarzyć, że będzie to za mało, aby np. dany czujnik wykrył logiczną jedynkę.
  2. Nie wszystkie piny mikrokontrolera (podczas pracy jako wejścia) tolerują 5 V, które może zostać podane właśnie np. przez taki czujnik.

To, czy punkt 1 jest dużym problemem, zależy od podłączanego układu. Niektóre elementy będą działać poprawnie, gdy zasilimy je z 3,3 V, a inne mogą wymagać dodatkowych zabiegów (lub innych sztuczek) – w dalszej części kursu jeszcze wrócimy do tego tematu.

Drugi problem dotyczy napięcia, które zostanie podane na wejście mikrokontrolera. W dokumentacji STM32L4, przy opisie wybranego przez nas pinu PA0, znajdziemy zapis FT_a, który oznacza, że dany pin toleruje napięcie 5 V – możemy więc spokojnie podłączyć sygnał Echo z czujnika wprost do PA0.

Informacja o tolerancji danego pinu dla 5 V na wejściu

Informacja o tolerancji danego pinu dla 5 V na wejściu

Warto jednak pamiętać, że nie wszystkie piny mikrokontrolera STM32L476RG tolerują 5 V. Jeśli nie sprawdzimy, czy dany pin posiada dopisek FT_a, to możemy przypadkiem uszkodzić układ. Dokładny opis tego parametru znaleźć można w dokumentacji mikrokontrolera.

Lista parametrów, którymi opisywane są GPIO (fragment dokumentacji)

Lista parametrów, którymi opisywane są GPIO (fragment dokumentacji)

Obsługa czujnika HC-SR04 za pomocą liczników

Mamy już podłączony układ, więc czas przystąpić do programowania. Zaczynamy tak jak zawsze, czyli tworzymy projekt z STM32L476RG, który pracuje z częstotliwością 80 MHz. Uruchamiamy debugger i USART2 w trybie asynchronicznym. Zaznaczamy też w opcjach projektu, że CubeMX ma wygenerować osobne pliki dla wszystkich modułów. Następnie (już standardowo) dodajemy przekierowanie printf na UART – wystarczy dodanie pliku nagłówkowego:

oraz kodu zbliżonego do poniższego:

Następnie wracamy do perspektywy CubeMX i zabieramy się za konfigurację TIM2. Jest to 32-bitowy licznik – nie jest on niezbędny do tego konkretnego zadania, ale wybieramy go w ramach treningu, żeby poznać bardziej rozbudowane peryferie.

Licznik wykorzystamy do dwóch zadań. Pierwszy kanał timera będzie pracował w trybie wejścia, dzięki czemu uda nam się zmierzyć czas trwania impulsu zwracanego przez czujnik. Z kolei trzeci kanał licznika wykorzystamy do wygenerowania sygnału uruchamiającego pomiar – użyjemy trybu PWM, dzięki czemu licznik będzie mógł sprzętowo wygenerować odpowiedni impuls startowy.

W praktyce oznacza to, że musimy zacząć od poniższych ustawień TIM2:

  • Clock Source: Internal Clock
  • Channel 1: Input Capture direct mode
  • Channel 3: PWM Generation CH3

Następnie przechodzimy do edycji parametrów licznika. Zaczynamy od preskalera – w tym przypadku ustawiamy go na 79, ponieważ częstotliwość taktowania układu (80 MHz) zostanie wtedy podzielona przez 80, co da nam 1 MHz (albo inaczej 1 μs). Potem ustawiamy parametr Counter Period, który w tym przypadku będzie regulował częstotliwość naszych pomiarów – w tym przypadku wystarczy nam pomiar co 1 s, wpisujemy więc 999999. Na koniec potrzebna jest jeszcze konfiguracja czasu trwania impulsu generowanego przez PWM. Tutaj, zgodnie z dokumentacją czujnika, wpisujemy 10 μs.

W praktyce oznacza to, że musimy ustawić następujące parametry dla TIM2:

  • Prescaler: 79
  • Counter Period: 999999
  • PWM Generation Channel 3 > Pulse: 10

W przypadku pozostałych parametrów zostawiamy domyślne wartości. Zapisujemy projekt i od razu zabieramy się za pisanie kodu. Właściwie wszystko, co jest potrzebne do uruchomienia PWM oraz pomiaru sygnału z czujnika, już jest nam znane, bo temat ten omówiliśmy w części 8 kursu.

Opisaliśmy tam funkcje, które będą tu potrzebne, czyli HAL_TIM_PWM_Start oraz HAL_TIM_IC_Start. Nowością będzie to, że obie te funkcje wykorzystamy w kontekście tego samego licznika sprzętowego. Pierwsza wersja kodu może wyglądać następująco:

Jak widać, licznik bez problemu może pełnić jednocześnie funkcję „wejścia” i „wyjścia”. Niestety, po uruchomieniu programu uzyskamy jednak dość zaskakujący efekt.

Wartość odczytana przez pierwszą wersję programu

Wartość odczytana przez pierwszą wersję programu

Wartość wyświetlana w terminalu będzie praktycznie stała – wskazania będą różne dla poszczególnych egzemplarzy czujnika, mogą się również wahać o kilka jednostek. Wartości te nie będą jednak zależały od tego, czy przed czujnikiem znajduje się jakaś przeszkoda. W rozwiązaniu tej zagadki znów pomocny będzie analizator stanów logicznych.

Podgląd komunikacji z czujnikiem za pomocą analizatora stanów logicznych

Podgląd komunikacji z czujnikiem za pomocą analizatora stanów logicznych

Jak widać, schemat „komunikacji” z czujnikiem się zgadza, jednak wcześniejsze przebiegi czasowe (które zostały przygotowane na bazie dokumentacji) zupełnie nie oddają skali czasu. Nasz układ działa poprawnie – na linii Trig pojawił się impuls wyzwalający, a na linii Echo otrzymaliśmy odpowiedź.

Dlaczego więc nasz pomiar nie jest poprawny? Pozostawiliśmy domyślne opcje licznika, więc pomiar dotyczy zbocza narastającego – liczony jest zatem czas od początku okresu, tj. od sygnału na linii Trig aż do pojawienia się zbocza narastającego, które oznacza dopiero początek odpowiedzi.

To, co uzyskaliśmy, też nam się przyda, bo czas między wyzwalaniem pomiaru a początkiem odpowiedzi jest praktycznie stały. Możemy więc zmienić konfigurację licznika tak, aby mierzyć moment pojawienia się zbocza opadającego, a czas, który właśnie zmierzyliśmy, po prostu odejmiemy od wyniku. Wracamy do CubeMX i zmieniamy parametr Polarity Selection dla Input Capture Channel 1 na Falling Edge. Dzięki temu będziemy zliczać czas do zbocza opadającego, czyli końca odpowiedzi.

Następnie ponownie uruchamiamy program (bez żadnej zmiany kodu). Teraz wyniki będą się zmieniały w miarę przybliżania/oddalania przeszkody do/od czujnika. Możemy już spokojnie zmienić program w taki sposób, aby odległość była wyświetlana w centymetrach. Wystarczy zmiana jednej linijki:

Teraz wynik będzie wyświetlany jako liczba zmiennoprzecinkowa, musimy tylko włączyć obsługę takich liczb za pomocą printf (Project > Properties > C/C++ Build > Settings > MCU Settings > Use float with printf...). Po uruchomieniu tej wersji programu otrzymamy już poprawną odległość.

Pomiar odległości i wyświetlanie wyników w centymetrach

Pomiar odległości i wyświetlanie wyników w centymetrach

Nasz program działa, ale jest daleki od ideału. Odejmowanie stałej wartości od wyniku to niewątpliwie zły pomysł, tym bardziej że ta wartość zależy od konkretnego egzemplarza tego czujnika. Na szczęście możemy rozwiązać ten problem znacznie lepiej (sprzętowo).

Sprzętowy pomiar obu zboczy sygnału

Wracamy do CubeMX i konfiguracji TIM2. Jeden kanał ma przypisany tylko jeden rejestr pamiętający wyniki pomiaru, więc nie możemy w nim zmieścić dwóch wartości. Możemy za to włączyć drugi kanał. Jeśli dla kanału drugiego wybierzemy opcję taką jak dla pierwszego (czyli Input Capture direct mode), to zobaczymy, że CubeMX automatycznie skonfiguruje pin PA1 jako wejście TIM2_CH2.

Wystarczy, że zmienimy tryb działania drugiego kanału na Input Capture indirect mode (będzie od razu widać, że PA1 nie jest wtedy używany). W trybie direct każdy kanał ma przypisane wejście o takim samym numerze, czyli kanał 1 jest połączony z wejściem CH1, kanał 2 z CH2 itd. Natomiast w trybie indirect pary, czyli kanał 1 i 2 oraz 3 i 4, są zamienione. Dlatego kanał 2 jest połączony z CH1, a gdybyśmy wybrali ten tryb dla kanału 1, używałby on wejścia CH2.

Dzięki takiemu ustawieniu jeden pin, czyli PA0, będzie połączony z dwoma kanałami naszego timera jednocześnie. Teraz możemy dla pierwszego ustawić reakcję na zbocze narastające, a dla drugiego na opadające. Czyli dla formalności ustawiamy:

  • Channel 1: Input Capture direct mode
  • Channel 2: Input Capture indirect mode
  • Channel 3: PWM Generation CH3
  • Następnie w parametrach:
    • Input Capture Channel 1 > Polarity Selection > Rising Edge
    • Input Capture Channel 2 > Polarity Selection > Falling Edge

Dzięki temu będziemy dokładnie wiedzieli, kiedy rozpoczął i zakończył się sygnał, który zwracany jest przez czujnik, i na tej podstawie automatycznie obliczymy odległość (bez odejmowania stałej).

Nowy kod wykorzystuje aż trzy kanały licznika sprzętowego. Dzięki temu możemy generować sygnał wyzwalający pomiar oraz odczytywać oba zbocza odpowiedzi całkowicie sprzętowo, więc układ będzie mógł wyświetlać poprawne wyniki niezależnie od użytego egzemplarza czujnika.

Obsługa wyświetlaczy 7-segmentowych

Wysyłanie wyników przez port szeregowy jest świetnym rozwiązaniem, ale pora (dla treningu) utrudnić sobie zadanie i wykorzystać wyświetlacz 7-segmentowy. Elementy tego typu są powoli wypierane przez nowsze rozwiązania, np. wyświetlacze graficzne, ale nadal można je spotkać w wielu urządzeniach.

Budowa typowego wyświetlacza 7-segmentowego

Budowa typowego wyświetlacza 7-segmentowego

Wyświetlacze 7-segmentowe to nic innego jak zbiór diod świecących, które włącza się w taki sposób, aby uzyskać pożądaną liczbę. Zadanie jest więc pozornie proste – miganie diodami już opisywaliśmy (na samym początku kursu). Obsługa tych wyświetlaczy jest jednak dla wielu osób problematyczna, dlatego pokażemy, jak obsłużyć je w pełni sprzętowo. Do wszystkiego dojdziemy stopniowo.

Wyświetlanie jednej cyfry

Zaczniemy od obsługi jednej cyfry wyświetlacza. W tym celu wystarczy go po prostu podłączyć tak, jak robiliśmy to na początku kursu – każda dioda do osobnego pinu, przez osobny rezystor. I taka wersja jest właściwie najlepsza. Możemy jednak zastosować pewne uproszczenie i dodać rezystor 330 R na wspólnej linii (katoda) – dzięki temu połączenie układu na płytce stykowej będzie znacznie łatwiejsze. Stosując takie uproszczenie, godzimy się jednak z tym, że jasność wyświetlacza będzie zależna od liczby segmentów, które są w danej chwili włączone (bo ten sam prąd będzie dzielony na więcej diod).

Schemat ideowy i montażowy do przykładu z tego ćwiczenia

Schemat ideowy i montażowy do przykładu z tego ćwiczenia

Oczywiście wspólna katoda mogłaby być podłączona przez rezystor prosto do masy. Podłączamy ją jednak do kolejnego wyprowadzenia naszego mikrokontrolera, bo takie rozwiązanie będzie nam potrzebne podczas kolejnych testów.

Teraz wracamy do programowania (zostajemy w tym samym projekcie). Zaczynamy od ustawienia GPIO. Wszystkie piny, do których podłączyliśmy wyświetlacz, konfigurujemy jako wyjścia i nadajemy im odpowiednie etykiety (SEG_A, SEG_B, SEG_C, SEG_D, SEG_E, SEG_F, SEG_G oraz SEG_1 dla katody).

Konfiguracja GPIO dla wyświetlacza 7-segmentowego

Konfiguracja GPIO dla wyświetlacza 7-segmentowego

Kod do obsługi wyświetlacza od razu wydzielimy do osobnych plików. Zaczynamy od utworzenia pliku seg7.h, do którego wstawiamy:

Funkcja seg7_show_digit będzie wyświetlała cyfrę podaną jako parametr na wyświetlaczu. Jej kod definiujemy oczywiście w kolejnym pliku, czyli seg7.c, który też musimy utworzyć. Pierwsza, wstępna wersja będzie długa, ale prosta do zrozumienia (zaraz ją skrócimy).

Wszystko powinno być chyba jasne – małego komentarza może wymagać jedynie zapis z dzieleniem modulo 10 (wewnątrz instrukcji sterującej switch). Jest to proste zabezpieczenie, bez którego podanie liczby dwucyfrowej sprawiłoby, że na wyświetlaczu nie pojawiłaby się żadna wartość. Dzięki modulo na wyświetlaczu pojawi się np. 1, gdy jako parametr podamy 21.

Teraz możemy przejść do programu głównego i wykorzystać naszą nową funkcję (na razie tymczasowo kasujemy lub zakomentowujemy poprzedni kod związany z obsługą czujnika):

Po uruchomieniu powinniśmy zobaczyć, że na wyświetlaczu pojawiają się kolejne cyfry. Jeśli wszystko działa poprawnie, to możemy przejść dalej, czyli do optymalizacji kodu. W przypadku problemów warto się teraz zatrzymać i sprawdzić np. poprawność połączeń.

Skracanie kodu obsługi wyświetlacza

Nasza dotychczasowa funkcja sprawdza cyfrę podaną jako argument i odpowiednio włącza segmenty wyświetlacza. Taki program oczywiście działa, ale jest strasznie długi i można go na wiele sposobów poprawić. Zacznijmy od eliminacji powtarzającego się kodu.

W aktualnej wersji dla każdej cyfry wywołujemy HAL_GPIO_WritePin aż siedem razy. Możemy więc wstawić ten kod do funkcji pomocniczej, która będzie ustawiała wyjścia zgodnie z wartościami bitów zmiennej przekazanej jako jej parametr. Do pliku seg7.c dodajemy więc funkcję statyczną:

Teraz możemy zmienić kod funkcji seg7_show_digit na nieco krótszą wersję:

Jest nieco lepiej, ale to nie ostatnia możliwość skrócenia tego kodu, bo zamiast oddzielnej analizy każdego przypadku możemy utworzyć tablicę zawierającą informację o tym, jakie mają być stany pinów podczas włączania konkretnej cyfry.

Dzięki temu wystarczy, że wywołanie funkcji set_output pojawi się w kodzie tylko raz:

Takiej wersji kodu będziemy się teraz trzymać. Oczywiście każde podejście ma swoich zwolenników. Pierwsza wersja jest bardzo długa, ale najbardziej czytelna. Ostatnia wersja kodu jest znacznie krótsza, lecz może być trudniejsza w zrozumieniu – chcieliśmy jednak pokazać w kursie kolejną „sztuczkę”.

Wyświetlanie dwóch cyfr

Nasz wyświetlacz posiada dwie cyfry i moglibyśmy po prostu podłączyć drugi zestaw diod do innych GPIO, ale takie rozwiązanie byłoby nieefektywne. Wystarczy, że do osobnego pinu mikrokontrolera podłączymy wspólną katodę od drugiego wyświetlacza (oczywiście przez rezystor). Z kolei segmenty poszczególnych cyfr możemy śmiało ze sobą połączyć.

Schemat ideowy i montażowy drugiej wersji układu z wyświetlaczem 7-segmentowym

Schemat ideowy i montażowy drugiej wersji układu z wyświetlaczem 7-segmentowym

Przy takim połączeniu wykorzystujemy o wiele mniej pinów mikrokontrolera, ale nie jesteśmy w stanie wyświetlić jednocześnie różnych cyfr. Rozwiążemy ten problem, włączając tylko jedną cyfrę w danej chwili i bardzo szybko przełączając się między jedną a drugą cyfrą wyświetlacza. Dzięki temu będziemy mogli wyświetlać różne cyfry, używając tylko jednego pinu na każdą dodatkową cyfrę.

Jak wiemy, diody świecą tylko wtedy, gdy są podłączone zgodnie z kierunkiem przewodzenia prądu. Zatem jeśli na wspólnej katodzie wystawimy stan wysoki, dana cyfra będzie zgaszona. Natomiast ustawienie stanu niskiego pozwoli włączać diody wybranej cyfry zgodnie ze stanem pinów podłączonych do wspólnych segmentów. Wracamy więc do CubeMX i dodajemy konfigurację pinu PA11 jako wyjścia SEG_2. Następnie konfigurujemy PA11 oraz PB12 jako wyjścia typu open-drain z domyślną wartością High.

Moglibyśmy zostawić domyślny tryb push-pull, ale wówczas diody wyłączonej cyfry byłyby podłączane do pełnego napięcia w kierunku zaporowym. Nie ma w tym nic złego, bo napięcie 3,3 V nie uszkodzi naszego wyświetlacza, ale lepszym rozwiązaniem jest użycie trybu open-drain. Tak jak pisaliśmy w części o I2C – w tym trybie tranzystor NMOS zwiera wyjście do masy, gdy wystawiany jest stan niski, a wysterowanie stanem wysokim pozostawia wyjście w stanie wysokiej impedancji.

Teraz uruchamiamy timer TIM6. Wykorzystamy go do naprzemiennego włączania cyfr wyświetlacza. Ustawiamy preskaler na 7999, dzięki czemu licznik będzie zliczał impulsy o częstotliwości 10 kHz. Od razu ustawiamy też okres, czyli parametr Counter Period, na 1999.

Przy takich ustawieniach przerwanie od tego licznika będzie pojawiało się pięć razy na sekundę, dzięki czemu dosłownie zobaczymy, jak działa ten kod. Na koniec w zakładce NVIC Settings włączamy przerwanie timera TIM6 oraz zmniejszamy przypisany mu priorytet (np. na 10). Teraz możemy zapisać zmiany i wrócić do naszego programu.

Zaczynamy od zmiany pliku seg7.h – dodajemy do niego informację o kolejnej funkcji:

Funkcja seg7_show będzie tym, czego oczekuje użytkownik naszej biblioteki – jej wywołanie będzie ustawiało wyświetlaną wartość, czyli obie cyfry. Natomiast seg7_update to funkcja pomocnicza, która wyłącza aktualnie włączoną cyfrę i włącza drugą. Częste wywoływanie tej funkcji będzie sprawiało, że obie cyfry będą dla człowieka widoczne jednocześnie.

Przechodzimy do pliku seg7.c i zaczynamy od dodania dwóch zmiennych:

Pierwsza to po prostu aktualnie wyświetlana wartość. Będziemy ustawiali ją w funkcji seg7_show:

Tym, co najważniejsze, jest funkcja seg7_update. Będzie ona wyświetlała cyfrę o numerze active_digit oraz zmieniała wartość zmiennej na kolejną:

Pierwsze dwie linijki ustawiają stan wysoki na pinach podłączonych do wspólnych katod obu cyfr, co powoduje wyłączenie wyświetlacza. Następnie na wyjściach ustawiamy stan odpowiadający temu, co ma być wyświetlone na danej pozycji wyświetlacza.

Jeśli aktywna jest pierwsza cyfra, to przekazujemy do seg7_show_digit wartość actual_value – funkcja i tak wyświetli tylko pierwszą cyfrę z dwucyfrowej liczby. Natomiast jeśli aktywna jest druga cyfra, to przekazujemy wartość actual_value podzieloną przez 10, co sprawi, że na wyświetlaczu zostanie pokazana właściwa wartość. W kolejnym kroku zmieniany jest stan linii połączonej z katodą aktywnej cyfry, co powoduje jej włączenie. Na koniec wartość active_digit zmieniana jest na przeciwną, więc kolejne wywołanie seg7_update włączy drugą cyfrę.

Teraz możemy przejść do pliku main i dodać do programu procedurę obsługi przerwania:

Jej treść to po prostu wywołanie funkcji seg7_update, która będzie włączała odpowiednie cyfry wyświetlacza. Na koniec edycja głównej części kodu. Po pierwsze uruchamiamy przerwanie od licznika TIM6, a po drugie dodajemy licznik, dzięki któremu sprawdzimy działanie tego układu.

Gdy teraz uruchomimy nasz program, zobaczymy, że wyświetla on kolejne liczby, ale w danej chwili włączana jest tylko jedna cyfra – był to jednak efekt zamierzony, aby można było łatwiej zrozumieć, jak działa ten mechanizm. W celu poprawienia uzyskanego efektu wystarczy, że zwiększymy częstotliwość, z jaką generowane jest przerwanie TIM6. Wracamy zatem do CubeMX i zmieniamy okres na 99. Nasz program będzie działał tak samo jak poprzednio – jedyna różnica będzie taka, że teraz przełączanie wyświetlaczy odbywa się sto razy na sekundę, więc ludzkie oko nie widzi migania.

Efekt działania programu – dwucyfrowy licznik

Efekt działania programu – dwucyfrowy licznik

Wyświetlanie pomiarów na wyświetlaczu

Teraz możemy połączyć wcześniejszy pomiar odległości z wyświetlaczem – przy okazji pokażemy, jak napisać program, który będzie działał w pełni w przerwaniach. Cały czas wykorzystujemy takie samo podłączenie i te same liczniki, które zostały opisane na początku tego poradnika. Przechodzimy tylko do edycji parametrów licznika TIM2, który odpowiada za odpytywanie czujnika odległości. Zmniejszamy tam parametr Period z 999999 np. na 99999, dzięki czemu zwiększymy częstotliwość pomiarów.

Włączamy też obsługę przerwania od TIM2 i (tradycyjnie) zmniejszamy jego priorytet (np. na 10). Teraz możemy zapisać zmiany w projekcie i wrócić do edycji kodu. Następnie przechodzimy do pliku main i zaczynamy od edycji procedury obsługi przerwań:

Obsługujemy w niej oba liczniki. W przypadku TIM6 przekazujemy obsługę do funkcji seg7_update, a w przerwaniu od TIM2 pobieramy wynik pomiaru i przekazujemy go do seg7_show. Teraz możemy przystąpić do zmian głównej części kodu, która może być dość zaskakująca:

Uruchamiamy liczniki bazowe dla TIM2 i TIM6, konfigurujemy również kanały TIM2. Po tej konfiguracji program główny może być już pusty, bo cały program działa w przerwaniach i zupełnie nie „zagraca” nam głównej pętli programu, w której moglibyśmy zająć się ważniejszymi tematami.

Dalmierz cyfrowy, który działa dzięki przerwaniom

Dalmierz cyfrowy, który działa dzięki przerwaniom

Prędkość dźwięku w powietrzu?

Przy obliczaniu odległości mierzonej za pomocą ultradźwiękowego czujnika zastosowaliśmy popularną metodę polegającą na dzieleniu uzyskanego wyniku wyrażonego w mikrosekundach przez wartość 58. Otrzymywaliśmy wynik w centymetrach, a całość działa całkiem sprawnie.

Użyty przez nas współczynnik 58 odpowiadał prędkości dźwięku 344,8 m/s, czyli temperaturze nieco ponad 20°C. Jednak gdybyśmy chcieli korzystać z dalmierza nie tylko w temperaturze pokojowej, warto byłoby zastanowić się, jaki błąd wprowadza taki stały współczynnik.

W temperaturze 0°C prędkość dźwięku wynosi 331,8 m/s, więc błąd to około 3,8%. Niby niewiele, ale jeśli będziemy mierzyli odległość 1 m, to „pomylimy się” o prawie 4 cm, a to dość dużo. Na szczęście możemy rozbudować układ o czujnik temperatury, a przy okazji poznamy kolejne tajniki programowania STM32.

LM35 – analogowy czujnik temperatury

Tym razem w roli czujnika temperatury wykorzystamy analogowy termometr LM35. W dokumentacji układu przeczytamy, że musi on być zasilany co najmniej z 4 V (w tym przypadku będzie to więc 5 V). Korzystanie z tego czujnika jest bardzo proste – podłączamy zasilanie, a na jego wyjściu otrzymujemy napięcie proporcjonalne do aktualnej temperatury. Do przetestowania tego czujnika nie potrzebujemy nawet mikrokontrolera – wynik możemy odczytać zwykłym multimetrem.

Schemat ideowy i montażowy dla pomiaru temperatury za pomocą LM35

Schemat ideowy i montażowy dla pomiaru temperatury za pomocą LM35

Współczynnik zależności napięcia od temperatury dla układu LM35 to 10 mV/C. W naszym przypadku napięcie wynosiło około 0,258 V, co można przeliczyć na temperaturę 25,8°C. Warto zwrócić uwagę, że pomimo zasilania układu z 5 V na jego wyjściu otrzymujemy stosunkowo niskie napięcie – na tyle, że będziemy mogli podać je bezpiecznie na wejście przetwornika ADC.

Podłączenie i test czujnika

Teraz możemy odłączyć multimetr i podłączyć wyjście czujnika do pinu PA6 – oczywiście pozostawiamy na płytce stykowej wcześniejsze elementy, tj. wyświetlacz 7-segmentowy oraz czujnik ultradźwiękowy.

Schemat ideowy i montażowy ostatecznej wersji czujnika odległości

Schemat ideowy i montażowy ostatecznej wersji czujnika odległości

Wiemy już, że czujnik LM35 zasilany jest z 5 V, ale na wyjściu pojawia się napięcie 10 mV/C, więc nawet jeśli będziemy testować układ przy 50°C, na wyjściu pojawi się raptem 0,5 V. Dlatego możemy podłączyć czujnik bezpośrednio do wejścia ADC, nie martwiąc się, że uszkodzimy nasz mikrokontroler zbyt wysokim napięciem.

Wracamy więc do widoku CubeMX i przechodzimy do modułu ADC1. Uruchamiamy odczyt na kanale numer 11, czyli wybieramy dla niego opcję IN11 Single-ended. Następnie w parametrach musimy ustawić następujące opcje:

  • ADC_Settings > Continous Conversion Mode: Enabled
  • ADC_Settings > Overrun behaviour: Overrun data overwritten
  • ADC_Regular_ConversionMode > Rank 1 > Sampling Time: 640.5 Cycles

Oprócz tego po aktywacji przetwornika ADC zostaniemy prawdopodobnie powiadomieni o tym, że konieczna jest zmiana ustawień zegarów. Warto zatem przejść do zakładki Clock Configuration, aby zatwierdzić automatycznie zaproponowane zmiany.

Konfiguracja przetwornika ADC (oraz czerwone ostrzeżenie w zakładce Clock Configuration)

Konfiguracja przetwornika ADC (oraz czerwone ostrzeżenie w zakładce Clock Configuration)

Teraz możemy zapisać zmiany, wygenerować nowy kod i przystąpić do napisania programu. Do głównej funkcji main dodajemy kalibrację i uruchomienie przetwornika ADC, do tego wewnątrz pętli while dodajemy instrukcję, która będzie wypisywała w terminalu aktualny odczyt z ADC.

Po uruchomieniu tej wersji programu powinniśmy uzyskać ciąg wartości – w naszym przypadku było to około 324, co można łatwo przeliczyć: 324 * 3,3 V / 4096 = 0,26 V, co daje 26°C. Warto więc zmienić zawartość pętli while – możemy od razu wyświetlać temperaturę w stopniach Celsjusza oraz prędkość dźwięku dla danych warunków:

Tym razem na ekranie powinniśmy widzieć efekt zbliżony do poniższego – oczywiście wartości powinny się zmieniać np. po ogrzaniu termometru.

Pomiar napięcia przez ADC, przeliczanie odczytów na temperaturę i prędkość dźwięku

Pomiar napięcia przez ADC, przeliczanie odczytów na temperaturę i prędkość dźwięku

W związku z tym, że wszystko działa już poprawnie, możemy przenieść fragment kodu obliczający prędkość dźwięku dla aktualnych warunków atmosferycznych do osobnej funkcji, którą dodajemy do pliku main (jest to funkcja statyczna, więc dodajemy ją nad naszymi wcześniejszymi funkcjami):

Na koniec uwzględniamy prędkość dźwięku w trakcie obliczania odległości i usuwamy kod testowy z pętli głównej. Nowa wersja procedury obsługi przerwań będzie wyglądała następująco: 

Dlaczego akurat tak obliczyliśmy odległość? Zacznijmy od wartości stop-start. Jest to wyrażony w mikrosekundach czas, w którym dźwięk przebył drogę do przeszkody i z powrotem. W związku z tym, aby wyrazić wynik w sekundach, dzielimy wartość przez 1 000 000, a ponieważ interesuje nas odległość (czyli droga dźwięku w jedną stronę), to dzielimy ją jeszcze przez 2. Chcemy mieć wynik nie w metrach, ale w centymetrach, więc powinniśmy pomnożyć obliczoną wartość przez 100. Skoro jednak dzielimy przez milion, następnie dzielimy przez 2, a później mnożymy przez 100, możemy to uprościć do dzielenia przez 20 000 – i tak właśnie zrealizowano to w programie.

Wzmacniacz operacyjny

Jak widać, nasz układ działał poprawnie, ale czujnik LM35 wykorzystuje bardzo małą część zakresu naszego przetwornika analogowo-cyfrowego. Dla temperatury od 0°C do 70°C na wyjściu czujnika pojawi się maksymalnie 0,7 V, czyli niewiele w porównaniu z 3,3 V. Oczywiście to nic złego i projekt ten mógłby zostać zakończony na tym etapie, ale (głównie w ramach treningu) jeszcze go rozbudujemy.

Podczas omawiania ADC w STM32L4 wspominaliśmy w ramach ciekawostki, że nasz mikrokontroler ma jeszcze inne moduły analogowe, w tym dwa wzmacniacze operacyjne. Możemy więc wykorzystać jeden z nich, aby wzmocnić napięcie odczytywane z czujnika.

Pozostawiamy układ na płytce stykowej w obecnej formie i wracamy do CubeMX. Zaczynamy od tego, że musimy wyłączyć pomiar z kanału 11 – wybieramy więc Disable dla kanału IN11. Następnie aktywujemy moduł OPAMP2 i ustawiamy następujące parametry:

  • Mode: PGA Not Connected
  • Power Supply Range: Power Supply Range High
  • PGA Gain: 4

Po zmianie trybu działania wzmacniacza CubeMX przypisał do pinu PA6 funkcję OPAMP_VINP, czyli wejście wzmacniacza – oczywiście nieprzypadkowo wcześniej podłączyliśmy czujnik właśnie do tego wyprowadzenia mikrokontrolera. Mamy więc już podłączony LM35 do wejścia wzmacniacza. Z kolei wyjście wzmacniacza zostało automatycznie przypisane do pinu PB0 opisanego jako OPAMP2_VOUT –pojawi się tam sygnał, który będzie czterokrotnie wzmocniony (zgodnie z wybranym parametrem).

Konfiguracja wzmacniacza OPAMP2 w STM32L4

Konfiguracja wzmacniacza OPAMP2 w STM32L4

Jeśli teraz zapiszemy projekt, to podczas kompilacji pojawią się błędy, bo wyłączyliśmy przetwornik analogowo-cyfrowy. Możemy jednak zakomentować cały kod odwołujący się do ADC i dodać zamiast tego uruchomienie naszego wzmacniacza:

Teraz po wgraniu programu możemy za pomocą zwykłego multimetru zmierzyć, jakie napięcie pojawia się na pinie PB0. Powinniśmy zmierzyć, że jest na nim napięcie 4 razy wyższe niż na wyjściu czujnika. Czyli np. jeśli zmierzymy tam 1,047 V, będzie to oznaczało, że temperatura to około 26°C (bo musimy pamiętać, że napięcie z czujnika jest mnożone razy 4). W ramach dodatkowych ćwiczeń można również sprawdzić, jak układ będzie działał przy wzmocnieniu razy 2 lub 8 (użycie wzmocnienia razy 8 pozwoliłoby na lepsze wykorzystanie zakresu ADC, ale ograniczyłoby zakres pomiaru do 41,25°C).

Pomiar napięcia wyjściowego wzmacniacza

Przechodzimy do CubeMX i wracamy do konfiguracji ADC1. Wybieramy kanał IN15 i ustawiamy w nim opcję OPAMP2 Output Single-ended – dzięki temu będziemy mogli zmierzyć przetwornikiem ADC napięcie, które pojawia się na wyjściu wzmacniacza operacyjnego. Pozostałe parametry ustawiamy tak jak poprzednio, czyli:

  • Continous Conversion Mode: Enabled
  • Overrun behaviour: Overrun data overwritten
  • Sampling Time: 640.5 Cycles

Musimy jeszcze zmienić konfigurację pinu PB0. Klikamy w niego lewym przyciskiem myszy i z menu wybieramy opcję ADC1_IN15 (z zielonym plusem po lewej stronie).

Konfiguracja przetwornika ADC do pomiaru napięcia z wyjścia wzmacniacza operacyjnego

Konfiguracja przetwornika ADC do pomiaru napięcia z wyjścia wzmacniacza operacyjnego

Po zapisaniu projektu możemy już wrócić do kodu i usunąć komentarze przy funkcjach związanych z przetwornikiem. Dla testu możemy także wykorzystać wcześniejszy kod wyświetlający dane z ADC – musimy tylko edytować linijkę, w której obliczamy temperaturę (dodajemy dzielenie przez 4).

W wyniku działania tej wersji kodu zobaczymy, że wartości odczytywane przez przetwornik ADC są faktycznie czterokrotnie większe od tego, co było poprzednio – wzmacniacz działa więc poprawnie.

Działanie programu z włączonym wzmacniaczem operacyjnym

Działanie programu z włączonym wzmacniaczem operacyjnym

Na koniec, kiedy wiemy już, że wzmacniacz działa poprawnie, możemy usunąć treść pętli while. Trzeba jeszcze edytować funkcję obliczającą prędkość dźwięku, używaną podczas obsługi naszego czujnika (dodajemy do niej dzielenie przez 4).

Teraz nasz program powinien działać dokładnie tak samo jak poprzednio – tzn. wszystko powinno działać w tle dzięki przerwaniom i licznikom – od wykonania i interpretacji pomiaru, przez pomiar i wzmocnienie sygnału z czujnika temperatury, aż po pokazywanie wyników na wyświetlaczach 7-seg.

Zadanie domowe

  1. Rozbuduj program czujnika w taki sposób, aby na wyświetlaczu były pokazywane dwie kreski: - -, gdy pomiar z czujnika wyjdzie poza zakres 0–99.
  2. Dodaj do układu obsługę diody RGB, która za pomocą płynnych przejść kolorów powinna informować o tym, jak daleko znajdujemy się od przeszkody (kolor zielony oznacza, że daleko; kolor czerwony – blisko).
  3. Wykorzystaj informacje z części 6 kursu, w której omówiliśmy oszczędzanie energii na STM32L4, i uśpij mikrokontroler, aby obniżyć pobór prądu zbudowanego dalmierza.

Podsumowanie – co powinieneś zapamiętać?

Najistotniejsze, aby z tej części zapamiętać trzy rzeczy, które przydają się w praktyce bardzo często. Po pierwsze dokładny pomiar czasu trwania impulsu za pomocą licznika. Po drugie obsługa wyświetlaczy 7-segmentowych – ciągle można je spotkać w praktyce, a nieumiejętne obsługiwanie tych elementów generuje sporo problemów. Po trzecie – i najważniejsze – warto zapamiętać, że wszystkie te operacje mogą odbywać się niejako „automatycznie” – wystarczy odpowiednio wykorzystać liczniki i przerwania.

Czy wpis był pomocny? Oceń go:

Średnia ocena 4.8 / 5. Głosów łącznie: 30

Nikt jeszcze nie głosował, bądź pierwszy!

Artykuł nie był pomocny? Jak możemy go poprawić? Wpisz swoje sugestie poniżej. Jeśli masz pytanie to zadaj je w komentarzu - ten formularz jest anonimowy, nie będziemy mogli Ci odpowiedzieć!

W kolejnej części kursu STM32L4 zajmiemy się popularnymi i lubianymi diodami RGB, którymi można sterować cyfrowo – mowa oczywiście o diodach WS2812. Będzie to dobra okazja do tego, aby w dość nietypowy sposób wykorzystać PWM.

Nawigacja kursu

Główny autor kursu: Piotr Bugalski
Współautor: Damian Szymański, ilustracje: Piotr Adamczyk
Oficjalnym partnerem tego kursu jest firma STMicroelectronics
Zakaz kopiowania treści kursów oraz grafik bez zgody FORBOT.pl

HC-SR04, kursSTM32L4, liczniki, stm32l4

Trwa ładowanie komentarzy...