Skocz do zawartości
Zaloguj się, aby obserwować  
kermit

[Kurs] Programowanie mikrokontrolerów AVR w języku assembler - część 5

Pomocna odpowiedź

Witam w kolejnej części kursu programowania mikrokontrolerów AVR w języku assembler, dziś zajmiemy się przerwaniami. Co to są przerwania? Jak nazwa sugeruje ich istota polega przerwaniu czegoś, w naszym wypadku chodzi o przerwanie wykonywania programu i skok do tzw. Procedury obsługi przerwania(ISR)- czyli wydzielonego fragmentu programu do którego wykonywany jest skok, gdy jakiś układ zgłasza przerwanie. Układem, który zgłasza przerwanie może być jeden z modułów peryferyjnych w naszym mikrokontrolerze, np. Przetwornik analogowo - cyfrowy o którym pisałem w poprzedniej części.

Po co nam te przerwania:

-Otóż przerwania mogą bardzo przyspieszyć wykonywanie naszego programu, aby to udowodnić przedstawię przykład z przetwornikiem analogowo-cyfrowym, otóż jak pamiętamy z poprzedniej części kursu, aby odczytać wartość napięcia jaką zmierzy przetwornik musieliśmy czekać w pętli aż wartość w rejestrze ADC zostanie uaktualniona, jako że przetwornik ten działa dość wolno to większość czasu nasz program tkwił w pętli sprawdzając, czy pomiar jest już gotowy, taka pętla bardzo spowalnia wykonywanie naszego programu. W takim wypadku najlepszym rozwiązaniem jest użycie przerwania od przetwornika ADC, gdy przetwornik wykona pomiar zgłasza przerwanie, wykonywanie programu głównego jest wstrzymywane, następuje skok do procedury obsługi przerwania gdzie wartość pomiaru wpisywana jest do rejestru ogólnego przeznaczenia, po napotkaniu rozkazu "reti" dokonywany jest powrót do miejsca w którym był program zanim nastąpiło przerwanie.

Przerwania dzielimy na wewnętrzne, czyli takie które przychodzą od modułów wewnętrznych mikrokontrolera(przetwornik A/C, timery, UART, TWI itp.) oraz przerwania zewnętrzne, czyli takie które wyzwalane jest przez jakiś układ zewnętrzny, przerwanie takie wyzwalane jest poprzez podanie sygnału na odpowiednie wejście mikrokontrolera(wejścia te oznaczane są jako INTn, np. W naszej atmedze32 mamy 3 takie wejścia- INT0(PD2), INT1(PD3) oraz INT2(PB2) ).

jak konfigurujemy przerwania zewnętrzne?

Aby skonfigurować przerwania zewnętrzne musimy skonfigurować rejestry MCUCR oraz GICR

Opis pierwszego z nich możemy znaleźć na stronie 66 noty aplikacyjnej atmegi32

jak widzimy 4 bardziej znaczące bity są zaznaczone szarym kolorem i nie są one przeznaczone do konfiguracji przerwań zewnętrznych

Bity które nas interesują to te o nazwie ISC01 oraz ISC00 służą one do określenia jaki sygnał na wejściu INT1 ma wyzwalać przerwanie, ustawiamy je wg. Tej tabelki:

jako że na wejściu INT0 mamy przycisk, który po wciśnięciu zwiera to wejście do masy to wybieramy wyzwalanie przerwania zboczem opadającym, czyli ustawiamy bit ISC01

bity ISC11 oraz ISC10 odpowiadają za ustawienie przerwania INT1, jako że my nie używamy tego wejścia to pozostawiamy je w spokoju.

Kolejnym rejestrem do ustawienia jest rejestr GICR, którego struktura prezentuje się tak:

I tutaj akurat wielkiej filozofii nie ma, bity INT0,1,2 służą do włączania poszczególnych przerwań zewnętrznych.

Ustawiamy bit INT0.

