Pátá část pokračování stránek o základech programování. Dnes si povíme o tom, co je to nepřímé (indirect) adresování, a k čemu se dá použít. A tentokrát přidáme funkční příklad na vyzkoušení, který bude již umět "něco" ukazovat.
Pojmem přímé a nepřímé adresování označujeme druhy přístupu do paměti. Ať se jedná o paměť programovou nebo datovou.
Přímé adresování znamená, že v instrukci je přímo uvedeno, odkud nebo kam se mají data přenést.
Jedná se například o instrukci MOV A,R0 nebo MOV R0,data (kde data je nějaká adresa v paměti RAM) atd. Tuto adresu nejsme schopni měnit za běhu programu - tato adresa je pevná, je určena již při překladu zdrojového kódu.
Nepřímé adresování pracuje tak, že si můžeme určit, se kterou adresou máme pracovat za běhu programu.
Rozdíly mezi tímto adresováním si popíšeme velmi jednoduše opět na "kuchyňském" příkladu.
Analogie s kuchyní budiž například v tom, že pokud někomu řeknete "ukliď nůž do příborníku" je to přímé určení místa (přímé adresování). Pokud ale řeknete "zeptej se, kam patří nůž" je to nepřímé adresování. U tohoto nepřímého adresování záleží na tom koho se ptáte, zda nůž skončí v příborníku nebo v odpadkovém koši (tedy jakou Vám řeknou adresu pro umístění nože).
Opravdové rozdíly v instrukcích si ukážeme na příkladu:
MOV R0,#15 - tato instrukce uloží do registru R0 číslo 15
MOV@R0,#15 - tato instrukce uloží číslo 15 na adresu v registru R0. Nerozumíte ? Tak jinak.
Po provedení instrukce MOV R0,#15 bude v registru R0 hodnota 15. Pokud ale použiji druhou instrukci a v registru R0
budu mít například hodnotu 20H a provedu instrukci MOV @R0,#15 pak dojde k tomu, že se hodnota 15 přesune na adresu uvedenou v R0. Ne tedy do R0, ale na hodnotu v R0. Čili hodnota 15 se uloží
do buňky vnitřní RAM procesoru s adresou 20H.
Tady je na místě vysvětlení, že symbol "zavináč" zde znamená nepřímé adresování.
Další možné tvary nepřímého adresování se vztahují k instrukci MOV. Jsou to tvary:
MOVX cílový byte,zdrojový byte - Instrukce přesune byte z/do střadače do/z vnější paměti dat.
MOVC A,@A+registr - Instrukce přesune byte z programové paměti (operační kód nebo konstantu) do střadače.
Poslední instrukci si opravdu dobře zapamatujte, budeme ji za chvíli potřebovat. Tato instrukce se dá totiž využít pro čtení dat z tabulky. Jestliže si uložíme do programové paměti při programování čipu nějaká data (samozřejmě pevná) jsme schopni se v nich pohybovat (rozuměj číst je) tak, že zadáme do
registru v této instrukci počátek dat, a následně změnou hodnoty v Acc (indexem) se pohybujeme po této tabulce. Např. máme v programu zapsána následující data:
tabznak: DB 63,6,91,79,102,109,125,7,127,111,64,0 - direktiva DB znamená uložení dat přímo do paměti programu, návěští tabznak je v podstatě pouze označení počátku dat. Následně pro čtení hodnot z této tabulky provedeme toto:
MOV DPTR,#tabznak - nejprve do registrového páru DPTR uložíme adresu, kde tabulka začíná (všimněte si znaku # pro uložení konstanty - adresa v paměti programu nemůže být ani jiná, než konstanta). Tím máme tedy v DPTR uložen počátek tabulky tabznak
MOV A,#3 - do akumulátoru si uložíme pozici (index), jehož obsah chceme číst. Samozřejmě můžeme použít instrukci MOV A,cislo kdy v proměnné číslo je předem uložen index.
MOVC A,@A+DPTR - a nyní přesuneme hodnotu z pozice paměti, kterou vypočteme součtem hodnoty v akumulátoru s registrem DPTR do akumulátoru. Takže v akumulátoru máme hodnotu 3 (předem uloženou), součet s DPTR (ve kterém je počátek tabulky) nás přesune na adresu tabznak+3, což je pozice hodnoty na čtvrtém místě (první pozice je 0 !! ) - t.j.
79. Tato hodnota je zapsána do akumulátoru a my s ní můžeme dále pracovat. Pokud si do A uložíme například 0 pak po provedení MOVC A,@A+DPTR dostaneme zpět v A hodnotu (posuv DPTR+0) 63 - hodnotu z nulté pozice.
Více o (nejen) těchto instrukcích naleznete na jiné stránce tohoto webu.
Funkční příklad
Přejděme tedy ke konkrétnímu příkladu. Máme za úkol zobrazovat na sedmisegmentovém displeji (dvojčíslicovka) postupně čísla od 01 do 99. Po přetečení přes údaj 99 se bude pokračovat s čítáním opět od 00.
Interval čítání (zvyšování) údaje bude 1 sekunda.
Zadání jako ze školy, co říkáte. Takže jak na to ? V krátkosti si shrneme, co víme (a umíme) z minulých částí. Tím je zobrazení čísla na sedmisegmentovce. Dále upotřebíme znalost časovače a obsluhu jeho přetečení v přerušení. A nakonec si ukážeme využití nepřímého adresování.
Promyslím si, jak nejlépe naprogramovat tento úkol. Takže začnu od časování. Protože potřebuji volat zvyšování čísla po 1 sekundě, poběží mi stále časovač, který bude nastaven pro vyvolání přerušení po 10 milisekundách. Po těchto 10 milisekundách bude snižován (dekrementován) 1 byte softwarově
vytvořeného čítače (prostě hodnota v 1 byte) a po dosažení nuly se provede zvýšení hodnoty o 1 na displeji. Proč interval časovače zrovna 10 milisekund, když by bylo snazší využít třeba 50 msec. ? Je to proto, že časovač zároveň využijeme pro obsluhu multiplexu sedmisegmentovek - po 10 milisekundách se tedy bude střídat svit jednotlivých číslovek. To je pro oko nepostřehnutelné a bude tedy vypadat, že svítí obě čísla najednou (viz základy multiplexu). Takže ještě jednou - čítač poběží, po každém jeho přetečení (vyvolání přerušení) se
"přehodí" číslicovky (to bude tedy po 10 msec.) a odečte se 1 z byte softwarového čítače. Pokud tento softwarový čítač dosáhne nuly (100x10 msec.) je zvýšena hodnota zobrazovaná na displeji.
Vlastní zobrazování hodnot bude probíhat tak, že použijeme tabulku, ve které budeme mít uloženy jednotlivé reprezentace znaků na displeji. To znamená, že na nultou pozici si uložíme reprezentaci pro zobrazení nuly na displeji, na první si uložíme reprezentaci pro zobrazení jedničky atd. Pokud nerozumíte pojmu reprezentace znaku na segmentovce, nečetli jste patrně pozorně tuto stránku. Pro vlastní zobrazovaná čísla budeme potřebovat pracovní registr (buňku) v RAM, kde bude uložená právě zobrazovaná hodnota a jejíž obsah budeme po 1 sekundě zvyšovat (inkrementovat). Zde narazíme v programu na nám jednu ještě neznámou instrukci a tou je DA - dekadická korekce. K čemu to ?
Pokud budeme mít pracovní registr (buňku v RAM) a tu budeme zvyšovat (a její obsah zobrazovat na displeji) pak zjistíme, že k přetečení na místě jedniček dojde až po 16 inkrementacích (zvýšeních) neboť procesor pracuje v hexadecimální soustavě. Problém je, že my ale chceme napočítat do 9 a pak zvýšit řád desítek a jednotky vynulovat, a ne pokračovat hexadecimálne A,B,C,D,E a F. K tomuto ošetření právě slouží instrukce dekadické korekce. Podrobný popis její funkce naleznete na jiné stránce tohoto webu. Nám v tomto okamžiku postačí vědět, že proměnná cislo nabývá hodnot vždy od 00 do 99, přičemž ke zvyšování desítek dojde po napočítání jedniček do 9 (19 -> 20, 39 -> 40). Totéž platí i
pro desítky, kdy k přetečení celého byte dojde po 99.
Když se vrátíme k funkci programu, pak víme, jak se zvyšuje číslo v proměnné cislo a po jaké době se přepínají segmentovky. Vlastní přepínání segmentovek je řešeno tak, že je použita proměnná anoda, do které se ukládá číslo právě svítící segmentovky. Při dalším průchodu se vždy otestuje, která segmentovka svítí, přepne se na druhou a stav se uloží. Následně se opět vše opakuje a opět se přepne na prvou. Šlo by to vyřešit jednodušeji, ale tento příklad je vytržen z konstrukce. Následně je třeba ještě rozlišit, jaké číslo se zobrazí na jaké segmentovce - tedy desítky na první a jednotky na druhé. Musíme tedy nejprve hodnotu v proměnné cislo rozdělit a následně zajistit správné zobrazení. Programově je to vyřešeno tak, že např. při zobrazování desítek (tedy první pozice)
načteme obsah proměnné cislo (pro příklad její obsah třeba 39) do akumulátoru. Následně provedeme tzv. vymaskování. Protože potřebujeme pouze první pozici, kterou zobrazíme na první polovině číslicovky. K tomu s úspěchem použijeme logickou instrukci ANL. Tato instrukce provede logický součin akumulátoru s nějakou hodnotou. Pokud si vše ukážeme binárně, bude pochopení snazší:
Hodnota proměnné "cislo" v akumulátoru 39H = 00111001B
provedeme log. součin s konstantou 0F0H = 11110000B (maska)
-----------------
výsledek 30H = 00110000B
Logický součin ponechá původní hodnotu tam, kde se násobí jedničkou, tam kde se násobí nulou je ve výsledku nula (jako v normálním násobení). Výsledek je číslo 30. My potřebujeme zobrazit na první segmentovce číslo 3, proto provedeme instrukci SWAP A (převrácení vyššího a nižšího nibble) a tím dostaneme číslo 03. Následně bude volána rutina reprezentace, která přičte k začátku tabulky tabznak číslo v akumulátoru (MOVC A,@A+DPTR) a vrátí v akumulátoru
hodnotu reprezentace čísla 3 (hodnota 79), kterou když odešleme na port, číslo 3 se na segmentovce rozsvítí. Tato hodnota, ještě než se na port (katody segmentovky) odešle, se musí negovat (převrátit) z již zmiňovaného důvodu (na stránce segmentovek) zapojení bran procesoru x51. Po odeslání hodnoty na port
následuje návrat z rutiny zobrazení a provedením instrukce CLR anoda_0_dis_1 se provede sepnutí tranzistoru v anodě segmentovky pro desítky, tím se rozsvítí segmenty a my vidíme na první číslicovce dvojčísla krásnou trojku.
Pro zobrazení jednotek na druhé polovině číslicovky platí v podstatě analogický postup. Pouze přehodíme masku:
Hodnota proměnné "cislo" v akumulátoru 39H = 00111001B
provedeme log. součin s konstantou 0FH = 00001111B (maska)
-----------------
výsledek 09H = 00001001B
Protože potřebujeme dolní část čísla, kterou zde již máme, SWAP se provádět nebude. Následně bude opět volána rutina reprezentace, která přičte k začátku tabulky tabznak číslo v akumulátoru (MOVC A,@A+DPTR) a vrátí v akumulátoru
hodnotu reprezentace čísla 9 (hodnota 111), kterou když odešleme na port, a sepneme tranzistor, číslo 9 se na segmentovce rozsvítí. A tak stále dokola.
Pokud je to na Vás moc, a není to pochopitelné, stáhněte si simulátor x51 "ADSim" a v něm si program krokujte a sledujte jednotlivé hodnoty a volání přerušení od časovače. Samozřejmě je dobré si při popisu výše vzít k ruce vytištěný výpis programu a postupně si jej procházet.
Výpis programu:
$MOD52
$TITLE(BYTE SIGNED MULTIPLY)
$PAGEWIDTH(132)
$DEBUG
$OBJECT
$NOPAGING
org 00
AJMP start ;cold START
org 000Bh ;preruseni timer
jmp prerus_t0
;************ Zacatek programu ***********************
prerus_t0: push psw ;uschovej pouzivane
push acc
;----------------Zobrazovani na displej-----------------------------
call zhasni
mov TH0,#0D8H
mov TL0,#0F0H
djnz pocitadlo_preruseni,nepricitat
;pricteni
mov pocitadlo_preruseni,#100D
mov a,cislo
add a,#1H
da a
mov cislo,a
;zobrazeni
nepricitat:
MOV DPTR,#tabznak ;nacti prvni adresu tabulky znaku
MOV A,anoda
CJNE A,#01,pokr_01 ;anoda stovky
MOV A,cislo ;nacti hodnotu v BCD
anl A,#0F0H ;maskuj vyssi nibble
swap A ;posun na spravnou pozici
CALL reprezentace ;volej vyslani na port
CLR anoda_0_dis_1 ;sepni anodu
inc anoda
SJMP konprerus ;ven
;-------------------------------------------------------------------------------------
pokr_01: MOV A,cislo ;nacti hodnotu v BCD
anl A,#0FH ;maskuj nizsi nibble
CALL reprezentace ;volej vyslani na port
CLR anoda_1_dis_1
mov anoda,#1H
SJMP konprerus ;ven
;-----------------------------------------------------------------------------------
reprezentace: MOVC A,@A+DPTR ;neni nula cti reprezentaci cisla v tabulce
CPL A ;otoc
MOV P1,A ;posli na port
RET
;-----------------------------------------------------------------------------------
konprerus: pop acc ;obnov pouziva v preruseni
pop psw
reti
;**********************************************
;obsazeni portu PORT 1 jsou katody / 8 vyvodu
;PORT 3 - cast jsou anody
bseg at 30
anoda_0_dis_1 bit P3.0
anoda_1_dis_1 bit P3.4
cislo equ 30H
anoda equ 31H
pocitadlo_preruseni equ 32H
;**********************************************
; H L A V N I S M Y C K A
;**********************************************
cseg at 60h
start:
mov IE,#10000010b ; preruseni od t0
mov TMOD,#00000001b ; t0 - 16bit s predvolbou
call zhasni
mov TH0,#0D8H
mov TL0,#0F0H
mov cislo,#0H
setb tr0 ;spust casovac
mov pocitadlo_preruseni,#100D
jmp $
;-------------------------------------------------
;zhasne cislo/pro zjednoduseni se zhasinaji vsechny najednou (i "zhasnute")
zhasni: setb anoda_0_dis_1 ;nastav bit do H = vypni 1.tranzistor
setb anoda_1_dis_1 ;vypni 2. tranzistor
ret ;navrat z podprogramu
;--------------------------------------
pausa: mov r0,#05H ;nacti hodnoty
pausa_1: mov r1,#0FFH
djnz R1,$ ;dekrementuj (sniz) R1,pokud neni nula opet dekrementuj
djnz R0,pausa_1 ;R1 je nula, dekrementuj, pokud neni 0 opakuj rutinu "pausa"
ret
tabznak: DB 63,6,91,79,102,109,125,7,127,111,64,0
;reprezentace cisel 0,1, 2 ,3, 4 , 5 , 6 ,7 ,8 , 9 , -,prazdny znak
END
Ke stažení jsou tyto soubory:
| | Příklad - ASM i HEX soubor a schéma |
Tak, další kapitolu máme za sebou. Ta dnešní již mohla leckomu "provětrat" šedou kůru mozkovou, ale chce to prostě čas, a hlavně nepřestat, dokud není vše naprosto pochopitelné. Zkuste si zapojení postavit, pohrát si s nastavením časů časovače a dívat se, "co to udělá" atd., prostě se snažit, ovládnout cílevědomě chování programu podle svého přání.
Příště se podíváme na USART u x51. (pro neznalé - sériová komunikace jednočipu s okolím)
|
© DH servis 2002 - |