Funkcje API w obsłudze przerwań

Aby system mikroprocesorowy poprawnie radził sobie z zewnętrznymi zdarzeniami muszą być one obsługiwane za pomocą przerwań. Ma to szczególne znaczenia dla zadań krytycznych, w których nie może być mowy o zbyt dużych opóźnieniach w wykonaniu owego zadania, ani tym bardziej o pominięciu zdarzenia. Często nie zauważa się tego problemu, ponieważ wydaje się, że na przykład cykliczne sprawdzanie w pętli stanu danego wejścia jest wystarczające. Niestety takie podejście prędzej czy później powoduje generowanie błędów w pracy systemu. Jeżeli mamy do czynienia z projektem hobbistycznym, to nie jest to specjalnie dotkliwe, jednakże w przypadku rozwiązań komercyjnych nie można już sobie na takie błędy pozwolić.

W mikrokontrolerach z rdzeniem Cortex-M3 za obsługę przerwań odpowiada sprzętowy kontroler przerwań (NVIC). Dzięki niemu podprogram, który ma się wykonać po nadejściu przerwania jest wywoływany szybciej, a sama implementacja obsługi przerwań jest łatwiejsza, niż miało to miejsce w przypadku rdzeni ARM7 i ARM9.

List. 1. Przykładowy program wykorzystujący przerwanie zewnętrzne

Fragment programu, który działa w oparciu o zewnętrzne przerwanie przedstawiono na list. 1. Najważniejsza jest właściwa konfiguracja MCU, natomiast program, jaki ma być docelowo wywołany umieszcza się w pliku stm32f10x_it.c, który, przy wykorzystaniu pakietów CrossWorks, Keil lub IAR, znajduje się domyślnie w projekcie.
System ustalania priorytetów w mikrokontrolerach wyposażonych w rdzeń Cortex M3 jest rozdzielony na dwie części. Przerwania mają przypisany priorytet główny, tzw. Preemption prioritet, ponadto ustalany jest dodatkowy poziom podpriorytetów (subprioritet).

 

 

 

Rys. 1. Relacje pomiędzy przerwaniami

Relacje pomiędzy nimi przedstawiono na rys. 1. Taki podział ma uzasadnienie w działaniu: gdy obsługiwane jest w danej chwili przerwanie i nadejdzie zgłoszenie od innego przerwania, to NVIC porównuje priorytety główne, i jeżeli nowe przerwanie dysponuje wyższym priorytetem, to następuje jego wywłaszczenie i ono będzie teraz wykonywane. Wartości podpriorytetów mają jedynie znaczenie w momencie, gdy wystąpią dwa przerwania o takim samym priorytecie głównym w tym samym czasie. W takiej sytuacji w pierwszej kolejności zostanie obsłużone przerwanie o wyższym podpriorytecie. Programista wybierając tzw. grupy priorytetów (Priority Group) może ustalać relację pomiędzy liczbą poziomów priorytetów głównych, a liczbą podpriorytetów w zależności od wymagań danej aplikacji. Takich „zestawów” mikrokontrolery STM32 obsługują pięć (od 0 do 4). Jeżeli w docelowym urządzeniu przewidujemy konieczność zastosowania większej liczby zagnieżdżonych przerwań, to w takim wypadku należy wybrać odpowiednio wysoką grupę priorytetów. Zasada jest taka, że im mniejszy numer grupy priorytetów, tym mniej jest do dyspozycji priorytetów głównych, a więcej podpriorytetów (tab. 1).

Tab. 1. Grupy priorytetów

Grupa Preempiton priority Subpriority
NVIC_PriorityGroup_0 0 bitów 4 bity
NVIC_PriorityGroup_1 1 bit 3 bity
NVIC_PriorityGroup_2 2 bity 2 bity
NVIC_PriorityGroup_3 3 bity 1 bit
NVIC_PriorityGroup_4 4 bity 0 bitów

 

Wynika z niej, że w omawianym przypadku z list. 1 jest wybrana opcja zawierająca dwa priorytety główne (preemption priority), każdy dysponuje ośmioma podpriorytetami (subpriority). Sumarycznie otrzymujemy w ten sposób 16 możliwych poziomów priorytetów, czyli tyle, ile obsługują mikrokontrolery z rodziny STM32.
Po wybraniu grupy priorytetów należy odpowiednio ustawić parametry wykorzystywanego przerwania. W tym przykładzie będzie to przerwanie pochodzące od przycisku użytkownika na płycie uruchomieniowej, czyli wyprowadzenie PB9. Ponieważ jest to przerwanie zewnętrzne o numerze 9, to trzeba poinformować NVIC o tym, że będzie ono wykorzystywane. Dokonujemy tego modyfikując pole NVIC_IRQChannel struktury inicjującej. Po tej czynności należy ustalić priorytety dla tego konkretnego przerwania. Zarówno priorytet główny, jak i podpriorytet zostają ustawione na zera, a co za tym idzie, to przerwanie ma pierwszeństwo w obsłużeniu. Gdy wszystkie pola struktury są już wypełnione, jest ona przekazywana, podobnie jak miało to miejsce w przypadku konfiguracji GPIO, przez referencję do funkcji inicjującej. W ten sposób NVIC jest przygotowany na przyjęcie przerwania, należy jeszcze skonfigurować samo przerwanie zgodnie z wymaganiami.
Wyprowadzenie PB9 jest przyporządkowane do zewnętrznego przerwania o numerze 9 (EXTI_Line9), stąd informujemy o tym MCU wypełniając pole EXTI_Line. Kolejne dwie linie kodu definiują zachowanie wyprowadzenia. Będzie ono pracować w trybie przerwania oraz będzie reagować na zbocze opadające. W chwili, gdy takie zdarzenie wystąpi, mikrokontroler przystępuje do wykonywania funkcji EXTI9_5_IRQHandler.

