Blog:Programovanie v assembleri i8080 pre PMD 85 (9)

Z PMD 85 Infoserver

Programovanie v assembleri i8080 pre PMD 85 (9)


Obsah

V predošlej kapitole sme si ukázali, ako vypisovať jednotlivé znaky, či už za pomoci rutín z Monitora alebo vlastnými rutinami. Čo sa týka vlastných rutín pre výpis znakov, tých spôsobov výpisu sa dá vymyslieť určite viac, to však nechám na vašu tvorivosť a fantáziu, ale možno si neskôr ešte niečo ukážeme.

Vypisovanie textov

Textom budeme rozumieť nejaký reťazec znakov uzavretý v úvodzovkách, ktorý budeme definovať za pomoci pseudoinštrukcie db. Dôležitou vecou pri vypisovaní reťazcov je vedieť, aký je daný reťazec dlhý, resp. ako je ukončený. To sa dá urobiť rôznymi spôsobmi:

  • Dĺžka reťazca je v jednom byte pred samotným reťazcom.
  • Reťazec je ukončený nulovým bytom (alebo iným kódom, ktorý nie je tlačiteľným znakom).
  • Reťazec je ukončený znakom, ktorého kód má nastavený 7. bit (zo ZX Spectra sa vžil termín "s invertovaným" znakom, pretože sa v rôznych debuggeroch takýto byte zobrazoval ako invertovaný znak). Tento spôsob teda umožňuje používať iba znaky do kódu 127 (čo ale nevylučuje používať aj znaky s diakritikou, ak si vytvoríme font s vlastným kódovaním znakov), ale ušetrí sa jeden byte na každý reťazec.

Ktorý spôsob budete používať, záleží od vašej voľby, ale my si ukážeme všetky spôsoby a pre každý z týchto spôsobov si vytvoríme macro, ktoré nám opäť sprehľadní kód.

;------------------------------------------------------------------------------

		cpu	8080
		page	0
		title	"ASM Program 2"

;------------------------------------------------------------------------------

		org	0		; program začne od adresy 0

;------------------------------------------------------------------------------
; definícia adries rutín v Monitore
ERASE		equ	85A7h
MONIT		equ	8C40h

; systémové premenné
COLOR		equ	0C03Ah

; adresa VRAM
VRAM		equ	0C000h
AV		function row,col,VRAM+row*300h+col

;------------------------------------------------------------------------------
; - dp - pripraví text txt tak, že jeho dĺžka je v prvom byte pred textom
dp		macro	txt
		  db	strlen(txt)
		  db	txt
		endm

;------------------------------------------------------------------------------
; - dz - pripraví text txt tak, že ho ukončí nulou
dz		macro	txt
		  db	txt
		  db	0
		endm

;------------------------------------------------------------------------------
; - dm - pripraví text txt tak, že posledné písmeno má svoj kód s nastaveným 7. bitom
dm		macro	txt
		  if	strlen(txt) > 1
		    db	substr(txt, 0, strlen(txt) - 1)
		  endif
		  if	strlen(txt) > 0
		    db	charfromstr(txt, strlen(txt) - 1) | 80h
		  endif
		endm

;------------------------------------------------------------------------------
Start:		di			; zakázanie prerušení
		lxi	sp,8000h	; nastav zásobník na koniec RAM

		xra	a		; nastav normálny výpis znakov
		sta	COLOR

		call	ERASE		; zmazanie obrazovky

		lxi	h,AV(4,2)	; do HL adresu VRAM pre 4. riadok a 2. stĺpec
		lxi	d,T_Text1	; adresa 1. textu
		call	PrtTxtPasAV	; vypíš text, ktorého dĺžka je v prvom byte

		lxi	h,AV(6,8)	; do HL adresu VRAM pre 6. riadok a 8. stĺpec
		lxi	d,T_Text2	; adresa 2. textu
		call	PrtTxt0AV	; vypíš text ukončený nulou

		lxi	h,AV(8,0)	; do HL adresu VRAM pre 8. riadok a 0. stĺpec
		lxi	d,T_Text3	; adresa 3. textu
		call	PrtTxtMAV	; vypíš text, ktorý má posledný znak "invertovaný"

		; návrat do Monitora (Os ready)
		jmp	MONIT

;------------------------------------------------------------------------------
; Výpis reťazca "pascalovského" typu, kedy byte dĺžky reťazca predchádza samotný
; reťazec.
; I: DE=adresa textu (prvý byte je dĺžka), HL=adresa VRAM
; O: DE=adresa posledného znaku textu, B=0
; M: DE, B, AF
PrtTxtPasAV:	shld	PrtCharDiaPos+1	; ulož adresu VRAM do inštrukcie
					; LXI H,nn v rutine PrtCharDia

