STM32Duino: Arduino dla STM32 (cz. 5)

W poprzednich artykułach na temat STM32duino pokazaliśmy jak skonfigurować środowisko programistyczne Arduino do pracy z mikrokontrolerami STM32 oraz jak zrealizować aplikację używającą portów wejścia/wyjścia, interfejsu szeregowego UART i przetwornika A/C. Ta część kończy wątek pojedynczych peryferiów demonstrując sposób wykorzystania timera w trybie PWM.

Arduino i STM32 (STM32duino) – przygotowanie do pracy

Pracę należy rozpocząć uruchamiając środowisko programistyczne Arduino. Przygotowanie do pisania kodu obejmuje kilka prostych kroków.
Po pierwsze konieczne jest stworzenie nowego projektu (zwanego sketchem w Arduino). W tym celu użytkownik wybiera z menu głównego środowiska programistycznego od nowa STM32duino kolejno File i New (skrót klawiaturowy CTRL+N).

Po drugie należy wskazać używaną platformę sprzętową. Przykładowo autor wybrał płytkę NUCLEO-L476RG z 64-pinowym układem STM32L476RG. W tym celu należy ponownie użyć menu głównego środowiska programistycznego. Wybór platformy przebiega w dwóch etapach. Najpierw klikając Tools i Board wybrać należy Nucleo-64. Następnie wybierając Tools i Board Part Number wybrać należy Nucleo L476.

Po trzecie należy zapisać projekt na dysku twardym. Jednocześnie warto zmienić nazwę projektu na bardziej intuicyjną, gdyż domyślna nazwa zawiera tylko słowo sketch oraz nazwę aktualnego miesiąca i dnia.

Chcąc zapisać projekt STM32duino oraz zmienić jego nazwę użytkownik znowu korzysta z menu głównego środowiska programistycznego wybierając kolejno File i Save as… (skrót klawiaturowy CTRL+Shift+S). W ten sposób otworzone zostanie okno, które pozwoli na wybranie ścieżki na dysku twardym, pod którą zapisany zostanie projekt. Jednocześnie okno pozwala na wpisanie nowej nazwy projektu.

Rys. 1. Kolejne kroki przygotowania środowiska Arduino do pisania kodu dla wybranej platformy z układem STM32

Arduino i STM32 (STM32duino) – struktura kodu

Każdy stworzony od nowa w STM32duino projekt (sketch) zawiera domyślnie kilka linii kodu tworzących szkielet aplikacji. Kod ten pokazano w listingu 1.

Składa się on z dwóch funkcji. Pierwsza funkcja nosi nazwę setup. Jej przeznaczenie można łatwo rozszyfrować zapoznając się z komentarzem. Programista umieszcza w niej kod, który ma zostać wykonany tylko raz, na początku aplikacji, a więc po doprowadzeniu napięcia zasilania do mikrokontrolera. Są to głównie funkcje konfigurujące peryferia mikrokontrolera (np. ustawiające parametry przetwornika A/C lub interfejsu UART) lub inicjujące podzespoły zewnętrzne (np. moduł Bluetooth).

Druga funkcja otrzymała nazwę loop. Ponownie w rozszyfrowaniu jej przeznaczenia pomaga komentarz. Jest to nieskończona pętla, w której programista umieszcza funkcje wykonywane cyklicznie (np. odczyt stanu przycisku lub odebranie danych z czujnika).

Zatem programista tworzy kod aplikacji uzupełniając ciała funkcji setup i loop o dodatkowy kod języka Arduino. Kod ten składa się z typowych elementów języka programowania takich jak zmienne, funkcje, instrukcje warunkowe czy też operacje matematyczne.

Listing 1. Kod nowego projektu w środowisku programistycznym Arduino

Timer – podstawowe informacje

