LinkedIn YouTube Facebook
Szukaj

Wstecz
Artykuły

Mikrokontrolery AVR XMEGA w praktyce, część 6. Przerwania

W procesorach ATmega kontroler przerwań był tak prosty, że prostszy już być nie może. Można go było włączyć lub wyłączyć. W XMEGA kontroler przerwań został znacznie rozbudowany i traktowany jest jako pełnoprawny układ peryferyjny o nazwie PMIC, czyli Programmable Multilevel Interrupt Controller.

Największym plusem PMIC jest to, że mamy do dyspozycji trzy priorytety przerwań – niski, średni i wysoki. Oznacza to, że procedura obsługi przerwania o niskim priorytecie może być przerwana przez przerwanie o priorytecie średnim lub wysokim. Przerwanie o priorytecie wysokim nie może być przerwane wcale (wyjątek: wykrycie nieprawidłowego sygnału taktującego). Dzięki temu możemy ustalić jakie zadania są dla nas najważniejsze, by procesor mógł na nie reagować jak najszybciej, a mniej ważne zadania zostawił do dokończenia na później. Jest też dostępny scheduler round-robin, by mieć kontrolę nad kolejnością wykonywanych przerwań o tym samym priorytecie, w razie natłoku zgłoszeń. Jest to konieczne, gdyż w procesorach XMEGA mamy bardzo wiele różnych przerwań do dyspozycji – w ATxmega128A3U, zamontowanym na płytce X3-DIL64 z Leon Instruments, jest ponad 100 wektorów przerwań!

Przerwania dzielą się na maskowalne i niemaskowane. Niemaskowalne jest zaledwie jedno – wykrycie nieprawidłowego działania generatora sygnału zegarowego. Przerwania maskowalne mogą generować wszystkie układy peryferyjne i podobnie jak w ATmega, musimy je odblokować, aby móc je wykorzystać. Kolejnym podobieństwem jest konieczność użycia makra sei(), aby uruchomić system przerwań.

W tej części kursu zrobimy prosty program, demonstrujący działanie przerwań o różnych priorytetach. Poznamy również jak skonfigurować przerwania INT od portów, omawianych w poprzedniej części kursu. W pętli głównej procesor będzie zajmował się wyłącznie mruganiem diodą, podłączoną do pinu B0. Przerwania będą wywoływane przyciskami na pinach E5 i E6, a procedury tych przerwań będą powodowały mruganie diodami, odpowiednio, podłączonymi do pinów C1 i C0.

Oprócz mrugania diodami, wykorzystamy również popularny wyświetlacz tekstowy 16×2 ze sterownikiem HD44780. Nie będę tu omawiał jego obsługi i wykorzystamy gotową bibliotekę, którą znajdziesz na płycie dołączonej do EP (zostało to omówione w części 5 kursu). Biblioteka jest autorstwa Radosława Kwietna (radzio.dxp.pl) i przystosowałem ją do wykorzystania w mikrokontrolerach XMEGA.

Schemat układu wykorzystywanego w tej części kursu przedstawiono na rysunku 1, a zdjęcie układu zbudowanego na płytce stykowej przedstawia rysunek 2.

 

Rys. 1. Schemat układu demonstrującego działanie przerwań

Rys. 1. Schemat układu demonstrującego działanie przerwań

 

Rys. 2. Zdjęcie układu zmontowanego na płytce stykowej

Rys. 2. Zdjęcie układu zmontowanego na płytce stykowej

 

Program zaczynamy, jak zwykle, od konfiguracji portów. W tym fragmencie kodu jedyna nowość to PORT_ISC_FALLING_gc wpisane do rejestrów PORTxCTRL. W ten sposób decydujemy, jakie konkretnie zdarzenie ma wywoływać przerwanie. FALLING oznacza zbocze opadające, a możliwe są jeszcze opcje: RISING (zbocze rosnące), BOTHEDGES (zbocze rosnące lub opadające) oraz LEVEL, czyli stan logicznego zera.

 PORTE.DIRSET = PIN0_bm;                // pin E0 jako wyjście
 PORTC.DIRSET = PIN0_bm | PIN1_bm;      // pin C0 i C1 jako wyjście
 PORTE.DIRCLR = PIN5_bm | PIN6_bm;      // pin E5 i E6 jako wejście
 PORTE.PIN5CTRL = PORT_OPC_PULLUP_gc|   // pull-up na E5
    PORT_ISC_FALLING_gc;                // przerwanie ma wywoływać zbocze opadające
 PORTE.PIN6CTRL = PORT_OPC_PULLUP_gc|   // pull-up na E6
    PORT_ISC_FALLING_gc;                // przerwanie ma wywoływać zbocze opadające

