Przerwania


Tematy pokrewne   Podrozdziały
(w budowie)
  Co to jest przerwanie
Rodzaje przerwań
Obsługa przerwań

 

 

Co to jest przerwanie?

 
   
W trakcie działania programu mogą pojawić się różne zdarzenia, na które musi reagować mikrokontroler. Zdarzenia te zwykle są związane z działaniem układu, w którym pracuje mikrokontroler. Zadanie to da się zrealizować za pomocą pętli, która kolejno sprawdza każde z nich. Niestety, zajmuje to cenny czas pracy mikrokontrolera oraz komplikuje sam program. Dlatego wymyślono przerwania (ang. interrupts). Przerwanie jest zdarzeniem, które przerywa wykonywanie programu głównego i uruchamia specjalną funkcję obsługi przerwania. Gdy funkcja obsłuży przerwanie, następuje powrót do przerwanego programu i wznowienie jego wykonywania od miejsca, w którym został przerwany.

Zaletą tego rozwiązania jest to, ze mikrokontroler nie musi programowo sprawdzać cały czas, czy zaszło zdarzenie związane z przerwaniem. Może on w tym czasie realizować swój podstawowy program, a nawet może się zupełnie wyłączyć i pobierać jedynie minimalny prąd podtrzymujący – jest to szczególnie cenne w urządzeniach zasilanych bateryjnie.

Aby wykorzystywać przerwania w swoich programach, musisz wiedzieć, co może być ich źródłem, jak je uaktywnić i jak je obsłużyć. Tym właśnie zajmiemy się w dalszej części tego rozdziału.

 

 

Rodzaje przerwań

 
   
Przerwania mogą być wewnętrzne (tzn. pochodzić od wewnętrznych składników mikrokontrolera) lub zewnętrzne (tzn. generowane przez urządzenie zewnętrzne, którym steruje mikrokontroler).

Przerwania zewnętrzne

Na pewno zauważyłeś już, że wyprowadzenia mikrokontrolera ATTINY13 (innych mikrokontrolerów też) 1...3 i 5...7 posiadają po kilka opisów. Ich normalna funkcja to linie portu B od PB0 do PB5. Jednakże w pewnych przypadkach funkcje te można zmienić. Tak robiliśmy w poprzednim rozdziale, gdzie PB0 i PB1 pełniły funkcje kanałów PWM OC0A i OC0B i służyły do sprzętowej generacji przebiegów prostokątnych o różnych współczynnikach wypełnienia. Również linia PB2 może być wykorzystywana jako T0, czyli źródło zewnętrznego zegara dla licznika wbudowanego w mikrokontroler. Przy programowaniu mikrokontrolera za pomocą programatora wykorzystywane są linie PB0 (MOSI), PB1 (MISO), PB2 (SCK) i PB5 (RESET).

Zwróć uwagę, że w opisach końcówek kontrolera mamy dodatkowe nazwy:

PB0: PCINT0
PB1: PCINT1, INT0
PB2: PCINT2
PB3: PCINT3
PB4: PCINT4
PB5: PCINT5, RESET

Każda z tych końcówek może być źródłem przerwania zewnętrznego dla mikrokontrolera. INT oznacza po prostu przerwanie (ang. INTerrupt), a PCINT oznacza przerwanie przy zmianie stanu końcówki (Pin Change INTerrupt). Końcówkę PB5 już znasz. Jeśli źródło zewnętrzne ustawi na niej stan logiczny 0 (np. poprzez zwarcie do masy), to mikrokontroler jest resetowany bez względu na to, co w danej chwili wykonuje. Po resecie mikrokontroler zaczyna wykonywać swój program od początku. Jest to zatem również przerwanie, tyle że nie wywołuje jakiejś specjalnej funkcji obsługi i nie musi być specjalnie uaktywniane w języku C (z poziomu języka maszynowego, asemblera, można po resecie wykonać określoną funkcję, lecz język C rezerwuje to przerwanie do restartu programu). Z funkcji RESET korzysta programator ISP. Wymusza on najpierw stan niski na tym wejściu, a wtedy mikrokontroler przechodzi w specjalny tryb i końcówki PB0, PB1 i PB2 stają się liniami komunikacyjnymi MOSI, MISO i SCK z programatorem. Po liniach tych programator przesyła informacje dla wewnętrznych pamięci FLASH, RAM i EPROM (sygnały SCK : zegar i MOSI : zapisywane dane bit po bicie) lub odczytuje dane z tych pamięci (sygnały SCK : zegar i MISO : odczytywane dane bit po bicie).