;------------------------------------------------------------------------------
; Výpis reťazca "pascalovského" typu, kedy byte dĺžky reťazca predchádza samotný
; reťazec.
; I: DE=adresa textu (prvý byte je dĺžka)
; O: DE=adresa posledného znaku textu, B=0
; M: DE, B, AF
PrtTxtPas:	ldax	d		; prevezmi do A dĺžku reťazca
		mov	b,a		; a ulož ju do B
PrtTxtPasL:	inx	d		; posuň ukazateľ na znaky reťazca
		ldax	d		; prevezmi znak do A
		call	PrtCharDia	; vypíš znak
		dcr	b		; zníž počítadlo znakov
		jnz	PrtTxtPasL	; a opakuj pre celý reťazec
		ret

;------------------------------------------------------------------------------
; Výpis reťazca ukončeného nulou.
; I: DE=adresa textu ukončeného nulou, HL=adresa VRAM
; O: DE=adresa ukončovacej nuly
; M: DE, AF
PrtTxt0AV:	shld	PrtCharDiaPos+1	; ulož adresu VRAM do inštrukcie
					; LXI H,nn v rutine PrtCharDia

;------------------------------------------------------------------------------
; Výpis reťazca ukončeného nulou.
; I: DE=adresa textu ukončeného nulou
; O: DE=adresa ukončovacej nuly
; M: DE, AF
PrtTxt0:	ldax	d		; prevezmi znak do A
		ora	a		; je to ukončovacia nula?
		rz			; áno, vráť sa
		call	PrtCharDia	; vypíš znak
		inx	d		; posuň ukazateľ na ďalší znak reťazca
		jmp	PrtTxt0		; opakuj pre celý reťazec

;------------------------------------------------------------------------------
; Výpis reťazca, ktorý má v poslednom znaku nastavený 7. bit ("invertovaný" znak).
; I: DE=adresa textu ukončeného nulou, HL=adresa VRAM
; O: DE=adresa posledného znaku textu
; M: DE, AF
PrtTxtMAV:	shld	PrtCharDiaPos+1	; ulož adresu VRAM do inštrukcie
					; LXI H,nn v rutine PrtCharDia

;------------------------------------------------------------------------------
; Výpis reťazca, ktorý má v poslednom znaku nastavený 7. bit ("invertovaný" znak).
; I: DE=adresa textu ukončeného nulou
; O: DE=adresa posledného znaku textu
; M: DE, AF
PrtTxtM:	ldax	d		; prevezmi znak do A
		ani	7Fh		; odmaskuj iba platné bity
		call	PrtCharDia	; vypíš znak
		ldax	d		; opäť vezmi znak do A
		ora	a		; je to posledný znak s nastaveným 7. bitom?
		rm			; áno, vráť sa
		inx	d		; posuň ukazateľ na ďalší znak reťazca
		jmp	PrtTxtM		; opakuj pre celý reťazec

;------------------------------------------------------------------------------
; Vypísanie znaku, ktorého kód je v A.
; Rutina využíva vlastný font, kde sú znaky vysoké 8 bodov (mikroriadkov).
; Riadok je ale vysoký 12 mikroriadkov a horné 4 mikroriadky slúžia pre
; diakritické znamienka.
; I: A=znak <32, 127> a <192, 255>, [PrtCharDiaPos+1]=adresa VRAM
; O: -
; M: AF
PrtCharDia:	cpi	32		; ak je to riadiaci kód, návrat
		rc			; neplatný kód znaku, ihneď sa vráť
		cpi	127+1		; ak je to znak zo základného ASCII
		jc	PrtCharDiaN	; skoč ďalej s CY=1
		cpi	192		; kód znaku medzi <128, 191>
		rc			; nie je podporovaný, vráť sa
PrtCharDiaN:	push	h		; odpamätaj registre
		push	d
		push	b
		push	psw		; odpamätaj kód znaku aj CY
		jc	PrtCharDiaN1	; skoč, ak je to znak bez diakritiky
		ani	3Fh		; odmaskuj poradie znaku s diakritikou
		mov	c,a		; v tabuľke a ulož do BC
		mvi	b,0
		lxi	h,CsChar	; tabuľka zodp. znakov bez diakritiky
		dad	b		; pripočítaj offset
		mov	a,m		; a ulož do A základný znak
