Delay z użyciem SysTick Timera na STM32 | STM32 na Rejestrach #3

msalamon.pl 1 rok temu
Zdjęcie: systick timer na stm32


Delay z użyciem SysTick Timera na STM32

Miganie diodą z poprzedniego artykułu miało jedną, sporą wadę. Tak naprawdę nie do końca kontrolowaliśmy czas pomiędzy zmianą stanów na pinie GPIO. Taka głupawa pętla for nie ma sprecyzowanego czasu działania. Można byłoby oczywiście sprawdzić jakie instrukcje zawiera i policzyć cykle zegara, ale tak się nie robi… Do odmierzania czasu służą w mikrokontrolerach Timery i właśnie z jednego z nich nauczymy się dzisiaj korzystać.

Seria STM32 na Rejestrach na YouTube

Wpisy te powstają równolegle do serii na moim YouTube o tej samej tematyce. jeżeli wolisz wersję video to zapraszam Cię właśnie tam. Artykuły te są skrótem z tego, co pokazuję na YouTube.

Podstawa czasu w systemach embedded

Prawdopodobnie zawsze będziesz potrzebował mierzyć stały odcinek czasu. Będzie to miało choćby swoją nazwę – podstawa czasu. W zależności od potrzeb będzie to 1, 10, 100 milisekund. To jedyne zadanie, które potrzebuje wykonywać taki Timer od podstawy czasu.

STM32 ma mnóstwo zaawansowanych Timerów, których szkoda na to, aby po prostu mierzyć stały odcinek czasu. Na szczęście mamy zegar systemowy SysTick Timer. Co interesujące jest on częścią rdzenia ARMowego (co też ma swoje konsekwencje), więc jest identyczny w każdym mikrokontrolerze z ARM Cortex-M.

Jest stosunkowo prosty. Zlicza tylko w dół od zadanej wartości. Po dojściu do zera wraca do ustalonej wartości i ponownie liczy w dół. Po każdym przejściu przez zero generuje tzw. przerwanie.

Przerwania poznamy dopiero za jakiś czas, dlatego tutaj przejdziemy krótko przez jego konfigurację. Głównym celem tutaj jest użycie SysTicka jako opóźnienia czasowego.

Siłą rzeczy dotkniemy też nieco konfiguracji zegarów w STM32. Ten temat też chcę zostawić na później, aby w pełni go omówić, więc przejdziemy przez totalne minimum, które jest potrzebne do tej lekcji.

SysTick jako element rdzenia

Skoro już wiemy, iż SysTick jest częścią rdzenia, to gdzie szukać informacji na jego temat? No właśnie… w dokumentacji rdzenia. Nie mikrokontrolera! Tylko nie mamy aktualnie takiej. Na szczęście nie będziemy potrzebowali. Gdybyś jednak chciał zajrzeć to masz tutaj: Cortex-M0+ Technical Reference Manual

ARM odwalił za nas całą robotę. Napisali dla nas funkcję, która odpowiednio ustawia SysTick według tego, co do niej przekażemy.

Nazywa się uint32_t SysTick_Config(uint32_t ticks) i jest częścią bibliotek CMSIS. Dokładnie znajduje się w pliku core_cmX.h, gdzie X to numer rdzenia Cortex-M. W naszym wypadku mamy do czynienia z Cortex-M0+.

Oprócz ustawienia liczby Ticków funkcja ta sprawdza, czy nie chcemy wpisać więcej niż 24 bity, ustawia przerwanie na najniższy z dostępnych priorytetów i włącza je oraz włącza samo zliczanie.

Funkcja ta przyjmuje w argumencie ilość ticków zegara, które ma zliczyć. Pojawiają się 2 problem do rozwiązania:

  1. Jakiego zegara?
  2. Ile ticków dla wybranej podstawy czasu?

Jaka częstotliwość dociera na SysTick Timer w STM32C0C6T6?

Każdy z Timerów zlicza sygnał zegarowy. Musi by więc czymś taktowany. W naszym mikrokontrolerze SysTick może być taktowany dwiema wartościami. HCLK, czyli w uproszczeniu mówiąc częstotliwość rdzenia lub HCLK/8. Możemy to sobie gwałtownie sprawdzić np. w widoku zegarów w CubeMX. Tam będzie najszybciej.