Pozostałe przerwania (INT0, PCINT0...PCINT5) muszą zostać odpowiednio uaktywnione, aby mikroprocesor na nie zaczął reagować, tzn, aby w momencie ich pojawienia się była wywoływana odpowiednia funkcja obsługi przerwań (dodatkowo, aby mikrokontroler reagował na PCINT5, należy wyłączyć funkcję RESET – da się to zrobić za pomocą tzw. fusebitów, czyli bitów bezpiecznikowych, lecz wtedy stracisz możliwość programowania mikrokontrolera i dlatego nie polecam tego rozwiązania początkującym koderom, gdyż odzyskanie mikrokontrolera po takim przeprogramowaniu wymaga specjalnego programatora, zobacz tu). O tym za chwilę.

Linia INT0 może reagować na poziom niski, zmianę stanu albo zmianę poziomu z 0 na 1 lub z 1 na 0 (to nie to samo co zmiana stanu, ponieważ tutaj przerwanie występuje tylko w jednym przypadku, gdy sygnał na linii INT0 zmienia się np. z 0 na 1, a przy zmianie z 1 na 0 przerwania już nie będzie). Posiada ona najwyższy priorytet (ważność) wśród przerwań, zaraz po RESET. Jest to najbardziej uniwersalna linia przerwań zewnętrznych

Linie PCINTx reagują jedynie na zmianę stanu odpowiednich końcówek (zmiana z 0 na 1 lub z 1 na 0, w obu przypadkach przerwanie wystąpi).

Przerwania zewnętrzne, jeśli są uaktywnione, działają niezależnie od konfiguracji linii PB0...PB5. Jeśli ustawimy je jako wyjścia, to mikrokontroler będzie mógł programowo wywoływać określone przerwanie przez zmianę stanu linii portu. Jednak najczęściej ustawiamy je jako wejścia, a stan linii wymusza sterowane przez mikrokontroler urządzenie w momencie, gdy należy je w jakiś sposób obsłużyć.

Przerwania wewnętrzne

Przerwania wewnętrzne są generowane przez wewnętrzne komponenty mikrokontrolera. Dla ATTINY13 są zdefiniowane następujące przerwania wewnętrzne:
  • Przerwanie od licznika TCNT0, gdy następuje jego przewinięcie. Przerwanie to pojawia się, gdy zostaje ustawiony na 1 znacznik TOV0 (Timer overflow – przepełnienie licznika) w rejestrze TIFR0 (Timer Interrupt Flag Register – rejestr znaczników przerwań licznika). Znacznik ten jest zawsze ustawiany, gdy licznik rozpoczyna nowy cykl zliczania po osiągnięciu ostatniej wartości poprzedniego cyklu.
  • Przerwanie zgłaszające gotowość pamięci EEPROM. Tematem tym zajmiemy się później.
  • Przerwanie od komparatora analogowego. Tematem tym również zajmiemy się później.
  • Przerwanie w momencie osiągnięcia przez licznik TCNT0 wartości przechowywanej w rejestrze porównawczym OCR0A.
  • Przerwanie w momencie osiągnięcia przez licznik TCNT0 wartości przechowywanej w rejestrze porównawczym OCR0B.
  • Przerwanie od licznika Watchdog (pies łańcuchowy). Licznik Watchdog jest specjalną funkcją mikrokontrolera, która pozwala reagować na zawieszanie się wykonywania programu lub przekroczenie czasu oczekiwania na jakieś zdarzenie zewnętrzne (np. sygnał gotowości urządzenia sterowanego przez mikrokontroler). Jeśli ją włączysz, to w swoim programie musisz co pewien czas ustawiać licznik Watchdog. Jeśli tego nie zrobisz, bo np. program się zawiesi, to wystąpi przerwanie i funkcja je obsługująca będzie mogła odpowiednio zareagować na taką sytuację - np. poprzez zresetowanie mikrokontrolera. Tematem tym zajmiemy się później.
  • Przerwanie informujące o zakończeniu konwersji analogowej. Tematem zajmiemy się później.

Jak to działa