PrtCharDiaN1:	sui	32		; uprav kód znaku na rozsah od 0
		add	a		; kód znaku x2
		mov	l,a		; ulož do HL
		mvi	h,0
		dad	h		; x4
		dad	h		; x8; HL = (kód_znaku - 32) * 8
		lxi	b,Font		; BC=bázová adresa fontu
		dad	b		; pripočítaj k offsetu znaku
		pop	psw		; obnov kód znaku a CY
		push	h		; adresu predlohy znaku na zásobník
		mvi	l,NN		; bez diakritického znamienka
		jc	PrtCharDiaN2	; pre základne znaky
		ani	3Fh		; odmaskuj poradie znaku s diakritikou
		mov	c,a		; v tabuľke a ulož do BC
		mvi	b,0
		lxi	h,CsDiaChar	; nájdi v tabuľke nižší byte predlohy
		dad	b		; diakritického znamienka
		mov	l,m		; a ulož do L
PrtCharDiaN2:	mvi	h,FontDia/256	; vyšší byte adresy predlohy diakr. zn.

		xchg			; DE=predloha diakritického znamienka
PrtCharDiaPos:	lxi	h,0		; adresa VRAM
PrtCharDiaC:	mvi	c,0		; atribút do C

		mvi	b,4		; 4 mikroriadky nad znakom diakr. zn.
PrtCharDiaK:	ldax	d		; vezmi byte predlohy diakr. zn.
		inx	d		; posun na ďalší byte predlohy
		xra	c		; pridaj atribút
		mov	m,a		; ulož do VRAM
		mov	a,l		; posun na nasledujúci mikroriadok
		adi	40h
		mov	l,a
		jnc	PrtCharDiaK0	; skoč, ak nedošlo k pretečeniu
		inr	h		; inak inkrementuj ešte H
PrtCharDiaK0:	dcr	b		; opakuj pre celé diakritické znamienko
		jnz	PrtCharDiaK

		pop	d		; obnov predlohu základného znaku
		mvi	b,8		; výška znaku 8 mikroriadkov
PrtCharDiaL:	ldax	d		; vezmi byte predlohy
		inx	d		; posun na ďalší byte predlohy
		xra	c		; pridaj atribút
		mov	m,a		; ulož do VRAM
		mov	a,l		; posun na nasledujúci mikroriadok
		adi	40h
		mov	l,a
		jnc	PrtCharDiaL0	; skoč, ak nedošlo k pretečeniu
		inr	h		; inak inkrementuj ešte H
PrtCharDiaL0:	dcr	b		; opakuj pre celý znak
		jnz	PrtCharDiaL
		lhld	PrtCharDiaPos+1	; aktuálna adresa VRAM
		inx	h		; posun na ďalšiu znakovú pozíciu VRAM
		mov	a,l		; opustili sme textový riadok?
		ani	3Fh
		cpi	48		; 48 znakov na riadok
		jc	PrtCharDiaE	; nie, návrat
		mov	a,l		; áno, prejdi na ďalší textový riadok
		ani	0C0h		; CR - návrat na začiatok riadku
		mov	l,a
		inr	h		; LF - ďalší riadok (12 mikroriadkov)
		inr	h
		inr	h
PrtCharDiaE:	shld	PrtCharDiaPos+1	; ulož novú adresu VRAM
		pop	b		; obnov registre
		pop	d
		pop	h
		ret

;------------------------------------------------------------------------------
;++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;------------------------------------------------------------------------------
; tabuľka základných znakov, ktoré korešpondujú so znakmi s diakritikou podľa
; kódovania v KOI-8čs
CsChar:		db	" a cder uiullonooarstu e yz     "
		db	" A CDER UIULLONOOARSTU E YZ     "

; tabuľka nižších bytov adresy predlohy diakritického znamienka
CsDiaChar:	db	NN,D1,NN,M1,M1,M1,D1,NN
		db	P1,D1,K1,D2,M3,P1,M1,D1
		db	V1,P1,M1,M1,M3,D1,NN,D1
		db	NN,D1,M1,NN,NN,NN,NN,NN
		db	NN,D2,NN,M2,M2,M2,D2,NN
		db	P2,D2,K2,D2,M3,P2,M2,D2
		db	V2,P2,M2,M2,M2,D2,NN,D2
		db	NN,D2,M2,NN,NN,NN,NN,NN

;------------------------------------------------------------------------------
; dáta fontu
Font:		binclude "../data/font.bin"

