MCS-51
Wybrane zagadnienia z programowania w asemblerze

Obsługa przerwań - informacje ogólne

W opracowaniu dotyczącym budowy mikrokontrolerów rodziny MCS-51 przedstawiono informacje omawiające system przerwań mikrokontrolera MCS-51 od strony sprzętowej, tj. opis rejestrów z nim związanych, poziomy i priorytety przerwań oraz adresy wektorów przerwań. W tym miejscy zostaną przedstawiono zagadnienia związane z oprogramowaniem tego systemu.

Program realizujący obsługę przerwań występujących w mikrokontrolerze rodziny MCS-51 wymaga specyficznej konstrukcji. Wynika to z zaimplementowanego sposobu ich obsługi. Mianowicie w początkowej części pamięci kodu wyznaczone są wybrane adresy powiązane z różnymi źródłami przerwań, pod które następuje sprzętowo wygenerowany skok, kiedy dane źródło przerwania staje się aktywne. Zatem aby programowo obsłużyć dane przerwanie, należy pod wymienionym w tabeli adresem umieścić pierwszą instrukcję związaną z jego obsługą.

Żródło przerwania Adres
Zewnętrzne INT0 0003h
Licznik-czasomierz T0 000Bh
Zewnętrzne INT1 0013h
Licznik-czasomierz T1 001Bh
Port szeregowy 0023h
Licznik-czasomierz T2 002Bh

Dodatkowo w tworzonym programie zawsze istnieje pewna część główna realizująca choćby aktywację systemu przerwań od wybranych zdarzeń. Mikrokontroler rozpoczyna wykonywanie programu od adresu 0000H i w pierwszej kolejności powinien wykonać właśnie instrukcje należące do części głównej. Dlatego konieczne jest ominięcie wykorzystywanych wektorów przerwań poprzez umieszczenie pod adresem 0000H rozkazu skoku do programu głównego. Wspomniane adresy związane z wektorami przerwań występują w odstępach co 8 bajtów. Obsługując przerwania, których adresy występują bezpośrednio po sobie dysponuje się niewielką przestrzenią do umieszczenia rozbudowanego podprogramu obsługi przerwania. Dlatego pod wspomnianymi adresami umieszcza się tylko instrukcje skoków bezwarunkowych do właściwych podprogramów realizujących odpowiednie instrukcje.

W momencie przejścia mikrokontrolera do obsługi przerwania, na stos odkładana jest zawartość licznika rozkazów PC. Ostatnią instrukcją jaka powinna być wykonana w podprogramie obsługi przerwania jest instrukcja RETI. Powoduje ona zdjęcie ze stosu odłożonej zawartości licznika rozkazów PC. Dzięki temu możliwe jest dalsze kontynuowanie przerwanego wcześniej kodu programu.

Poniżej przedstawiono przykładowy program, w którym zaimplementowano obsługę przerwań od liczników T0 oraz wejścia INT1.

 1          SJMP POCZ
 2     ORG 000Bh
 3          SJMP LICZ_T0      ;skok do właściwego kodu obsługi przerwania
 4     ORG 0013h
 5          SJMP PRZ_INT1     ;skok do właściwego kodu obsługi przerwania
 6
 7 POCZ:    MOV  TMOD,#02h    ;ustalenie trybu pracy licznika
 8          MOV  TH0,#0FDh    ;wartości początkowe..
 9          MOV  TL0,#0FDh    ;..dla licznika
10          MOV  IE,#86h      ;aktywacja przerwań od T0 i INT1
11          SETB TR0          ;start licznika T0
12          SJMP $
13 
14 LICZ_T0: ...               ;podprogram obsługi przerwania..
15          ...               ;..od licznika T0
16          RETI
17 
18 PRZ_INT1:...               ;podprogram obsługi przerwania..
19          ...               ;..od wejścia INT1
20          RETI
21
22          END