Ćwiczenie 1:

Gdy wiemy już co musimy poustawiać pora wkońcu coś zrobić :

montujemy układ według powyższego schematu i przystępujemy do pisania programu. W pierwszym programie użyjemy przerwania zewnętrznego, będzie ono wywoływane poprzez zwarcie [przyciskiem] linii PB3 do masy. W procedurze obsługi przerwania znajdzie się instrukcja która zapali naszą diodę, wykona opóźnienie o 1s i powróci do programu głównego.

Możliwe, że niektórym bardziej doświadczonym osobom w tym momencie zapaliła się w głowie czerwona lampka, czytając o tym, że w przerwaniu ma być wykonywane opóźnienie. Otóż przerwania i opóźnienia nie są zbyt dobrym połączeniem i należy pamiętać, że w przerwaniach raczej nie powinno się używać opóźnień, ani długich pętli, wyjątkiem są bardzo proste programy lub takie gdzie używamy tylko jedno przerwanie, czyli w naszym wypadku nie będzie problemu, ale warto zapamiętać tą regułę na przyszłość

Nasz program będzie wyglądał tak:

.nolist 
.include "m32def.inc"
.list 

.cseg
.org 0

//tablica wektorów przerwań	
jmp start			
.org 0x0002	rjmp EXT_INT0; External Interrupt Request 0
.org 0x0004 reti	; External Interrupt Request 1
.org 0x0006	reti; External Interrupt Request 2
.org 0x0008	reti; Timer/Counter2 Compare Match
.org 0x000a	reti; Timer/Counter2 Overflow
.org 0x000c	reti; Timer/Counter1 Capture Event
.org 0x000e	reti; Timer/Counter1 Compare Match A
.org 0x0010	reti; Timer/Counter1 Compare Match B
.org 0x0012	reti; Timer/Counter1 Overflow
.org 0x0014	reti; Timer/Counter0 Compare Match
.org 0x0016	reti; Timer/Counter0 Overflow
.org 0x0018	reti; Serial Transfer Complete
.org 0x001a	reti; USART, Rx Complete
.org 0x001c	reti; USART Data Register Empty
.org 0x001e	reti; USART, Tx Complete
.org 0x0020 reti; ADC Conversion Complete
.org 0x0022	reti; EEPROM Ready
.org 0x0024	reti; Analog Comparator
.org 0x0026	reti; 2-wire Serial Interface
.org 0x0028	reti; Store Program Memory Ready


start: //początek programu
cli	   //wyłączenie przerwań
ldi R16, HIGH(RAMEND)
out SPH, R16
ldi R16, LOW(RAMEND)
out SPH, R16

sei		//włączenie przerwań

sbi DDRC, 0			//ustawienie linii do której podłączona jest dioda jako wyjście	
cbi DDRD, 2			//ustawienie linii PD2 jako wejście, zwarcie tej linii do masy wyzwoli przerwanie 
sbi PORTD, 2		//podciągnięcie linii PD2 do napięcia zasilania 

ldi R16, (1<<ISC01)	 //ustawienie sygnału wyzwalającego przerwanie INT0- zbocze opadające
out MCUCR, R16		

ldi R16, (1<<INT0)	//zezwolenie na przerwanie od INT0
out GICR, R16


main:
cbi PORTC, 0		//zgaś diodę 
rjmp main

EXT_INT0:			//procedura obsługi przerwania INT0
sbi PORTC, 0 		//zapal diode LED

; ============================= 
;    podprogram opózniający
;     	   1 sekunda:
; ----------------------------- 
; delaying 999999 cycles:
         ldi  R17, $09
WGLOOP0:  ldi  R18, $BC
WGLOOP1:  ldi  R19, $C4
WGLOOP2:  dec  R19
         brne WGLOOP2
         dec  R18
         brne WGLOOP1
         dec  R17
         brne WGLOOP0