; dáta diakritických znamienok
; Potrebujeme, aby predlohy diakritických znamienok neprekročili 256 bytovú
; "stránku", teda aby mali všetky predlohy rovnaký vyšší byte adresy.
	if (($ / 256) <> (($ + 48) / 256))
		align	256		; zarovnanie na adresu MOD 256
	endif
FontDia:	binclude "../data/font-dia.bin"

; definícia návestí na nižší byte adresy predlohy diakritických znamienok
NN		equ	(FontDia+0)&255	 ; nič
D1		equ	(FontDia+4)&255	 ; dĺžeň nad malým písmenom
M1		equ	(FontDia+8)&255	 ; mäkčeň nad malým písmenom
M3		equ	(FontDia+12)&255 ; mäkčeň pre malé/veľké L a malé T
P1		equ	(FontDia+16)&255 ; prehláska nad malým písmenom
K1		equ	(FontDia+20)&255 ; krúžok nad malým U
V1		equ	(FontDia+24)&255 ; vokáň nad malým O
D2		equ	(FontDia+28)&255 ; dĺžeň nad veľkým písmenom
M2		equ	(FontDia+32)&255 ; mäkčeň nad veľkým písmenom
P2		equ	(FontDia+36)&255 ; prehláska nad veľkým písmenom
K2		equ	(FontDia+40)&255 ; krúžok nad veľkým U
V2		equ	(FontDia+44)&255 ; vokáň nad veľkým O

;------------------------------------------------------------------------------
; definícia vlastného charsetu (transformačnej tabuľky) kódovania znakov
; CP1250 -> KOI8čs
		charset	'á',0C1h
		charset	'č',0C3h
		charset	'ď',0C4h
		charset	'ě',0C5h
		charset	'ŕ',0C6h
		charset	'ü',0C8h
		charset	'í',0C9h
		charset	'ů',0CAh
		charset	'ĺ',0CBh
		charset	'ľ',0CCh
		charset	'ö',0CDh
		charset	'ň',0CEh
		charset	'ó',0CFh
		charset	'ô',0D0h
		charset	'ä',0D1h
		charset	'ř',0D2h
		charset	'š',0D3h
		charset	'ť',0D4h
		charset	'ú',0D5h
		charset	'é',0D7h
		charset	'ý',0D9h
		charset	'ž',0DAh
		charset	'Á',0E1h
		charset	'Č',0E3h
		charset	'Ď',0E4h
		charset	'Ě',0E5h
		charset	'Ŕ',0E6h
		charset	'Ü',0E8h
		charset	'Í',0E9h
		charset	'Ů',0EAh
		charset	'Ĺ',0EBh
		charset	'Ľ',0ECh
		charset	'Ö',0EDh
		charset	'Ň',0EEh
		charset	'Ó',0EFh
		charset	'Ô',0F0h
		charset	'Ä',0F1h
		charset	'Ř',0F2h
		charset	'Š',0F3h
		charset	'Ť',0F4h
		charset	'Ú',0F5h
		charset	'É',0F7h
		charset	'Ý',0F9h
		charset	'Ž',0FAh

;------------------------------------------------------------------------------
; Texty (tento súbor je uložený v kódovaní CP1250)
T_Text1:	dp	"Tento reťazec má dĺžku uvedenú v prvom byte."

T_Text2:	dz	"Tento reťazec je ukončený nulou."

T_Text3:	dm	"Retazec s nastavenym 7. bitom v poslednom znaku."

;------------------------------------------------------------------------------
; zrušenie špeciálneho charsetu
		charset

;------------------------------------------------------------------------------

		end

;------------------------------------------------------------------------------

Na začiatku sme si teda zadefinovali 3 makrá dp, dz a dm pre každý zo spôsobov definície reťazca. V makrách použité funkcie strlen, substr a charfromstr sú interné funkcie macroassemblera AS. Samotné makrá sú myslím dostatočne zrozumiteľné, takže nepotrebujú ďalšie vysvetlenie. Na konci sme si za pomoci týchto makier zadefinovali 3 rôzne reťazce rovnakým spôsobom, ako by sme to robili pomocou pseudoinštrukcie db. V tomto prípade sa ale reťazec pripraví príslušným spôsobom a je vidieť, že takto je kód prehľadnejší. Odporúčam pozrieť si listing prekladu (súbor s príponou .lst), kde uvidíte, ako sú makrá rozvinuté do kódu. Listing prekladu je veľmi užitočný hlavne pri debuggovaní, kde vidíme konkrétne adresy, na ktorých sa jednotlivé rutiny a dáta nachádzajú.