Aby zrozumieć sposób obsługi przerwań, musisz posiadać nieco wiadomości na temat sposobu pracy mikroprocesora, który znajduje się wewnątrz każdego mikrokontrolera. Z poziomu języka C nie widzimy wewnętrznej struktury komputera, ponieważ szczegóły są przed nami ukryte (nie jest to złośliwość twórców tego języka, lecz działanie celowe – dzięki temu programy w języku C są w większości przenośne). Każdy program w języku C musi zostać przetłumaczony na ciąg poleceń dla mikroprocesora. Polecenia te tworzą kod programu i są umieszczane w komórkach pamięci programu. Mikroprocesor w trakcie wykonywania go adresuje odpowiednią komórkę tej pamięci i pobiera z niej kod polecenia, który następnie dekoduje (czyli rozpoznaje) i wykonuje odpowiednie operacje, po czym przechodzi do kolejnego polecenia w programie. Wynika z tego, że instrukcje maszynowe znajdują się pod określonymi adresami. Funkcja języka C jest zbiorem takich instrukcji, które tworzą w pamięci programu spójny blok kodu. Adres pierwszej instrukcji w tym bloku jest adresem funkcji. Gdy w języku C wywołujesz w swoim programie funkcję, to mikroprocesor zaczyna wykonywać jej kod, począwszy od tej pierwszej instrukcji. Wywołanie funkcji wymaga zatem podania adresu początku funkcji w pamięci programu. W języku C adresem tym jest niejawnie nazwa funkcji. Kompilator w trakcie tłumaczenia twojego programu w języku C na instrukcje maszynowe dla mikroprocesora rozpoznaje funkcje i przydziela im odpowiednie obszary w pamięci programu. Zapamiętuje przy tym adresy początków tych obszarów jako nazwy funkcji.

Obsługa przerwań również polega na wykonaniu funkcji, która jest umieszczona w określonym miejscu pamięci programu. Skąd mikroprocesor wie, gdzie znajduje się odpowiednia funkcja? Otóż do tego celu wykorzystany jest początek pamięci programu, gdzie w kolejnych komórkach umieszczane są skoki do funkcji, które obsługują określony rodzaj przerwania. Komórki przechowujące te skoki nazywamy wektorami przerwań.  W mikrokontrolerze ATTINY13 zdefiniowano następujące wektory przerwań:

 

Numer wektora Adres wektora Źródło przerwania
1 0x0000 Stan niski na wejściu RESET,
włączenie zasilania,
spadek napięcia zasilania poniżej dolnej granicy,
reset od licznika watchdog
2 0x0001 Przerwania od linii INT0 (PB1)
3 0x0002 Przerwania od linii PCINT (PB0...PB5)
4 0x0003 Przerwanie przy przepełnieniu licznika TCNT0
5 0x0004 Przerwanie przy gotowości pamięci EEPROM
6 0x0005 Przerwanie od komparatora analogowego
7 0x0006 Przerwanie przy zrównaniu się stanu licznika z rejestrem OCR0A
8 0x0007 Przerwanie przy zrównaniu się stanu licznika z rejestrem OCR0B
9 0x0008 Przerwanie od licznika watchdog
10 0x0009 Przerwanie przy zakończeniu konwersji analogowo-cyfrowej

Każdy wektor przerwań obejmuje dwa bajty, czyli pojedynczą komórkę pamięci programu. Pamięć programu podzielona jest na komórki dwubajtowe, ponieważ każdy rozkaz maszynowy mikroprocesora zawartego w mikrokontrolerze ma długość 16 lub 32 bity, czyli dwa lub cztery bajty. Na przykład pamięć 1KB w ATTINY13 podzielona jest na 512 komórek i tyle może zawierać rozkazów 16 bitowych (rozkazy 32 bitowe stosowane są rzadko). W każdym wektorze przerwań możemy umieścić jeden rozkaz 16-to bitowy. Jest to najczęściej rozkaz skoku w odpowiednie miejsce programu, gdzie znajduje się funkcja obsługująca dane przerwanie. Może to wyglądać następująco (fragment kodu maszynowego z instrukcji producenta):