Domyślnie mikrokontrolery STM32 startują z wewnętrznego oscylatora. Skoro przez cały czas działamy na domyślnym zegarze HSI48, to na SysTick wchodzi właśnie sygnał 48MHz. Ale czy na pewno? Lepiej posłużyć się dokumentacją. CubeMX mimo, iż jest ustawiony na wartość domyślną, to zauważ, iż on wskazuje tutaj HSE, czyli zewnętrzny zegar. Nigdy go nie włączaliśmy, więc wychodzi na to, iż CubeMX ma inne pojęcie wartości domyślnej niż to, co jest w Reference Manualu. Może być tutaj więcej różnic!

To, co nas interesuje to dzielniki zegarów. W STM32 jest ich kilka. Skoro pracujemy na HSI, to interesują nas dzielniki dla niego. W rejestrach RCC mamy np. wartości HSIDIV i HSIKERDIV.

HSIDIV służy do generowania HSISYS. Ten sygnał jest zaraz na początku drzewa zegarów i wstępnie dzieli zegar. On jest domyślnie ustawiony na 4!!! Nie mamy więc do czynienia z 48 MHz, a z 12 MHz. To ogromna różnica. Mamy dwie drogi:

  1. Zostawić i działać na 12 MHz
  2. Uprzeć się i zmienić dzielnik na wartość 1

Zobaczmy najpierw co się dzieje dalej.

HSIKER to drugi z dzielników który może nas zainteresować bo jest ustawiony na wartość 3. Zobaczmy gdzie on idzie. Najłatwiej będzie w CubeMX.

Na szczęście nie korzystamy z sygnału HSIKER, więc jest dla nas niegroźny. Jednak na przyszłość musimy pamiętać o tym, iż jest on ustawiony na wartość inną niż 1.

Dalej mamy rejestr RCC_CFGR, który również zawiera dzielniki. One z kolei tworzą nam sygnały SYSCLK i HCLK. Zobacz, iż domyślnie wszystko w tym rejestrze jest wyzerowane, więc wszystkie te prescalery będa ustawione na 1.

Wróćmy do decyzji. Co robić? 12, czy 48 MHz? Nie mamy jeszcze pełnej wiedzy o zegarach, dlatego zalecam zostawienie na 12 MHz. Przy zmianie na 48 MHz trzeba będzie jeszcze zadbać o coś innego niż tylko zegar.

Generalnie w Internecie możesz spotkać się z wieloma opiniami, iż zegary w STM32 to coś bardzo skomplikowanego i… tak jest. Tutaj mamy tego mało, ale duże układy mogą zawrócić w głowie.

Zawsze polecam patrzeć co jest ustawione domyślnie w Reference Manualu. Do tego wizualizacja z CubeMX, aby przyśpieszyć analizę.

Decyzja: Zostajemy na taktowaniu HCLK 12 MHz. Jednocześnie SysTick też będzie taktowany 12 MHz.

Ile Ticków dla wybranej podstawy czasu?

W naszych programach za podstawę czasu najczęściej przyjmiemy 1ms. Ile Ticków musi zostać zliczone, aby minęła jedna milisekunda? Oraz czy zmieści się ta liczba w 24-bitowej wartości? Trzeba to policzyć.

1 sekunda = 12000000

0,001 s = X

X = 12000000 * 0,001 = 12000000 / 1000 = 12000 ticków przy taktowaniu 12 MHz

Można do tego dojść nieco inaczej – prościej. Podziel częstotliwość taktowania przez to ile “podstaw” czasu mieści się w sekundzie. W naszym wypadku ile pojedynczych milisekund znajduje się w sekundzie, czyli 1000.

12000000 / 1000 = 12000 ticków

Możemy wpisać wyliczoną wartość lub wpisać działanie z dzieleniem.

// Configure SysTick Timer // 1s = 12 000 000 // 0,001 = X SysTick_Config(12000000 / 1000);

Czy to się zmieści w 24-bitowym liczniku? 2^24 to 16 777 216. Zmieści się na luzie.

Przerwanie SysTick Timera