Pre výpis znaku používame našu rutinu PrtCharDia, ktorú sme si ale mierne upravili. Zmena je ale iba v tom, že cieľová adres VRAM už nevstupuje do rutiny v registri HL, ale je ju treba pripraviť zápisom na adresu PrtCharDiaPos+1 vo vnútri samotnej rutiny. Adresa VRAM sa potom podľa očakávania po vypísaní znaku inkrementuje a uloží sa opäť na adresu PrtCharDiaPos+1. Tým sme dosiahli, že rutina PrtCharDia nemení ani register HL, čo sa nám môže hodiť.

V predošlej kapitole som na to priamo nepoukázal, ale asi ste si všimli, že sme farbu textu v rutine PrtCharDia nastavovali zápisom atribútu na adresu PrtCharDiaC+1, teda prakticky sme modifikovali hodnotu číselného operandu priamo v inštrukcii mvi c,0. Teraz rovnakým spôsobom nastavujeme aj adresu VRAM. Na adrese PrtCharDiaPos je inštrukcia lxi h,0, kedy zápisom na adresu PrtCharDiaPos+1 modifikujeme operand v tejto inštrukcii. Táto technika samomodifikujúceho sa kódu sa používa veľmi často, pretože daná hodnota jednak nemusí byť niekde inde v pamäti a zároveň to skráti a aj zrýchli kód. Samozrejme, ak by sme písali kód, ktorý bude bežať v ROM, táto technika sa, pochopiteľne, nedá použiť.

Prvá rutina PrtTxtPas, resp. PrtTxtPasAV vypisuje reťazec zadefinovaný makrom dp. Adresa reťazca vstupuje do rutiny v registri DE. Na úvod sa do registra B pripraví dĺžka reťazca a potom sa postupne vyberajú a vypisujú jednotlivé znaky. Vo výpisovej slučke sa ukazateľ na znaky reťazca posunie pred vypísaním znaku, keďže pri prvej iterácii potrebujeme preskočiť byte dĺžky reťazca. Po každom vypísanom znaku sa zníži počítadlo znakov v registri B. Kým nie je počítadlo nulové, pokračuje sa vo vypisovaní znakov. Keďže rutina PrtCharDia nemení okrem AF žiadne iné registre, nemusíme to pri volaní tejto rutiny robiť my. Pri volaní rutiny PrtTxtPasAV musí byť cieľová adresa VRAM v registri HL, ale pri volaní rutiny PrtTxtPas sa pokračuje pri výpise od poslednej pozície.

Druhá rutina PrtTxt0, resp. PrtTxt0AV vypisuje reťazec zadefinovaný makrom dz. Adresa reťazca vstupuje do rutiny opäť v registri DE. Výpisová slučka sa líši tým, že hneď na začiatku sa prevezme znak do akumulátora a overuje sa, či je to už koniec reťazca, teda že je akumulátor nulový. Ak to tak je (nastaví sa príznak Z), urobí sa návrat z rutiny. Po výpise znaku sa inkrementuje ukazateľ na text a slučka sa opakuje. Pre obe verzie rutín platí to isté, čo v predošlom odstavci.

Tretia rutina PrtTxtM, resp. PrtTxtMAV vypisuje reťazec zadefinovaný makrom dm. Pred vypísaním znaku sa vynuluje prípadne nastavený 7. bit v kóde znaku. Až po vypísaní znaku sa overuje, či to bol posledný znak, teda či jeho kód mal nastavený 7. bit a to testovaním príznaku znamienka S.

V hlavnom programe sú už obvyklé veci a jednoducho sa iba zavolajú jednotlivé rutiny s predchádzajúcim nastavením používaných registrov.
Za zmienku ešte stojí, že na zmazanie obrazovky sme použili rutinu Monitora ERASE. Tá zmaže obrazovku vyplnením VRAM hodnotou systémovej premennej COLOR. Rutina ERASE mení všetky registre.

Nakoniec je potrebné vysvetliť definíciu vlastného charsetu. Keďže náš zdrojový text píšeme na PC, kedy je zdrojový súbor uložený v kódovaní CP1250 (ANSI Central European) a naša rutina vypisuje znaky v kódovaní KOI-88čs, potrebujeme, aby sa naše texty už pri kompilácii "transformovali" do správneho kódovania. Toto nám umožňuje pseudoinštrukcia charset. Pomocou nej definujeme dvojice hodnôt pre transformáciu jedného kódu znaku na iný. Prvá hodnota je znak zapísaný v PC editore v kódovaní CP1250 a druhá hodnota je kód daného znaku v KOI-8čs (viď tabuľka znakov v návode k PMD 85-3). Táto definícia zmeny charsetu musí byť, samozrejme, uvedená skôr, ako je zadefinovaný prvý text, ktorý používa znaky s diakritikou. Pre vypnutie "prekódovania" znakov sa použije pseudoinštrukcia charset bez parametrov. Táto pseudoinštrukcia môže mať aj iný počet parametrov, ale to si už preštudujte v návode ku makroassembleru AS.

