Praca mikrokontrolera często musi być zależna od czasu. Należy w tym celu wygenerować sygnał, który będzie podstawą taktowania systemu.
W tym odcinku opiszę podstawową konfigurację liczników. Przejdziemy również przez ustawiania zegarów. Na koniec użyjemy debuggera, dzięki czemu możliwe będzie m.in. wgrywanie programu z poziomu IDE.
Uwaga! Ten kurs został zarchiwizowany. Sprawdź najnowszy kurs STM32 »
Konfiguracja zegara w STM32 F4
Aby dostosować taktowanie zegara mikrokontrolera do naszych potrzeb musimy wiedzieć jak go skonfigurować. Zadanie to nie należy do najprostszych, szczególnie dla osób rozpoczynających swoją przygodę z rodziną ARM.
Na szczęście w kreatorze STM32CubeMX umieszczono graficzne narzędzie do obsługi zegarów, które znacząco upraszcza cały proces.
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 »
Pierwsza konfiguracja zegarów
Stwórzmy nowy projekt. Po otworzeniu zakładki Clock Configuration, zobaczymy poniższy widok:
Opcje dotyczące konfiguracji zegarów.
Zacznijmy od końca. W sekcji zaznaczonej na zielonoznajdują się magistrale, które dostarczają taktowanie zegara do poszczególnych partii mikrokontrolera.
Magistrale dostarczające taktowanie do peryferiów.
Dla nas najbardziej interesujące są magistrale oznaczone jako:
FCLK - taktowanie głównego zegara mikrokontrolera. W naszym wypadku maksymalna częstotliwość z jaką może pracować mikrokontroler, to 100MHz.
APB1 - magistrala low-speed. Na niej znajdują się peryferia takie jak I2C1, I2C2, I2C3, USART2, SPI2, SPI3, I2S2, I2S3 oraz timery 2, 3, 4, 5.
Ta magistrala nie może być taktowana częstotliwością przekraczającą 50MHz.
APB2 - magistrala high-speed. Dostarcza taktowanie dla peryferiów takich jak SPI1, SPI4, SPI5, ADC1, USART1, USART6 oraz dla timerów 1, 9, 10, 11. Maksymalna częstotliwość na tej magistrali, to 100MHz.
Każda z wymienionych magistrali ma dwie podmagistrale. Jedna dostarcza sygnał do peryferiów - Peripheral Clocks, a druga do timerów - Timer Clocks.
Uwaga! Dla magistrali APB1 ograniczenie do 50MHz istnieje tylko dla części obsługującej peryferia. Timery znajdujące się na APB1 Timer Clocks mogą być taktowane częstotliwością 100MHz!
Do czego mogą nam się przydać te informacje? Chcąc określić z jaką częstotliwością będą wykonywane pomiary ADC lub z jaką częstotliwością będzie liczył timer generujący przerwania, musimy wiedzieć jakie taktowanie jest podawane na te peryferia.
Następną od prawej strony sekcją jest zaznaczona na pomarańczowo bateria różnych mnożników i dzielników. Ustawiane są tam ostateczne szlify sygnału wchodzącego na magistrale.
Sekcja mnożników i dzielników.
Kolejnym obszarem jest oznaczony na niebieskowybór źródła taktowania.
Wybór źródła taktowania.
Użytkownik może wybrać jedno z trzech:
HSI (High Speed Internal) - wewnętrzny oscylator RC generujący taktowanie o częstotliwości 16MHz. Taki sygnał można prawie bezpośrednio podać na magistrale.
HSE (High Speed External) - zewnętrzny sygnał pochodzący zazwyczaj od rezonatora kwarcowego (popularnie zwanego kwarcem). Taki rodzaj sygnału taktującego charakteryzuje się dużo większą dokładnością i stabilnością w porównaniu do wewnętrznego oscylatora RC.
PLLCLK (Phase Locked Loop Clock) - pętla fazowa pozwala na zwiększenie częstotliwości sygnału pochodzącego z HSI lub HSE. Parametry wewnętrzne pętli konfiguruje się w sekcji oznaczonej na czerwono. Wybór źródła taktowania, które ma być jej wejściem ustala się w sekcji zaznaczonej koloremżółtym.
Parametry wewnętrzne pętli oraz źródło taktowania.
Ostatnim wyróżnionym obszarem jest fioletowyprostokąt, w którym znajdują się informacje dotyczące sygnałów wejściowych.
Informacje dotyczące sygnałów wejściowych.
Pozostałe elementy konfiguratora nie będą nam potrzebne podczas kursu!
Konfiguracja zegarów - maksymalna częstotliwość
Wiemy już z jakich elementów składa się panel konfiguracyjny zegarów. Moglibyśmy teraz przejść do konfiguracji poszczególnych sekcji, aby otrzymać maksymalne możliwe taktowanie. Na szczęście proces został uproszczony do tego stopnia, że jedyne czego wymaga od nas narzędzie konfiguracyjne, to informacja dotycząca częstotliwości, którą chcemy ustawić.
Informację o maksymalnym taktowaniu danego mikrokontrolera znajdziemy
zazwyczaj na pierwszej stronie jego dokumentacji.
Klikamy dwukrotnie w pole opisane FCLK.
Wpisujemy żądaną wartość (100MHz dla mikrokontrolera STM32F411).
Zatwierdzamy przyciskiem Enter.
Narzędzie samo zacznie wyszukiwać możliwe rozwiązania całej sieci połączeń tak, aby spełnić wymagania. Jeżeli okaże się że wymagane jest przełączenie się na inne źródło - program zasugeruje zmianę. Należy ją zaakceptować i poczekać na zakończenie procesu.
Informacja o konieczności zmiany źródła sygnału.
Jako efekt powyższych działań otrzymamy magistrale mikrokontrolera taktowane maksymalnymi częstotliwościami:
Magistrale mikrokontrolera skonfigurowane dla swoich maksymalnych wartości.
Warto zauważyć, że za wyjątkiem magistrali APB1 peripheral clocks, która jest taktowana częstotliwością 50MHz, na wszystkie inne podawany jest sygnał 100MHz.
Uwaga! Jeżeli APBI Timer clocks ma wartość 50MHz, należy ponownie kliknąć Enter.
Powinno to spowodować przeliczenie również tej magistrali na wartość maksymalną.
Wykorzystanie zewnętrznego źródła taktowania w STM32 F4
Aby uzyskać bardziej stabilny i dokładny sygnał zegarowy można wykorzystać zewnętrzny rezonator kwarcowy (kwarc). Na schemacie zaznaczony jest kwarc wraz z podłączonymi do niego rezystorami i kondensatorami.
Schemat podłączenia zewnętrznego rezonatora kwarcowego na płytce STM32F411Discovery.
Na płytce STM32F4 Discovery taki element (wraz elementami towarzyszącymi) znajduje się zaraz nad mikrokontrolerem:
Kwarc z towarzyszącymi rezystorami i kondensatorami.
Żeby go użyć, należy wybrać wejście HSE (High Speed External) w konfiguracji zegarów.
Panel konfiguracyjny zegarów w STM32CubeMx.
Jak widać sekcja odpowiedzialna za wybór zewnętrznego kwarcu nie jest dostępna. Aby ją uaktywnić, należy najpierw skonfigurować odpowiednie wyprowadzenia mikrokontrolera jako wejścia sygnału zegarowego. W tym celu rozwijamy listę RCC (Reset and Clock Control) i dla HSE wybieramy Crystal/Ceramic Resonator.
Konfiguracja wyprowadzeń mikrokontrolera jako wejścia rezonatora kwarcowego.
W tym momencie powinniśmy być już w stanie ustawić HSE jako źródło taktowania mikrokontrolera.
Należy pamiętać o wpisaniu wartości Input Frequency,
zgodnej z kwarcem, który znajduje się na płytce!
Ustawienie źródła taktowania mikrokontrolera na zewnętrzny rezonator kwarcowy.
Na końcu wystarczy wpisać pożądaną wartość taktowania na wyjściu. Od teraz w ten sposób będziemy konfigurować każdy nowo tworzony projekt.
Timery (liczniki) w STM32 F4
Timery w mikrokontrolerach STM32 są mocno rozbudowanymi peryferiami pozwalającymi na sprzętową realizację wielu przydatnych funkcji, takich jak:
pomiar czasu,
generowanie przerwań cyklicznych,
generowanie sygnału PWM,
dekodowanie sygnału kwadraturowego z enkoderów obrotowych,
dekodowanie parametrów sygnału PWM,
dekodowanie sygnału z czujników Halla.
Na wyposażeniu naszego mikrokontrolera znajduje się 8 timerów. Wszystkie są 16-bitowe, aczkolwiek w innych mikrokontrolerach STM32 często spotyka się też liczniki 32-bitowe. Poza rozmiarem szyny danych, timery różnią się od siebie ilością kanałów oraz funkcjonalnościami.
W tej części kursu STM32 F4 zajmiemy się obsługą prostego timera, aby wygenerować przerwanie stanowiące podstawę czasu dla naszego systemu.
Generowanie przerwań cyklicznych
Przerwania cykliczne służą do uruchamiania działań, które muszą być wywoływane z odpowiednią częstotliwością (na przykład pętla regulatora PID). Mikrokontrolery STM32 mają już domyślnie skonfigurowane i uruchomione takie przerwanie - SYSTICK.
Wywoływane jest ono z częstotliwością 1kHz i można z niego skorzystać definiując obsługę przerwania HAL_SYSTICK_Callback() na dowolnym STM32.
Dla nas jednak częstotliwość 1kHz jest na razie zbyt duża, dlatego skonfigurujemy swoje własne przerwanie, które będzie się wywoływało nieco rzadziej.
Krok 1. Tworzymy nowy projekt i konfigurujemy zegar na 100MHz. Krok 2. Rozwijamy panel timera 10. Zaznaczamy opcję Activated. Krok 3. Konfigurujemy pin PD15, aby móc świecić diodą.
Odpowiednia konfiguracja wyprowadzenia.
Krok 4. Przechodzimy do zakładki Configuration. Otwieramy panel konfiguracyjny timera nr 10. Znajdują się tam tylko 4 pozycje:
Opcje 4 timera (licznika).
Prescaler - wartość dzielnika wewnętrznego zegara, którym będzie taktowany timer.
Prescaler jest 16-bitowy, a więc może przyjąć wartość z zakresu 0 - 65535.
Counter Mode - definiuje, czy timer ma zliczać w górę, czy w dół.
Counter Period (AutoReload Register) - wartość, do której zlicza timer.
Internal Clock Division - jeszcze jedno miejsce, gdzie można dokonać dzielenia sygnału taktującego timer.
Jak działa licznik? Spróbujmy przedstawić to na przykładzie prostego timera liczącego w górę.
Po uruchomieniu w rejestrze licznika znajduje się wartość 0.
Z każdym kolejnym tikiem timera do tego rejestru dodawana jest wartość 1. Tiki odbywają się z częstotliwością dyktowaną przez taktowanie zegara timera, podzieloną przez wartość PSC (Prescaler) oraz CKD (Internal Clock Division).
W momencie doliczenia do wartości zapisanej w ARR (AutoReload Register) timer generuje przerwanie i resetuje rejestr licznika do 0.
Cały proces powtarza się od nowa.
Wynika z tego bardzo prosty wzór na częstotliwość generowanych przerwań.
INT_FREQ = TIM_CLK/(ARR+1)(PSC+1)(CKD+1)
gdzie:
INT_FREQ - częstotliwość generowanego przerwania.
TIM_CLK - częstotliwość taktowania magistrali, na której umieszczony jest timer.
ARR, PSC, CKD - opisane wcześniej rejestry.
Krok 5. Musimy znaleźć teraz odpowiednie wartości, dzięki którym wygenerujemy przerwanie o zadanej częstotliwości. Załóżmy, że ma to być wartość 2Hz. Z początku artykułu wiemy, że wszystkie magistrale timerów skonfigurowane są na częstotliwość maksymalną (100MHz).
Pozostają wartości ARR, PSC oraz CKD, które należy znaleźć. Podstawiając znane wartości i przekształcając wzór, otrzymujemy następujące zależności:
Musimy więc znaleźć takie wartości rejestrów, których iloczyn da nam w wyniku liczbę 50000000. Ponieważ na każdym z tych rejestrów jesteśmy w stanie zapisać wartość maksymalną 65535, widać, że będziemy potrzebować niezerowej wartości w co najmniej dwóch rejestrach.
Rozpocznijmy od prescalera. Ustawmy w nim wartość 9999 i podstawmy do wzoru:
Wartość CKD możemy pozostawić bez zmian (0). Pozostanie nam już wtedy tylko jeden rejestr do ustalenia. Z prostego "wzoru", który pozostał wyliczamy wartość ARR.
ARR+1 = 5000
ARR = 4999
Wpisujemy znalezione wartości do panelu konfiguracyjnego:
Wpisanie obliczonych ustawień do licznika.
Krok 6. Włączamy przerwania dla timera 10.
Włączenie przerwań od licznika.
Krok 6. Generujemy program o nazwie 03_Timer_Interrupt i importujemy kod do IDE.
Krok 7. Musimy teraz odnaleźć odpowiednią funkcję, która uruchomi timer generujący przerwania. Proponuję przeszukać funkcje pojawiające się po rozwinięciu propozycji do frazy HAL_TIM. Chcemy uruchomić timer stanowiący podstawę czasu systemu, więc naszą funkcją będzie:
C
85
86
87
88
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim10);
/* USER CODE END 2 */
Krok 8. Ostatnią rzeczą która pozostała nam do zrobienia, to zdefiniowanie kodu przerwania, które będzie wywoływane przez licznik. Standardowo wyszukujemy frazę weak*callback i w wynikach wyszukiwania otwieramy zakładkę timera.
Poszukiwanie odpowiedniej funkcji od przerwania.
Widzimy, że timer generuje wiele różnych przerwań, które możemy obsłużyć. Nas interesować będzie przerwanie wywoływane po upłynięciu sprecyzowanego wcześniej czasu:
PeriodElapsedCallback
Przy okazji implementacji obsługi przerwania warto zauważyć jedną rzecz. Niezależnie od tego ile timerów skonfigurujemy w takim trybie, funkcja callback służąca do obsługi przerwania jest tylko jedna. Jak w takim razie rozróżnić, czy przerwanie pochodzi od timera 2, 3, czy może 13?
W tym celu wykorzystamy parametr, z którym wywoływany jest callback. Dzięki niemu jesteśmy w stanie określić, które z peryferiów wywołało daną funkcję.
Przykład wykorzystania znajduje się poniżej.
C
53
54
55
56
57
58
59
60
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
Zanim to jednak zrobimy, poznamy kilka przydatnych funkcjonalności IDE.
Łatwe utrzymanie porządku w Eclipsie
Pierwszą z nich jest minimalizacja liczby aktywnych projektów w celu utrzymania porządku. Jeżeli mamy zaimportowanych wiele projektów, korzystając ze skrótu ctrl+b niepotrzebnie budujemy wszystkie z nich. Można sobie z tym łatwo poradzić klikając prawym przyciskiem myszy na projekt nad którym aktualnie pracujemy, a następnie wybrać opcję Close Unrelated Projects.
Opcja ta zamyka również wszystkie niepotrzebne otwarte pliki projektów:
Funkcja Close Unrelated Projects.
Aby ponownie otworzyć zamknięte projekty, wystarczy kliknąć na nie dwukrotnie.
Szybsza budowa projektów - wielowątkowość
Drugą przydatną rzeczą jest wykorzystanie wielu wątków do równoległego budowania projektu. Znacznie przyspiesza to cały proces, który w przypadku kompilowania bibliotek do STMów potrafi trwać dość długo.
W tym celu wchodzimy w opcje projektu:
Opcje projektu.
Następnie przechodzimy do zakładki C/C++ Build > Behaviouri zaznaczamy opcję:
Enable parallel build
Włączenie opcji szybszego budowania projektów.
Klikamy ok i budujemy nasz projekt.
Wgrywanie programu z poziomu SW4STM32
Po zapisaniu i zbudowaniu projektu przyszedł czas na jego wgranie. Przechodzenie za każdym razem do ST-Link Utility w tym celu nie jest jednak wygodne. Można to na szczęście zrobić z poziomu IDE. Klikamy prawym przyciskiem myszy na projekt i wybieramy:
Debug As > Ac6 STM32 C/C++ Application.
Ustawienia opcji debugowania.
Prawdopodobnie na ekranie pojawi się panel konfiguracji płytki. Wypełniamy go opcjami:
Debug device: ST-Link-V2
Debug interface: SWD
Ustawienia płytki.
Klikamy Ok
Częste błędy
Teraz nadszedł chyba najgorszy moment w całym kursie. Może się tu pojawić bardzo wiele błędów, których przyczyny nie zawsze są jasne.
Pierwszy z nich:
Pierwszy częsty błąd.
W moim przypadku ponowne zainstalowanie dodatków SW4STM32 zgodnie z instrukcją z artykułu nr 2 tego cyklu rozwiązało problem.
Kolejnym bardzo popularnym przypadkiem jest zatrzymywanie się procesu na poziomie 90-91%. Pojawi się wtedy prawdopodobnie taki komunikat:
Drugi częsty błąd.
Jest to zazwyczaj spowodowane tym, że jest już otwarta inna sesja połączenia z płytką. Należy więc rozłączyć połączenie w ST-Link Utility oraz usunąć wszystkie sesje w naszym środowisku.
Aby to zrobić, trzeba na zmianę klikać kwadratowy czerwony przycisk (Terminate), a następnie podwójny znak X (Remove All Terminated Launches), aż obydwa przyciski przestaną być aktywne.
Przyciski Terminate oraz Remove All Terminated Launches.
Jeżeli pojawiły się wam inne błędy, koniecznie piszcie o nich w komentarzach!
Jeżeli wszystko się udało, naszym oczom powinna się ukazać perspektywa debuggera. Program został wgrany i zatrzymany na breakpointcie ustawionym w funkcji main. Aby wznowić jego działanie, wystarczy kliknąć zieloną strzałkę lub wcisnąć F8.
Wznowienie pracy programu.
Obsługa debuggera
Po wejściu w debugger, użytkownik otrzymuje do swojej dyspozycji potężne narzędzie do wyszukiwania błędów w kodzie. Spróbujmy skorzystać z podstawowych funkcjonalności, jakich możemy użyć na co dzień. Dopiszmy kilka linijek kodu do naszego programu.
C
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/* USER CODE BEGIN PFP */
/* Private function prototypes -----------------------------------------------*/
Powyższy kod powoduje to, że zmiana stanu diody odbywać się będzie z dziesięciokrotnie mniejszą częstotliwością, czyli w tym wypadku 0.2Hz. Oznacza to, że zmiana stanu diody powinna nastąpić co 5 sekund. Wgrywamy program przez debugger i wciskamy F8 aby wznowić działanie.
Jeżeli udało nam się wgrać program na płytkę przez debugger co najmniej raz, każde kolejne wgranie możemy już uruchamiać klikając w piktogram owada znajdujący się na belce z głównymi opcji programu lub za pomocą klawisza F11.
W dowolnym momencie działania programu klikamy dwukrotnie na niebieskim pasku z lewej strony przy definicji funkcji callback (obszar zaznaczony na czerwono).
Okno debuggera.
Spowoduje to dodanie breakpointa. Jak tylko program znajdzie się w tym miejscu, to "wpadnie w niego" i zostanie zatrzymany na najbliższej instrukcji, co widać na powyższym zrzucie.
W prawym górnym rogu w sekcji Variableszielonym kolorem zaznaczona jest sekcja podglądu zmiennych, które znajdują się w funkcji, w której aktualnie jest debugger. Aby dodać nową zmienną do podglądu, należy kliknąć w pole Add new expression i wpisać jej nazwę.
Możemy tam nie tylko obserwować stan zmiennych, ale również je modyfikować. Wystarczy dwukrotnie kliknąć na pole zawierające wartość i wpisać nową.
Za pomocą przycisków znajdujących się w obszarze zaznaczonym na fioletowo możemy poruszać się w kodzie. Wciśnijmy przycisk Step Over (F6).Spowoduje to przejście do następnej instrukcji. Po wznowieniu działania programu przyciskiem F8 będzie on działał, aż znowu nie natrafi na breakpointa.
Zadanie dodatkowe
Cel zadania jest bardzo podobny do tego z poprzedniego artykułu, z tą różnicą, że teraz do jego realizacji wykorzystamy timery. Należy stworzyć cykl, który będzie po kolei włączał wszystkie diody, a następnie je wyłączał. Zapalanie i gaszenie ma się odbywać z częstotliwością 0.2Hz. Pomiędzy kolejnymi cyklami powinna występować przerwa o długości 2s.
Kod programu realizujący powyższe zadanie znajduje się w załączniku do artykułu.
Podsumowanie
W tym odcinku nauczyliśmy się konfigurować zegar mikrokontrolera. Wiemy już również jak skonfigurować prosty timer i przy jego pomocy generować przerwania cykliczne o określonej częstotliwości. Poznaliśmy też wygodniejszy sposób wgrywania programu na mikrokontroler.
W następnym odcinku nauczymy się komunikować z mikrokontrolerem za pomocą UARTA.
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...