Każdy port w XMEGA może generować przerwanie INT0 i INT1, jednak to my sami możemy wybrać, który pin jakie przerwanie ma wywoływać. Mało tego, nawet kilka pinów może wywoływać tę samą procedurę przerwania! Nic nie stoi na przeszkodzie, by jeden pin wywoływał zarówno INT0 i INT1, choć takie rozwiązanie raczej nie ma sensu praktycznego.

Na przykładzie poniższego kodu widać, jak przypisać piny do poszczególnych przerwań. Następnie, w rejestrze INTCTRL musimy ustalić priorytety przerwań INT0 i INT1 – dostępne opcje to LO, MED, HI albo można przerwanie wyłączyć wpisując PORT_INTxLVL_OFF_gc.

 PORTE.INT0MASK = PIN5_bm;               // pin E5 ma generować przerwania INT0
 PORTE.INT1MASK = PIN6_bm;               // pin E6 ma generować przerwania INT1
 PORTE.INTCTRL = PORT_INT0LVL_HI_gc|     // poziom HI dla przerwania INT0 portu E
    PORT_INT1LVL_LO_gc;                  // poziom LO dla przerwanie INT1 portu E

Przejdźmy teraz do skonfigurowania kontrolera przerwać PMIC. Musimy w jego rejestrze CTRL odblokować przerwania o priorytecie HI oraz LO. Na koniec, należy wpisać instrukcję sei(), dobrze znaną z ATmega i ATtiny, aby procesor mógł obsługiwać przerwania.

 PMIC.CTRL = PMIC_HILVLEN_bm|  // włączenie przerwań o priorytecie HI
    PMIC_LOLVLEN_bm;           // włączenie przerwań o priorytecie LO
 sei(); 

Pętla główna naszego programu jest trywialnie prosta.

 while(1) {
  LcdClear();                // czyszczenie wyświetlacza
  Lcd("main");               // wyświetlenie napisu
  PORTB.OUTTGL = PIN0_bm;    // mruganie diodą na B0
  _delay_ms(500);            // czekanie 500ms
 }

Zobaczmy, jak należy napisać procedurę obsługi przerwania.

ISR(PORTE_INT0_vect) {                     // procedura przerwania INT0 portu E
 for(uint8_t i = 0; i < 20; i++) {         // 10-krotne mrugnięcie diodą na C0
  LcdClear();                              // czyszczenie wyświetlacza
  Lcd("INT0");                             // wyświetlenie napisu
  Lcd2;                                    // przejście do drugiej linii
  Lcd("priorytet HI");
  PORTC.OUTTGL = PIN0_bm;                  // mruganie diodą na C0
  _delay_ms(100);
 }
}

Każdy port może generować przerwanie INT0 i INT1 – stąd nazwa przerwania PORTE_INT0_vect. Procedura zawiera pętlę, dzięki której dioda na pinie C0 mrugnie dziesięciokrotnie. Procedura przerwania INT1 jest bardzo podobna.

ISR(PORTE_INT1_vect) {                  // procedura przerwania INT1 portu E
 for(uint8_t i = 0; i < 20; i++) {      // 10-krotne mrugnięcie diodą na C1
  LcdClear();                           // czyszczenie wyświetlacza
  Lcd("INT1");                          // wyświetlenie napisu
  Lcd2;                                 // przejście do drugiej linii
  Lcd("priorytet LO");
  PORTC.OUTTGL = PIN1_bm;               // mruganie diodą na C1
  _delay_ms(100);
 }
}