Skoro mamy skonfigurowany SysTick to potrzebujemy obsłużyć jego przerwanie. Pozwól, iż przerwania omówię kiedy indziej, a tutaj trochę skrócę. Programowanie mikrokontrolerów to taka dziedzina, w której najlepiej jakbyś dowiedział się wszystkiego jednocześnie. Tak się nie da, więc czasami trzeba zrobić skrót, aby doprecyzować to trochę później.

Gdy SysTick zliczy nam zadaną wartość powiadania o tym specjalny blok odpowiadający za przerwania – NVIC. NVIC przerywa pracę głównego programu i umożliwia nam reakcję na to co się stało – tzw. obsługa przerwania.

W tej obsłudze przerwania będziemy zliczali ile pojedynczych milisekund upłynęło od początku działania programu.

Wykorzystamy do tego zmienną typu uint32_t Ticks. Jako iż będzie ona użyta również w przerwaniu, oraz w tym samym pliku kompilacji – musi być oznaczona jako volatile. Można bezpośrednio, a można skorzystać z definicji __IO, która znajduje się w nagłówkach CMSIS.

NVIC skacze do funkcji przerwania, która podana jest w tzw. wektorze przerwań. Pokażę Ci gdzie to jest w lekcji z przerwaniami.

Dla SysTicka obsługa ta nazywa się SysTick_Handler. W funkcji tej jedyne, co robimy to inkrementujemy, czyli zwiększamy wartość o jeden naszej zmiennej Tick. I tak będzie nam SysTick zliczał upływający czas.

Pamiętaj, iż przerwanie jest już ustawione i włączone przez wcześniejsze użycie funkcji SysTick_Config.

__IO uint32_t Tick; void SysTick_Handler(void) { Tick++; // Increase system timer }

Delay na SysTicku

Mamy zliczanie czasu, musimy teraz napisać funkcję opóźniającą.

Uwaga! To będzie tzw. funkcja blokująca. Jest ona niesamowicie prosta, ale niestety swoje kosztuje.

Na czas oczekiwania na opóźnienie mikrokontroler nie będzie robił nic innego (oprócz przerwań). Nasz program będzie zablokowany stał w tym jednym miejscu. Stąd nazwa – blokująca.

Na razie muszę Ci to tak pokazać, ale w kolejnych lekcjach pokażę Ci jeden ze sposobów jak można uniknąć blokowania kodu. Szerzej o tym rozprawiam się w moim kursie STM32 dla Początkujących: https://kursstm32.pl

Skoro zliczamy milisekundy, to będziemy opóźniali o te milisekundy. W Funkcji te potrzebujemy:

  1. Zapamiętać bieżący czas przy wejściu do funkcji. Bieżący czas mamy w zmiennej Tick.
  2. Odczekać zadaną w argumencie ilość czasu sprawdzając, czy zmienna Tick zwiększyła się o zadaną wartość
  3. Jeśli minął zadany czas – zwolnić pracę mikrokontrolera. Tyle

Będzie ona wyglądała w ten sposób:

void Delay(uint32_t Delay_ms) { uint32_t StartTime = Tick; while(Tick < (StartTime + Delay_ms)) { // Just wait } }

Działanie opóźnienia

Teraz w pętli głównej programu możesz skorzystać z naszego nowego opóźnienia. W argumencie Delaya wpisujesz liczbę milisekund do odczekania.

while(1) { // Set LED on PA5 LD4_ON; Delay(150); // Reset LED on PA5 LD4_OFF; Delay(150); }

Tym sposobem nasza dioda LD4 na NUCLEO-C031C6 miga tak jak jej zadamy. Włala!

Podsumowanie

Korzystanie z SysTicka jest dosyć proste. Wszystko dzięki gotowej funcji w bibliotekach CMSIS. Jedynie musisz zadbać o obsługę przerwania, która jak widzisz może być bardzo prosta.

W kolejnym wpisie zajmiemy się wejściem GPIO. Będziemy czytali stan na przycisku. Przyda nam się tam świeżo napisana funkcja opóźniająca.

Daj znać w komentarzu czy Ci się podobał ten wpis! Może masz jakąś propozycję co pokazać w ramach cyklu STM32 na Rejestrach? Podziel się tym artykułem ze znajomymi.

Idź do oryginalnego materiału