|
Představte si, že cosi vaříte na sporáku. Tedy vykonáváte program. Náhle zazvoní telefon. Co uděláte ? Přerušíte vaření a jdete zvednout sluchátko - vida přerušení.
Nyní provádíte obsluhu přerušení - telefonujete. Hlavní program (vaření) byl přerušen zvoněním telefonu. Pokud je tohle jasné, budeme pokračovat. Procesor x51 má dvě úrovně priority přerušení - vyšší a nižší. Jaký je v tom rozdíl ?
Opět se vraťme k příkladu do okamžiku, kdy telefonujete a jídlo je stále na sporáku. Jídlo se ale začne pálit. Co uděláte ? Odložíte sluchátko (přerušíte hovor) a jdete sundat kastrol ze sporáku. V počítačové mluvě jste tedy přerušili přerušení - resp. došlo k přerušení vykonávání obsluhy prvního přerušení (zvonění telefonu) důležitější činností (sundat hrnec). Po vykonání procedury "člověče sundej rychle ten kastrol" se vrátíme (z přerušení s vyšší prioritou) k dokončení původního přerušení (telefonického hovoru). Po dokončení obsluhy tohoto přerušení zavěsíme - obsluha tohoto přerušení ukončena - a vrátíme se do hlavního programu - tedy vrátíme kastrol na sporák, popadneme vařečku a budeme dál cosi kvedlat. Pokud to tedy ještě jednou zopakuji, pak přerušení hlavního programu je způsobeno nějakou událostí, kterou je nutno obsloužit. Přerušení může být vyvoláno nějakou událostí na zvoleném pinu procesoru (samozřejmě určeném výrobcem) nebo může být způsobeno příchodem znaku po sériové lince, a nebo třeba přetečením vnitřního časovače. Po dokončení obsluhy se procesor vrací do hlavního programu na místo, kde svoji práci přerušil. (nebudu zde zatím rozebírat podrobnosti) Jak jsem již zmínil, x51 má dvě úrovně priority přerušení. Nižší a vyšší. Jde v podstatě o to, že pokud probíhá obsluha přerušení s nižší prioritou (telefonování) může nastat událost, která vyvolá přerušení s vyšší prioritou (pálí se jídlo). V tomto okamžiku je, jak jsem již uvedl, zastavena obsluha přerušení s nižší prioritou a následuje skok na obsluhu přerušení s prioritou vyšší. No dobře, ale co se stane, pokud při obsluze přerušení s nižší prioritou (telefonování) přijde jiné přerušení, které má stejnou prioritu ? Pro příklad zvolme opět stav, kdy vaříme, zazvoní telefon, přerušíme vaření a telefonujeme. Najednou přiběhnou děti, že jim mám jít zkontrolovat domácí úkoly. Protože to nemá vyšší prioritu než můj telefonní hovor, zapamatuji si to (analogie - procesor si nastaví odpovídající příznak) a pokračuji v telefonním hovoru. Po dokončení hovoru se nevrátím do kuchyně k hrnci, ale následuje "obsloužení události" - tedy kontrola úkolů ratolestí. Po dokončení kontroly se vrátím do kuchyně - tedy do vykonávání hlavního programu. Nyní se vrátíme zpět k vlastnímu procesoru. Vlastní průběh přerušení musí mít nejaký řád. Funguje to tak, že pokud máme povolené to které přerušení (povoluje se v určitém registru určitým bitem), pak pokud toto přerušení nastane, je nastaven odpovídající příznak. Následně se po dokončení právě prováděné instrukce vyvolá přerušení. U x51 má každé přerušení svoji adresu, na kterou procesor skočí právě po vyvolání přerušení. Procesor po dokončení instrukce nejprve uloží adresu následující instrukce do zásobníku. Musí. Proč ? Je to proto, že procesor si musí "pamatovat" kde skončil - tedy lépe řečeno, kde má po návratu z přerušení pokračovat. Následně provede skok na výrobcem pevně danou adresu přiřazenou tomu kterému přerušení (více zde). Protože adresy (vektory) přerušení jsou u sebe celkem blízko, většinou se na tuto adresu pouze vloží instrukce skoku, která převede řízení na obslužnou rutinu přerušení. Příznak, který přerušení vyvolal je většinou (kromě příznaků od sériové linky) hardwarově vynulován (platí pro x51). Po dokončení přerušení program instrukcí RETI vrátíme na vykonávání hlavního programu. V tomto okamžiku je ze zásobníku vybrána uložená adresa (je to adresa následující instrukce v hlavním programu, před kterou bylo přerušení vyvoláno, pamatujete ?) a provede se na ni skok. Tím je obsluha ukončena. Až tohle přejde do krve, je nutné si uvědomit ještě několik souvisejících a podstatných věcí. Pokud v obsluze přerušení pracujete s akumulátorem (a že se s ním pracuje téměř vždy) MUSÍTE řešit jednu věc a tou je uschování jeho hodnoty při vstupu do přerušení. Totiž představte si, ze jste v hlavnim programu někde v polovině výpočtu a přijde přerušení. V obsluze přerušení budete potřebovat vypočítat něco jiného a pokud použijete akumulátor, pak v něm při návratu z přerušení bude naprosto jiná hodnota, než jaká tam původně byla. Čili pak bude onen načatý a přerušením přerušený výpočet pokračovat s úplně jiným číslem a Vy máte zaděláno na velmi slušný problém, neboť podle toho, jak přerušení nastane (kdy) bude program fungovat / nefungovat. A to se opravdu velmi dobře hledá. Řešení je naprosto jednoduché. Prostě vždy po vstupu do obsluhy přerušení si do zásobníku uložíme obsah jak akumulátoru, tak stavového slova (stavové slovo obsahuje informace o přetečení, nulovosti apod. a přepsání údajů platí jako pro výše uvedený akumulátor). Ukládání do zásobníku zajišťuje instrukce PUSH, vyjmutí zpět instrukce POP. Pamatujte, že musíte vybírat data v obráceném pořadí, než v jakém jste je ukládali. Data se v zásobníku ukládají postupně za sebou, tedy ta, která jsou uložena jako první se vybírají jako poslední !! |
;************Obsluha preruseni*********************** prerus_t0: push psw ;uschovej stavove slovo programu >--- push acc ;uschovej akumulator >-- | . | | . | | ;obsluha preruseni | | . | | . | | konprerus: pop acc ;obnov akumulator <---- | pop psw ;obnov stavove slovo programu <------ reti ;navrat z preruseni
$MOD52 $TITLE(BYTE SIGNED MULTIPLY) $PAGEWIDTH(132) $DEBUG $OBJECT $NOPAGING org 00 AJMP start ;cold START org 000Bh ;preruseni TMR0 jmp prerus_t0 ;************ Zacatek programu *********************** prerus_t0: push psw ;uschovej pouzivane push acc mov TH0,#03CH ;po preteceni nova hodnota mov TL0,#0B0H djnz pocitadlo,konprerus ;sniz pocitadlo preteceni mov pocitadlo,#0AH cpl LED konprerus: pop acc ;obnov pouzita v preruseni pop psw reti ;********************************************** bseg at 30 LED bit P1.2 pocitadlo equ 30H ;********************************************** ; H L A V N I S M Y C K A ;********************************************** cseg at 60h start: mov IE,#10000010b ; preruseni od t0 a globalne mov TMOD,#00000001b ; t0 - 16bit s predvolbou mov TH0,#03CH ;predvolba 15536 horni byte mov TL0,#0B0H ;predvolba casovace dolni byte mov pocitadlo,#0AH setb tr0 ;spust casovac jmp $ END |
Nejprve klasická hlavička. Následuje direktiva ORG00, která označuje nultou pozici paměti - zde procesor po resetu začíná pracovat. Na této direktivě máme skok na vlastní skutečný start programu (AJMP start). Na adrese 00BH je skok na adresu, na kterou procesor skočí, pokud je generováno přerušení přetečením vnitřního čítače / časovače 0 procesoru. O těchto adresách naleznete více zde. Dále máme pomocí registru TMOD nakonfigurován časovač 0 jako 16 bitový, který má jako zdroj hodin jádro procesoru (může čítat i pulsy z vnějšího pinu, ale o tom až jindy). Je inkrementovaný každým strojovým taktem procesoru. To znamená, že se do časovače přičte jednička každou mikrosekundu (při taktování procesoru 12MHz). K přetečení 16 bitového čítače tedy dojde po 65535 cyklech - tedy po 65,535 msec. (milisekundách). Dále máme v registru IE povoleno vyvolání přerušení po přetečení tohoto časovače a máme povoleno přerušení jako takové vůbec (bit EA). A jak to tedy pracuje ? Chceme blikat LED diodou po 0,5 sekundě (0,5 sec. svítí, 0,5 tma). To znamená, načítat 500 milisekund, změnit stav a opakovat. Protože ale časovač může čítat do max. 65535 (65 msec.) a pak přeteče do 00000, musíme si pomoci jinak. Nastavíme si časovač vždy tak, aby načítal 50 milisekund (50 000). Pak přeteče a vyvolá přerušení. My si vezmeme jeden byte RAM označený jako pocitadlo, které nám bude čítat počet přetečení hardwarového časovače (resp. bude počítat počet přerušení). My si tedy nastavíme hodnotu do časovače (registry časovače jsou přístupné i pro zápis) tak, aby k jeho přetečení došlo po 50 msec. Hodnota tedy bude 65536 usec. - 50000 usec. = 15536 usec (3CB0 v hexa). Jestliže tedy tuto hodnotu zadáme do časovače pak k přetečení dojde po 50 msec (počítá od 15536 nahoru, po 65536 se nuluje a volá přerušení). Jestliže dojde k přetečení, je vyvoláno přerušení. Tedy následuje skok na adresu 00BH. Tam je řízení předáno na adresu prerus_t0. Zde se nejprve uloží stav akumulátoru a stavové slovo programu do zásobníku. Dále se znova zadá hodnota 15536 do časovače (jinak by čítal od 00 00) a snížíme o jednu hodnotu softwarového počitadla - a zároveň porovnáme, zda bylo dosaženo nuly (DJNZ). Pokud ne, následuje skok na konec přerušení (konprerus) kde se vybere zpět stavové slovo a hodnota akumulátoru před přerušením. Pokud bylo dosaženo nuly (desátý vstup do přerušení) zadá se do tohoto počitadla nový stav (opět 10 čili 0AH) a změní se stav LED pomocí instrukce CPL (neguj). A následuje opět výstup přerušení (tedy přes výběr ACC a PSW). A protože nic jiného nemáme na práci, program v hlavní smyčce neustále běhá kolem dokola na instrukci jmp $ (skok na sebe sama), samozřejmě jinak může řešit cokoliv jiného, jen je z této činnosti vždy po 50 msec. "vyrušen" obsluhou časovače. Samozřejmě, že časovač je periferie, běžící naprosto samostatně a nezávisle, mimo jádro procesoru. Jinak v tomto příkladu by se samozřejmě ACC nemusel uschovávat v přerušení, neboť se s ním nepracuje, ale je dobré si na to zvyknout a dělat odkládání automaticky, člověk si pak ušetří horké chvilky. |
| Příklad - ASM i HEX soubor a schéma |
© DH servis 2002 - |