; ----------------------------- 
; delaying 1 cycle:
         nop
; ============================= 


reti			//rozkaz powrotu z przerwania

Jak widać w naszym programie pojawiło się coś nowego, a mianowicie tablica wektorów(adresów) przerwań, z racji tego, że w programie używamy tylko i wyłącznie jednego przerwania(INT1), to z jednego adresu(0x002) wykonywany jest skok do etykiety "EXT_INT0" pełna tablica wektorów przerwań, którą wklejamy do programu wygląda tak(w zależności od przerwania jakiego używamy, zastępujemy w odpowiednim miejscu rozkaz reti , rozkazem rjmp "etykieta procedury obsługi przerwania"):

jmp start			
.org 0x0002	reti; External Interrupt Request 0
.org 0x0004	reti; External Interrupt Request 1
.org 0x0006	reti; External Interrupt Request 2
.org 0x0008	reti; Timer/Counter2 Compare Match
.org 0x000a	reti; Timer/Counter2 Overflow
.org 0x000c	reti; Timer/Counter1 Capture Event
.org 0x000e	reti; Timer/Counter1 Compare Match A
.org 0x0010	reti; Timer/Counter1 Compare Match B
.org 0x0012	reti; Timer/Counter1 Overflow
.org 0x0014	reti; Timer/Counter0 Compare Match
.org 0x0016	reti; Timer/Counter0 Overflow
.org 0x0018	reti; Serial Transfer Complete
.org 0x001a	reti; USART, Rx Complete
.org 0x001c	reti; USART Data Register Empty
.org 0x001e	reti; USART, Tx Complete
.org 0x0020	reti; ADC Conversion Complete
.org 0x0022	reti; EEPROM Ready
.org 0x0024	reti; Analog Comparator
.org 0x0026	reti; 2-wire Serial Interface
.org 0x0028	reti; Store Program Memory Ready
start:

Wcześniej mówiłem, że w przerwaniu następuje skok do jakiegoś wydzielonego miejsca w programie i wykonanie tzw. Procedury obsługi przerwania(tak nazywamy część programu wykonywaną, gdy nastąpi przerwanie), a teraz chciałbym nadmienić, że skok do procedury obsługi przerwania nie jest bezpośredni, tylko najpierw następuje skok do tablicy wektorów, a dokładniej do którego adresu w tej tablicy, można wyczytać w tabeli 18 noty aplikacyjnej atmegi32

tablica ta wygląda tak:

i jak widać adres do którego zostanie wykonany skok zależy od przerwania jakie ma być wykonane.U nas skok zostaje wykonany do adresu 0x002, skąd dopiero następuje skok do miejsca w którym zawarta jest nasza procedura obsługi przerwania, w naszym programie miejsce to wskazuje etykieta EXT_INT0. W razie gdybyśmy w programie używali innych przerwań wystarczy w odpowiednim miejscu tablicy zastąpić instrukcje "reti" fragmentem który prezentuje się tak: "rjmp" "etykieta procedury obsługi przerwanie".

Ćwiczenie 2 – przerwania wewnętrzne

W tym ćwiczeniu wykorzystamy ten sam układ co w poprzednim, czyli diodę LED podłączoną anodą do portu PC0 oraz przycisk z jednej strony podłączony do PD2 a z drugiej do potencjału masy.

Tym razem w programie wykorzystamy dwa przerwania jedno które już znamy, czyli przerwanie zewnętrzne INT0 oraz jedno przerwanie wewnętrzne, które będzie pochodziło od timera1(16-bitowy, czyli wartość do której zlicza jest równa 65536). Zadanie programu będzie jak zwykle niezbyt skomplikowane- za pomocą przycisku S1 wyzwalamy przerwanie INT0 w którego procedurze obsługi znajdzie się instrukcja zapalenia diody, następnie po przepełnieniu timera1 nastąpi przerwanie w którym dioda zostanie zgaszona. Przepełenienie timera nastąpi po 65535 x 8(wart. Preskalera) = 524280 cykli co przy taktowaniu 1mhz daje ok. 0,52s(po tym czasie dioda zostanie zgaszona )