Powszechnie spotykanym w mikrokontrolerach zasobem są timery. Zasada ich działania opiera się na zliczaniu impulsów sygnału taktującego (generowanego wewnątrz mikrokontrolera lub doprowadzanego do niego z zewnątrz). Funkcjonalność ta umożliwia timerom na realizowanie przeróżnych zadań. Główne z nich to:

  • Wytwarzanie podstawy czasu dla aplikacji przez generowanie przerwania co określony interwał czasu. Jest to wygodny mechanizm dla implementacji zadań cyklicznych. Ponadto przerwanie timera może wybudzać mikrokontroler z trybu uśpienia.
  • Wytwarzanie sygnałów cyfrowych na wyjściu portu mikrokontrolera, w szczególności sygnału PWM. Tego typu sygnał przydatny jest np. w sterowaniu prędkością silników DC, w ustawianiu pozycji serwomechanizmu oraz w zmienianiu poziomu jasności oświetlenia opartego na diodach LED.
  • Określanie parametrów sygnałów cyfrowych na wejściu portu mikrokontrolera np. długości impulsu lub liczby impulsów. Druga z wymienionych funkcjonalności używana jest np. w tradycyjnych (mechanicznych) miernikach wody.

Cechą wspólną wszystkich sygnałów wytwarzanych przez timer jest ich cyfrowy charakter. Oznacza to, że sygnał może przyjąć jeden z dwóch stanów: niski i wysoki. Wartości napięć tych stanów odpowiada wartościom, jakie może przyjąć port mikrokontrolera: stan niski to 0V, natomiast stan wysoki to typowo 3.3V lub 5V.

Kilka słów o PWM

Szczególnym przypadkiem sygnału generowanego przez timer jest sygnał PWM. Jest to sygnał o zadanych: częstotliwości/okresie oraz poziomie wypełnienia (procentowo wyrażonym czasie trwania poziomu wysokiego w jednym okresie sygnału). Wytwarzanie sygnałów PWM przez timer pokazano w praktyce na rysunku 2. CLK (clock) jest sygnałem taktującym timer.

Z każdym kolejnym zboczem sygnału zegarowego wartość licznika CNT (counter) zwiększa się o 1. Gdy na skutek zliczania wartość CNT osiągnie wartość równą CCR1 (capture/compare register 1), poziom logiczny sygnału na wyjściu portu skojarzonego z kanałem pierwszym zmienia się. Zazwyczaj timer dysponuje więcej niż jednym kanałem. Wtedy ich użycie jest możliwe w analogiczny sposób do kanału pierwszego.

W przypadku trzech kanałów zdefiniowane są zatem wartości CCR2 i CCR3. Gdy CNT zrówna się najpierw z CCR2, a potem z CCR3, poziomy logiczne sygnałów na wyjściu portów skojarzonych z kanałami drugim i trzecim również zmieniają się. Wartość graniczną zliczania wyznacza ARR (auto-reload register). Osiągnięcie tej wartości przez CNT skutkuje jego wyzerowaniem.

W tym momencie czasu poziomy logiczne sygnałów wszystkich trzech kanałów zmieniają się, a licznik rozpoczyna zliczanie od początku. Zatem jest tak, że wartości CCR1, CCR2 oraz CCR3 określają poziom wypełnienia sygnałów PWM odpowiednio dla kanału pierwszego, drugiego i trzeciego, natomiast wartość ARR określa częstotliwość/okres wszystkich trzech sygnałów PWM.

Rys. 2. Sposób wytwarzania sygnałów PWM przez timer

Timer w trybie PWM – funkcje Arduino

Sygnał PWM można wytwarzać na pinach cyfrowych mikrokontrolera oznaczonych w Arduino literą ‘D’, która pochodzi od słowa Digital. Wyprowadzeń takich jest łącznie szesnaście, a zakres ich indeksów to 0..15.

Do włączenia timera i konfiguracji sygnału PWM wystarczy znajomość jednej funkcji. Nazywa się ona analogWrite. Sama nazwa jest nieco myląca, ponieważ można odnieść wrażenie, że mamy do czynienie nie z timerem, a z przetwornikiem C/A, który wytwarza napięcie o wartości wskazanej przez użytkownika. Otóż związek timera w trypie PWM z napięciem polega na tym, że w istocie sygnał PWM o zadanym wypełnieniu wytwarza napięcie, którego zmierzona wartość jest proporcjonalna do poziomu wypełnienia. Przykładowo jeśli niski i wysoki poziom logiczny to odpowiednio 0V i 3.3V, a wypełnienie sygnału PWM wynosi 25%, to woltomierz wskaże napięcie 0.825V (0.25 * 3.3V).