Adres  Etykiety Kod maszynowy        Komentarze
0x0000          rjmp RESET           ; Obsługa RESET
0x0001          rjmp EXT_INT0        ; Obsługa IRQ0
0x0002          rjmp PCINT0          ; PCINT0 Handler
0x0003          rjmp TIM0_OVF        ; Obsługa przepełnienia TCNT0
0x0004          rjmp EE_RDY          ; Obsługa EEPROM
0x0005          rjmp ANA_COMP        ; Obsługa komparatora analogowego
0x0006          rjmp TIM0_COMPA      ; Obsługa zrównania TCNT0 Z OCR0A
0x0007          rjmp TIM0_COMPB      ; Obsługa zrównania TCNT0 Z OCR0B
0x0008          rjmp WATCHDOG        ; Obsługa Watchdog
0x0009          rjmp ADC             ; Obsługa konwersji analogowo-cyfrowej
                                     ;

0x000A RESET:   ldi r16, low(RAMEND) ; Start głównego programu
0x000B          out SPL,r16          ; Ustaw wskaźnik stosu na szczyt pamięci RAM
0x000C          sei                  ; Włącz przerwania
0x000D          <instr> xxx
... ... ... ...

Gdy pojawia się zdarzenie wyzwalające przerwanie i dane przerwanie zostało uaktywnione (o tym za chwilę), to mikroprocesor wykonuje instrukcję zawartą w określonym wektorze przerwań. Wcześniej zapamiętuje on oczywiście adres miejsca w programie, który wykonywał przed przerwaniem. Instrukcja w wektorze przerwań wykonuje skok (rjmp = relative jump, czyli skok względny) do odpowiedniej procedury obsługi przerwania, zwanej ISR (ang. Interrupt Service Routine), która wykonuje pożądane działania, po czym następuje powrót w miejsce programu, w którym wystąpiło przerwanie i program jest kontynuowany. Nie ma zatem w tym nic tajemniczego.

 

 

Obsługa przerwań

 
   
W języku C przerwania są standardowo wyłączone. Oznacza to, że musisz je jawnie uaktywnić, aby mikrokontroler zaczął na nie reagować. Najpierw do programu należy dołączyć plik nagłówkowy:

avr/interrupt.h

w którym są zdefiniowane wszystkie elementy programowe do obsługi przerwań.

W następnym kroku tworzymy funkcję, która dane przerwanie ma obsługiwać. Funkcja ta nie ma typu i posiada specjalną nazwę ISR (ang. Interrupt Service Routine - procedura obsługi przerwania). Parametrem tej funkcji jest wektor odpowiedniego przerwania. Nazwy tych wektorów są następujące:

Adres wektora Nazwa wektora Źródło przerwania
0x0001 INT0_vect Przerwania od linii INT0 (PB1)
0x0002 PCINT0_vect Przerwania od linii PCINT
0x0003 TIM0_OVF_vect Przerwanie przy przepełnieniu licznika TCNT0
0x0004 EE_RDY_vect Przerwanie przy gotowości pamięci EEPROM
0x0005 ANA_COMP_vect Przerwanie od komparatora analogowego
0x0006 TIM0_COMPA_vect Przerwanie przy zrównaniu się stanu licznika z rejestrem OCR0A
0x0007 TIM0_COMPB_vect Przerwanie przy zrównaniu się stanu licznika z rejestrem OCR0B
0x0008 WDT_vect Przerwanie od licznika watchdog
0x0009 ADC_vect Przerwanie przy zakończeniu konwersji analogowo-cyfrowej

Wewnątrz funkcji zawieramy kod, który będzie wykonywany po przyjęciu przerwania. Na przykład poniższa funkcja zmienia stan linii PB0 na przeciwny przy każdym przepełnieniu licznika TCNT0 (uwaga: to jeszcze nie jest kompletny program, czytaj dalej!):

#include <avr/interrupt.h>

ISR(TIM0_OVF_vect)
{
   PINB = 1;
}

int main(void)
{
  
}

Gdy masz już funkcję ISR, musisz w programie uaktywnić obsługę przerwania dla tej funkcji, a wcześniej odpowiednio skonfigurować rejestry mikrokontrolera. Obsługę przerwań uaktywniamy przez ustawienie bitów w rejestrach sterujących tymi przerwaniami. Na koniec należy włączyć reakcję na przerwania. Dokonuje się tego przez wywołanie bezargumentowej funkcji: sei() (ang. Set Interrupts – ustaw przerwania). Z kolei funkcja cli() (ang. Clear Interrupts - blokuj przerwania) blokuje reakcje na przerwania.

 

Przerwanie INT0