Zobaczmy więc, jak to działa w praktyce, wykorzystując płytkę rozwojową X3-DIL64 z Leon Instruments, którą można kupić w KAMAMI. Po wgraniu programu mruga dioda podłączona do B0. Naciśnięcie przycisków wywołuje odpowiadające im procedury przerwań. Co się stanie, jeśli wciśniemy przycisk E6, wywołujący przerwanie o priorytecie niskim, a chwilę potem E5, który wywoła przerwanie o wyższym priorytecie? Dioda C1 przestanie mrugać, a dioda na C0 zacznie. Kiedy C0 skończy migać, procesor wróci do mrugania diodą C1.

Należy wyraźnie zaznaczyć, że procedury przerwań powinny być wykonywane jak najszybciej i nie powinno być w nich żadnych funkcji opóźniających. W tym artykule została zastosowana funkcja _delay_ms(), aby móc zobaczyć działanie przerwań i ich priorytetów. Pamiętaj, że w normalnym programie stosowanie opóźnień w przerwaniach jest wysoce niewskazane.

Kompletny program

#define  F_CPU 2000000UL
#include < avr/io.h >
#include < avr/interrupt.h >
#include < util/delay.h >
#include "hd44780.h"

int main(void) {
    
 // konfiguracja portów
 PORTE.DIRSET = PIN0_bm;                // pin E0 jako wyjście
 PORTC.DIRSET = PIN0_bm | PIN1_bm;      // pin C0 i C1 jako wyjście
 PORTE.DIRCLR = PIN5_bm | PIN6_bm;      // pin E5 jako wejście
 PORTE.PIN5CTRL = PORT_OPC_PULLUP_gc|   // pull-up na E5
    PORT_ISC_FALLING_gc;                // przerwanie ma wywoływać zbocze opadające
 PORTE.PIN6CTRL = PORT_OPC_PULLUP_gc|   // pull-up na E6
    PORT_ISC_FALLING_gc;                // przerwanie ma wywoływać zbocze opadające
 
 // konfiguracja przerwań portów
 PORTE.INT0MASK = PIN5_bm;              // pin E5 ma generować przerwania INT0
 PORTE.INT1MASK = PIN6_bm;              // pin E6 ma generować przerwania INT1
 PORTE.INTCTRL = PORT_INT0LVL_HI_gc|    // poziom HI dla przerwania INT0 portu E
    PORT_INT1LVL_LO_gc;                 // poziom LO dla przerwanie INT1 portu E
 
 // włączenie przerwań
 PMIC.CTRL = PMIC_HILVLEN_bm |          // włączenie przerwań o priorytecie HI
    PMIC_LOLVLEN_bm;                    // włączenie przerwań o priorytecie LO
 sei();                                 // globalne włączenie przerwań
 LcdInit();                             // inicjalizacja wyświetlacza
 
 // pętla główna
 while(1) {
  LcdClear();                           // czyszczenie wyświetlacza
  Lcd("main");                          // wyświetlenie napisu
  PORTE.OUTTGL = PIN0_bm;               // mruganie diodą na B0
  _delay_ms(500);                       // czekanie 500ms
    }
}

ISR(PORTE_INT0_vect) {                  // procedura przerwania INT0 portu E
 for(uint8_t i = 0; i < 20; i++) {      // 10-krotne mrugnięcie diodą na C0
  LcdClear();                           // czyszczenie wyświetlacza
  Lcd("INT0");                          // wyświetlenie napisu
  Lcd2;                                 // przejście do drugiej linii
  Lcd("priorytet HI");
  PORTC.OUTTGL = PIN0_bm;               // mruganie diodą na C0
  _delay_ms(100);
 }
}

ISR(PORTE_INT1_vect) {                  // procedura przerwania INT1 portu E
 for(uint8_t i = 0; i < 20; i++) {      // 10-krotne mrugnięcie diodą na C1
  LcdClear();                           // czyszczenie wyświetlacza
  Lcd("INT1");                          // wyświetlenie napisu
  Lcd2;                                 // przejście do drugiej linii
  Lcd("priorytet LO");
  PORTC.OUTTGL = PIN1_bm;               // mruganie diodą na C1
  _delay_ms(100);
 }
}

Dystrybutorem zestawu X3-DIL64 jest KAMAMI.pl.

Dominik Leon Bieczyński

http://leon-instruments.blogspot.com