Funkcja analogWrite przyjmuje dwa argumenty. Pierwszy argument wskazuje wyprowadzenie, które ma zostać użyte do wygenerowania sygnału PWM. Drugim argumentem jest liczba z przedziału 0..255. Wartość ta odpowiada za poziom wypełnienia sygnału PWM. Jako że poziom ten zmienia się w zakresie 0..100, łatwo się domyślić, że wartość argumentu funkcji 255 odpowiada poziomowi wypełnienia 100, a inne wartości są proporcjonalnie przeskalowane.

Timer w trybie PWM

Przykładowa aplikacja wykorzystuje timer do generowania sygnału PWM. Wybrany do tego celu został pin oznaczony w Arduino jako D13, a więc wyprowadzenie mikrokontrolera PA5. Wybór ten wynika z tego, że na używanej przez autora płytce NUCLEO-L476RG wyprowadzenie PA5 połączone jest z diodą LED. Zadaniem aplikacji jest dynamiczna zmiana jasności świecenia diody LED poprzez zmianę współczynnika wypełnienia sygnału PWM. Wypełnienie początkowe sygnału PWM to 0%. Z każdym wykonaniem pętli poziom wypełnienia się zwiększa, aż do momentu, gdy osiągnie wartość 100%. Następnie proces się odwraca i poziom wypełnienia zmniejsza się stopniowo, aż osiągnie wartość 0%. Pomiędzy każdą zmianą poziomu wypełniania umieszczono pętlę opóźniającą. Powoduje to, że zmiany są na tyle powolne, że oko ludzkie może je zauważyć.

Przykładowy program

Po zapoznaniu się z funkcją Arduino do obsługi timera w trybie PWM jesteśmy gotowi do napisania programu realizującego zmianę świecenia diody LED. Zaprezentowany na listingu 2 kod zawiera kolejno:

  • Przed funkcją setup:
    • deklarację zmiennej typu całkowitego Jasnosc do przechowywania wartości odpowiadającej współczynnikowi wypełnienia sygnału PWM,
    • deklarację zmiennej typu całkowitego Zmiana_jasnosci do przechowywania wartości o jaką zmieni się współczynnik wypełniania w kolejnej iteracji pętli,
  • wewnątrz funkcji setup:
    • wywołanie funkcji pinMode konfigurującej pin PA5 jako wyjście cyfrowe,
  • wewnątrz funkcji loop:
    • wywołanie funkcji analogWrite generującej sygnał PWM na pinie PA5 z wypełnieniem zgodnym ze zmienną Jasnosc,
    • modyfikację zmiennej Jasnosc o wartość Zmiana_jasnosci,
    • sprawdzenie wartości zmiennej Jasnosc i w przypadku osiągnięcia wartości minimalnej (0) lub maksymalnej (255), zmienienie na przeciwny znaku zmiennej Zmiana_jasnosci,
    • wywołanie funkcji opóźniającej delay dla wytworzenia odstępu czasu pomiędzy kolejnymi zmianami współczynnika wypełnienia sygnału PWM.

Po napisaniu kodu program należy skompilować wybierając przycisk Verify (skrót klawiaturowy CTRL+R). Po zakończeniu procesu kompilacji należy wgrać program do pamięci mikrokontrolera wybierając przycisk Upload (skrót klawiaturowy CTRL+U).

Listing 2. Kod programu realizującego generowanie sygnału PWM

Działanie aplikacji napisanej w STM32duino można sprawdzić obserwując zieloną diodę LED na płytce NUCLEO-L476RG. Jej jasność powinna płynnie zmieniać się w sposób naprzemienny: od stanu wyłączenia do stanu świecenia z pełną mocą i od stanu świecenia pełną mocą do stanu wyłączenia.

Podsumowanie

Piąty artykuł z cyklu traktującego o Arduino i STM32 (STM32duino) zamyka wątek pracy z peryferiami. Na przestrzeni całego cyklu autor koncentrował się kolejno na portach wejścia/wyjścia, interfejsie szeregowym UART, przetworniku A/C oraz finalnie na timerze w trybie PWM.
Kolejny artykuł kończący cały cykl dotyczyć będzie realizacji bardziej zaawansowanych aplikacji, a więc takich, które na bazie peryferiów realizują np. odczyt danych z cznujnika czy komunikację Bluetooth.

O autorze