Teraz pokażemy praktycznie, jak w programie obsługiwać określone przerwania. Na początek zajmiemy się przerwaniem od linii INT0 (PB1). Przerwanie to jest najbardziej uniwersalnym przerwaniem zewnętrznym, ponieważ może być generowane na cztery różne sposoby.

Najpierw w rejestrze MCUCR (ang. Micro Controler Unit Control Register - rejestr sterujący jednostką mikrokontrolera) ustawiamy sposób generacji przerwania przez linię INT0.

bit 7 6 5 4 3 2 1 0
nazwa PUD SE SM1 SM0 ISC1 ISC0
R/W R R/W R/W R/W R/W R R/W R/W
stan 0 0 0 0 0 0 0 0

Rejestr sterowania mikrokontrolerem MCUCR

Do tego celu przeznaczone są dwa bity ISC1 i ISC0 (ang. Interrupt Sense Control – sterowanie sposobem wykrywania przerwania). Mogą one przybrać jedną z czterech kombinacji wartości:

ISC1 ISC0 Opis
0 0 Przerwanie generuje stan niski 0 na linii INT0.
0 1 Przerwanie generuje zmiana stanu logicznego linii INT0.
1 0 Przerwanie generuje zbocze opadające sygnału na linii INT0, czyli przejście 1 → 0.
1 1 Przerwanie generuje zbocze narastające sygnału na linii INT0, czyli przejście 0 → 1.

Gdy określimy sposób generowania przerwania w zależności od stanu linii INT0, musimy uaktywnić przerwanie przez ustawienie na 1 bitu INT0 w rejestrze GIMSK (ang. General Interrupt Mask Register – rejestr maskowania przerwań zewnętrznych).

bit 7 6 5 4 3 2 1 0
nazwa INT0 PCIE
R/W R R/W R/W R R R R R
stan 0 0 0 0 0 0 0 0

Rejestr maskowania przerwań zewnętrznych GIMSK

Ostatnią rzeczą pozostaje włączenie przerwań za pomocą funkcji sei().

Poniższy program umożliwi ci pobawienie się z przerwaniem INT. Podłącz do płytki bazowej płytkę aplikacyjną APP001 i ustaw zworkę J1 w położeniu dolnym. W ten sposób do linii PB1 będzie dołączony przycisk W1 z płytki. Za pomocą tego przycisku możesz zmieniać stan linii PB1, czyli INT0. gdy przycisk nie jest naciśniety, na linii INT0 mamy stan logiczny 1. Gdy przycisk naciśniesz, stan ten zmieni się na 0.

/*
 * main.c
 *
 *  Created on: 7 lut 2016
 *      Author: Jerzy
 *  Opis:
 *  obsługuje naciśnięcie przycisku W1 za pomocą przerwania INT0.
 *  Przy każdym naciśnięciu zapala się lub gaśnie dioda D0.
 *  Konfiguracja jumperów:
 *  J0: góra
 *  J1: dół
 */

#include <avr/interrupt.h>
#include <avr/delay.h>

// Obsługa przerwania INT0
ISR(INT0_vect)
{
    PORTB ^= 1;    // zmieniamy stan diody D0 na przeciwny
    _delay_ms(10); // czekamy na wygaśnięcie drgań styków
}

int main(void)
{
    uint8_t licznik = 0;

    cli();    // na wszelki wypadek blokujemy przyjmowanie przerwań
    MCUCR = (MCUCR & 0b1111100) | 0b10; // przerwanie przy zmianie INT0 1->0
    GIMSK |= (1<<INT0); // Uaktywniamy przerwanie INT0
    DDRB  = 0b11101;    // Linie PB4..PB2 i PB0 jako wyjścia
    PORTB = 0b00010;    // Do PB1 opornik podciągający
    sei();   // odblokowujemy przyjmowanie przerwań
    while(1)
    {
        _delay_ms(50);
        licznik++;
        PORTB = (licznik & 0b11100) | (PORTB & 0b00001)|0b10;
    }
}

W programie ustawiamy obsługę przerwań INT0 przy zmianie stanu linii PB1 z 1 na 0, co odpowiada naciśnięciu przycisku. Program główny w pętli zlicza obiegi co 50 ms w zmiennej licznik. Następnie do portu B są przesyłane 3 górne bity licznika i sterują one diodami D2...D4. Przycisk W1 jest obsługiwany niezależnie przez przerwanie INT0. W momencie naciśnięcia tego przycisku zmienia się stan linii PB1 (INT0) z 1 na 0 i następuje wygenerowanie przerwania. W procedurze obsługi mikrokontroler zapala lub gasi diodę D0. W programie nie obsługujemy drgań styków, dlatego czasami reakcja na przycisk nie jest prawidłowa. To cena prostoty. W praktycznych projektach musisz jednak pamiętać o tym niekorzystnym zjawisku i wprowadzać odpowiednie opóźnienia przy odczycie stanu przycisków mechanicznych.