Ďalšie spôsoby výpisu textu

Uvedené rutiny pre výpis textu vyžadovali, aby adresy textu a VRAM boli pripravené v registroch DE a HL. Ukážeme si ale aj iný spôsob predania parametra do rutiny a to tak, že bude uvedené za volaním rutiny.

		...
		call	PrtTxt0R	; vypíš text
		dw	AV(10,1)	; cieľová adresa VRAM
		dw	T_Text4		; adresa textu
		...

;------------------------------------------------------------------------------
; Vypísanie textu ukončeného nulou. Adresa VRAM a adresa textu sú uvedené za
; volaním rutiny.
; I: adresa VRAM a textu za volaním rutiny ako dw
; O: -
; M: HL, DE, AF
PrtTxt0R:	pop	h		; návratová adresa z rutiny do HL
		mov	e,m		; do DE prevezmi adresu VRAM, ktorá
		inx	h		; je uvedená za volaním rutiny
		mov	d,m
		inx	h
		xchg			; prehoď adresu VRAM do HL
		shld	PrtCharDiaPos+1	; a ulož do rutiny PrtCharDia
		xchg			; vráť do HL adresu za volaním rutiny
		mov	e,m		; do DE teraz prevezmi adresu textu
		inx	h
		mov	d,m
		inx	h
		push	h		; ulož novú návratovú adresu na zásobník
PrtTxt0RL	ldax	d		; prevezmi znak do A
		ora	a		; je to ukončovacia nula?
		rz			; áno, vráť sa
		call	PrtCharDia	; vypíš znak
		inx	d		; posuň ukazateľ na ďalší znak reťazca
		jmp	PrtTxt0RL	; opakuj pre celý reťazec

;------------------------------------------------------------------------------
T_Text4:	db	"Tento reťazec je ukončený nulou,"
		db	" jeho adresa,   spolu s cieľovou adresou VRAM, sú"
		db	" uvedené za    volaním rutiny PrtTxt0VA"
		dz	" - najprv adresa VRAM,  potom adresa textu."

Ako sme si povedali v popise inštrukcií, pri vykonávaní inštrukcie call sa na zásobník uloží návratová adresa, čo je adresa inštrukcie za touto inštrukciou. V našom prípade tam ale pri volaní rutiny PrtTxt0R nie je žiadna inštrukcia, ale sú tam dve dvojbytové hodnoty. Preto si na začiatku rutiny PrtTxt0R vyberieme z vrcholu zásobníka "návratovú" adresu do HL a odtiaľ si postupne vyzdvihneme najprv adresu VRAM (ktorú uložíme do rutiny PrtCharDia) a potom adresu textu do DE. Následne už skutočnú návratovú adresu za týmito dvoma adresami uložíme späť na zásobník. Zbytok rutiny je už známy.
Keďže T_Text4 používa znaky s diakritikou, musí byť v bloku za definíciou charset, tak ako ostatné texty. Tu sme to pre skrátenie textu už neuviedli. Pri definícii textu T_Text4 si ešte všimnite, že až na poslednom riadku textu je uvedené makro dz a predošlé riadky sú definované pomocou pseudoinštrukcie db. Ukončenie reťazca nulou chceme až na jeho konci a keďže sme chceli pre sprehľadnenie zdrojového kódu rozdeliť tento dlhý text do viacerých riadkov, museli sme pre prvé 3 riadky použiť pseudoinštrukciu db.

Za volaním rutiny ale môže byť aj samotný text. Princíp bude rovnaký. Zo zásobníka si vyberieme návratovú adresu, kde budeme mať adresu VRAM a aj samotný text.

		...
		call	PrtTxt0RT	; vypíš text
		dw	AV(15,3)	; cieľová adresa VRAM
		dz	"Tento text je za volanim rutiny PrtTxt0RT."
		...