Użycie dyrektyw ORG (linie 2 i 4) zapewnia umieszczenie instrukcji skoków (linie 3 i 5) do podprogramów obsługi przerwań pod właściwymi adresami. Prezentowany przykład nie ma rozbudowanej części głównej programu, dlatego wykorzystano tutaj instrukcję skoku krótkiego SJMP. Jeśli jednak byłby on bardziej rozbudowany, konieczne było by zastosowanie instrukcji skoku dalekiego LJMP. Przerwania związane z dalszymi adresami nie są używane w niniejszej aplikacji, dlatego pierwsza instrukcja głównej części programu może wystąpić już po instrukcji 5.

Na poniższym rysunku przedstawiono w sposób obrazowy rozmieszczenie w pamięci kodu instrukcji programu, którego listing przedstawiono powyżej. W białych polach znajdują się kody poszczególnych instrukcji programu. Mikrokontroler rozpoczyna wykonywanie programu odczytując kod instrukcji umieszczony pod adresem 0000H. Ze względu na brak jakiejkolwiek dyrektywy związanej z określeniem adresu pamięci kodu, w rozpatrywanym przykładzie pod tym adresem zostanie umieszczony pierwszy bajt 2-bajtowej instrukcji 1. Zatem w pierwszej kolejności wykonywana jest instrukcja skoku, która przekierowuje wykonywanie programu do części głównej. Brak tej instrukcji spowodowałby, że pod adresem 0000H znajdowałaby się bliżej nieokreślona wartość reprezentująca jakąś instrukcję. Dodatkowo brak instrukcji 1 mógłby spowodować wykonanie instrukcji 3 bez wystąpienia przerwania lecz w wyniku naturalnej kolejności wykonywania instrukcji.

Obrazowa prezentacja rozmieszczenia instrukcji w pamięci kodu przykładowego programu obsługi przerwania.

Aby w wyniku pojawienia się źródła przerwania wystąpiło wspomniane przekierowanie pod odpowiedni adres, w części głównej programu powinna wystąpić co najmniej instrukcja, która aktywuje przyjmowanie przerwań z wybranych źródeł (linia 10) oraz ewentualne ustawienie poziomu priorytetu obsługiwanych przerwań. Ponieważ w prezentowanym przykładzie następuje obsługa przerwania od licznika, konieczne jest ustawienie odpowiedniego trybu jego pracy (instrukcja 7), ewentualne ustawienie wartości początkowej (instrukcje 8 i 9) oraz włączenie licznika do pracy (instrukcja 11). Instrukcja 12 kończy wykonywanie programu głównego. Część główna programu może być również w jakiś sposób zapętlona wykonując sukcesywnie pewien fragment kodu. Jeśli program główny nie działa w pętli, tak jak w prezentowanym przykładzie, to jego ostatnią instrukcją powinna być właśnie instrukcja skoku na siebie. W przeciwnym wypadku po jego wykonaniu zostanie wykonany również podprogram obsługi przerwania (w prezentowanym przykładzie związanego z licznikiem T0), zaś wykonanie jego ostatniej instrukcji RETI spowoduje zdjęcie ze stosu dwóch bajtów i przeładowanie nimi licznika PC, co może spowodować przejście do wykonywania instrukcji z bliżej nieokreślonej części pamięci programu. Podsumowując, część główna programu musi być zorganizowana w taki sposób, aby instrukcje przynależne do podprogramów obsługi przerwań nie były wykonane bez wystąpienia właściwego przerwania.

W omawianym przykładzie wystąpienie przerwania może nastąpić tylko podczas wykonywaniu instrukcji 12, a dalsze działanie programu opiera się na przerwaniach od licznika T0 i wejścia INT1. Jeśli w momencie wykonywania instrukcji 12 pojawi się np. przerwanie od licznika T0, to mikrokontroler kończy jej wykonywanie i odkłada na stos adres następnej instrukcji z programu głównego do wykonania (w prezentowanym przypadku jest to adres tej samej instrukcji). Następnie licznik rozkazów PC przeładowywany jest adresem wektora właściwym dla danego przerwania, co powoduje wykonanie instrukcji skoku do właściwego podprogramu obsługi przerwania.