Poeksperymentuj w tym programie z innymi ustawieniami bitów ISC1 i ISC2 w porcie MCUCR.

Uwaga. Przerwanie INT0 jest generowane bez względu na to czy linia PB1 pracuje jako wejście, czy jako wyjście danych. Jeśli ustawimy ją jako wyjście, to mikrokontroler poprzez zmianę jej stanu będzie mógł programowo generować przerwanie INT0. To samo dotyczy opisanego dalej przerwania PCINT.

 

Przerwanie PCINT

Przerwanie to powstaje, gdy stan wybranej linii PB0...PB5 zmienia poziom logiczny (tzn. zmienia się z 0 na 1 lub z 1 na 0). Najpierw w rejestrze PCMSK (ang. Pin Change Mask Register – rejestr maski przerwań zmiany stanu końcówek) ustawiamy bity określające końcówki PB0...PB5 mikrokontrolera, które będą uczestniczyć w generacji przerwania. Tutaj końcówki BP0...PB5 nazywają się PCINT0...PCINT5 z uwagi na pełnienie nowej funkcji (końcówka PCINT5 posiada funkcję RESET, która ma wyższy priorytet od PCINT5, dlatego jeśli RESET nie jest wyłączony za pomocą ustawień fusebitów, to przerwanie PCINT5 nie będzie miało szansy wystąpić).
bit 7 6 5 4 3 2 1 0
nazwa PCINT5 PCINT4 PCINT3 PCINT2 PCINT1 PCINT0
R/W R R R/W R/W R/W R/W R/W R/W
stan 0 0 0 0 0 0 0 0

Rejestr maski przerwań zmiany stanu końcówek PCMSK

Po określeniu końcówek mikrokontrolera generujących przerwanie PCINT należy włączyć to przerwanie przez ustawienie na 1 bitu PCIE (ang. Pin Change Interrupt Enable – włączenie przerwań zmiany stanu końcówek).

bit 7 6 5 4 3 2 1 0
nazwa INT0 PCIE
R/W R R/W R/W R R R R R
stan 0 0 0 0 0 0 0 0

Rejestr maskowania przerwań zewnętrznych GIMSK

Na koniec włączamy obsługę przerwań przez wywołanie funkcji sei().

Do płytki bazowej podłącz płytkę aplikacyjną APP001 i ustaw obie zworki w dół. W ten sposób do linii PB0 i PB1 zostaną podłączone przyciski, którymi będziesz mógł zmieniać stan tych linii.

/*
 * main.c
 *
 *  Created on: 7 lut 2016
 *      Author: Jerzy
 *  Opis:
 *  Program jest przykładem wykorzystania przerwań zewnętrznych PCINT.
 *  Na diodach D2...D4 jest przesuwany punkt świetlny. Przyciski
 *  W0 i W1 są obsługiwane przez przerwanie PCINT i sterują kierunkiem
 *  przesuwania punktu świetlnego.
 *  J0: dół
 *  J1: dół
 */

#include <avr/interrupt.h>
#include <avr/delay.h>

// Określa kierunek przesuwania punktu
volatile uint8_t dir = 1;

// Obsługa przerwania PCINT
ISR(PCINT0_vect)
{
    if(!(PINB & 0b01)) dir = 0; // Wciśnięty W0
    else
    if(!(PINB & 0b10)) dir = 1; // Wciśnięty W1
}

int main(void)
{
    uint8_t shft = 1;
    cli();    // na wszelki wypadek blokujemy przyjmowanie przerwań
    PCMSK  = 0b000011;  // Przerwanie przy zmianie PB0 i PB1
    GIMSK  = (1<<PCIE); // Uaktywniamy przerwanie PCINT
    DDRB   = 0b11100;   // Linie PB4..PB2 jako wyjścia
    PORTB  = 0b00011;   // Do PB0 i PB1 oporniki podciągające
    sei();   // odblokowujemy przyjmowanie przerwań
    while(1)
    {
        _delay_ms(200);
        if(dir)
        {
            shft <<= 1;
            if(shft == 0b1000) shft = 1;
        }
        else
        {
            shft >>= 1;
            if(!shft) shft = 0b100;
        }
        PORTB = (shft << 2) | 0b11;
    }
}