tak prezentuje się nasz program:

.nolist 
.include "m32def.inc"
.list 

.cseg
.org 0
jmp start			
.org 0x0002	rjmp EXT_INT0; External Interrupt Request 0
.org 0x0004 reti	; External Interrupt Request 1
.org 0x0006	reti; External Interrupt Request 2
.org 0x0008	reti; Timer/Counter2 Compare Match
.org 0x000a	reti; Timer/Counter2 Overflow
.org 0x000c	reti; Timer/Counter1 Capture Event
.org 0x000e	reti; Timer/Counter1 Compare Match A
.org 0x0010	reti; Timer/Counter1 Compare Match B
.org 0x0012	rjmp OVF1; Timer/Counter1 Overflow
.org 0x0014	reti; Timer/Counter0 Compare Match
.org 0x0016	reti; Timer/Counter0 Overflow
.org 0x0018	reti; Serial Transfer Complete
.org 0x001a	reti; USART, Rx Complete
.org 0x001c	reti; USART Data Register Empty
.org 0x001e	reti; USART, Tx Complete
.org 0x0020 reti; ADC Conversion Complete
.org 0x0022	reti; EEPROM Ready
.org 0x0024	reti; Analog Comparator
.org 0x0026	reti; 2-wire Serial Interface
.org 0x0028	reti; Store Program Memory Ready
//koniec tablicy

start: //początek programu
cli	   //wyłączenie przerwań
ldi R16, HIGH(RAMEND)
out SPH, R16
ldi R16, LOW(RAMEND)
out SPH, R16



sbi DDRC, 0		//ustawienie linii do której podłączona jest dioda jako wyjście	
cbi DDRD, 2		//ustawienie linii PD2 jako wejście, zwarcie tej linii do masy wyzwoli przerwanie 
sbi PORTD, 2	//podciągnięcie linii PD2 do napięcia zasilania 

ldi R16, (1<<ISC01)	 //ustawienie przerwań zewnętrnych
out MCUCR, R16

ldi R16, (1<<INT0)
out GICR, R16
sei						//włączenie przerwań

ldi R16, (1<<CS11)		//ustawienie preskalera
out TCCR1B, R16

ldi R16, (1<<TOIE1)		//zezwól na przerwania od przepełnienia timera1
out TIMSK, R16

ldi R16,0 				//ustawienie trybu działania timera w tryb normalny
out TCCR1A, R16

main:					//pętla główna
nop
rjmp main


EXT_INT0:				//procedura obsługi przerwania INT0
sbi PORTC, 0			//zapal diode

clr R16					//wyzerowanie zawartości timera1
out TCNT1H, R16
out TCNT1L, R16
reti					//powrót z przerwania

OVF1:					//procedura obsługi przerwania od przepełnienia timera1,nastąpi ono 65535 x 8= ok.0.52s po wyzerowaniu licznika timera1	
cbi PORTC, 0			//zgaś diodę
reti					//powrót z przerwania

nowe instrukcje które wystąpiły w programie:

sei – "global interrupt enable" – globalne zezwolenie na przerwania

clr – "clear register" – wyczyść "nazwa rejestru ogólnego przeznaczenia"

reti – "interrupt return" - powrót z procedury obsługi przerwania

Na koniec chciałbym poinformować o dwóch ważnych zagadnieniach:

-Przerwania mają swoje priorytety.Znaczy to, że jeśli w tym samym momencie zostaną zgłoszone 2 lub więcej przerwań to jako pierwsze zostanie wykonane to przerwanie, które jest wyżej w tablicy wektorów(czyli przerwanie, które ma niższy adres )

