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

Lepší kvalitu schématu naleznete v archivu ke stažení na konci stránky 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:


Velikost 33 kByte  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)


Vytisknout stránku

Zpátky Zpátky
© DH servis 2002 -