;------------------------------------------------------------------------------
; Vypísanie textu ukončeného nulou. Adresa VRAM a aj samotný text sú uvedené za
; volaním rutiny.
; I: Adresa VRAM a text za volaním rutiny
; O: -
; M: HL, DE, AF
PrtTxt0RT:	pop	h		; návratová adresa z rutiny do HL
		mov	e,m		; do DE prevezmi adresu VRAM, ktorá
		inx	h		; je uvedená za volaním rutiny
		mov	d,m
		inx	h
		xchg			; prehoď adresu VRAM do HL
		shld	PrtCharDiaPos+1	; a ulož do rutiny PrtCharDia
					; v DE je teraz adresa textu
PrtTxt0RTL	ldax	d		; prevezmi znak do A
		ora	a		; je to ukončovacia nula?
		jz	JumpDE		; áno, skoč sa nepriamo vrátiť z rutiny
		call	PrtCharDia	; vypíš znak
		inx	d		; posuň ukazateľ na ďalší znak reťazca
		jmp	PrtTxt0RL	; opakuj pre celý reťazec

JumpDE:		xchg			; prehoď adresu skoku z DE do HL
JumpHL:		pchl			; skoč na adresu v HL

Vyzdvihnutie cieľovej adresy VRAM je rovnaký, ako v predošlom prípade. Po uložení adresy VRAM do rutiny PrtCharDia nám ale v DE zostane ukazateľ na samotný text a rutina pokračuje ako obvykle vypisovaním jednotivých znakov. Líši sa reakcia na ukončovaciu nulu, kde namiesto okamžitého návratu pomocou inštrukcie rz, skočíme na nepriamy návrat z rutiny (jz JumpDE). Návrat pomocou inštrukcie rz ani nemôžeme urobiť, pretože na zásobníku naša návratová adresa nie je, na rozdiel od predošlého prípadu, kedy sme ju tam uložili. My máme návratovú adresu prakticky v registri DE a preto skokom na návestie JumpDE najprv prehodíme túto adresu do HL a nepriamo na ňu skočíme.

Tu ešte upriamim pozornosť na jednu maličkosť. Určite ste si všimli, že po dosiahnutí konca reťazca register DE ukazuje na ukončovaciu nulu reťazca a práve na túto adresu sa vlastne prevedie návrat z rutiny. Máte pravdu, exaktne vzaté, mali by sme sa vrátiť o byte neskôr, pretože až tam začína ďalšia "platná" inštrukcia. Keďže ale 0 je kód inštrukcie nop, nevadí to a nič nebezpečné sa nestane.

Nepriamy návrat z rutiny by sa dal urobiť aj iným spôsobom a to uložením registra DE na zásobník push de a obvyklým návratom inštrukciou ret, ale táto alternatíva trvá 21T, oproti 9T z nášho riešenia.

Text za volaním rutiny v našom prípade nepoužíva znaky s diakritikou, keďže definíciu charsetu máme až neskôr. Šlo by to samozrejme realizovať tak, že definíciu charsetu by sme mali hneď na začiatku zdrojového textu (napr. v mieste, kde sme si definovali makrá) a tak všetky texty v zdrojovom texte by už mohli byť s diakritikou.

Vypisovanie textov z tabuľky

Niekedy je vhodné mať všetky texty/reťazce na jednom mieste a mať ich "očíslované". To znamená, že pre vypísanie konkrétneho reťazca zavoláme príslušnú rutinu s číslom reťazca.
Ukážeme si dva spôsoby. Prvý spôsob bude spočívať vo vyhľadávaní koncov textov a tým nájdení toho požadovaného (rutina PrtTxtN1). Druhý spôsob bude postavený na tabuľke adries textov (rutina PrtTxtN2).

		...
		; vypísanie textu podľa čísla textu - verzia vyhľadania textu
PrtTN1: 	lxi	h,AV(1,0)	; počiatočná adresa VRAM
		mvi	c,8		; 8 reťazcov
PrtTN1L:	mov	b,c		; číslo reťazca do B
		call	PrtTxtN1	; vypíš daný reťazec
		inr	h		; prejdi na ďalší textový riadok
		inr	h
		inr	h
		dcr	c		; opakuj pre všetky reťazce
		jnz	PrtTN1L

		; vypísanie textu podľa čísla textu - verzia s tabuľkou
PrtTN2: 	lxi	h,AV(11,0)	; počiatočná adresa VRAM
		mvi	b,8		; 8 reťazcov
PrtTN2L:	push	b		; odpamätaj počítadlo na zásobník
		call	PrtTxtN2	; vypíš daný reťazec
		pop	b		; obnov počítadlo
		inr	h		; prejdi na ďalší textový riadok
		inr	h
		inr	h
		dcr	b		; opakuj pre všetky reťazce
		jnz	PrtTN2L
		...