Przy pisaniu programów uruchamianych w systemach działających w oparciu o program monitora umieszczony w pamięci programu np. od adresu 0000H do 0FFFH, przed instrukcją 1 należy umieścić dyrektywę CSEG AT 1000H, a wartości występujące w liniach 2 i 4 zwiększyć o 1000H. Po modyfikacji początkowe linie kodu powyższego programu będą miały postać jak poniżej.

    CSEG AT 1000h
         SJMP POCZ
    ORG 100Bh
         SJMP LICZ_T0      ;skok do właściwego kodu obsługi przerwania
    ORG 1013h
         SJMP PRZ_INT1     ;skok do właściwego kodu obsługi przerwania
         ...

Jeśli podprogram obsługi przerwania operuje bezpośrednio lub pośrednio na rejestrach, które są jednocześnie wykorzystywane w części głównej kodu programu, takich jak któryś z rejestrów roboczych R0-R7, rejestr akumulatora, B lub DPTR, których zmiana wartości może spowodować błędne działanie przerwanego programu, to konieczne jest odłożenie ich na stos zaraz po wejściu do podprogramu obsługi przerwania, a tuż przed wyjściem ściągnięcie ich ze stosu.

Należy pamiętać, że ściąganie wartości ze stosu musi odbywać się w odwrotnej kolejności niż ich odkładanie na stos. Związane jest to z faktem, iż stos w mikrokontrolerze omawianej rodziny ma strukturę LIFO (ang. Last Input, First Output).

 1          SJMP POCZ1
 2     ORG 000BH
 3          SJMP LICZ_T0
 4 
 5 POCZ1:   MOV  TMOD,#02H       ;ustalenie trybu pracy liczników
 6          MOV  TH0,#0FDH       ;wartości początkowe..
 7          MOV  TL0,#0FDH       ;..dla licznika T0
 8          MOV  IE,#82H         ;aktywacja przerwania od T0
 9          SETB TR0             ;start licznika T0
10          MOV  DPTR,#0F800H    ;zapis adresu do rejestru DPTR
11 POCZ2:   MOV  R0,#81H         ;zapis adresu do rejestru R0
12 DALEJ:   MOV  A,@R0           ;odczyt wartości z komórki w IDATA
13          DEC  R0              ;dekrementacja wartości adresu
14          MOVX @DPTR,A         ;zapis odczytanej wartości do XDATA
15          INC  R0              ;zwiększenie adresu..
16          INC  R0              ;..o 2
17          CJNE R0,#0C0H,DALEJ  ;czy ostatnia komórka?
18          SJMP POCZ2           ;jeśli ostatnia to skocz na POCZ2
19 
20 LICZ_T0: PUSH ACC             ;odłożenie rejestrów..
21          PUSH DPL             ;..na..
22          PUSH DPH             ;..stos
23          ...                  ;podprogram obsługi przerwania..
24          ...                  ;..od licznika T0,..
25          MOV DPTR, #0F805H    ;..w którym m.in. ..
26          MOV A,#0FFH          ;..używane są..
27          MOVX @DPTR,A         ;..rejestry A oraz DPTR
28          ...
29          ...
30          POP DPH              ;zdjęcie rejestrów..
31          POP DPL              ;..ze..
32          POP ACC              ;..stosu
33          RETI
34
35          END

W zaprezentowanym przykładzie zarówno w programie głównym, który kopiuje fragment wewnętrznej pamięci danych do urządzenia, które jest widziane jako komórka zewnętrznej pamięci danych, jak i w kodzie obsługującym przerwanie, wykorzystywane są rejestry A i DPTR. Brak odłożenia kopii ich zawartości na stos może spowodować błędne działanie programu. Zakładając bowiem, że przerwanie wystąpiło podczas realizacji instrukcji 12, tj. po odczycie wartości z wewnętrznej pamięci danych a przed jej zapisem do zewnętrznej pamięci danych, bez odłożenia kopii rejestru akumulatora, wartość właściwa dla programu głównego zostałaby zastąpiona wartością z przerwania. Podobnie brak odłożenia kopii DPTR na stos zmieniłby adres urządzenia związany z programem głównym.

W przypadku, gdy konieczne jest odłożenie na stos wszystkich rejestrów roboczych, łatwiejsza i szybsza jest zmiana aktywnego banku rejestrów.