środa, 16 marca 2011

Kurs XMega (06): Przerwania

Autor: Dominik Leon Bieczyński
Redakcja: Dondu

Artykuł jest fragmentem cyklu: Kurs mikrokontrolerów XMega by Leon-Instruments

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.

Pliki do pobrania: XMega-przerwania-int.zip (kopia)

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 4 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 16x2 ze sterownikiem HD44780. Nie będę tu omawiał jego obsługi i wykorzystamy gotową bibliotekę, którą omawialiśmy w 5 odcinku kursu. Biblioteka jest autorstwa Radosława Kwietna (radzio.dxp.pl) i przystosowałem ją do wykorzystania w mikrokontrolerach XMEGA.


Podłączenia wyświetlacza do XMega - schemat układu do ćwiczenia przerwań.
Schemat


A tak wyglądać powinien zmontowany układ.


Podłączenia wyświetlacza do XMega - zdjęcie układu do ćwiczenia przerwań.
Zdjęcie układu


Program zaczynamy, jak zwykle, od konfiguracji portów. W tym fragmencie kodu jedyna nowość to PORT_ISC_FALLING_gc wpisane do rejestrów PINxCTRL. 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 wywołuje zbocze opadające
    PORTE.PIN6CTRL  =    PORT_OPC_PULLUP_gc| // pull-up na E6
                         PORT_ISC_FALLING_gc;// przerwanie wywołuje 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 INT0.

    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. 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, a potem wróci do pętli głównej i mrugania diodą na B0.

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 niniejszym 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.






#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 wywołuje zbocze opadające
    PORTE.PIN6CTRL = PORT_OPC_PULLUP_gc| // pull-up na E6
                     PORT_ISC_FALLING_gc;// przerwanie wywołuje 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);
    }
}


Dominik Leon Bieczyński
leon-instruments.pl

4 komentarze:

  1. Czas chyba przesiąść się na xmega.Szkoda tylko, że nie ma ich w obudowach DIP.

    OdpowiedzUsuń
    Odpowiedzi
    1. TQFP lutuje się wbrew pozorom łątwiej i szybciej. Natomiast do nauki polecam przejściówki na DIL np. z Leon Instrumets.

      Usuń
    2. a u mnie wyskakuje ERROR "static declaration of '__vector_64' follows non-static declaration". Dlaczego?

      Usuń
    3. Opisz ten problem na forum.

      Usuń