-w przypadku gdy używamy przerwań musimy zaimplementować w programie stos, ponieważ w momencie skoku do procedury obsługi przerwania adres powrotny jest odkładany właśnie na stos i gdy program napotka instrukcję "reti" adres ten jest zdejmowany ze stosu i następuje powrót do miejsca w którym był program zanim nastąpiło przerwanie.

Zakończenie:

Jako, że kończy się wrzesień a wraz z nim niestety, również mój wolny czas, to jak narazie była to ostatnia część naszego kursu, możliwe że za jakiś czas pojawią się następne części, ale jak narazie kurs można uważać za zakończony.Mam nadzieję, że kurs był pomocny przy pierwszych krokach z językiem assembler.W razie jakiś pytań lub jeśli ktoś zobaczył by jakieś błędy - proszę pisać na w temacie lub na PW.

Pozdrawiam!!!

asm.thumb.jpg.b9a3689ebf622ff8abba37b46d6c58c8.jpg

  • Lubię! 2

Udostępnij ten post


Link to post
Share on other sites

Tradycyjnie, jak przy każdej części - proszę poprawić interpunkcję 🙂

Udostępnij ten post


Link to post
Share on other sites

Pozostaje mi po raz kolejny pogratulowac tak obfitej serii artykułów. Ta cześć jest najlepiej opisana, co mnie bardzo cieszy 😋. Powodzenia na studiach 🙂

Udostępnij ten post


Link to post
Share on other sites
Bity które nas interesują to te o nazwie ISC10 oraz ISC11

Chyba coś namieszałeś. Piszesz dobrze, że należą one do INT1, ale potem używasz INT0, więc lepiej od niego dać tabelkę. Bo tak to mamy tabelkę z ISC10 i ISC11, a piszesz o ustawianiu ISC01 w celu aktywowania przy opadającym zboczu. Początkujący mogą się w tym zagubić (jakieś 5 razy przeczytałem, a potem zajrzałem do datasheeta). Wymusza to myślenie, ale nie każdy będzie miał tyle samozaparcia, by odkryć dlaczego tak, a nie inaczej. Czytam dalej, jak coś znajdę to dopiszę.

O tym, że to dobra seria, nie muszę pisać. To oczywiste 😉

Prywata: Miałem dziś ruszyć od zera Twoje arty i programator, ale miałem małe prace remontowe w pokoju, które mam nadzieję jutro skończyć. Wtedy nie zostanie mi nic, jak tylko siedzieć i wtrawiać mózg w mikrokontrolery 😃 (i asm!)

Udostępnij ten post


Link to post
Share on other sites

Szkoda tylko, że koledze kończy się czas, a nie opisał najważniejszego zastosowania ASM, nie tylko dla AVRów, czyli jako wstawek do języków wysokiego poziomu, bo to właśnie tam mają największe zastosowanie. Jeżeli chodzi o sterownie, to przydatny jest głównie przy obsłudze WatchDoga, oraz Timera jeżeli nie ma trybu CTC, i w.. no właśnie skomplikowanych obliczeniach, gdzie liczy się szybkość wykonania kodu, a żaden z tych tematów nie został poruszony. czyli mamy serię artykułów o ASM, ale nie poruszających jego zastosowań praktycznych, z punktu widzenia współczesnych jego zastosowań.

Udostępnij ten post


Link to post
Share on other sites

BlackJack, mam wrażenie jak byś na siłę chciał znaleźć negatywy w każdym wątku. Ta seria artykułów to kurs programowania asm, a nie opis jego zastosowań. Sądzę, że temat jest zrealizowany bardzo dobrze 🙂 Według mnie najważniejszym zastosowaniem assemblera nie są wstawki w kodach języków wyższego poziomu, a nawet gdyby, to z tym kursem nie powinien być to problem. Nie rozumiem też, dlaczego musisz obsługiwać watchdoga i timery asmem ;o

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

Zaloguj się, aby obserwować  

×
×
  • Utwórz nowe...