Mikrokontrolery AVR część 5 – przerwania

Po dłuższej przerwie przystąpiłem do napisania kolejnej części mojego kursu. Ta część będzie poświęcona przerwaniom. Opisze podstawy pracy z przerwaniami, oraz przerwania zewnętrzne.

Zacznijmy od tego co to w zasadzie są te przerwania. Jest to układ, który po pojawieniu się odpowiedniego sygnału powoduje skoku do konkretnego miejsca w programie. Skok ten następuje zaraz po wykonaniu trwającej czynności(rozkazu), więc można odnieść wrażenie, że dokładnie z chwilą wystąpienia sygnału, bo rozkazy kontroler wykonuje bardzo szybko, szczególnie jak jest taktowany wysoką częstotliwością. Sygnałami może na przykład być: stan pinu kontrolera, przepełnienie licznika, zakończenie pomiaru poprzez przetwornik analogowo-cyfrowy, sygnał reset. Jak pisałem wcześniej po wystąpieniu przerwania następuje skok do odpowiedniego miejsca w programie. Miejsce to jest określone przez producenta kontrolerów. W mikrokontrolerach AVR występuje coś takiego ja tablica wektorów przerwań. Jest to miejsce na początku programu, gdzie powinny znajdować się skoki do podprogramów obsługujących poszczególne przerwania. Trochę to może być teraz nie zrozumiałe, ale zaraz wyjaśnię. Trzeba tutaj zahaczyć trochę o asemblera. Nie jasne może być co to jest podprogram, a jest to taka bardzo prosta funkcja, do której nie możemy wysłać żadnego parametru, ani nie może nam nic zwrócić. Wiec gdy wystąpi sygnał przerwania kontroler przeskakuje do wektora przerwania, które wystąpiło, a tam trafia na skok do podprogramu. W języku c w ogóle nie musimy się tym, zajmować, ponieważ mamy stosowne biblioteki. Musimy tylko dołączyć je do pliku i utworzyć definicje funkcji, które zostały już w tej bibliotece zadeklarowane. Czyli łatwo i przyjemnie.

Ale co się dzieje gdy kończy się podprogram obsługi przerwania. W tedy znajduje się tam rozkaz powrotu do wykonywania normalnie programu i zakończenia przerwania. Ale z skąd ma on wiedzieć gdzie był zanim wystąpiło przerwanie. Otóż, gdy wystąpił sygnał przerwania, zanim kontroler spowodował skok zapisał sobie gdzie jest i dzięki temu później wie gdzie wrócić.

Jeszcze trzeba wspomnieć o sytuacji, kiedy to wystąpią dwa przerwania w jednej chwili. Jest to rozwiązane w prosty sposób, każde przerwanie ma swój priorytet. Ten priorytet jest zależny od adresu wektora przerwania, im niższy adres tym wyższy priorytet. Na samym dole tej układanki jest reset, który musi działać zawsze.

Ale przerwania nie działają tak same od siebie, trzeba je najpierw odpowiednio skonfigurować.

Zaraz po włączeniu zasilania dla kontrolera ma on wyłączone wszystkie przerwania. Więc trzeba je włączyć, to nie jest trudne, w tym celu należy ustawić bit I w rejestrze SREG. Ale przed tym włączeniem należy skonfigurować najpierw przerwania, które chcemy obsłużyć. Skonfigurować, czyli ustawić na jaki sygnał mają reagować i jeszcze dodatkowo włączyć każde przerwanie, które chcemy obsłużyć.

Przerwania zewnętrzne

Mikrokontroler Atmega8 ma dwa przerwania zewnętrzne. Int0 oraz Int1, znajdują się one na porcie D. Cztery różne sygnały mogą wywołać przerwanie: stan niski, zmiana stanu, zbocze narastające i zbocze opadające. Do wyboru sygnału na jaki przerwanie będzie reagować służą bity ISC00, ISC01, ISC10, ISC11, a znajdują się one w rejestrze MCUCR.

Rejestr MCUCR

SE SM2 SM1 SM0 ICS11 ICS10 ICS01 ICS00
7 6 5 4 3 2 1 0

Tabela stanów dla przerwania INT0

ISC01 ISC00 Opis
0 0 Poziom niski na INT0 generuje przerwanie
0 1 Zmiana stanu na INT0 generuje przerwanie
1 0 Zbocze Opadające na wejściu INT0 powoduje przerwanie
1 1 Zbocze narastające na INT0 generuje przerwanie

Tabela stanów dla przerwania INT1

Zmiana stanu na INT1 generuje przerwanie

ISC11 ISC10 Opis
0 0 Poziom niski na INT1 generuje przerwanie
0 1 Jaka kolwiek zmiana INT1 generuje przerwanie
1 0 Zbocze Opadające na wejściu INT1 powoduje przerwanie
1 1 Zbocze narastające na INT1 generuje przerwanie

Z powyższych tabel wynika wszystko, jak skonfigurować te przerwania. Ale teraz musimy jeszcze je włączyć. W tym celu musimy ustawić bity INT0 i INT1 rejestru GICR.

Rejestr GICR

INT1 INT0 IVSEL IVCE
7 6 5 4 3 2 1 0

A teraz przejdźmy do praktyki

Pierwszą rzeczą jako należy zrobić, to dołączyć pliki biblioteczne dla przerwań, czyli dopisujemy na początku programu linię.

#include <avr/interrupt.h>

Następnie musimy skonfigurować przerwania. Zasłużmy, że chcemy, aby w naszym programie działały oba przerwania i były one aktywowane zboczem opadającym.

MCUCR |= (1<<ISC11);

Teraz pozostało nam włączenie przerwań INT0 i INT1 oraz włączenie całego systemu przerwań. Włączamy przerwania:

GICR |= (1<<INT0) | (1<<INT1);

A teraz cały system przerwań:

sei();

Poprzez wywołanie funkcji zostaje ustawiony bit I z rejestru SREG. Jest to makro zdefiniowane w bibliotece dla przerwań. Jeżeli chcielibyśmy wyłączyć przerwania, to wywołujemy funkcje cli().

Teraz jeszcze musimy stworzyć definicje funkcji, które mają zostać wywołane przez przerwanie. Więc umieszczamy w programie funkcje takie jak:

SIGNAL(SIG_INTERRUPT0)

{
    //miejsce na instrukcje
}

SIGNAL(SIG_INTERRUPT1)
{
    //miejsce na instrukcje
}

No to teraz większy przykład zbierający wszystko w całość

Mamy siedem diod podpiętych do pinów portu c kontrolera atmega8, a do pinu 2 portu D dopinamy przycisk. Oczywiście będziemy też potrzebowali wszystkich elementów koniecznych do pracy kontrolera.

#define F_CPU 1000000 //ustawienie oscylatora na 1MHz
#include <avr/io.h> //dołączenie podstawowej biblioteki
#include <avr/interrupt.h> //dołączenie biblioteki z przerwaniami
#include <util/delay.h> //dołączenie biblioteki z opóźnieniami
#define LED PORTC //zdefiniowanie stałych
#define PORTD2 2

int main()
{
     DDRC = ffh; //konfiguracja portu jako wyjścia
     DDRD &= ~(1<<PORTD2); //konfiguracja 2 pinu portu D jako wejście
     PORTD |= 1<<PORTD2; //ustawienie rezystora podciągającego
     MCUCR |= 1<<ISC01; //konfiguracja przerwania INT0
     GICR |= 1<<INT0; //włączenie przerwania INT0

     sei(); //globalne włączenie przerwań

     while(1) //pusta pętla
    {

    }

}

SIGNAL(SIG_INTERRUPT0) //początek funkcji obsługi przerwania
{
    LED = ffh; //zaświecenie LEDów
    _delay_ms(1000); //odczekanie 1s(1000 milisekund)
    LED = 00h; //zgaszenie LEDów
}

Ten prosty przykład pokazuje jak działają przerwania i jak z nich korzystać. W pustej pętli programu można umieścić co tylko chcemy, a zawsze jak zostanie wciśnięty przycisk, to diody zaświecą się na 1 sekundę.

To było by tyle na tą część, w następnym artykule opisze podstawowe zagadnienia związane z timerami.

Komentarze do „Mikrokontrolery AVR część 5 – przerwania

  1. Dobry artykuł, tłumaczy podstawy w przystępny sposób (ciężko znaleźć podobne w internecie). Jedno tylko zastrzeżenie – przerwania powinny trwać jak najkrócej, więc raczej powinno się unikać delay’ów w funkcjach obsługujących je. Z tego samego powodu zalecane jest używanie zmiennych volatile (przede wszystkim w pętlach). Oczywiście na potrzeby poradnika obecność delay w SIGNAL() jest usprawiedliwiona 🙂

  2. Bardzo miły poradnik, przyjemnie się go czyta. Nie mogłem się powstrzymać przed skomentowaniem dlatego robię to już w części 5 :). Keep up the good work! Natomiast drobna prośba do autora, czy byłoby możliwe zredagowanie tekstu, niestety nie ukrywam jestem początkujący w materii ale nieco trudności sprawiły pewne drobne niedociągnięcia w artykule: Tytuły tabeli stanów dla przerwań w rejestrze MCUCR obie są zatytułowane INT0, chyba w jednym z tytułów powinno być INT1(nie jestem pewien). Obrazek ze schematem jest niewielkiej rozdzielczości, trudno z niego cokolwiek odczytać. Szkoda że autor nie wyjaśnia przy okazji znaczenia reszty bitów w rejestrach, wiem że to wybiega poza temat artykułu ale fajnie byłoby wiedzieć.
    Pozdrawiam i dziękuję za kawał dobrego tekstu!

  3. Tekst jest jak najbardziej przystępny, jednak mam uwagi co do kodu. Przede wszystkim, brakuje średników! Po drugie, while jest pętlą warunkową, więc w nawiasach okrągłych musi być jakiś warunek. Zapewne autor miał na myśli pętlę nieskończoną, a zatem while(1), a nie while(), to nie jest wywołanie funkcji bezargumentowej ;).

    • Już poprawiłem kod, średniki już są chyba wszędzie. Pętla while też poprawiona. Mam jakiś paskudny odruch z jakiegoś języka, i często zapominam o tej jedynce.

  4. Jeśli wpiszemy do rejestru MCUCR coś takiego jak w przykładzie:
    MCUCR |= (1<<ISC01) | (1<<ISC11);
    To będziemy wywoływać przerwanie zboczem narastającym nie opadającym;)

    • Faktycznie, dzięki za zwrócenie uwagi. Muszę kiedyś te wszystkie przykładu w kursie po sprawdzać, bo to jest najbardziej nawalająca część w całym opisie.

      EDIT
      A co ciekawe, to w przykładzie było dobrze, musiałem to jakoś źle podkopiować z jakiegoś programu, tylko podczas pisania opisu, a jak pisałem przykład, to już się skoncentrowałem i wyszło dobrze 🙂

    • Według dokumentacji Atmega dla takiego rozkazu
      MCUCR |= (1<<ISC01) | (1<<ISC11);
      przerwanie wywoływane jest na zbocze opadające
      ISC01=1 ISC00=0 The falling edge of INT0 generates an interrupt request

  5. Dobrze ale po wywolaniu przerwania nastepuje przymulenie na 1s petli glownej while () ?!

    Gdyby tam byla jakas procedura to bedzie sie zamrazac na 1 sekunde kiedy bedzie obslugiwane przerwanie

    • To jest program przykładowy, który ma tylko zobrazować działanie przerwań. W rzeczywistości jak by ktoś zastosował takie rozwiązanie, to urządzenie może działać bardzo kiepsko. Tak jak mówisz, zablokuje się wykonywanie pętli głównej, ale też przez ten czas kontroler nie będzie reagował na inne przerwania.
      W tym przypadku, gdy program nie robi nic innego, takie rozwiązanie jest dopuszczalne, choć bardzo nie eleganckie, jak w zasadzie wszystkie rozwiązania z długimi czasami wstrzymania kontrolera.

  6. Witam, jeżeli wystąpią 2 przerwania to co dzieje się z tym o mniejszym priorytecie ? Jest jakoś kolejkowane i wykona się po tym o wyższym priorytecie czy przepada ? Jeżeli jest kolejkowane to jakie jest ograniczenie takiej kolejki (do ilu wpisów) ?

    • Szczerze mówiąc to trochę zagięło mnie to pytanie, po przeszukaniu dokumentacji nie znalazłem jeszcze pewnej odpowiedzi. Muszę, jeszcze spojrzeć na ogólną dokumentację AVR, ale to jutro, bo do pracy muszę iść. Ale lekkim rozwiązaniem problemu może być sprawdzanie flag, gdzie każdy element wywołujący przerwania, podnosi flagę o wystąpieniu przerwania i ta flaga jest podniesiona aż do momentu odczytania. Jednak szerzej jutro na spokojnie to przeanalizuje.

Skomentuj Luki Anuluj pisanie odpowiedzi

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.