GÓRNICZO - HUTNICZA
IM. STANISŁAWA STASZICA W KRAKOWIE
WYDZIAŁ ELEKTROTECHNIKI, AUTOMATYKI, INFORMATYKI I ELEKTRONIKI
KATEDRA AUTOMATYKI NAPĘDU I URZĄDZEŃ PRZEMYSŁOWYCH
MIKROPROCESOROWE METODY STEROWANIA
Mikrokontrolery rodziny MCS-51
Cz. IV.
Autor:
Dr inż. Zbigniew Waradzyn
Kraków 2005
Kilka pożytecznych instrukcji
30.1. Operacje logiczne na bitach
suma logiczna
Przykładem instrukcji realizującej sumę logiczną jest orl ad, #n - instrukcja wykonująca sumę logiczną, bit po bicie, podanych argumentów i wpisująca wynik do komórki podanej jako pierwszy argument - w powyższym przykładzie do komórki o adresie ad.
Suma logiczna dwóch bitów wynosi 1, jeśli przynajmniej jeden z nich jest w stanie 1; jeśli natomiast oba bity mają stan 0 - ich suma też wynosi 0.
Wniosek: wykonanie sumy logicznej dowolnego bitu i wartości 1 powoduje ustawienie tegoż bitu, natomiast wykonanie sumy logicznej dowolnego bitu i wartości 0 nie zmienia wartości bitu.
Przykład:
Poniższa instrukcja realizuje sumę logiczną zawartości komórki o adresie 30H i liczby 43H:
orl 30H, #43H
A oto efekt jej wykonania (x oznacza dowolny stan bitu, czyli 1 lub 0):
stan komórki 30H przed wykonaniem instrukcji xxxx xxxx
drugi argument instrukcji orl (maska) 0100 0011
stan komórki 30H po wykonaniu instrukcji x1xx xx11
Instrukcja orl może być użyta do ustawiania pojedynczych bitów w komórkach, które nie są adresowalne bitowo lub do ustawienia kilku bitów jednocześnie w dowolnej komórce pamięci. Występująca w powyższej instrukcji liczba 43 określa, które bity w komórce 30H mają być ustawione i jest nazywana maską.
Praktyczny przykład zastosowania instrukcji anl ... jest podany w dalszej części opracowania, przy omawianiu ustawiania trybów pracy liczników.
iloczyn logiczny
Przykładem instrukcji realizującej iloczyn logiczny jest anl ad, #n - instrukcja wykonująca iloczyn logiczny, bit po bicie, podanych argumentów i wpisująca wynik do komórki podanej jako pierwszy argument - w powyższym przykładzie do komórki o adresie ad.
Iloczyn logiczny dwóch bitów wynosi 0, jeśli przynajmniej jeden z nich jest w stanie 0; jeśli natomiast oba bity mają stan 1 - ich iloczyn też wynosi 1.
Wniosek: wykonanie iloczynu logicznego dowolnego bitu przez wartość 0 powoduje jego wyzerowanie, a przez wartość 1 - nie zmienia wartości bitu.
Przykład:
Poniższa instrukcja wykonuje iloczyn logiczny zawartości komórki o adresie 30H i liczby EBH:
anl 30H, #0EBH
A oto efekt jej wykonania (x oznacza dowolny stan bitu):
stan komórki 30H przed wykonaniem instrukcji xxxx xxxx
drugi argument instrukcji anl (maska) 1110 1011
stan komórki 30H po wykonaniu instrukcji xxx0 x0xx
Instrukcja anl może być użyta do zerowania pojedynczych bitów w komórkach, które nie są adresowalne bitowo lub do zerowania kilku bitów jednocześnie w dowolnej komórce pamięci. Praktyczny przykład zastosowania tej instrukcji jest podany w dalszej części opracowania przy omawianiu ustawiania trybów pracy liczników.
suma modulo 2 (EXCLUSIVE-OR, różnica symetryczna)
Przykładem instrukcji realizującej sumę modulo 2 jest xrl ad, #n - instrukcja wykonująca sumę mod 2, bit po bicie, podanych argumentów i wpisująca wynik do komórki podanej jako pierwszy argument - w powyższym przykładzie do komórki o adresie ad.
Suma mod 2 dwóch bitów wynosi 1, jeśli oba bity mają różne stany; jeśli natomiast oba bity mają ten sam stan - suma mod 2 wynosi 0.
Wniosek: wykonanie sumy mod 2 dowolnego bitu i wartości 1 powoduje zmianę stanu bitu, natomiast wykonanie sumy mod 2 dowolnego bitu i wartości 0 - nie zmienia wartości bitu.
Przykład:
Poniższa instrukcja realizuje sumę mod 2 zawartości komórki o adresie 30H i liczby 43H:
xrl 30H, #43H
A oto efekt jej wykonania (
oznacza dowolny stan bitu, a
- stan przeciwny do
):
stan komórki 30H przed wykonaniem instrukcji
drugi argument instrukcji xrl (maska)
stan komórki 30H po wykonaniu instrukcji
Instrukcja xrl może być użyta do negacji stanu pojedynczych bitów w komórkach, które nie są adresowalne bitowo lub do negacji stanu kilku bitów jednocześnie w dowolnej komórce pamięci. Praktyczny przykład zastosowania tej instrukcji jest podany w następnym punkcie, przy obliczaniu liczby przeciwnej.
kopiowanie bitów
Skopiowanie zawartości 8-bitowej komórki z wewnętrznej pamięci mikrokontrolera do innej komórki z tejże pamięci można zrealizować na przykład instrukcją
mov ad1, ad2
w której ad2 oznacza adres komórki źródłowej (tej, której zawartość kopiujemy), a ad1 - adres komórki docelowej (tej, do której kopiujemy). Przykładowo, za pomocą instrukcji mov 30H, P1 kopiujemy stan wejść portu P1 do komórki o adresie 30H, a za pomocą instrukcji mov 65H, 30H kopiujemy zawartość komórki o adresie 30H do komórki o adresie 65H.
Pytanie: W jaki sposób skopiować stan bitu?
Odpowiedź: Do kopiowania stanu bitu służą także instrukcje mov ... , ale musi w nich występować znacznik przeniesienia CY (Carry), który w tych instrukcjach jest oznaczony przez C. Są dwie instrukcje tego typu i mają one postać (nadmieniano już o tym w p. 14.1):
mov C, bit - skopiowanie wartości bitu o adresie bit do znacznika przeniesienia CY,
mov bit, C - skopiowanie wartości znacznika przeniesienia CY do bitu o adresie bit.
Przykładowo, w wyniku wykonania ciągu instrukcji
mov C, P1.3
mov ACC.0, C
stan linii portu P1.3 zostanie, poprzez znacznik przeniesienia CY, skopiowany do najmłodszego bitu akumulatora, a instrukcje
mov C, 7FH
mov 0H, C
spowodują skopiowanie stanu bitu 7FH (to najstarszy bit komórki 2FH - pamiętasz?) do bitu o adresie 0H (najmłodszy bit komórki 20H).
30.2. Obliczanie liczby przeciwnej - przypomnienie i uzupełnienie
Liczbą przeciwną do danej liczby x1 jest liczba x2 taka, że x2 = - x1; zachodzi więc x1 + x2 = 0.
Jeśli liczbę zapisaną w kodzie dwójkowym (binarnym) interpretujemy jako liczbę ze znakiem w kodzie U2 (kod uzupełnień do dwóch), jak to zostało opisane w p. 20, to obliczenie liczby przeciwnej do danej liczby można wykonać w dwu etapach:
zanegować każdy z bitów z osobna,
do tak otrzymanej liczby dodać 1.
Jeżeli zadana liczba jest ujemna, to liczba przeciwna jest zarazem jej wartością bezwzględną.
Przykład: Weźmy liczbę 5, binarnie 0000 0101b.
liczba zadana 5 |
0000 0101b |
liczba po zanegowania bitów |
1111 1010b |
suma obu liczb |
1111 1111b |
Suma obu powyższych liczb daje same jedynki, jeśli więc dodamy jeszcze 1, uzyskamy same zera (pomijając „jeden dalej”). Liczbą przeciwną do 5, czyli -5, będzie więc liczba powstała przez zanegowanie bitów liczby 5 przedstawionej w postaci dwójkowej, a następnie zwiększenie powstałej liczby o 1, czyli liczba 1111 1011b.
Programowa realizacja obliczenia liczby przeciwnej (zakładamy, że liczba zadana znajduje się w komórce pamięci o nazwie Liczba) jest następująca:
xrl Liczba, #0FFh ; negacja wszystkich bitów (p. 30.1c)
inc Liczba ; powiększenie o 1
Po wykonaniu tych instrukcji obliczona liczba przeciwna będzie znajdować się w tej samej komórce pamięci, w której znajdowała się wcześniej liczba zadana; u nas - komórka Liczba.
Negacji wszystkich bitów możemy dokonać też instrukcją cpl A (negacja wszystkich bitów akumulatora). Liczbę przeciwną możemy w takim przypadku obliczyć następująco:
mov A, Liczba ; przeniesienie liczby do akumulatora
cpl A ; negacja wszystkich bitów akumulatora
inc A ; powiększenie zawartości akumulatora o 1
mov Liczba, A ; przeniesienie liczby przeciwnej do komórki Liczba
Takie rozwiązanie ma jednak tę wadę, ze wymaga wykonania dwóch dodatkowych instrukcji kopiowania: najpierw do akumulatora, a by w nim dokonać negacji, a następnie z powrotem do komórki Liczba.
Uwaga: Zastosowana instrukcja cpl A dotyczy akumulatora i realizuje negację wszystkich bitów akumulatora. Wcześniej była omawiana instrukcja cpl bit, negująca stan pojedynczego bitu o adresie bit. Jeśli więc użyjemy instrukcji cpl z argumentem, np. cpl 20H, to ta instrukcja dokona wyzerowania bitu o adresie 20H, a nie komórki 20H.
30.3. Względne adresy skoków w instrukcjach - przypomnienie i uzupełnienie
W wielu instrukcjach argumentem jest względny adres skoku d, np. w poznanych już instrukcjach:
jnb bit, d
jb bit, d
sjmp d
djnz ad, d
Względny adres skoku d występuje także w instrukcjach:
jnc d - skok następuje, jeśli znacznik przeniesienia CY nie jest ustawiony,
jc d - skok następuje, jeśli znacznik przeniesienia CY jest ustawiony.
Działanie obu powyższych instrukcji jest podobne do działania instrukcji jnb bit, d oraz jb bit, d, z tą różnicą, że testowany jest nie stan bitu o zadanym adresie bit, ale znacznik przeniesienia CY. Znacznik ten jest ustawiany lub zerowany w wyniku wykonania niektórych instrukcji - przykłady zostaną podane poniżej.
We wszystkich powyższych instrukcjach następuje w przypadku spełnienia warunku (w instrukcji sjmp d - bezwarunkowo) skok o d pozycji (komórek) w pamięci programu, przy czym liczba d jest traktowana jako liczba ze znakiem. Zakres skoku to maksymalnie 127 komórek do przodu lub 128 komórek do tyłu (gdy d jest liczba ujemną). Skok następuje od miejsca, z którego byłaby pobrana kolejna instrukcja, gdyby nie było skoku. Względny adres skoku został omówiony na konkretnym przykładzie w p. 21.4.4.
Aby zwolnić programistę od żmudnego obliczania o ile komórek należy skoczyć, wprowadzono ułatwienie polegające na tym, że programista zamiast względnego adresu skoku wpisuje etykietę instrukcji, do której należy wykonać skok, np.:
jnc Zwieksz
.........
Zwieksz:
........
Obliczenia o ile komórek należy skoczyć i w którym kierunku, czyli wyliczenia wartości d, dokonuje komputer podczas asemblacji, czyli przekształcania napisanego przez programistę programu źródłowego na kod binarny.
Uwaga1:
Nazwy instrukcji w pliku źródłowym mogą być pisane zarówno dużymi, jak i małymi literami.
Uwaga2:
Od omówionych wcześniej instrukcji (jnb bit, d; jnb bit, d; sjmp d; itp.), w których występuje względny adres skoku d, należy odróżnić instrukcję skoku bezwzględnego ljmp adr16, której argumentem (adr16) jest podany bezpośrednio 16-bitowy adres w pamięci programu, do którego należy wykonać skok. Ten adres jest wyliczany podczas asemblacji, natomiast programista wpisuje tu etykietę (podobnie, jak w przypadku instrukcji z względnym adresem skoku) , np.
A1:
.........
ljmp A1
.........
Uwaga3:
Inne kryterium podziału rozróżnia instrukcje:
skoków bezwarunkowych - skok wykonywany jest bezwarunkowo, np. sjmp d; ljmp ad16,
skoków warunkowych - skok jest wykonywany tylko wtedy, jeśli spełniony jest odpowiedni warunek, np. jnb bit, d; jc d, djnz ad, d; itd.
30.4. Porównywanie dwu wielkości
W programach często występuje konieczność porównywania ze sobą dwu wielkości. Poniżej omówiono realizację porównania dwu wielkości za pomocą instrukcji cjne oraz instrukcji odejmowania subb.
instrukcja cjne wielkosc1, wielkosc2, d
Instrukcja ta występuje w kilku odmianach (jak i wiele innych instrukcji) i porównuje ze sobą dwie wielkości, po czym realizuje skok względny, jeśli te wielkości różnią się od siebie (compare and jump if not equal - porównaj i skocz, jeśli nie są równe). Symbol d oznacza względny adres skoku (p. 30.3). Dodatkowo, instrukcja ta porównuje obie wielkości traktując je jako liczby bez znaku i odpowiednio ustawia lub zeruje znacznik przeniesienia CY:
jeśli wielkość pierwsza jest większa lub równa drugiej, znacznik CY jest zerowany,
jeśli wielkość pierwsza jest mniejsza od drugiej, znacznik CY jest ustawiany (podobnie jak np. przy wykonywaniu odejmowania, co będzie omówione w następnym podpunkcie).
Uwaga: Jeśli uważnie przeczytałeś p. 26 „Kody instrukcji i argumenty instrukcji”, to powinieneś teraz mieć słuszną wątpliwość: z zapisu cjne wielkosc1, wielkosc2, d wynikałoby, że instrukcja cjne ... ma 3 argumenty, a tam było napisane, że instrukcja może być maksymalnie 3-bajtowa - kod instrukcji i 2 argumenty. Otóż wielkosc1 występująca w instrukcji cjne ... musi być zawarta w akumulatorze lub jednym z rejestrów R0 ÷ R7 (ewentualnie być wskazywana przez jeden z rejestrów R0 ÷ R7 - instrukcja cjne @Ri, #n, d) i miejsce jej występowania jest już zawarte w kodzie instrukcji, np. cjne A, ad, d (kod B5H) lub cjne R7, #n, d (kod BFH), zmniejszając liczbę argumentów instrukcji w pamięci programu do dwóch. Natomiast nie może istnieć instrukcja postaci cjne ad1, ad2, d, gdyż wymagałaby ona umieszczenia w pamięci programu aż 3 argumentów oprócz kodu instrukcji.
Oto przykład zastosowania instrukcji cjne:
WARTOSC equ 30H ; nadanie nazwy WARTOSC stałej wynoszącej 30H
..............
cjne A, # WARTOSC, TestZnaku
Rowne:
................ ; A = WARTOSC
TestZnaku:
jnc Wiekszy
Mniejszy:
............. ; A < WARTOSC
Wiekszy:
........... ; A > WARTOSC
Przedstawiony fragment programu porównuje zawartość akumulatora z liczbą zadaną w stałej WARTOSC i jeśli te wielkości są różne, testuje znacznik CY. W zależności od tego, która z porównywanych wielkości jest większa następuje przejście do różnych miejsc w programie i wykonanie różnych instrukcji:
jeśli oba argumenty są równe, wykonywane są instrukcje począwszy od etykiety Rowne,
jeśli zawartość akumulatora jest mniejsza od liczby zadanej w stałej WARTOSC, wykonywane są instrukcje począwszy od etykiety Mniejszy,
jeśli zawartość akumulatora jest większa od liczby zadanej w stałej WARTOSC, wykonywane są instrukcje począwszy od etykiety Wiekszy.
Pytanie: W jaki sposób zrealizować skok w określone miejsce w programie przy warunku „mniejszy lub równy” albo „większy lub równy”?
Odpowiedź: Można wykorzystać przedstawiony wyżej fragment programu i w przypadku równości obu argumentów wykonać skok bezwarunkowy w określone miejsce, co jest pokazane w poniższych przykładach:
jeśli chcemy wyróżnić nierówność A ≤ WARTOSC, należy w przypadku równości obu argumentów wykonać skok do etykiety Mniejszy - przykład z lewej strony poniżej,
jeśli chcemy wyróżnić nierówność A ≥ WARTOSC, należy w przypadku równości obu argumentów wykonać skok do etykiety Wiekszy - przykład z prawej strony poniżej.
WARTOSC equ 30H
..............
cjne A, # WARTOSC, TestZnaku
Rowne:
sjmp Mniejszy
TestZnaku:
jnc Wiekszy
Mniejszy:
............. ; A ≤ WARTOSC
Wiekszy:
........... ; A > WARTOSC
WARTOSC equ 30H
..............
cjne A, # WARTOSC, TestZnaku
Rowne:
sjmp Wiekszy
TestZnaku:
jnc Wiekszy
Mniejszy:
............. ; A <WARTOSC
Wiekszy:
........... ; A ≥ WARTOSC
W przypadku sprawdzania, czy pierwszy argument jest większy lub równy drugiemu, można też to zrobić krócej, jak w poniższym przykładzie:
WARTOSC equ 30H
..............
cjne A, # WARTOSC, TestZnaku
TestZnaku:
jnc Wiekszy
Mniejszy:
............. ; A < WARTOSC
Wiekszy:
........... ; A ≥ WARTOSC
instrukcja subb A, arg
Instrukcja odejmowania odejmuje argument od zawartości akumulatora i dodatkowo odejmuje 1, jeśli znacznik CY jest ustawiony, po czym zapisuje wynik do akumulatora. Poprzednia zawartość akumulatora zostaje więc utracona. Odejmowanie można zapisać tak:
A A - arg - CY.
Jeśli znacznik CY ma nie brać udziału w odejmowaniu, należy go wyzerować przed wykonaniem instrukcji odejmowania.
Po wykonaniu odejmowania znacznik CY zostaje:
wyzerowany, jeśli otrzymana różnica jest dodatnia lub równa zero,
ustawiony, jeśli otrzymana różnica jest ujemna.
W poniższym przypadku zostaną wykonane instrukcje począwszy od etykiety Wiekszy, jeśli zawartość akumulatora jest większa lub równa liczbie zadanej w stałej WARTOSC; w przeciwnym wypadku zostaną wykonane instrukcje począwszy od etykiety Mniejszy.
WARTOSC equ 30H
..............
clr C
subb A, # WARTOSC
jnc Wiekszy
Mniejszy:
............. ; A < WARTOSC
Wiekszy:
........... ; A ≥ WARTOSC
Symboliczne nazwy komórek i bitów
W programie źródłowym przes_a.s03 podawaliśmy bezpośrednio nazwy linii portu oraz adresy wykorzystywanych komórek (patrz kod źródłowy programu w p.16). Przykładowo, wejście W1 (rys.9.1) testowaliśmy instrukcją jnb P3.4, A2, a do realizacji opóźnienia korzystaliśmy m. in. z komórki 30h, np. w instrukcji djnz 30h, B1. Takie bezpośrednie stosowanie adresów może być bardzo uciążliwe, zwłaszcza w dłuższych programach, gdyż wymaga od programisty ciągłego pamiętania o funkcjach, jakie pełnią poszczególne bity i komórki pamięci. Dużym ułatwieniem może być zastosowanie symbolicznych nazw komórek i bitów, które od razu będą się nam kojarzyć z funkcją pełnioną przez dany bit lub komórkę. W takim przypadku na początku programu jednokrotnie przypisuje się nazwy poszczególnym bitom i komórkom pamięci, a w dalszej części programu używamy już tylko wprowadzonych nazw. Wpisanie właściwych wartości zamiast nazw symbolicznych następuje automatycznie potem, podczas asemblacji programu źródłowego.
Poniżej podano kod źródłowy programu przes_b.s03. Program ten jest dokładnie taki sam, jak program przes_a.s03, z tą różnicą, że zastosowano w nim symboliczne nazwy komórek i linii portów. Przypisanie nazwy do adresu dokonuje się przy użyciu słowa equ, np. w wierszu W1 equ P3.4 dokonano przypisania nazwy W1 linii portu P3.4.
*****************************************************************
; Program przes_b.s03
; autor: dr inz. Zbigniew Waradzyn KANiUP AGH Krakow
;*****************************************************************
; W zaleznosci od stanu wejsc P3.4 i P3.5 nastepuje:
; - stabilne swiecenie dwu diod,
; - swiecenie jednej lub dwu diod z przesuwem w prawo,
; - swiecenie jednej lub dwu diod z przesuwem w lewo.
;-----------------------------------------------------------------
; Nadanie nazw wejsciom, wyjsciom i zmiennym
W1 equ P3.4
W2 equ P3.5
D1 equ P1.1
D2 equ P1.2
D3 equ P1.3
D4 equ P1.4
D5 equ P1.5
D6 equ P1.6
Licznik1 equ 30h
Licznik2 equ 31h
Licznik3 equ 32h
;*****************************************************************
; Poczatek programu od adresu 0000h
;*****************************************************************
; Swieca diody D1 i D4, pozostale zgaszone
clr D1 ; zapalenie diody D1
clr D4 ; zapalenie diody D4
setb D2 ; zgaszenie diody D2
setb D3 ; zgaszenie diody D3
setb D5 ; zgaszenie diody D5
setb D6 ; zgaszenie diody D6
setb P1.0
setb P1.7
;-----------------------------------------------------------------
;*****************************************************************
; POCZATEK PETLI GLOWNEJ PROGRAMU
; Sprawdzanie, czy ma byc przesuw
A1:
jnb W1, A2
;-----------------------------------------------------------------
; Nie ma przesuwu - swieca diody 1 i 4, pozostale zgaszone
A3:
mov P1, #0EDh ; zapalenie i zgaszenie diod jedna instrukcja
sjmp A1
;*****************************************************************
; Jest przesuw - testowanie kierunku przesuwu
A2:
jb W2, A4
;-----------------------------------------------------------------
; Przesuw w prawo
A5:
mov A, P1
rr A
mov P1, A
sjmp A6
;-----------------------------------------------------------------
;-----------------------------------------------------------------
; Przesuw w lewo
A4:
mov A, P1
rl A
mov P1, A
;*****************************************************************
; Blok opozniajacy - ok. 0.5 sek.
;-----------------------------------------------------------------
; Wpis wartosci poczatkowych do komorek pomocn.(liczn. opozn.)
A6:
mov Licznik1, #255 ; licznik opozn. 1
mov Licznik2, #255 ; licznik opozn. 2
mov Licznik3, #4 ; licznik opozn. 3
; Pierwsza petla opozniajaca (ok. 500 mikrosekund)
B1:
djnz Licznik1, B1
; Wejscie w to miejsce w programie co ok. 500 mikrosekund
mov Licznik1, #255 ; ponowne wpisanie wartosci poczatk.
; do licznika opozn.1
; Druga petla opozniajaca
djnz Licznik2, B1
; Wejscie w to miejsce w programie co ok. 0.125 sekundy
mov Licznik2, #255 ; ponowne wpisanie wartosci poczatk.
; do licznika opozn.2
; Trzecia petla opozniajaca
djnz Licznik3, B1
; Wejscie w to miejsce w programie co ok. 0.5 sekundy - koniec
; opoznienia
;-----------------------------------------------------------------
sjmp A1
; Koniec programu
end ; ta instrukcja ZAWSZE konieczna na koncu programu
Uwaga 1:
Przypisywanie nazw odbywa się dokładnie w taki sam sposób, niezależnie od tego, czy nazwa ma zastępować adres bitu, adres komórki, czy też stałą (przykład użycia stałej został podany już w p.30.4). Podczas asemblacji w miejsce nazwy zostanie użyta przypisana jej wielkość, zaś sposób interpretacji będzie zależał od kontekstu. Przykłady z powyższego programu:
nazwy D1 użyto w instrukcji clr D1, będzie więc ona potraktowana jako adres bitu,
nazwy Licznik1 użyto w instrukcji mov Licznik1, #255, będzie więc ona potraktowana jako adres komórki.
Uwaga 2:
Otrzymany plik wynikowy przes_b.hex jest dokładnie taki sam, jak plik przes_a.hex, co można łatwo sprawdzić porównując zawartość obu plików (warto także porównać zawartość pliku przes_b.lst z zawartością pliku przes_a.lst). Sposób uzyskania pliku w postaci *.hex został opisany w p. 21).
Uwaga 3:
Przypisania nazw symbolicznych komórkom nie należy podawać w algorytmach w postaci oddzielnych bloczków, jak np.
W algorytmach można posługiwać się zarówno nazwami jak i adresami (porównaj rys. 11.1). Ważne jest użycie takiego zapisu, aby w sposób zrozumiały dla czytelnika pokazywał, jaka operacja jest wykonywana.
32. Układ czasowo - licznikowy
Mikrokontroler 8051 zawiera dwa 16-bitowe liczniki. Jeden z nich oznaczony jest przez T0, a drugi przez oraz T1. Aktualny stan licznika T0 jest przechowywany w rejestrach TH0 (część bardziej znacząca, czyli starszy bajt, czyli starsze osiem bitów) oraz TL0 (część mniej znacząca). Analogicznie, aktualny stan licznika T1 jest przechowywany w rejestrach TH1 i TL1. Wszystkie wymienione rejestry znajdują się w obszarze SFR (rys. 17.2), a dla przykładu można podać, że adres rejestru TL0 to 8AH.
Liczniki te mogą pracować jako liczniki zliczające impulsy zewnętrzne (ang. counter) lub impulsy z wewnętrznego zegara (ang. timer). Liczniki te liczą zawsze „w przód”, czyli impuls wejściowy zwiększa stan licznika.
Licznik T0 może pracować w jednym z trybów 0, 1, 2 lub 3, a licznik T1 - w jednym z trybów 0, 1 lub 2, przy czym w trybach 0, 1 i 2 oba liczniki działają identycznie oraz każdy z nich działa niezależnie od drugiego. Poniżej podane są podstawowe informacje o pracy liczników w trybach 0 i 1.
32.1. Praca liczników w trybie 0 i 1
Schemat blokowy liczników T0 i T1 w trybie 0 i 1 przedstawia rys. 33.1 a). Schemat jest taki sam dla każdego z liczników z tym, że każdemu z nich odpowiadają oddzielne komórki pamięci: np. symbol TLi na schemacie oznacza rejestr TL0 w przypadku licznika T0 lub rejestr TL1 w przypadku licznika T1, zaś symbol TRi - bit TR0 w przypadku licznika T0 lub bit TR1 w przypadku licznika T1.
W zależności od stanu bitu C/T\ (przełącznik programowy) licznik może:
zliczać impulsy zewnętrzne (C/T\ = 1) - impulsy te muszą być podawane na wejście T0 mikrokontrolera w przypadku licznika T0 lub na wejście T1 w przypadku licznika T1. (Wejścia T0 i T1 mikrokontrolera to odpowiednio wyprowadzenia 14 (P3.4) i 15 (P3.5) na jego obudowie - rys. 8.2 b),
liczyć czas (C/T\ = 0) - przez zliczanie impulsów wewnętrznych o częstotliwości fXTAL/12; przy zegarze 12 MHz daje to częstotliwość zliczanych impulsów 1 MHz, więc czas trwania jednego impulsu podawanego na wejście licznika wynosi 1 μs.
Przy założeniu, że GATE = 0, licznik uruchamia się przez ustawienie bitu TR0 (licznik T0) lub TR1 - licznik T1.
Licznik w trybach 0 i 1 zlicza „w przód” (do „góry”) do osiągnięcia wartości maksymalnej, a następnie po kolejnym impulsie przyjmuje stan 0 (przepełnia się) i zlicza dalej „w kółko”. W wyniku przepełnienia następuje ustawienie specjalnego bitu, tzw. znacznika przepełnienia -
Rys. 32.1. Schematy liczników w poszczególnych trybach pracy [2]
TF0 lub TF1 - te bity są w rejestrze TCON (patrz poniżej). Wartość maksymalna osiągana przez licznik zależy od trybu pracy:
- tryb 0 - licznik „zachowuje się” jak licznik 13-bitowy (złożony z ośmiu bitów rejestru TH0 (TH1) i bitów 3 - 7 rejestru TL0 (TL1) - przepełnia się po zliczeniu 8192 impulsów),
- tryb 1 - licznik „zachowuje się” jak licznik 16-bitowy (zlicza od 0 do 65535).
Jeśli więc nie będziemy do licznika nic wpisywać* to licznik w trybie 0 przepełnia się po każdorazowym zliczeniu 8192 impulsów, zaś licznik w trybie 1 przepełnia się po każdorazowym zliczeniu 65536 impulsów.
* Uwaga: Możliwa jest modyfikacja stanu licznika (nawet podczas jego pracy) - czyli możemy dokonywać zapisu do rejestrów TL0, TH0, TL1 i TH1.
Pytanie.: Jak sterować licznikiem ? (gdzie są bity np. C/T, TR0 i jak ustawić tryb pracy licznika?)
Odp.: Do obsługi liczników przeznaczone są specjalne w rejestry TMOD i TCON, znajdujące się w obszarze SFR (rys. 17.2, 19.1):
|
|
|
T1 |
|
|
|
T0 |
|
|
TMOD |
GATE |
C/T |
M1 |
M0 |
GATE |
C/T |
M1 |
M0 |
|
89H |
MSB |
|
|
|
|
|
|
LSB |
|
|
|
|
|
|
|
|
|
|
|
TCON |
TF1 |
TR1 |
TF0 |
TR0 |
|
|
|
|
|
88H |
MSB |
|
|
|
|
|
|
LSB |
|
Cztery młodsze bity rejestru TMOD dotyczą licznika T0, a cztery starsze bity - licznika T1. Cztery młodsze bity rejestru TCON dotyczą przerwań, więc nie zostały tu pokazane.
Tryb pracy licznika ustala się przy użyciu bitów M1 i M0:
tryb 0 - M1 = 0, M0 = 0,
tryb 1 - M1 = 0, M0 = 1,
Pytanie.: Czy rejestry związane z licznikami są adresowane bitowo?
Odp.: Bitowo adresowany jest tylko rejestr TCON - ma on adres 88H. Natomiast rejestry TMOD, TL0, TL1, TH0 i TH1 są adresowane tylko bajtowo (końcówka ich adresu jest różna od 0 lub 8 - porównaj rys.17.2).
Pytanie: Chcemy wykorzystać licznik T1 do generacji opóźnienia o czasie trwania ok. 8 ms. Co należy zrobić, aby to uzyskać?
Odp.: a) Warunki:
- tryb 0 (M1 = 0, M0 = 0), wtedy licznik pracuje jak 13-bitowy, więc przepełnienie co 8192 impulsy (przy f XTAL = 12 MHz przepełnienie co ok. 8 ms),
- zliczanie impulsów zegarowych (C/T\ = 0),
- GATE = 0,
- TR1 = 1 - załączenie licznika.
po załączeniu napięcia zasilającego lub zresetowaniu mikrokontrolera wszystkie bity w rejestrach TCON i TMOD (oraz większości innych) są wyzerowane,
wystarczy więc tylko ustawić bit TR1, np. instrukcją setb TR1,
co ok. 8 ms zostanie ustawiony znacznik przepełnienia TF1, co będzie informacją o upływie kolejnych 8 ms (znacznik ten należy następnie programowo wyzerować).
Pytanie.: Co by było, gdyby wybrano tryb 1 pracy licznika, zamiast trybu 0?
Odp.: Licznik przepełniałby się co 65536 impulsów, czyli co ok. 65 ms, zamiast co ok. 8 ms.
Pytanie.: Co należałoby zrobić, aby licznik T0 zliczał impulsy zewnętrzne?
Odp.: Impulsy należy podać na wejście T0 (P3.4) mikrokontrolera oraz dodatkowo:
ustawić bit C/T\ dla licznika T0:
nie można tego zrobić instrukcją setb C/T\, bo rejestr TMOD nie jest adresowany bitowo - adres 89H nie jest podzielny przez 8,
można wykorzystać instrukcję mov TMOD, #0000 0100b - wtedy ten bit zostanie ustawiony, ale wszystkie pozostałe bity rejestru TMOD zostaną wyzerowane.
Pytanie.: Jak ustawić bit C/T\, bez zmieniania stanu pozostałych bitów rejestru TMOD?
Odp.: Można to zrobić instrukcją
orl TMOD, #0000 0100b w pozycji bitu, który ma być ustawiony, wpisuje się 1
Instrukcja orl wykonuje sumę logiczną (bit po bicie) podanych argumentów i wynik wpisuje do komórki podanej jako pierwszy argument - w powyższym przykładzie do rejestru TMOD (p.30.1a). Jeśli więc choć jeden z porównywanych bitów ma stan 1, to wynik wynosi 1 (x oznacza dowolny stan, poza tym, jeśli przed wykonaniem instrukcji i po jej wykonaniu w TMOD jest x, znaczy to, że stan tego bitu się nie zmienił):
stan TMOD przed wykonaniem instrukcji xxxxxxxx
drugi argument instrukcji orl 00000100
stan TMOD po wykonaniu instrukcji xxxxx1xx
Pytanie.: Jak wyzerować bit C/T\ , bez zmieniania stanu pozostałych bitów rejestru TMOD?
Odp.: Można to zrobić instrukcją
anl TMOD, #1111 1011b w pozycji bitu, który ma być wyzerowany, wpisuje się 0
Instrukcja anl wykonuje iloczyn logiczny (bit po bicie) podanych argumentów i wynik wpisuje do komórki podanej jako pierwszy argument - w powyższym przykładzie do rejestru TMOD (p.30.1b). Jeśli więc choć jeden z porównywanych bitów ma stan 0, to wynik wynosi 0.
stan TMOD przed wykonaniem instrukcji xxxxxxxx
drugi argument instrukcji anl 11111011
stan TMOD po wykonaniu instrukcji xxxxx0xx
Pytanie.: Jak skonfigurować licznik T1 do zliczania impulsów wewnętrznych w trybie 0 bez zmiany ustawień licznika T0?
Odp.: Można to zrobić instrukcją
anl TMOD, # 00001111b
lub równoważna jej instrukcją
anl TMOD, #0FH
Działanie tej instrukcji jest następujące:
stan TMOD przed wykonaniem instrukcji xxxxxxxx
drugi argument instrukcji anl 00001111
stan TMOD po wykonaniu instrukcji 0000xxxx
W wyniku wykonania powyższej instrukcji zostaną wyzerowane cztery bardziej znaczące bity rejestru TMOD dotyczące licznika T1 (co odpowiada naszym potrzebom - sprawdź!), zaś cztery mniej znaczące bity tegoż rejestru, dotyczące licznika T0, pozostaną bez zmian.
32.2. Algorytm opóźnienia zrealizowanego przy użyciu licznika
Pytanie.: Jak wykorzystać licznik T1 do generacji zadanego opóźnienia (zakładamy, że ma on przepełniać się co ok. 8 ms)?
Odp.: Wykorzystamy znacznik przepełnienia licznika T1, który nazywa się TF1 (rejestr TCON). W tym celu należy wykonać następujące kroki:
skonfigurować licznik do zliczania czasu - funkcja czasomierza (timera), z przepełnieniami co ok. 8 ms,
zadać wielkość opóźnienia, np. jako krotność czasu 8 ms,
uruchomić licznik,
czekać, aż TF1 zostanie ustawiony,
zarejestrować tę sytuację jako upływ kolejnego odcinka czasu i wyzerować TF1,
sprawdzić, czy minął zadany czas opóźnienia:
jeśli TAK, uznać opóźnienie za zrealizowane i przejść do dalszego ciągu programu,
jeśli NIE, czekać na następne ustawienie bitu TF1 - powrót do p. d).
Algorytm fragmentu programu realizującego opóźnienie przedstawiono na poniższym rysunku, przy czym założono, że:
przepełnienie licznika T1 (ustawienie znacznika TF1) następuje co ok. 8 ms,
uzyskane opóźnienie będzie wielokrotnością 8 ms, czyli
opóźnienie = n * 8 ms.
Ta liczba n będzie na początku każdego opóźnienia wpisywana do rejestru B.
licznik został odpowiednio skonfigurowany i uruchomiony wcześniej.
Poniżej przedstawiono ciąg instrukcji realizujących opóźnienie ok. 480 ms na podstawie powyższego algorytmu - przyjęto n = 60 (480 ms = 60 * 8 ms).
mov B, #60
Opoznienie:
jnb TF1, Opoznienie
clr TF1
djnz B, Opoznienie
Bardziej eleganckim rozwiązaniem będzie zapisanie naszej wartości n jako stałej (dobrym obyczajem jest pisanie stałych wielkimi literami), np.
MNOZNIK_OPOZN equ 60
...................
mov B, # MNOZNIK_OPOZN
Opoznienie:
jnb TF1, Opoznienie
clr TF1
djnz B, Opoznienie
Aby uzyskać lepszą dokładność przy odliczaniu pierwszego odcinka czasu 8 ms, można przed wpisem mnożnika opóźnienia do rejestru B wyzerować licznik T1 oraz znacznik TF1 instrukcjami:
mov TL1, #0
mov TH1, #0
clr TF1
Generację opóźnienia przy wykorzystaniu licznika T1 zademonstrowano w programie przes_c.s03. Program ten realizuje świecenie diod z przesuwem i efekt jego działania jest praktycznie taki sam, jak efekt działania przedstawionych wcześniej programów przes_a.s03 i przes_b.s03 - inna może być jedynie częstotliwość „przesuwu” diod. Jednakże kod wynikowy tego programu jest inny, gdyż zastosowano w nim zupełnie inną metodę realizacji opóźnienia (zawartość pliku przes_c.hex różni się od zawartości pliku przes_a.hex, czy przes_b.hex).
;****************************************************************************
; Program przes_c.s03
; Realizacja opoznienia przy wykorzystaniu timera i testowaniu jego znacznika
; autor: dr inz. Zbigniew Waradzyn KANiUP AGH Krakow
;****************************************************************************
; W zaleznosci od stanu wejsc P3.4 i P3.5 nastepuje:
; - stabilne swiecenie dwu diod,
; - swiecenie jednej lub dwu diod z przesuwem w prawo,
; - swiecenie jednej lub dwu diod z przesuwem w lewo.
;****************************************************************************
; Wprowadzenie stalej
MNOZNIK_OPOZN equ 60
;----------------------------------------------------------------------------
; Nadanie nazw zmiennym
W1 equ P3.4
W2 equ P3.5
D1 equ P1.1
D2 equ P1.2
D3 equ P1.3
D4 equ P1.4
D5 equ P1.5
D6 equ P1.6
;----------------------------------------------------------------------------
;****************************************************************************
; Poczatek programu od adresu 0000h
;****************************************************************************
; Uruchomienie licznika 1 w trybie 0, przepelnienie co ok. 8 ms
anl TMOD, #0FH ; *) patrz następna strona
setb TR1
;----------------------------------------------------------------------------
; Swieca diody D1 i D4, pozostale zgaszone
clr D1 ; zapalenie diody D1
clr D4 ; zapalenie diody D4
setb D2 ; zgaszenie diody D2
setb D3 ; zgaszenie diody D3
setb D5 ; zgaszenie diody D5
setb D6 ; zgaszenie diody D6
setb P1.0
setb P1.7
;****************************************************************************
; POCZATEK PETLI GLOWNEJ PROGRAMU
; Sprawdzanie, czy ma byc przesuw
A1:
jnb W1, A2
;----------------------------------------------------------------------------
; Nie ma przesuwu - swieca diody 1 i 4, pozostale zgaszone
A3:
mov P1, #0EDh ; zapalenie i zgaszenie diod jedna instrukcja
sjmp A1
;****************************************************************************
; Jest przesuw - testowanie kierunku przesuwu
A2:
jb W2, A4
;----------------------------------------------------------------------------
; Przesuw w prawo
A5:
mov A, P1
rr A
mov P1, A
sjmp A6
;----------------------------------------------------------------------------
; Przesuw w lewo
A4:
mov A, P1
rl A
mov P1, A
;****************************************************************************
; Blok opozniajacy - ok. 0.5 sek.
;----------------------------------------------------------------------------
A6:
mov B, #MNOZNIK_OPOZN
Opoznienie:
jnb TF1, Opoznienie
clr TF1
djnz B, Opoznienie
;----------------------------------------------------------------------------
; Wejscie w to miejsce w programie co ok. 0.5 sekundy - koniec opoznienia
;----------------------------------------------------------------------------
sjmp A1
; Koniec programu
end ; ta instrukcja ZAWSZE konieczna na koncu programu
;----------------------------------------------------------------------------
*) Instrukcja anl TMOD, #0FH, zerująca cztery bardziej znaczące bity rejestru TMOD (w celu uzyskania wymaganego skonfigurowania licznika T1 te bity muszą być wyzerowane) bez naruszania stanu czterech mniej znaczących bitów tego rejestru (dotyczą licznika T0, a w nim tutaj nic nie zmieniamy) nie jest tu konieczna, gdyż po załączeniu mikrokontrolera wszystkie bity rejestru TMOD (jak i większości innych rejestrów) są wyzerowane. Jednak można ją tu zamieścić dla porządku, gdyż w bardziej złożonym programie może ona okazać się konieczna, ponieważ np. licznik T1 może być we wcześniejszym fragmencie programu skonfigurowany w inny sposób, a licznik T0 też może być równocześnie wykorzystywany do innych celów.
33. Procedury użytkownika
Często bywa tak, że jakiś fragment programu powtarza się wielokrotnie lub stanowi pewną całość. Wtedy można go zapisać w postaci procedury, a następnie w odpowiednich miejscach tę procedurę wywoływać.
Poniższy rysunek przedstawia sytuację, gdy fragment E programu powtarza się trzykrotnie.
Innym sposobem realizacji tego programu jest zapisanie fragmentu E jako procedury, a następnie jej wywoływanie wymaganą ilość razy, co skraca kod źródłowy programu. Omawianą sytuację ilustruje poniższy rysunek.
W przedstawionym powyżej przypadku następuje trzykrotne wywołanie procedury znajdującej się w oddzielnym fragmencie programu E. W ogólnym przypadku każdą procedurę można wywoływać dowolną ilość razy. Do wywołania procedury wykorzystuje się instrukcję lcall Etykieta, gdzie Etykieta (może to być dowolna nazwa dopuszczana przez makroasembler) oznacza pierwszą instrukcję procedury. Procedura użytkownika musi kończyć się instrukcją ret, która oznacza, że należy wrócić do tego miejsca w programie, z którego wywołano procedurę i wykonać instrukcję znajdującą się bezpośrednio za instrukcją lcall.
W ogólnym przypadku korzystanie z procedury wygląda więc następująco:
lcall Etykieta ; wywołanie procedury
...................
Etykieta:
...................... ; zawartość procedury
......................
ret
Poniżej przedstawiono listing programu przes_d.s03, będącego modyfikacją programu przes_c.s03. W przedstawionym programie instrukcje realizujące opóźnienie przy wykorzystaniu licznika T1 tworzą procedurę, której początek wyznacza etykieta Opoznienie.
Aby w demonstrowanym przykładzie uzyskać opóźnienie n * 8 ms, należy przed wywołaniem procedury wpisać mnożnik opóźnienia n do rejestru B:
mov B, #n
lcall Opoznienie
W programie zrealizowano to następująco:
MNOZNIK_OPOZN equ 120
...................
mov B, # MNOZNIK_OPOZN
lcall Opoznienie
co daje opóźnienie ok. 1 s („przesuw” świecenia diod następuje co ok. 1 s).
Natomiast sama procedura generująca opóźnienie ma postać:
Opoznienie:
jnb TF1, Opoznienie
clr TF1
djnz B, Opoznienie
ret
czyli tworzą ją te same instrukcje, których użyto w programie przes_c.s03. Procedury zwykle umieszcza się na końcu programu, jak w programie przes_d.s03.
Po zakończeniu wykonywania procedury następuje powrót do instrukcji znajdującej się bezpośrednio za instrukcją wywołującą procedurę (lcall ...). Adres powrotu zapamiętywany jest na stosie.
Poniżej przedstawiono kod źródłowy programu w wersji przes_d.s03.
;****************************************************************************
; Program przes_d.s03
; Realizacja opoznienia przy wykorzystaniu timera i testowaniu jego znacznika
; przy czym jest to realizowane w oddzielnej procedurze
; autor: dr inz. Zbigniew Waradzyn KANiUP AGH Krakow
;****************************************************************************
; W zaleznosci od stanu wejsc P3.4 i P3.5 nastepuje:
; - stabilne swiecenie dwu diod,
; - swiecenie jednej lub dwu diod z przesuwem w prawo,
; - swiecenie jednej lub dwu diod z przesuwem w lewo.
;****************************************************************************
; Wprowadzenie stalej
MNOZNIK_OPOZN equ 120 ; opoznienie ok. 1 sekunda
;----------------------------------------------------------------------------
; Nadanie nazw zmiennym
W1 equ P3.4
W2 equ P3.5
D1 equ P1.1
D2 equ P1.2
D3 equ P1.3
D4 equ P1.4
D5 equ P1.5
D6 equ P1.6
;****************************************************************************
; Poczatek programu od adresu 0000h
;****************************************************************************
; Uruchomienie licznika 1 w trybie 0, przepelnienie co ok. 8 ms
anl TMOD, #0FH
setb TR1
;----------------------------------------------------------------------------
; Swieca diody D1 i D4, pozostale zgaszone
clr D1 ; zapalenie diody D1
clr D4 ; zapalenie diody D4
setb D2 ; zgaszenie diody D2
setb D3 ; zgaszenie diody D3
setb D5 ; zgaszenie diody D5
setb D6 ; zgaszenie diody D6
setb P1.0
setb P1.7
;****************************************************************************
; POCZATEK PETLI GLOWNEJ PROGRAMU
; Sprawdzanie, czy ma byc przesuw
A1:
jnb W1, A2
;----------------------------------------------------------------------------
; Nie ma przesuwu - swieca diody 1 i 4, pozostale zgaszone
A3:
mov P1, #0EDh ; zapalenie i zgaszenie diod jedna instrukcja
sjmp A1
;****************************************************************************
; Jest przesuw - testowanie kierunku przesuwu
A2:
jb W2, A4
;----------------------------------------------------------------------------
; Przesuw w prawo
A5:
mov A, P1
rr A
mov P1, A
sjmp A6
;----------------------------------------------------------------------------
; Przesuw w lewo
A4:
mov A, P1
rl A
mov P1, A
;****************************************************************************
; Blok opozniajacy - ok. 1 sek.
;----------------------------------------------------------------------------
A6:
mov B, #MNOZNIK_OPOZN
lcall Opoznienie
; Wejscie w to miejsce w programie co ok. 0.5 sekundy - koniec opoznienia
;----------------------------------------------------------------------------
sjmp A1
;****************************************************************************
; Procedura uzytkownika
;----------------------------------------------------------------------------
Opoznienie:
jnb TF1, Opoznienie
clr TF1
djnz B, Opoznienie
ret
;----------------------------------------------------------------------------
; Koniec programu
end ; ta instrukcja ZAWSZE konieczna na koncu programu
;----------------------------------------------------------------------------
34. Przerwania
Przerwanie polega na czasowym zawieszeniu aktualnie wykonywanego fragmentu programu i wykonaniu innego fragmentu programu, zwanego procedurą obsługi przerwania. Po jej wykonaniu następuje powrót do wykonywania przerwanego fragmentu programu.
Przerwania mogą nastąpić w wyniku zajścia pewnych ściśle określonych zdarzeń, tzw. źródeł przerwań. Zaleta przerwań polega na tym, że program nie musi w każdej pętli sprawdzać warunku, po spełnieniu którego (np. po zmianie stanu sygnału cyfrowego, naciśnięciu klawisza, upływie zadanego czasu, itp.) należy wykonać pewien ciąg instrukcji. Warunek testowany jest sprzętowo i po jego spełnieniu następuje natychmiastowy skok w to miejsce w programie, gdzie ten ciąg instrukcji jest umieszczony (procedura obsługi przerwania).
Podstawowa wersja mikrokontrolera posiada 5 źródeł przerwań (rys. 34.1):
przerwanie zewnętrzne INT0\ wywoływane przez sygnał zewnętrzny podany na wyprowadzenie INT0\ (P3.2) mikrokontrolera - porównaj rys. 8.2,
przerwanie od przepełnienia licznika T0,
przerwanie zewnętrzne INT1\ wywoływane przez sygnał zewnętrzny podany na wyprowadzenie INT1\ (P3.3) mikrokontrolera - porównaj rys. 8.2,
przerwanie od przepełnienia licznika T1,
przerwanie od portu szeregowego.
Z przerwaniami związane są 3 rejestry z obszaru rejestrów specjalnych SFR: IE, IP oraz TCON. Są one przedstawione poniżej.
|
globalne |
|
|
port szer. |
T1 |
INT1\ |
T0 |
INT0\ |
|
IE |
EA |
- |
|
ES |
ET1 |
EX1 |
ET0 |
EX0 |
|
A8H |
MSB |
|
|
|
|
|
|
LSB |
|
|
|
|
|
|
|
|
|
|
|
IP |
- |
- |
|
PS |
PT1 |
PX1 |
PT0 |
PX0 |
|
B8H |
MSB |
|
|
|
|
|
|
LSB |
|
|
|
|
|
|
|
|
|
|
|
TCON |
TF1 |
|
TF0 |
|
IE1 |
IT1 |
IE0 |
IT0 |
|
88H |
MSB |
|
|
|
|
|
|
LSB |
|
Rejestr IE zawiera bity służące do indywidualnego uaktywniania przerwań i bit EA globalnego uaktywnienia.
Rejestr IP zawiera bity służące do ustalania priorytetu przerwań (nazwy bitów jak w rejestrze IE z tym, że zamiast pierwszej litery E jest litera P),
Rejestr TCON zawiera znaczniki przerwań (TF0 i TF1 - przerwania od układu czasowo-licznikowego, IE0 i IE1 - przerwania od sygnałów zewnętrznych) oraz znaczniki IT0 i IT1 określające sposób zgłoszenia przerwań zewnętrznych (opadającym zboczem lub niskim poziomem sygnału zewnętrznego). Pozostałe dwa bity nie dotyczą systemu przerwań.
Wszystkie trzy przedstawione wyżej rejestry są adresowane bitowo.
Po załączeniu mikrokontrolera lub jego zresetowaniu (wyzerowaniu) wszystkie powyższe bity są wyzerowane, w konsekwencji - wszystkie przerwania są zamaskowane (nieaktywne).
Warunkiem uaktywnienia przerwania jest:
- ustawienie bitu odpowiadającego danemu przerwaniu (indywidualne uaktywnienia przerwania),
- ustawienie bitu globalnego uaktywnienia przerwań EA.
Przykładowo celem uaktywnienia przerwania zewnętrznego INT0\ należy ustawić bity EX0 i EA (rejestr IE) wykonując na przykład instrukcje:
setb EX0
setb EA ; pozostałe bity rejestru IE bez zmiany
lub
mov IE, #1000 0001b ; pozostałe bity rejestru IE wyzerowane
lub też jeszcze inaczej
orl IE, #1000 0001b ; pozostałe bity rejestru IE bez zmiany
Chcąc uruchomić w dalszej części programu przerwanie od licznika T1 wystarczy ustawić bit ET1 (jeśli EA jest już ustawiony). Wyzerowanie bitu EA powoduje zablokowanie (zamaskowanie) wszystkich indywidualnie uaktywnionych przerwań, zaś jego ponowne ustawienie - ich uaktywnienie.
Uwaga: Należy rozróżnić oznaczenia IE0 oraz IE.0. IE0 to bit 1 rejestru TCON, czyli TCON.1. Natomiast IE.0 to bit 0 rejestru IE, bit ten jest oznaczany przez EX0.
Przerwania INT0\ i INT1\ mogą nastąpić przy zerowym poziomie sygnału na wejściu przerwania (bit IT0 lub IT1 wyzerowany - stan domyślny) lub przy opadającym zboczu sygnału (bit IT0 lub IT1 ustawiony).
W chwili wystąpienia sytuacji powodującej przerwanie ustawiony zostaje sprzętowo znacznik przerwania (przykładowo bit IE0 - przerwanie INT0\, bit TF1 - przerwanie od T1). Po przyjęciu przerwania znacznik zostaje wyzerowany automatycznie (nie dotyczy to znaczników przerwania od portu szeregowego).
Uwaga1: Bity TF0 (licznik T0) i TF1 (licznik T1) pełnią podwójną rolę. Po pierwsze są znacznikami przepełnienia liczników odpowiednio T0 i T1 (p. 32) - są więc ustawiane po każdorazowym przepełnieniu licznika, także wtedy, gdy przerwanie od przepełnienia licznika jest zamaskowane. Dodatkowo, jeśli przerwanie od licznika jest odblokowane (aktywne) ustawienie bitu powoduje wystąpienie przerwania, czyli znaczniki te pełnią wtedy rolę znaczników przerwań.
Uwaga2: Należy wiedzieć jeszcze jedno:
jeśli przerwanie od przepełnienia licznika jest aktywne, bit TF0 (TF1) zostaje automatycznie wyzerowany po przyjęciu przerwania,
jeśli natomiast przerwanie od przepełnienia licznika nie jest aktywne, bit TF0 (TF1) nie zostaje wyzerowany i należy go wyzerować programowo (robiliśmy to w programie przec_c.s03 przy generacji opóźnienia).
Po wystąpieniu przerwania następuje skok do odpowiedniego, ściśle określonego miejsca w pamięci programu gdzie rozpoczyna się procedura obsługi przerwania, zaś po zakończeniu jej wykonywania - powrót do tego miejsca w programie, przy wykonywaniu którego nastąpiło przerwanie. Przed rozpoczęciem wykonywania procedury obsługi przerwania trzeba więc zapamiętać adres powrotu - jest on zapamiętywany na stosie. Zapamiętywanie tego adresu na stosie, a potem ponowne jego przysłanie po licznika rozkazów PC odbywa się automatycznie. Poniżej podano adresy skoku po przyjęciu przerwania (od tych właśnie adresów muszą zaczynać się procedury obsługi przerwań):
|
Przerwanie |
adres skoku |
|
INT0\ |
0003H |
|
T0 |
000BH |
|
INT1\ |
0013H |
|
T1 |
001BH |
|
port szeregowy |
0023H |
Przypomnienie: Po załączeniu lub zresetowaniu mikrokontrolera realizacja programu zaczyna się zawsze od adresu 0000H.
Priorytet przerwań:
są 2 poziomy priorytetu przerwań: niższy i wyższy,
po załączeniu lub zresetowaniu mikrokontrolera wszystkie przerwania są na niższym poziomie; przeniesienie danego przerwania na poziom wyższy dokonuje się przez ustawienie odpowiedniego bitu w rejestrze IP; przykładowo, celem nadania wyższego priorytetu przerwaniu od przepełnienia licznika T0 należy ustawić bit PT0,
przerwania będące na tym samym poziomie mają sztywno ustawiony priorytet - zgodny z kolejnością w jakiej przerwania są wymienione powyżej,
przy równoczesnym zgłoszeniu przerwań z tego samego poziomu wcześniej wykonywane jest przerwanie o wyższym sztywnym priorytecie,
przy równoczesnym zgłoszeniu przerwań z różnych poziomów priorytetu wcześniej wykonywane jest przerwanie znajdujące się na wyższym poziomie,
zgłoszenie przerwania z wyższego poziomu podczas wykonywania procedury obsługi przerwania z niższego poziomu powoduje przyjęcie tego przerwania, czyli rozpoczęcie wykonywania procedury obsługi przerwania z wyższego poziomu (wykonywanie procedury obsługi przerwania z niższego poziomu zostaje chwilowo zawieszone),
procedura obsługi przerwania z wyższego poziomu jest nieprzerywalna.
Przykład wykorzystania przerwań
Na rys. 34.2 podany jest algorytm programu przes_e.s03 realizującego świecenie diod z przesuwem oraz wykorzystującego do realizacji opóźnienia przerwania od przepełnienia licznika.
Program składa się z dwu części, każda jest przedstawiona w postaci oddzielnego algorytmu:
program główny zaczynający się od etykiety Init, działający w zamkniętej pętli,
procedura obsługi przerwania zaczynająca się od etykiety PrzerwT1, a kończąca instrukcją reti.
Po załączeniu mikrokontrolera realizacja programu rozpoczyna się od adresu 0000h. Ponieważ w programie korzystamy z procedury obsługi przerwania od przepełnienia licznika T1, zaczynającej się od adresu 001Bh, pisanie programu głównego w kolejnych komórkach począwszy od adresu 0000h, mogłoby spowodować kolizję - pod adresem 001BH mógłby znaleźć się kod lub argument jakiejś instrukcji z programu głównego, a nie kod pierwszej instrukcji procedury obsługi przerwania. Aby zapobiec takiej kolizji, umieszczono w programie następujące wiersze:
;**************************************************************************
; POCZATEK PROGRAMU
;--------------------------------------------------------------------------
org 0000h ; od adresu 0000h zaczyna sie realizacja programu
; po zalaczenie lub zresetowaniu mikrokontrolera
ljmp Init
org 001Bh ; od adresu 001Bh zaczyna sie realizacja procedury obslugi
; przerwania od przepelnienia licznika T1
ljmp PrzerwT1
;--------------------------------------------------------------------------
Init:
...................................................................................................................................................................
PrzerwT1:
...................................................................................................................................................................
end
Dyrektywa makroasemblera org adres (nie jest to instrukcja mikrokontrolera 8051, zaś o dyrektywach makroasemblera wspominano już w p.21.4) oznacza, że następujący po niej ciąg instrukcji zostanie umieszczony w pamięci programu począwszy od podanego adresu. Rysunek 34.3 przedstawia szkic rozmieszczenia powyższego programu w pamięci programu.
Po załączeniu lub wyzerowaniu mikrokontrolera następuje skok (instrukcja ljmp Init - adresy 0000h ... 0002h) do komórki o adresie 001Eh (etykieta Init), od którego zaczyna się program główny.
Po wystąpieniu przerwania od timera T1, realizowany jest program począwszy od adresu 001Bh (rys. 34.1). Instrukcja ljmp PrzerwT1 (adresy 001Bh ... 001Dh) powoduje skok do adresu wskazywanego przez etykietę PrzerwT1, oznaczonego u nas przez AdrT1, od którego rozpoczyna się procedura obsługi przerwania. Procedura ta jest realizowana, aż do napotkania instrukcji reti.
Program główny (adres początkowy 001Eh - etykieta Init) ulokowany jest bezpośrednio za instrukcją ljmp PrzerwT1, zgodnie z kolejnością instrukcji w programie.
Procedura obsługi przerwania (etykieta PrzerwT1) umieszczona jest bezpośrednio za programem głównym. Adres początkowy oznaczony u nas przez AdrT1 zależy od długości programu głównego.
Gdyby w programie korzystano jeszcze dodatkowo z przerwań od sygnału zewnętrznego podawanego na wejście INT0\, to szkielet naszego programu mógłby wyglądać tak:
;**************************************************************************
; POCZATEK PROGRAMU
;--------------------------------------------------------------------------
org 0000h ; od adresu 0000h zaczyna sie realizacja programu
; po zalaczenie lub zresetowaniu mikrokontrolera
ljmp Init
org 0003h ; od adresu 0003h zaczyna sie realizacja procedury obslugi
; przerwania od sygnalu zewnetrznego podanego na we INT0\
ljmp PrzerwINT0:
org 001Bh ; od adresu 001Bh zaczyna sie realizacja procedury obslugi
; przerwania od przepelnienia licznika T1
ljmp PrzerwT1
;--------------------------------------------------------------------------
Init:
...................................................................................................................................................................
PrzerwINT0:
...................................................................................................................................................................
PrzerwT1:
...................................................................................................................................................................
end
Poniżej przedstawiono kod źródłowy programu przes_e, którego algorytm zamieszczono na rys. 34.2.
;**************************************************************************
; Program przes_e.s03
; autor: dr inz. Zbigniew Waradzyn KANiUP AGH Krakow
;**************************************************************************
; W zaleznosci od stanu wejsc P3.4 i P3.5 nastepuje:
; - stabilne swiecenie dwu diod,
; - swiecenie jednej lub dwu diod z przesuwem w prawo,
; - swiecenie jednej lub dwu diod z przesuwem w lewo.
; Generacja opoznienia potrzebnego do uwidocznienia zmiany swiecenia diod
; realizowana jest tym programie w przerwaniu.
; Dzieki temu program glowny nie jest "zaangazowany" przy realizacji
; opoznienia i moze "zajac sie" czyms innym.
;--------------------------------------------------------------------------
; Wprowadzenie stalej
MNOZNIK_OPOZN equ 240 ; przesuw tym razem co 2 sekundy
;--------------------------------------------------------------------------
; Nadanie nazw zmiennym
W1 equ P3.4
W2 equ P3.5
D1 equ P1.1
D2 equ P1.2
D3 equ P1.3
D4 equ P1.4
D5 equ P1.5
D6 equ P1.6
;**************************************************************************
; POCZATEK PROGRAMU GLOWNEGO
;--------------------------------------------------------------------------
org 0000h ; od adresu 0000h zaczyna sie realizacja programu
; po zalaczenie lub zresetowaniu mikrokontrolera
ljmp Init
org 001Bh ; od adresu 0000h zaczyna sie realizacja procedury obslugi
; przerwania od przepelnienia licznika T1
ljmp PrzerwT1
;--------------------------------------------------------------------------
; Instrukcje inicjalizacyjne
Init:
setb TR1 ; uruchomienie licznika 1 w trybie 0, przepeln. co ok. 8 ms
setb EA ; globalne odblokowanie przerwan
mov B, #MNOZNIK_OPOZN ; dotyczy pierwszego opoznienia
;--------------------------------------------------------------------------
; Swieca diody D1 i D4, pozostale zgaszone
clr D1 ; zapalenie diody D1
clr D4 ; zapalenie diody D4
setb D2 ; zgaszenie diody D2
setb D3 ; zgaszenie diody D3
setb D5 ; zgaszenie diody D5
setb D6 ; zgaszenie diody D6
setb P1.0
setb P1.7
;**************************************************************************
; POCZATEK PETLI GLOWNEJ PROGRAMU
; Sprawdzanie, czy ma byc przesuw
A1:
jnb W1, A2
;--------------------------------------------------------------------------
; Nie ma przesuwu - swieca diody 1 i 4, pozostale zgaszone
; Przerwania od timera T1 sa niepotrzebne
A3:
clr ET1 ; zamaskowanie przerwan od przepelnienia T1
mov P1, #0EDh ; zapalenie i zgaszenie diod jedna instrukcja
sjmp A1
;--------------------------------------------------------------------------
;**************************************************************************
; Ma byc przesuw - nalezy uruchomic przerwania od przepelnienia T1
; Sprawdzanie, czy uplynal czas opoznienia, testowanie kierunku przesuwu
; oraz jego realizacja odbywaja sie w procedurze obslugi przerwania
A2:
setb ET1 ; zdjecie maski przerwan od przepelnienia T1
sjmp A1
; KONIEC PROGRAMU GLOWNEGO
;--------------------------------------------------------------------------
;**************************************************************************
; PROCEDURA OBSLUGI PRZERWANIA OD PRZEPELNIENIA LICZNIKA T1
; Procedura wykonywana co ok. 8 ms, jesli ma byc przesuw:
; - sprawdzanie, czy uplynal czas opoznienia
; - testowanie kierunku przesuwu
; - realizacja przesuwu
;--------------------------------------------------------------------------
PrzerwT1:
push ACC
djnz B, PrzerwT1End
; Ponizsze inmstrukcje wykonywane po zakonczeniu opoznienia
mov B, #MNOZNIK_OPOZN ; dotyczy nastepnego opoznienia
; Testowanie kierunku przesuwu
jb W2, A4
;--------------------------------------------------------------------------
; Przesuw w prawo
A5:
mov A, P1
rr A
mov P1, A
sjmp PrzerwT1End
;--------------------------------------------------------------------------
; Przesuw w lewo
A4:
mov A, P1
rl A
mov P1, A
;--------------------------------------------------------------------------
PrzerwT1End:
pop ACC
reti
;--------------------------------------------------------------------------
; Koniec programu
end ; ta instrukcja ZAWSZE konieczna na koncu programu
;--------------------------------------------------------------------------
Z przedstawionego programu wynika z tego, że:
po załączeniu mikrokontrolera wykonywana jest instrukcja zaczynająca się od komórki pamięci o adresie 0000H i w wyniku jej realizacji wykonywany jest skok bezwarunkowy do instrukcji o etykiecie Init,
po wystąpieniu przerwania od przepełnienia licznika T1 wykonywana jest instrukcja zaczynająca się od komórki pamięci o adresie 001BH i w wyniku jej realizacji wykonywany jest skok bezwarunkowy do instrukcji o etykiecie PrzerwT1.
W części inicjalizacyjnej programu głównego (począwszy od miejsca wskazanego etykietą Init) następuje załączenie licznika T1 w trybie 0 (licznik 13-bitowy), co powoduje, że przy zastosowaniu generatora zegarowego o częstotliwości 12 MHz licznik będzie się przepełniał co ok. 8 ms. Następnie dokonywane jest globalne uaktywnienie przerwań, wpisanie do rejestru B odpowiedniej wartości gwarantującej realizację pierwszego opóźnienia oraz wstępne zapalenie i zgaszenie odpowiednich diod.
Uwaga: Czas opóźnienia jest tutaj, tak samo jak w poprzednich przykładach, iloczynem wartości wpisywanej do rejestru B (MNOZNIK_OPOZN) i czasu 8 ms.
Proszę zwrócić uwagę, jak krótka jest główna pętla programu. W zależności od stanu styku W1, decydującego o tym, czy ma być przesuw, czy nie, realizowane jest uaktywnienie (zdjęcie maski) lub zablokowanie (zamaskowanie) przerwania od licznika T1. Program główny nie zajmuje się więc generacją opóźnienia, jak we wcześniej demonstrowanych wersjach i może „zająć się” czymś innym.
Uwaga: W przypadku wykorzystania przerwań program możliwa jest taka struktura programu, że program główny nie „robi” praktycznie nic, a całe działanie odbywa się podczas wykonywania procedur obsługi przerwań. Nie należy jednak zapominać, że także w takim przypadku program główny musi pracować w zamkniętej pętli.
Realizacja opóźnienia oraz wybór kierunku przesuwu odbywają się w procedurze obsługi przerwania od licznika T1 (etykieta PrzerwT1), która przy aktywnym przerwaniu wykonywana jest po każdym przepełnieniu licznika T1, czyli co ok. 8 ms. Procedura obsługi przerwania kończy się instrukcją reti.
Często bardzo ważne jest odłożenie na stosie zawartości akumulatora przed rozpoczęciem wykonywania procedury obsługi przerwania (push ACC) oraz jego odtworzenie na końcu procedury (pop ACC). Wynika to stąd, że zawartość akumulatora jest zmieniana w trakcie realizacji procedury obsługi przerwania i jeśli program główny korzysta z akumulatora może to spowodować błędne działanie programu. Przedstawia to poniższy przykład. Załóżmy, że w programie głównym znajdują się instrukcje:
; ....
mov A, P1
rl A
;
Podczas wykonywania programu przerwanie może mieć miejsce w dowolnej chwili. Jeśli więc przerwanie nastąpi podczas wykonywania instrukcji mov A, P1, to procedura obsługi przerwania zostania wykonana natychmiast po jej zakończeniu, czyli po pomiędzy podanymi wyżej instrukcjami. Jeśli procedura obsługi przerwania zmienia zawartość akumulatora, to jego stan tuż przed wykonaniem instrukcji rl A wcale nie będzie równy zawartości portu P1, jak wynika z programu, co spowoduje niewłaściwą pracę układu. Zapobiegnie temu omówione wyżej zapamiętanie na stosie zawartości akumulatora zrealizowane na samym początku procedury i jej odtworzenie tuż przed zakończeniem wykonywania procedury obsługi przerwania.
W przedstawionym programie przes_e program główny nie korzysta z akumulatora, więc odkładanie go na stosie nie jest konieczne. Jednak w większości przypadków trzeba to zrobić. Ogólna zasada jest taka, że jeśli procedura obsługi przerwania zmienia stan jakiejś komórki, która w tej procedurze wykorzystywana jest jako zmienna pomocnicza, a komórka ta wykorzystywana jest też w programie głównym, to trzeba ją odłożyć na stos. Najczęściej dotyczy to akumulatora ACC i rejestru PSW (zawiera m. in. znacznik CY), stąd w bardzo wielu procedurach obsługi przerwania należy odłożyć je na stos, co zwykle wygląda tak:
ProcObslPrzerw: ; procedura obslugi przerwania
push AC ; zapamietanie na stosie
push PSW
.... ; zawartość procedury
pop PSW ; odtworzenie zawartosci poczatkowej
pop ACC
reti
Należy pamiętać, że zdejmowanie ze stosu musi odbywać się w kolejności odwrotnej, niż odkładanie na niego.
35. Porównanie procedury użytkownika i procedury obsługi przerwania
W p. 33 omówiono procedury użytkownika, czyli fragmenty programu stanowiące pewną zamkniętą całość, wywoływane instrukcją lcall ... . W p. 34, przy omawianiu przerwań, opisano procedury obsługi przerwania. Poniżej zamieszczono porównanie obu typów procedur.
Podobieństwa
każda z wymienionych procedur stanowi ciąg instrukcji zajmujący pewien fragment pamięci programu,
każda z wymienionych procedur może być wywoływana wielokrotnie,
adres powrotu (adres instrukcji, do której należy przejść po zakończeniu wykonywania procedury) zapamiętywany jest na stosie i automatycznie ładowany do licznika rozkazów PC po zakończeniu wykonywania procedury.
Różnice
|
Procedura użytkownika |
Procedura obsługi przerwania |
|
może zaczynać się prawie w dowolnym miejscu w pamięci programu - na jej początek wskazuje etykieta, będąca argumentem instrukcji wywołującej lcall
|
zaczyna się w ściśle określonym miejscu w pamięci programu |
|
kończy się instrukcją ret
|
kończy się instrukcją reti |
|
wywoływana jest z miejsc programu wybranych przez programistę (instrukcja lcall) |
wywołana może być z dowolnego miejsca w programie w wyniku zajścia zdarzenia generującego przerwanie |
36. Prosty program do testowania współpracy mikrokontrolera z przetwornikami A/C i C/A
W niniejszym punkcie zostanie przedstawiony i omówiony prosty program ad_da0.s03 testujący poprawną współpracę przetworników A/C i C/A z mikrokontrolerem 8051. Założono przy tym, że zastosowano przetworniki oraz układ połączeń przedstawiony w p. 28 i 29 (układ „pięterek”).
Działanie programu jest bardzo proste: odczytywany jest stan sygnału analogowego podawanego na wejście IN0 przetwornika A/C, przetworzony sygnał analogowy w postaci cyfrowej przekazywany jest do mikrokontrolera, który natychmiast odsyła go do przetwornika C/A. Sygnał na wyjściu przetwornika C/A jest więc proporcjonalny do sygnału podawanego na wejście przetwornika A/C.
Poniżej przedstawiono kod źródłowy programu ad_da0.s03.
;================================================================
; Program testujacy przetwornik AD i DA
;.................................................................
;Zalozenia :
; AD mierzy wejscie analogowe i przetwarza go na sygnal cyfrowy, ktory
; jest nastepnie przetwarzany przez DA na sygnal analogowy
;==================================================================
;ADRESY PRZETWORNIKOW
;AD = 6000h DA = 8000h
;------------------------------------------------------------------
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;Inicjalizacja przerwan procesora
;+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
org 0000h
ljmp init
org 0013h
ljmp intpt1
;--------------------------------------------------------------------
;Inicjalizacja
;--------------------------------------------------------------------
init:
mov TCON,#04h ;przerwanie zboczem opadajacym
mov IE,#04h ;odblokowanie przerwania INT1
mov DPTR,#6000h ;adres AD
movx @DPTR,A ;pierwsze uruchomienie przetwornika AD
setb EA ;odblokowanie przerwan
;=======================================================================
;Glowna petla programu
;=======================================================================
main: sjmp main
;=======================================================================
;Obsluga przerwania
;=======================================================================
;.......................................................................
;Przerwanie zewnetrzne INT1\ -----------------
;-przetwornik generuje przerwanie gdy zakonczy przetwarzanie
;-wynik z przetwornika zostaje odczytany do ACC
;-przetwornik AD zostaje uruchomiony kolejny raz
;-wynik z AD zostaje wyslany na przetwornik DA
;.......................................................................
intpt1:
mov DPTR,#6000h
movx A,@DPTR ;odczyt wyniku z AD
movx @DPTR,A ;uruchomienie AD
mov DPTR,#8000h ;adres DA
movx @DPTR,A ;wyslanie wyniku AD na DA
reti
;=======================================================================
end
Program składa się z dwu części: programu głównego i procedury obsługi przerwania od sygnału zewnętrznego podanego na wejście INT1\ mikrokontrolera. Przerwanie to jest generowane przez przetwornik A/C po zakończeniu przetwarzania sygnału (p. 28.3).
Po załączeniu mikrokontrolera realizacja programu rozpoczyna się od wykonania instrukcji, której kod znajduje się w komórce pamięci programu o adresie 0000H. Tą instrukcją jest skok bezwarunkowy do adresu oznaczonego etykietą init. Ten skok jest wykonywany po to, aby „przeskoczyć” adres 0013H, od którego zaczyna się wykorzystywana w tym programie procedura obsługi przerwania od sygnału zewnętrznego podanego na wejście INT1\ (końcówka 13) mikrokontrolera. W przeciwnym wypadku moglibyśmy wpisać coś niewłaściwego do komórki 0013H i następnych. Temat ten został omówiony w p. 34.
od adresu oznaczonego etykietą init zaczynają się instrukcje inicjalizujące pracę układu:
przerwania zewnętrzne INT1\ będą zgłaszane opadającym zboczem sygnału - ustawienie bitu IT1 w rejestrze TCON instrukcją mov TCON,#04h,
odblokowanie przerwania INT1\ - instrukcja mov IE,#04h,
pierwsze uruchomienie przetwornika A/C (p. 28.2, 28.3):
wybór przetwornika A/C oraz wejścia analogowego AN0 - instrukcja mov DPTR,#6000h,
generacja sygnału WR\ - instrukcja movx @DPTR,A,
globalne odblokowanie przerwań - instrukcja setb EA,
po wykonaniu instrukcji inicjalizujących pracę układu program działa w nieskończonej pętli, z której nie ma wyjścia (main: sjmp main). Wszystkie operacje obsługi przetworników wykonywane są w procedurze obsługi przerwania INT1\.
po zakończeniu przetwarzania przetwornik A/C generuje na swoim wyjściu EOC impuls, który po zanegowaniu podawany jest na wejście przerwań INT1\ mikrokontrolera (rys. 28.3). Po przyjęciu przerwania mikrokontroler „wyskakuje” ze swojej nieskończonej pętli i wykonuje instrukcje począwszy od adresu 0013H. Pierwszą instrukcją jest skok bezwarunkowy do adresu oznaczonego etykietą intpt1.
od adresu oznaczonego etykietą intpt1 rozpoczyna się właściwa procedura obsługi przerwania. W jej ramach wykonywane są następujące czynności:
odczyt wyniku z przetwornika A/C:
wybór przetwornika A/C - instrukcja mov DPTR,#6000h,
przesłanie wyniku z przetwornika do akumulatora - instrukcja movx A, @DPTR,
ponowne uruchomienie przetwornika A/C - instrukcja movx @DPTR,A. Tutaj wystarczy wygenerować sygnał WR\ powyższą instrukcją, gdyż wybór przetwornika został dokonany dwie linijki wyżej,
przesłanie odczytanej danej z akumulatora do przetwornika C/A:
wybór przetwornika C/A - instrukcja mov DPTR,#8000h (p. 29),
przesłanie wyniku z akumulatora do przetwornika - instrukcja movx @DPTR, A,
zakończenie obsługi przerwania następuje w wyniku napotkania instrukcji reti. Następuje wtedy powrót do programu głównego do nieskończonej pętli. Ponieważ przetwornik A/C został ponownie uruchomiony w procedurze obsługi przerwania, dokonuje kolejnego pomiaru i przetwarzania. Po jego zakończeniu zostanie wygenerowane następne przerwanie, w którym odczytany nowy pomiaru wynik zostanie znowu przesłany do przetwornika C/A oraz zostanie dokonane kolejne uruchomienie przetwornika A/C, po czym znowu nastąpi powrót do pętli. I tak dalej w nieskończoność, choć może nie całkiem - raczej do wyłączenia układu lub ... jego uszkodzenia.
37. Stos - uzupełnienie
Jeżeli podczas realizacji programu przez mikrokontroler ma być wykonana procedura użytkownika (wywoływana np. instrukcją lcall NazwaProcedury) lub procedura obsługi przerwania, to z aktualnie wykonywanego fragmentu programu należy skoczyć w to miejsce pamięci programu, gdzie rozpoczyna się procedura. Po jej wykonaniu należy powrócić tam, skąd wyskoczyliśmy (a dokładniej - do instrukcji znajdującej się bezpośrednio po instrukcji lcall NazwaProcedury lub, w przypadku przerwania, do instrukcji znajdującej się bezpośrednio za ostatnią instrukcją wykonaną przed realizacją procedury obsługi przerwania).
Pytanie.: Z powyższego wynika wniosek: należy w jakiś sposób zapamiętać adres powrotu. Ale jak?
Odp.: W tym celu korzysta się ze stosu.
O stosie była już mowa w p. 18.4. Wspominano o nim także przy omawianiu procedur użytkownika (p.33) oraz przerwań (p.34). Oto podsumowanie informacji o stosie:
- stos to fragment wewnętrznej użytkowej pamięci RAM, wykorzystywany do chwilowego przechowywania danych (pamięć RAM ma adresy 00h ÷ 7Fh - razem 128B),
- wierzchołek stosu to adres powyżej którego dane będą zapisywane na stos i od którego ściąga się dane ze stosu,
- istnieje specjalny rejestr wskaźnik stosu SP (Stack Pointer), zawarty w pamięci SFR - adres 81H, wskazujący na wierzchołek stosu (wartość wpisana do SP to adres komórki będącej wierzchołkiem stosu),
- po odłożeniu danej na stos lub zdjęciu danej ze stosu wskaźnik stosu SP jest automatycznie aktualizowany,
- odkładanie na stos i zdejmowanie ze stosu może być:
- automatyczne - przy wykonywaniu procedur,
- programowe:
- odłożenie na stos - rozkaz push ad (kod C0),
- zdjęcie ze stosu - rozkaz pop ad (kod D0),
- każdorazowo po załączeniu mikrokontrolera lub jego wyzerowaniu wskaźnik stosu SP zawiera wartość 07 (wybrany jest wtedy zerowy bank pamięci i komórka 07 jest równocześnie rejestrem R7),
- stos można przenieść programowo w inne miejsce wewnętrznej użytkowej pamięci RAM; w tym celu należy zmienić zawartość wskaźnika stosu (np. instrukcją mov SP, #50h),
- konieczna jest kontrola rozmiaru stosu - jeżeli obejmie on obszar przeznaczony dla innych danych - może je zniszczyć (nadpisać). Dlatego należy oszacować ile komórek może być jednocześnie przechowywanych na stosie i zapewnić wystarczającą ich ilość. Jeśli np. w procedurze obsługi przerwania odkładamy na stos zawartość akumulatora i rejestru PSW, to na stos musimy zarezerwować minimum 4 komórki pamięci: dwie na przechowanie adresu powrotu z procedury oraz po jednej na przechowanie zawartości akumulatora i PSW. Gdybyśmy z wnętrza omawianej procedury wywoływali także procedurę użytkownika, to zależałoby zarezerwować na stos dalsze dwie komórki.
Porównanie ze stosem książek:
- wskaźnik stosu pokazuje zawsze na książkę będącą na samej górze,
- książki zdejmujemy w kolejności odwrotnej do tej, w jakiej je kładliśmy (najpierw zdejmujemy te książkę, którą położyliśmy ostatnio).
Pytanie.: Które komórki pamięci zostaną zmodyfikowane po odłożeniu na stos dwóch bajtów (np. w przypadku wykonywania procedury), jeśli na początku wskaźnik stosu SP zawierał wartość 07?
Odp.: Komórki 08 i 09 (komórka 07 nie jest modyfikowana). Powyższą sytuację możemy porównać ze stosem książek, gdy na spodzie było już 8 starych książek (liczenie od 0, stąd 8 nie 7 książek), których nie chcemy ruszać.
Uwaga: W powyższym przypadku błędem byłoby wykorzystywanie komórek 08 lub 09 do własnych celów w przypadku wykorzystywania jakiejś procedury w programie, gdyż ich zawartość zostałaby zniszczona podczas jej wykonywania.
Podsumowując:
Podczas realizacji programu adres następnej instrukcji do pobrania jest zawsze w liczniku rozkazów (PC - Program Counter) - licznik jest 16-bitowy. Wobec tego przed realizacją procedury (znajduje się ona w innym fragmencie pamięci programu) ten adres należy zapamiętać - jest on odkładany na stosie - zajmuje 2 bajty. Odbywa się to wszystko automatycznie. Po zakończeniu wykonywania procedury adres jest zdejmowany ze stosu i z powrotem wpisywany do licznika rozkazów PC, umożliwiając wykonanie tejże instrukcji.
Przebieg opisanych operacji można zaobserwować na symulatorze, np. sim51.exe.
Opis działania stosu można znaleźć także w literaturze, np. [8] - s. 93-101, [6] - s. 50-51.
MIKROPROCESOROWE METODY STEROWANIA MIKROKONTROLERY RODZINY MCS51 - 127 -
ZBIGNIEW WARADZYN AGH Kraków WEAIiE Katedra Automatyki Napędu i Urządzeń Przemysłowych
MIKROPROCESOROWE METODY STEROWANIA MIKROKONTROLERY RODZINY MCS51 - 164 -
ZBIGNIEW WARADZYN AGH Kraków WEAIiE Katedra Automatyki Napędu i Urządzeń Przemysłowych
przerwanie od T1
załączenie lub reset
Rys. 34.3. Przykład rozmieszczenia programu w pamięci programu
Adres Instrukcje
0000h
0001h ljmp Init
0002h
0003h
0004h
..........
001Ah
001Bh
001Ch ljmp PrzerwT1
001Dh
001Eh Init:
...........
...........
...........
AdrT1. PrzerwT1:
...........
...........
........... reti
...........
...........
Rys.34.2. Algorytm programu przes_e
akumulator ze stosu
akumulator na stos
PrzerwT1End
przygotowanie do
następnego opóźnienia
reti
TAK
koniec
opóźnienia
B = n
NIE
B = 0 ?
TAK
B=B-1
PrzerwT1
przesuw w prawo
A5
NIE
przesuw w lewo ?
(W2 otwarty? P3.5 = 1?)
przesuw w lewo
A4
TAK
przygotowanie do
pierwszego opóźnienia
B = n
A2
odblokuj przerwania od T1
maskuj przerwania od T1
globalne odblokowanie przerwań
uruchomienie T1 w trybie 0 (przepełnienie co 8 ms)
Init
zapal D1 i D4
A1
A3
NIE
TAK
ma być przesuw?
(W1 zamknięty? P3.4 = 0?) ?))
NIE
TAK
ma być przesuw?
(W1 zamknięty?)
zgaś D2, D3, D5, D6
zapal D1 i D4
zgaś D2, D3, D5, D6
procedura
obsługi
przerwania
wykonywany fragment programu
Y
X
E
Y
X
D
C
B
A
Y
X
Y
X
E
B
E
A
D
E
C
T
zeruj TF1
B = B - 1
B = 0?
Opoznienie
Start
TF1 = 1? (czy kolejne 8 ms?)
B=n
T - koniec opóźnienia
N
Dioda1 = P1.1
Licznik1=30H
N