Program cyklicznie przesuwa punkt świetlny na diodach D2...D4. Przyciski W0 i W1 sterują kierunkiem tego przesuwu. Ich naciśnięcie powoduje wygenerowanie przerwania PCINT. W procedurze obsługi tego przerwania sprawdzamy, który przycisk został naciśniety i odpowiednio ustawiamy zmienną dir, która steruje kierunkiem przesuwu punktu świetlnego. Przyciski można naciskać w dowolnej chwili.

Nowością jest utworzenie zmiennej dir z dyrektywą volatile. Dyrektywa ta jest informacja dla kompilatora, że zawartość zmiennej może się zmieniać w dowolnej chwili i nie należy jej buforować w programie, co kompilator często robi w celu zwiększenia szybkości działania kodu. Jeśli przed definicją zmiennej umieścimy dyrektywę volatile, to program będzie zawsze pobierał zawartość tej zmiennej z pamięci i nie będzie jej sobie zapamiętywał w tymczasowym miejscu. Jest to konieczne, ponieważ zmienną tą zmienia procedura przerwania PCINT w nieprzewidywalnym momencie. Spróbuj uruchomić ten program bez tej dyrektywy, a przestanie on poprawnie działać.

Z przerwaniami INT i PCINT związany jest jeszcze rejestr znaczników przerwań GIFR (ang. General Interrupt Flag Register).

bit 7 6 5 4 3 2 1 0
nazwa INTF0 PCIF
R/W R R/W R/W R R R R R
stan 0 0 0 0 0 0 0 0

Rejestr znaczników przerwań zewnętrznych GIFR

Jak widzisz, ma on podobną budowę do rejestru GIMSK, który maskuje (tzn. uaktywnia lub blokuje) przerwania INT i PCINT na tych samych bitach. Bity INTF0 i PCIF zostają ustawione na 1, gdy wystąpi warunek przerwania INT lub PCINT. Jeśli zostanie przy tym wywołana procedura ISR danego przerwania, to odpowiedni bit rejestru znaczników przerwań zostanie automatycznie wyzerowany. Można też wyzerować dany bit wpisując do niego 1 (nie zero! jak w zwykłych rejestrach). Rejestr ten może czasem być użyteczny, gdy jedna procedura przerwań chce sprawdzić, czy wystąpiło inne przerwanie. Normalnie nie musisz z niego korzystać.

 

Przerwania licznikowe

Są to przerwania wewnętrzne. Powstają wtedy, gdy licznik TCNT0 ulega przewinięciu, tzn. gdy osiągnie maksymalną wartość i rozpoczyna nowy cykl zliczania lub zrównuje swój stan z jednym z rejestrów porównawczych OCR0A lub OCR0B.

Przerwanie przepełnienia licznika TIM0_OVF powstaje, gdy zostanie ustawiony na 1 znacznik TOV0 w rejestrze TIFR0 (ang. Timer/Counter Interrupt Flag Register – rejestr znaczników przerwań licznika).

bit 7 6 5 4 3 2 1 0
nazwa OCF0B OCF0A TOV0
R/W R R R R R/W R/W R/W R
stan 0 0 0 0 0 0 0 0

Rejestr TIFR0

Znaczniki w tym rejestrze mogą być zerowane programowo przez wpisanie do odpowiedniego bitu 1 (nie zero!). Po przyjęciu przerwania odpowiedni znacznik jest automatycznie zerowany (nie musisz tego specjalnie robić w swojej procedurze ISR).

Znacznik TOV0 jest ustawiany zależnie od trybu pracy licznika, który ustawiamy bitami WGMx w rejestrach sterujących licznika TCCR0A i TCCR0B:

bit 7 6 5 4 3 2 1 0
nazwa COM0A1 COM0A0 COM0B1 COM0B0 WGM01 WGM00
R/W R/W R/W R/W R/W R R R/W R/W
stan 0 0 0 0 0 0 0 0

Rejestr sterowania licznikiem TCCR0A