SysTick Timer
Zadaniem każdego systemu operacyjnego jest takie zarządzenie uruchomionymi wątkami, aby wszystkie zostały optymalnie obsłużone. Można to zrealizować na kilka sposobów. Jednym z nich jest cykliczne – w pewnych określonych ramach czasowych – przełączanie kontekstu zadań. Implementacje tego typu systemów operacyjnych w mikrokontrolerach z rdzeniem Cortex M3 inżynierowie z firmy ARM znacznie uprościli wbudowując w kontroler przerwań NVIC timer SysTick. Jest to 24–bitowy licznik, który zliczając w dół odmierza określone, zdefiniowane przez programistę interwały czasowe. Każde przejście licznika przez zero i rozpoczęcie nowego cyklu odliczania powoduje wygenerowanie przerwania, które może zajmować się przełączaniem kontekstów uruchomionych w systemie zadań. Takie podejście ma też ważną zaletę, mianowicie zawieszenie się któregoś z realizowanych zadań nie spowoduje zawieszenia całego systemu, ponieważ zawieszony proces zostanie przerwany na rzecz innych zadań przy najbliższym przepełnieniu timera SysTick.

List. 2. Procedura odmierzania czasu z wykorzystaniem timera SysTick

Wbudowanie oddzielnego układu czasowego w architekturę Cortex pozwala na dużo łatwiejsze przenoszenie aplikacji napisanych na mikrokontrolery różnych producentów wykorzystujących tę platformę. Dzieje się tak dlatego, ponieważ w każdym takim mikrokontrolerze znajduje się taki sam timer SysTick.
Oczywiście SysTick może być wykorzystywany do różnych zadań. Konstruktor ustala wartość, od jakiej timer ma liczyć w dół, tym samym wyznacza, jakie odcinki czasowe będą odmierzane. Widać zatem, że może być on również wykorzystywany do standardowego odmierzania czasu i dla takiego przypadku, pokazanego na list. 2, zostanie omówiona konfiguracja oraz uruchomienie timera SysTick. Aby jakikolwiek układ czasowy w ogóle działał, musi być taktowany jakimś sygnałem zegarowym. SysTick może zliczać impulsy z taką częstotliwością, z jaką pracuje rdzeń mikrokontrolera, lub z tą częstotliwością podzieloną przez osiem. Ta ostatnia opcja jest zastosowana w omawianym przypadku, mamy więc 9 MHz. Licznik liczy w dół, więc regulacja odcinków czasowych, po których są generowane przerwania obywa się poprzez ustawienie wartości początkowej, czym zajmuje się funkcja SysTick_SetReload(). Do poprawnej pracy timera pozostaje jeszcze odblokowanie jego przerwania i włączenie odliczania. Funkcja obsługująca przerwanie to SysTickHandler, jej cykliczne wywoływania powodują odliczenie żądanego czasu i okresową zmianę stanu portu GPIOC, a tym samym miganie diodami LED.

Pod względem wyposażenia w układy czasowo–licznikowe, mikrokontrolery STM32 prezentują się dość okazale. W sumie mamy do dyspozycji aż siedem timerów (wliczając SysTick), które mogą pracować w różnych konfiguracjach. Ponieważ możliwość odmierzania czasu została już przedstawiona przy okazji omawiania timera SysTick, to teraz zajmiemy się generacją czterech sygnałów PWM o różnym współczynniku wypełnienia. Do tego celu wykorzystamy Timer 3 wraz z jego czterema kanałami. Pozwoli to na wygenerowanie żądanych sygnałów, z tym, że rzecz jasna będą one miały taki sam okres. Fragment programu odpowiedzialny za odpowiednie skonfigurowanie układu licznikowego przedstawiono na list. 3. Oprócz standardowej konfiguracji sygnałów zegarowych, która jest wykonywana przez funkcję RCC_Conf, należy włączyć taktowanie timera i GPIO (funkcja GPIO_Conf, której zadaniem jest pełne przemapowanie Timera 3 jest identyczna z opisaną w artykule).

List. 3. Fragment programu odpowiedzialny za skonfigurowanie układu licznikowego

Timery

W następnych krokach definiujemy podstawowe parametry pracy licznika. Będzie on pracował jako licznik w górę z częstotliwością taktowania 36 MHz (nie dzielimy sygnału zegarowego i nie wykorzystujemy preskalera). Wartość, do jakiej będzie liczył timer to 999, po czym nastąpi przepełnienie i proces liczenia rozpocznie się od nowa. Przy takich ustawieniach częstotliwość generowanego przebiegu PWM wyniesie: f=36 [MHz]/(999+1)=36 [kHz]. Kolejnym krokiem jest konfiguracja poszczególnych kanałów timera. Informujemy MCU o tym, że poszczególne kanały będą pracować w trybie PWM, długość impulsu będzie wynosiła odpowiednio (licząc od kanału pierwszego): 950, 350, 250, 100 taktów licznika. Aktywnym stanem jest stan wysoki, co oznacza, że np. dla kanału drugiego stan wysoki będzie panował na wyjściu PC7 przez 350 taktów, a pozostałe 650 to stan niski, zatem współczynnik wypełnienia wyniesie w tym przypadku 35%. Po zaprogramowaniu mikrokontrolera i uruchomieniu układu, diody od LD1 do LD4 będą świecić z różną intensywnością.

 

Krzysztof Paprocki
Paprocki.krzysztof@gmail.com

Autor: