"U potrazi za tartufima AVR-a" - ISR
U ovom programu još uvijek nedostaje ISR. (Prekidna servisna rutina). Jasno je kako smo mi uključili globalne prekide (I Bit SREG-a), te smo uključili i prekid TIMERA kada nabroji do OCR0A vrijednosti.
Za bilo koji prekid AVR-a potrebno je uključiti samo ove 2 stvari, dakle globalni I bit SREG-a i interrupt koji želimo omogućiti (u našem slučaju prekid TIMER-a 0).
Svakako trebamo primjetiti da se prekid neće dogoditi ukoliko ne pokrenemo TIMER (CS Bitovi), jer u toj situaciji TIMER će biti čvrsto na vrijednosti 0, neće brojati, pa tako nikada neće ni nabrojati do OCR0A registra.
U našoj konfiguraciji prekida sve smo super napravili, odabrali smo mod rada CTC (WGM Bitovi), podesili godnju vrijednost TIMER-a u OCR0A registru, pokrenuli smo TIMER (CS Bitovi), Omogućili smo prekid TIMER-a kada dođe do OCR0A registra (TIMSK reg), te smo omogućili globalne prekide (I BIT SREG-a).
No postoji jedan problem: Što kad se interrupt stvarno dogodi???
U jednom od davnih postova "Bitka kod HEX-a, Juriš na assembler" napisao sam palenje LED-a u assembleru, te palenje LED-a u C-u. Jedan od tih programa je drastično veći od drugog i tome naravno postoji pravi razlog do kojeg smo trenutno došli. Evo paste tih programa za palenje LED-a.
Palenje LED-a pisano u C-u:
Code: Select all
:1000000019C020C01FC01EC01DC01CC01BC01AC00C
:1000100019C018C017C016C015C014C013C012C034
:1000200011C010C00FC00EC00DC00CC00BC00AC064
:1000300009C008C011241FBECFEFD4E0DEBFCDBF82
:1000400002D004C0DDCF81E087B9FFCFF894FFCFA5
:00000001FF
Palenje LED-a pisano u assembleru:
Code: Select all
:0600000081E087B9FFCF8B
:00000001FF
Za korištenje prekida moramo jasno odvojiti 2 usko povezane stvari -> Hardware i Software.
Prvo moramo znati da bilo koji prekid u mikrokontroleru uvijek poziva Hardware MCU-a, a druga opako bitna stvar je činjenica da se prekid događa na razini assemblera, a ne C-a ili Bascoma.
Ovo je jako bitno jer ljudski mozak u C-u očekuje da će se prekid programa dogoditi nakon naredbe C jezika, a on se uz vraga dogodi nakon jedne assemblerske naredbe. Iz C-a ne možemo niti vidjeti kako se dogodi prekid, niti možemo razumjeti stvarni prekid jer je on naprosto skriven iza samog prevodilca.
U toj priči nemoguće je izbjeći pojam ISR_VECTOR jer je on direktno vezan za HARDWARE prekida,
Davno sam rekao da mikrokontroler starta sa adrese 0 u FLASH-u, no nisam rekao da ta specifična adresa ima i svoje ime, a zove se RESET_VECTOR. No nije to jedini VECTOR u AVR računalu. Nakon adrese 0 idu ISR VECTORI. Nije slučajnost to što u ATMEGA88 računalu postoji 1 RESET_VECTOR i 25 ISR_VECTOR-a.
To znači da svaki prekid u mikrokontroleru ima vlastiti ISR vector. (Postoji 25 izvora prekida, te 25 ISR_VECTOR-a).
Ovo je jedan od razloga zašto je C napravio tako velik program za palenje LED-a. Compiler zna točnu lokaciju VECTORA i namjerno ih programski preskoči jer zna svrhu VECTOR-a i zato je prvih 26 adresa u FLASH-u sigurno assemblerska naredba skoka RJMP. (RESET_VECTOR + 25 ISR_VECTOR)
No isto tako kad sam ja palio LED iz assemblera znam da su tu VECTORI, ali također znam jedan podatak koji compiler ne zna, a to je da sam siguran 100% kako se neće dogoditi niti jedan prekid u programu za palenje LED-a. (U tom programu sigurno nisam uključio I BIT SREG-a). Tu informaciju assemblerski programer može iskoristiti i namjerno napisati program preko ISR_VECTOR-a. Zdrava pamet ovo nikada ne radi i u C-u se to ne može napraviti jer su svi VECTORI naprosto briga compilera i on će ih uvijek preskočiti sa assemblerskom RJMP naredbom. No ja sam si dozvolio slobodu assemblera, pa sam namjerno upalio LED preko VECTOR-a kako bih imao što manji program.
Dio prekida koji će HARDWARE odraditi upravo se tiče ovih VECTORA, pa evo da napišem i sve VECTORE ATMEGA88 MCU-a:
1 0x000(1) RESET External Pin, Power-on Reset, Brown-out Reset and Watchdog System Reset
2 0x001 INT0 External Interrupt Request 0
3 0x002 INT1 External Interrupt Request 1
4 0x003 PCINT0 Pin Change Interrupt Request 0
5 0x004 PCINT1 Pin Change Interrupt Request 1
6 0x005 PCINT2 Pin Change Interrupt Request 2
7 0x006 WDT Watchdog Time-out Interrupt
8 0x007 TIMER2 COMPA Timer/Counter2 Compare Match A
9 0x008 TIMER2 COMPB Timer/Counter2 Compare Match B
10 0x009 TIMER2 OVF Timer/Counter2 Overflow
11 0x00A TIMER1 CAPT Timer/Counter1 Capture Event
12 0x00B TIMER1 COMPA Timer/Counter1 Compare Match A
13 0x00C TIMER1 COMPB Timer/Coutner1 Compare Match B
14 0x00D TIMER1 OVF Timer/Counter1 Overflow
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A
16 0x00F TIMER0 COMPB Timer/Counter0 Compare Match B
17 0x010 TIMER0 OVF Timer/Counter0 Overflow
18 0x011 SPI, STC SPI Serial Transfer Complete
19 0x012 USART, RX USART Rx Complete
20 0x013 USART, UDRE USART, Data Register Empty
21 0x014 USART, TX USART, Tx Complete
22 0x015 ADC ADC Conversion Complete
23 0x016 EE READY EEPROM Ready
24 0x017 ANALOG COMP Analog Comparator
25 0x018 TWI 2-wire Serial Interface
26 0x019 SPM READY Store Program Memory Ready
Nama najzanimljiviji ISR_VECTOR je upravo ovaj na adresi 0x00E FLASH-a:
15 0x00E TIMER0 COMPA Timer/Counter0 Compare Match A
Ponovno se vraćam na onaj problem: Što kad se interrupt stvarno dogodi???
E tada ćemo zbog samog HARDWARE-a računala sigurno programski završiti na gore navedenom VECTORU. Nije to sve što će hardware za nas odraditi, pa će usput siguno isključiti i I BIT SREG-a (dakle isključiti globalne prekide) kako se niti jedan drugi prekid ne bi mogao dogoditi, pospremiti PC na stack, obrisati TIFR za ovaj prekid, te tek se onda čudom pojaviti upravo na ovoj adresi u FLASH-u.
Sada ne moramo točno analizirati sve što odradi sam hardware jer ćemo to kasnije još detaljnije proučavati, no kada program dođe na VECTOR onda mi preuzimamo stvar.
Prekid nema nikakvog smisla ako ne postoji dio programa koji će se odraditi u prekidu, a taj dio se zove ISR ili prekidna servisna rutina. (U njoj mi moramo povećati varijablu RAM-a). Pošto svaki prekid TIMER-a uvijek i isključivo uvijek završava na specifičnom ISR VECTORU tamo mora biti naredba skoka na našu prekidnu rutinu.
Sve što mi moramo napraviti je reći compileru koja je naša prekidna rutina, kako bi on znao kamo treba skočiti kada se program pojavi na ovom specifičnom ISR_VECTORU.
Kada compileru kažemo koje je ISR rutina, on će sigurno na poziciju vectora staviti assemblersku naredbu RJMP koja skače na ISR, a mi ćemo se čudom u C-u tamo i pojaviti, a da ne znamo ni zašto smo točno, niti kako smo točno došli u ISR.
Zdravo seljački: Što je ISR_VECTOR?
Adresa u FLASH memoriji na koju će skočiti program kada se dogodi specifičan prekid, a služi samo da bi na toj adresi mi mogli napisati instrukciju koja skače na našu ISR rutinu koja se nalazi negdje u FLASH-u.
Ovim načinom ISR rutinu možemo smjestiti gdje hoćemo i ona može biti velika koliko hoćemo jer VECTOR uvijek skače na adresu gdje sama servisna rutina počinje.
Iz ovog svakako treba izvući i zaključak:
ISR nije ništa drugo nego obični dio programa u FLASH memoriji, a od glavnog programa, koji se vrti u while(1), razlikuje se samo po tome što ISR_VECTOR skače upravo na taj dio FLASH programa, a to se dogodi svaki puta kada TIMER pozove prekid. Postoji tu još detalja, a o njima naravno kasnije...
Jedina korist od toga svege je činjenica da će se ISR rutina izvršiti svakih 1mS vremena i to je najbitnije u ovoj pomalo kompliciranijoj prici.
To be continued...