bit 7 6 5 4 3 2 1 0
nazwa FOC0A FOC0B WGM02 CS02 CS01 CS00
R/W W W R R R/W R/W R/W R/W
stan 0 0 0 0 0 0 0 0

Rejestr sterowania licznikiem TCCR0B

Reguły ustawiania znacznika TOV0, a zatem również generowania przerwania, są następujące:

Tryb WGM2 WGM1 WGM0 Rodzaj operacji Ustawianie TOV0
0 0 0 0 Normalna praca TCNT0: 255 → 0
1 0 0 1 Poprawny fazowo PWM TCNT0: 0 → 1
2 0 1 0 CTC TCNT0: 255 → 0
3 0 1 1 Szybki PWM TCNT0: 255 → 0
4 1 0 0 Zarezerwowane  
5 1 0 1 Poprawny fazowo PWM TCNT0: 0 → 1
6 1 1 0 Zarezerwowane  
7 1 1 1 Szybki PWM TCNT0: 255 → 0

Zatem, aby wygenerować przerwanie od licznika TCNT0, należy najpierw ustawić pożądany tryb pracy tego licznika (omówiliśmy te tryby w poprzednim rozdziale), a następnie włączyć przerwania od licznika ustawiając na 1 bit TOIE0 w rejestrze maskowania przerwań licznika TIMSK0:

bit 7 6 5 4 3 2 1 0
nazwa OCIE0B OCIE0A TOIE0
R/W R R R R R/W R/W R/W R
stan 0 0 0 0 0 0 0 0

Rejestr maskowania przerwań licznika TIMSK0

Na koniec uaktywniamy reagowanie na przerwania przez wywołanie funkcji sei().

Do płytki bazowej dołącz płytkę aplikacyjną APP001 i ustaw pierwszą zworkę w dół, aby do linii PB0 był dołączony przycisk W0. Następnie prześlij do mikrokontrolera poniższy program:

/*
 * main.c
 *
 *  Created on: 7 lut 2016
 *      Author: Jerzy
 *  Opis:
 *  Program jest przykładem wykorzystania przerwania wewnętrznego od
 *  przepełnienia licznika. W procedurze obsługi przerwania przesuwane są
 *  w lewo bity portu B. Bit PB0 pochodzi z wejścia i jest negowany przed
 *  wykonaniem przesunięcia. W efekcie każde naciśnięcie przycisku zapala
 *  kolejne diody D1...D4. Zwolnienie przycisku kolejno je gasi.
 *  Konfiguracja jumperów:
 *  J0: dół
 *  J1: góra
 */

#include <avr/interrupt.h>

// Obsługa przerwania TIM0_OVF
ISR(TIM0_OVF_vect)
{
    PORTB = ((PINB ^ 1) << 1) | 1;
}

int main(void)
{
    cli();                // na wszelki wypadek blokujemy przyjmowanie przerwań
    DDRB   = 0b11110;     // Linie PB1..PB4 jako wyjścia
    PORTB  = 0b00001;     // Do PB0 opornik podciągający
    TCCR0B = 0b100;       // 3906,25 Hz -> do licznika, tryb normalny
    TIMSK0 |= (1<<TOIE0); // uaktywniamy przerwania od przepełnienia licznika
    sei();                // odblokowujemy przyjmowanie przerwań
    while(1);             // W pętli nic nie robimy
}

Przerwania od licznika są często wykorzystywane w celu okresowego wykonywania określonej procedury. Tak właśnie działa powyższy program. Do licznika trafiają impulsy zegarowe z preskalera ustawionego na dzielenie częstotliwości zegara 1MHz przez 256. Daje to częstotliwość około 3906 Hz. Gdy licznik zliczy 256 takich impulsów, nastąpi przepełnienie i zostanie wygenerowane przerwanie TIM0_OVF. W procedurze obsługi tego przerwania odczytujemy stan portu B, negujemy bit BP0, który pochodzi od przycisku, przesuwamy wszystkie bity o 1 w lewo i zapisujemy je do portu PORTB. Ostatni bit jest zawsze ustawiany na 1, aby do przycisku był podłączony opornik podciągający, inaczej wejście PB0 będzie zbierało zakłócenia z okolicy. Sprawdź, co się będzie działo, jeśli ten bit nie będzie ustawiany na 1, tzn. w procedurze obsługi przerwania zmień instrukcję na:

PORTB = ((PINB ^ 1) << 1);

 

 

 

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2017 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.