;------------------------------------------------------------------------------
; Vypísanie textu podľa čísla textu - verzia vyhľadania textu.
; I: HL=adresa VRAM, B=číslo textu <1, N>
; O: -
; M: DE, B, AF
PrtTxtN1:	lxi	d,T_TextBase	; bázová adresa textov
PrtTxtN1B:	dcr	b		; zníž číslo textu
		jz	PrtTxt0AV	; našli sme, skoč ho vypísať
PrtTxtN1L:	ldax	d		; vezmi znak
		inx	d		; posuň ukazateľ
		ora	a		; koniec textu?
		jnz	PrtTxtN1L	; nie, hľadaj jeho koniec
		jmp	PrtTxtN1B	; opakuj, kým nenájdeš požadovaný text

;------------------------------------------------------------------------------
; Vypísanie textu podľa čísla textu - verzia s tabuľkou adries textov.
; I: HL=adresa VRAM, B=číslo textu <1, N>
; O: -
; M: DE, BC, AF
PrtTxtN2:	xchg			; adresa VRAM dočasne do DE
		mov	a,b		; číslo textu do A
		add	a		; x2 - adresa v tabuľke má 2 byty
		mov	c,a		; ulož offset do BC
		mvi	b,0
		lxi	h,T_TxtBTab-2	; adresa tabuľky adries textov
		dad	b		; pripočítaj offset
		mov	a,m		; vyber adresu textu do HL
		inx	h
		mov	h,m
		mov	l,a
		xchg			; prehoď adresu VRAM a adresu textu
		jmp	PrtTxt0AV	; a skoč vypísať text

;------------------------------------------------------------------------------
; Tabuľka textov
T_TextBase:
T_TxtB1:	dz	"Prvý"
T_TxtB2:	dz	"Druhý"
T_TxtB3:	dz	"Tretí"
T_TxtB4:	dz	"Štvrtý"
T_TxtB5:	dz	"Piaty"
T_TxtB6:	dz	"Šiesty"
T_TxtB7:	dz	"Siedmy"
T_TxtB8:	dz	"Ôsmy"

; Tabuľka adries textov
T_TxtBTab:	dw	T_TxtB1,T_TxtB2,T_TxtB3,T_TxtB4
		dw	T_TxtB5,T_TxtB6,T_TxtB7,T_TxtB8

V prvej rutine PrtTxtN1 sa najprv pripraví do registra DE bázová adresa jednotlivých textov. Následne sa zníži číslo požadovaného textu v registri B a ak sa register B vynuloval, máme v registri DE adresu požadovaného textu a môžeme ho vypísať. Inak sa potom prechádza po jednotlivých znakoch aktuálneho textu a hľadá sa jeho koniec. Tým sa dostaneme na začiatok ďalšieho textu. Toto sa opakuje, kým sa teda nevynuluje register B.
Vo fragmente kódu od návestia PrtTN1 sa postupne vypíše všetkých 8 textov od ôsmeho po prvý. Číslo textu a zároveň počítadlo je v registri C, keďže rutina PrtTxtN1 register B vynuluje. Jednotlivé texty sa vypisujú na osobitných riadkoch, čo sa udeje už známou inkrementáciu vyššieho bytu adresy VRAM o 3.

V druhej rutine PrtTxtN2 sa číslo textu vynásobí dvoma, čím dostaneme offset do tabuľky adries jednotlivých textov T_TxtBTab. Do registra HL sa uloží adresa tejto tabuľky znížená o 2, keďže číslujeme texty od 1. Po pripočítaní offsetu k adrese tabuľky sa vyberie už samotná adresa textu do HL a po prehodení s adresou VRAM sa text vypíše.
Fragment kódu pre výpis všetkých textov touto rutinou je veľmi podobný predošlému, len počítadlo a teda aj číslo textu je priamo v registri B a preto sa musí odpamätávať na zásobníku.

Každá z týchto dvoch rutín má svoje výhody aj nevýhody. Prvá je časovo náročnejšia a čím viac je textov, tým dlhšie trvá nájdenie posledného textu. Nepotrebuje však osobitnú tabuľku adries textov, ktorá pri veľkom počte textov zaberá dvojnásobok ich počtu. Druhá rutina potrebuje pre nájdenie ktoréhokoľvek textu vždy rovnaký čas, ale vyžaduje už spomenutú tabuľku adries textov.

Na stiahnutie

Opäť je tu na stiahnutie celý projekt pre túto kapitolu a opäť sú všetky príklady v jednom zdrojovom súbore.


<<Vypisujeme znaky na obrazovku Vypisujeme čísla >>