KursyPoradnikiInspirujące DIYForum

Kurs STM32 F4 – #6 – Liczniki, konfiguracja zegara, debugger

Kurs STM32 F4 – #6 – Liczniki, konfiguracja zegara, debugger

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.


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.

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.

Zamów w Botland.com.pl »

Pierwsza konfiguracja zegarów

Stwórzmy nowy projekt. Po otworzeniu zakładki Clock Configuration, zobaczymy poniższy widok:

Najważniejsze opcje dotyczące konfiguracji zegarów.

Opcje dotyczące konfiguracji zegarów.

Zacznijmy od końca. W sekcji zaznaczonej na zielono znajdują się magistrale, które dostarczają taktowanie zegara do poszczególnych partii mikrokontrolera.

stm32_f4_zegary_zielone

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.
  • 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.

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.

Sekcja mnożników i dzielników.

Kolejnym obszarem jest oznaczony na niebiesko wybór źródła taktowania.

Wybó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 czerwonoWybó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.

Parametry wewnętrzne pętli oraz źródło taktowania.

Ostatnim wyróżnionym obszarem jest fioletowy prostokąt, w którym znajdują się informacje dotyczące sygnałów wejściowych.

Informacje dotyczące sygnałów wejściowych.

Informacje dotyczące sygnałów wejściowych.

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ć.

  1. Klikamy dwukrotnie w pole opisane FCLK.
  2. Wpisujemy żądaną wartość (100MHz dla mikrokontrolera STM32F411).
  3. 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.

KursF4_6_3

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.

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.

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 do mikrokontrolera na płytce STM32F411Discovery

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.

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

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

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.

Ustawienie źródła taktowania mikrokontrolera na zewnętrzny rezonator kwarcowy.

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.

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ą.

KursF4_6_5

Odpowiednia konfiguracja wyprowadzenia.

Krok 4. Przechodzimy do zakładki Configuration. Otwieramy panel konfiguracyjny timera nr 10. Znajdują się tam tylko 4 pozycje:

KursF4_6_6

Opcje 4 timera (licznika).

  • Prescaler - wartość dzielnika wewnętrznego zegara, którym będzie taktowany timer.
  • 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ę.

  1. Po uruchomieniu w rejestrze licznika znajduje się wartość 0.
  2. 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).
  3. W momencie doliczenia do wartości zapisanej w ARR (AutoReload Register) timer generuje przerwanie i resetuje rejestr licznika do 0.
  4. 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:

2 = 100000000/(ARR+1)(PSC+1)(CKD+1)
2(ARR+1)(PSC+1)(CKD+1) = 100000000
(ARR+1)(PSC+1)(CKD+1) = 50000000

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:

(ARR+1)(9999+1)(CKD+1) = 50000000
10000(ARR+1)(CKD+1) = 50000000
(ARR+1)(CKD+1) = 5000

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:

KursF4_6_18

Wpisanie obliczonych ustawień do licznika.

Krok 6. Włączamy przerwania dla timera 10.

KursF4_6_7

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:

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.

KursF4_6_8

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.

Krok 9. Budujemy projekt.

Ł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:

KursF4_6_9

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:

KursF4_6_10

Opcje projektu.

Następnie przechodzimy do zakładki C/C++ Build > Behaviour i zaznaczamy opcję:

Enable parallel build

KursF4_6_11

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.

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.

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.

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:

KursF4_6_16

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.

Przyciski Terminate oraz Remove All Terminated Launches.

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.

KursF4_6_14

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.

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.

W dowolnym momencie działania programu klikamy dwukrotnie na niebieskim pasku z lewej strony przy definicji funkcji callback (obszar zaznaczony na czerwono).

Okno debuggera.

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 Variables zielonym 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ę.

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.

Nawigacja kursu

Jeśli macie jakieś pytania lub problemy z przedstawionym materiałem, piszcie w komentarzach!

Autor kursu: Bartek (Popeye) Kurosz
Redakcja, zdjęcia, wideo: Damian (Treker) Szymański

Załączniki

Rozwiązanie zadania dodatkowego (zip, 8 MB)

Kody programu oraz wersja skompilowana.

Cube, debugger, F4, kursSTM32F4, liczniki, stm32, timery

Trwa ładowanie komentarzy...