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

Z PMD 85 Infoserver

Programovanie v assembleri i8080 pre PMD 85 (8)


Obsah

Máme za sebou rýchly prehľad inštrukcií a aj pripravené prostredie pre editáciu zdrojových textov, tak sa môžeme konečne pustiť do praktických príkladov.

Každá kapitola bude zrejme ako jeden projekt, sem tam budeme niečo navzájom medzi projektami zdieľať a vždy tu bude celý projekt na stiahnutie.

Keď si stiahnete daný balíček a budete ho chcieť použiť u seba, nezabudnite si zmeniť absolútne cesty v nasledujúcich súboroch:

  • build.bat - Dávkový súbor - PROJPATH
  • XYZ.ppr - Projektový súbor PSPADu - Compilator.FileName a DefaultDir
  • .vscode\XYZ.code-workspace - Projektový súbor VS Code - oba atribúty path


Ako bolo spomenuté v súvislosti so šablónou Snapu, budeme pracovať s PMD 85-2A. Sú na to tieto dôvody:

  • ROM Monitor je štandardne od adresy 8000h
  • je omnoho lepší, ako ten pôvodný na PMD 85-1
  • Video RAM sa neprekrýva s ROM Monitorom, ako je to u PMD 85-3 (na PMD 85-3 táto skutočnosť prináša menšie nepríjemnosti so stránkovaním pri potrebe používať rutiny Monitora a aj pracovať s Video RAM po vlastnej osi)
  • ak by sme to potrebovali, môžeme využiť rozšírenú pamäť a AllRAM režim

Ak sa teda budeme odkazovať na rutiny v Monitore alebo systémové premenné, vždy sa to bude týkať "dvojkovej verzie" Monitora. Rovnako tak názvy rutín a systémových premenných budú vychádzať z komentovaného výpisu Monitora PMD 85-2.


A ešte na záver musím spomenúť jednu dôležitú vec a to rozloženie pamäte PMD 85-2A:

  • 0000h - 7FFFh : Užívateľská RAM 32 kB - v tejto oblasti budú bežať naše programy
  • 8000h - 8FFFh : Monitor ROM 4 kB
  • 9000h - 9FFFh : Užívateľská RAM 4 kB - táto pamäť nie je na PMD 85-1 a -2 a pri našich pokusoch ju nevyužijeme
  • 0A000h - 0AFFFh : Monitor ROM 4 kB (zrkadlo z adresy 8000h)
  • 0B000h - 0BFFFh : Užívateľská RAM 4 kB - táto pamäť nie je na PMD 85-1 a -2 a pri našich pokusoch ju nevyužijeme
  • 0C000h - 0FFFFh : Video RAM a zápisník systému (systémové premenné)

Ak je zapnutý AllRAM režim, tak je ROM odpojená a v celom adresnom priestore je RAM.

Zdrojový text

Ešte malá odbočka, než sa dostaneme k prvému príkladu. Hlavný zdrojový súbor musí začínať pseudoinštrukciou cpu, ktorá definuje mikroprocesor, ktorého sa nasledujúci kód týka, inak bude kompilátor hlásiť desiatky, podľa brata stovky, chýb.

Ak som napísal Hlavný zdrojový súbor, mal som na mysli to, že v projekte môže byť viac zdrojových súborov a tie budeme vkladať do hlavného zdrojového súboru osobitne pomocou pseudoinštrukcie include.

Je to na každého osobnej ľubovôli a vkuse, ale ja osobne preferujem nasledujúce konvencie pri písaní programu v assembleri:

  • editor je nastavený tak, že sa vkladajú skutočné tabulátory a ich veľkosť je nastavená na 8 znakov
  • prázdne polia inštrukčného riadku sa teda "preskakujú" tabulátorom, nie medzerami
  • kompilátor AS spúšťam s prepínačom U, čím sa prepne do case-sensitive režimu, kedy sa prihliada na veľkosť písmen symbolov
  • inštrukcie a pseudoinštrukcie sa píšu malými písmenami
  • pre pole návestia je vyhradených 16 znakov, teda 2 tabulátory
  • návestie je ukončené dvojbodkou a teda môže mať maximálne 14 znakov, aby zostala aspoň jedna medzera pred inštrukciou
  • výnimkou je návestie pred pseudoinštrukciou equ alebo macro, kde sa dvojbodka nepíše
  • názvy návestí sú v štýle CamelCase s veľkým písmenom na začiatku
  • poznámka za inštrukciou začína na znaku 41 (ak parametre inštrukcie náhodou presahujú cez stĺpec 41, tak sa poznámka oddelí jednou medzerou)

Nie je to nemeniteľná doktrína, ale zvoliť si jednotnú "štábnu kultúru" je veľmi dôležité, aby potom zdrojové súbory neboli Každej pes, jiná ves.

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

		cpu	8080		; definícia cieľového mikroprocesora
		page	0		; nechceme stránkovať listing programu
		title	"ASM Program 1"	; nepodstatný titulok programu ;-)

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

		org	0		; program začína od adresy 0

;------------------------------------------------------------------------------
Start:		...
		tu bude náš hlavný program
		...
;------------------------------------------------------------------------------

;------------------------------------------------------------------------------
; ďalšie zdrojové súbory sa budú pripájať osobitne takto
		include "subor2.a8080"
		include "subor3.a8080"

;------------------------------------------------------------------------------
		end			; označenie konca programu

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

Vypisujeme znaky na obrazovku

Na PMD 85 nie je typický textový režim zobrazovania. Všetko, čo sa zobrazuje, je v "grafickom režime", teda sa priamo zapisuje do Video RAM(ďalej VRAM), čo ale prináša výhodu v možnosti zobrazovať "akékoľvek" znaky. Je to ale samozrejme náročnejšie na čas procesora, lebo si to musí sám "odmakať".

Organizácia VRAM je popísaná v príslušnom článku, tak len v krátkosti. Rozlíšenie je 288 x 256 bodov, z jedného bytu je zobrazovaných 6 bodov a aj preto má obvykle jeden znak na šírku 6 bodov. Z toho potom vidíme, že na riadok sa vojde 288 / 6 = 48 znakov. Výška predlohy znaku je 8 bodov - 8 bytov na znak. Počet riadkov na obrazovke v štandardnej rutine pre vypísanie znaku závisí od veľkosti riadkovania v systémových premenných a štandardne je to 9 mikroriadkov. Takto sa na obrazovku vojde 25 riadkov mimo dialógového riadku.

Výpis znaku rutinami Monitora

V Monitore PMD 85 je štandardná rutina PRTOUT pre výpis znaku na adrese 8500h. Táto rutina sa postará o "odriadkovanie" na konci riadku, ale aj o rolovanie obrazu, ak je kurzor na poslednom znaku posledného riadku obrazovky. Zároveň akceptuje jediný "riadiaci kód" 1Ch pre zmazanie celej obrazovky a nastavenie kurzora vľavo hore. Kód znaku, ktorý sa má vypísať, sa predáva do rutiny v registri A a mení iba register AF. Rutina používa mnoho systémových premenných, ale my nateraz využijeme iba tieto dve:

  • CURSOR / 0C03Eh - adresa vo VRAM kam sa vypíše znak - presnejšie ukazuje 2 mikroriadky pod znakom
  • COLOR / 0C03Ah - "farba" vypisovaného znaku - horné dva bity predstavujú farbové atribúty; dolných 6 bitov určuje, či sa znak vypíše normálne (00h) alebo invertovane (3Fh)

S výpisom textu súvisí znaková sada, alebo tiež znakový generátor alebo znakový font, skrátene iba font. Font je v Monitore 2 a ďalších "rozkúskovaný" na viac častí a adresy fontu sú v systémových premenných ako tabuľka adries od adresy TASCII / 0C0B0h (v Monitore 1 je iba jediná systémová premenná pre adresu fontu TAS00 / 0C03Ch, tá je ale v Monitore 2 nevyužitá).

  • 0C0B0h : kódy 00h1Fh - nevyužité
  • 0C0B2h : kódy 20h3Fh - medzera až otáznik
  • 0C0B4h : kódy 40h5Fh - veľké písmená
  • 0C0B6h : kódy 60h7Fh - malé písmená
  • 0C0B8h : kódy 80h9Fh - nevyužité
  • 0C0BAh : kódy 0A0h0BFh - nevyužité
  • 0C0BCh : kódy 0C0h0DFh - nevyužité (na PMD 85-3 malé písmená s diakritikou)
  • 0C0BEh : kódy 0E0h0FFh - nevyužité (na PMD 85-3 veľké písmená s diakritikou)

Pre získanie adresy predlohy konkrétneho znaku slúži rutina ADRAS / 84CEh, ktorej vstupným parametrom je kód znaku v registri A. Adresa predlohy znaku +8 sa vráti v registri HL. Ak taký znak nie je definovaný, vráti sa adresa "štvorčeka", ktorý určite poznáte napr. z príkazu DUMP v Monitore. Rutina mení všetky registre. My túto rutinu ale nebudeme teraz potrebovať, lebo je volaná už samotnou rutinou PRTOUT.

Druhou užitočnou rutinou pre výpis znaku je rutina WRCHAR / 854Ah. Na rozdiel od rutiny PRTOUT, WRCHAR skutočne iba vypíše znak, ktorého kód je v registri A a to do VRAM od adresy, ktorá je v registri HL (systémová premenná CURSOR sa tu nepoužíva). Systémová premenná COLOR sa uplatní rovnako, ako pri rutine PRTOUT. Rutina WRCHAR nemení okrem AF žiadne iné registre (a teda ani HL!).

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

		cpu	8080
		page	0
		title	"ASM Program 1A"

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

		org	0		; program začne od adresy 0

;------------------------------------------------------------------------------
; definícia adries rutín v Monitore
PRTOUT		equ	8500h
WRCHAR		equ	854Ah
MONIT		equ	8C40h

; systémové premenné
COLOR		equ	0C03Ah
CURSOR		equ	0C03Eh
TASCII5		equ	0C0BAh		; adresa ukazateľa na znaky s kódmi 0A0h až 0BFh

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

		; výpis pomocou PRTOUT
		mvi	a,1Ch		; riadiaci kód pre vymazanie obrazovky
					; do akumulátora
		call	PRTOUT		; zavolaj rutinu pre výpis znaku
					; v tomto prípade dôjde k vymazaniu
					; obrazovky

		mvi	a,'A'		; vypíšeme znak 'A' - keďže sme zmazali
		call	PRTOUT		; obrazovku, vypíše sa do ľavého horného
					; rohu obrazovky

		lxi	h,0D110h	; do HL ulož adresu pozície vo VRAM
					; približne do druhej štvrtiny obrazovky
		shld	CURSOR		; ulož ako pozíciu kurzora
		mvi	a,3Fh		; nastav inverzný výpis
		sta	COLOR
		mvi	a,'X'		; vypíšeme písmeno 'X'
		call	PRTOUT

		; výpis pomocou WRCHAR
		mvi	a,40h		; nastav výpis "zníženým jasom"
		sta	COLOR
		lhld	CURSOR		; do HL ulož aktuálnu adresu kurzora
		mvi	a,'Y'		; vypíš 'Y'
		call	WRCHAR
		mvi	a,80h		; nastav výpis "blikaním"
		sta	COLOR
		inx	h		; posun na ďalšiu pozíciu za 'Y'
		mvi	a,'W'		; a vypíšeme blikajúce 'W'
		call	WRCHAR

		xra	a		; nastav normálny výpis
		sta	COLOR
		lxi	h,CharsA0+8	; nastav adresu "fontu" pre znaky
		shld	TASCII5		; od 0A0h do 0BFh - v našom prípade
					; je to iba 5 znakov
					; adresa musí byť zvýšená o 8
		lxi	h,0E014h	; do HL ulož adresu pozície vo VRAM
					; približne v strede obrazovky
		lxi	b,5*256+0A0h	; vypíšeme našich 5 znakov
PrintRomX:	mov	a,c		; vypisovaný znak do A
		call	WRCHAR		; vypíš ho
		inx	h		; posuň adresu VRAM
		mvi	a,' '		; vypíš medzeru
		call	WRCHAR
		inx	h		; posuň adresu VRAM
		inr	c		; inkrementuj kód znaku
		dcr	b		; zníž počítadlo znakov
		jnz	PrintRomX	; a opakuj

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

;------------------------------------------------------------------------------
; predloha znakov
CharsA0:	db	0,20,20,0,8,34,28,0	; "Smajlik"
		db	0,4,14,14,31,14,14,4	; karo
		db	0,10,31,31,31,14,4,0	; srdce
		db	0,4,14,31,31,31,4,4	; pika
		db	0,14,4,21,31,21,4,4	; kriz

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

		end

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

Pár poznámok k programu. Na začiatku sme zakázali prerušenia, aj keď to teraz nie je veľmi dôležité, ale je dobrý zvyk to urobiť. Zároveň sme si nastavili adresu zásobníka na koniec pamäte (Pamätáte sa? Zásobník "rastie" k nižším adresám.). Na konci programu máme skok na návestie MONIT, čo je bezpečný návrat do Monitora, kde sa vypíše obligátne "++ Os ready ++".

Ďalej upozorním na definíciu adries rutín a systémových premenných Monitora pomocou pseudoinštrukcie equ (equation). V kóde tak potom používame tieto zadefinované symboly, kód je zrozumiteľnejší a čitateľnejší a keby sme potrebovali z nejakých dôvodov zmeniť niektorú z tých adries, tak to urobíme iba na jednom mieste. Za pseudoinštrukciou equ nemusí byť iba číselná konštanta, ale aj číselný výraz.

Asi je to jasné, ale pripomeniem, že pseudoinštrukcia org (origin) určuje adresu, na ktorú sa budú ukladať nasledujúce inštrukcie. V kóde môže byť zmena adresy cieľového kódu neobmedzene, kompilátor tieto "roztrhané" bloky kódu potom pospája a prázdne miesta vyplní nulami. V našom prípade nám to stačí na začiatku programu.

Ďalšou novinkou je pseudoinštrukcia db (define byte), ktorou sme si "zadefinovali" naše nové znaky (8 bytov na znak). Za pseudoinštrukciou db môže nasledovať zoznam čiarkami oddelených výrazov, ktorých výsledná hodnota je 8 bitová, alebo reťazec znakov v úvodzovkách (použijeme v ďalších príkladoch). Podobne existuje aj pseudoinštrukcia dw (define word) pre vloženie 16 bitových hodnôt do pamäte. Opäť to môžu byť výrazy, ktorých výsledkom je ale 16 bitová hodnota. Podľa očakávania sa hodnoty do pamäte ukladajú v poradí najprv nižší byte a potom vyšší byte.

Výpis znaku vlastnými rutinami

Nie vždy ale chceme, alebo môžeme, využívať rutiny z ROM, preto si teraz vytvoríme vlastné rutiny na vykreslenie znaku. K tomu zároveň použijeme vlastný font. Ten si môžeme pripraviť v 8bit Strite Editore. V projekte je v adresári data zdrojový "spritový" súbor font.sprite (môžete ho otvoriť v Sprite editore a znakovú sadu si upraviť) a aj exportovaný binárny súbor font.bin s čistými dátami fontu. Tento súbor si pomocou pseudoinštrukcie binclude vložíme do kódu.

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

		cpu	8080
		page	0
		title	"ASM Program 1B"

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

		org	0		; program začne od adresy 0

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

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

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

; Ukážka výpisu znakov vlastnými rutinami
		call	Cls		; zmaž obrazovku

		; vypísanie tabuľky znakov
		lxi	h,AV(5,8)	; 5. riadok, 8. stĺpec
		lxi	b,6*256+' '	; 6 skupín, začneme medzerou
		mvi	e,0		; atribút
PrtChrTblL2:	mvi	d,16		; 16 znakov v riadku
		mov	a,e		; atribút pre tento riadok
		sta	PrintCharC+1	; ulož do inštrukcie
PrtChrTblL1:	mov	a,c		; vypisovaný znak do A
		call	PrintChar	; vypíš ho
		mvi	a,' '		; medzi znakmi budú medzery
		call	PrintChar
		inr	c		; inkrementuj kód znaku
		dcr	d		; zníž počítadlo znakov v riadku
		jnz	PrtChrTblL1	; opakuj pre celý riadok
		call	PrintCharCRLF	; odriadkuj
		call	PrintCharCRLF	; a ešte raz
		mvi	l,8		; opäť 8. stĺpec
		mov	a,e		; ďalšia "farba"
		adi	40h
		mov	e,a
		jnz	PrtChrTblR	; prvé 4 riadky neinvertovane
		mvi	e,3Fh		; ďalšie riadky invertovane
PrtChrTblR:	dcr	b		; zníž počítadlo skupín
		jnz	PrtChrTblL2	; opakuj pre všetky skupiny

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

;------------------------------------------------------------------------------
; Vypísanie znaku, ktorého kód je v A do VRAM od adresy v HL.
; Rutina využíva vlastný font, kde sú znaky vysoké 8 bodov (mikroriadkov).
; Riadok je ale vysoký 12 mikroriadkov a horné 4 mikroriadky budú slúžiť pre
; diakritické znamienka.
; I: HL=adresa VRAM, A=znak <32, 127>
; O: HL=adresa VRAM +1
; M: AF, HL
PrintChar:	sui	32		; uprav kód znaku na rozsah od 0 do 95
		rc			; neplatný kód znaku, ihneď sa vráť
		cpi	(127+1-32)	; maximálny kód znaku je 127
		rnc			; ak je to mimo, vráť sa
		push	h		; odpamätaj registre
		push	d
		push	b
		xchg			; adresa VRAM do DE
		add	a		; kód znaku x2
		mov	l,a		; x2 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
		xchg			; DE=predloha znaku, HL=adresa VRAM
PrintCharC:	mvi	c,0		; atribút do C

		mvi	b,4		; 4 mikroriadky nad znakom budú nateraz prázdne
PrintCharK:	mov	m,c		; do VRAM ulož iba atribút
		mov	a,l		; posun na nasledujúci mikroriadok
		adi	40h
		mov	l,a
		jnc	PrintCharK0	; skoč, ak nedošlo k pretečeniu
		inr	h		; inak inkrementuj ešte H
PrintCharK0:	dcr	b		; opakuj 4x
		jnz	PrintCharK

		mvi	b,8		; výška znaku 8 mikroriadkov
PrintCharL:	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	PrintCharL0	; skoč, ak nedošlo k pretečeniu
		inr	h		; inak inkrementuj ešte H
PrintCharL0:	dcr	b		; opakuj pre celý znak
		jnz	PrintCharL
		pop	b		; obnov registre
		pop	d
		pop	h
		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
		rc			; nie, návrat
PrintCharCRLF:	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
		ret

;------------------------------------------------------------------------------
; Zmazanie obrazovky.
; I: -
; O: -
; M: všetky
Cls:		lxi	h,VRAM		; adresa VRAM do HL
		lxi	d,48*256+0	; D=48 - šírka obrazovky,
					; E=0 - 256 mikroriadkov
		lxi	b,16		; BC=offset na prechod na ďalší mikroriadok
ClsK:		mov	a,d		; A=48 - počítadlo stĺpcov
ClsL:		mov	m,b		; vymaž byte vo VRAM
		inx	h		; posuň sa na ďalší byte obrazovky
		dcr	a		; zníž počítadlo šírky
		jnz	ClsL		; opakuj pre celú šírku
		dad	b		; prejdi na ďalší mikroriadok
		dcr	e		; zníž počítadlo mikroriadkov
		jnz	ClsK		; opakuj pre všetky mikroriadky
		ret

;------------------------------------------------------------------------------
Font:		binclude "../data/font.bin"

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

		end

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

V uvedenom príklade máme opäť niekoľko nových vecí. Na začiatku máme zadefinovanú funkciu AV (Adresa vo VRAM), ktorá nám z riadku a stĺpca uvedených ako parametre funkcie, vypočíta skutočnú adresu VRAM. V našom prípade sme si zadefinovali, že riadok bude mať 12 mikroriadkov (12*40h=300h), k čomu sa ešte dostaneme. Zadefinovanie takýchto funkcií nám sprehľadňuje kód a opäť pri potrebe zmeniť nejakú drobnosť, stačí túto zmenu urobiť na jednom mieste. Použitie funkcie v kóde sa správa podobne ako macro, to znamená, že dôjde k "rozvinutiu" funkcie v danom mieste použitia a až následne dôjde ku kompilácii daného riadku. My sme použili funkciu AV na začiatku pri nastavení adresy VRAM, odkiaľ sa bude vypisovať tabuľka znakov.

Pre zmazanie obrazovky sme si vytvorili vlastnú rutinu Cls. Všimnite si, že pri nulovaní bytov VRAM zapisujeme iba do prvých 48 bytov v mikroriadku a 16 bytov "vedľa" VRAM preskakujeme. To je dôležité buď preto, že ak chceme naďalej používať nejaké rutiny z ROM, tie môžu byť závislé od systémových premenných, ktoré sú pravé "vedľa" VRAM, a tak ich nesmieme zmazať. Druhá možnosť je, že my sami si uložíme nejaký vlastný kód alebo dáta "vedľa" VRAM a tak určite nebudeme chcieť, aby sme si ich zmazali.

Naša rutina PrintChar sa chová podobne, ako rutina WRCHAR z Monitora. Znak, ktorý sa má vytlačiť, vstupuje do rutiny v registri A a adresa VRAM v registri HL. Na rozdiel od rutiny v ROM, naša rutina spôsobuje aj prechod na začiatok ďalšieho riadku, ak sa dosiahne pravý okraj obrazovky. Druhou odlišnosťou je, že výška riadku je 12 mikroriadkov. Znak má síce na výšku 8 mikroriadkov, ale 4 mikroriadky nad znakom využijeme pre diakritické znamienka, čo si ukážeme neskôr. Teraz bude tento priestor nad znakom vyplnený hodnotou atribútu.

Rutina PrintChar na začiatku overuje, či je kód znaku v povolenom rozsahu 32 až 127. Ak je kód mimo, prevedie sa ihneď návrat z rutiny. Pre nájdenie predlohy znaku sa kód znaku najprv upraví na rozsah 0 až 95, vynásobí sa ôsmimi (jeden znak zaberá vo fonte 8 bytov) a pripočíta sa bázová adresa fontu. Pri zapisovaní jednotlivých bytov predlohy znaku do VRAM sa každý byte predlohy spojí pomocou inštrukcie xra c s atribútom. Inštrukcia xor je použitá preto, aby mohla byť urobená inverzia znaku, ak má dolných 6 bitov atribútu hodnotu 3Fh. Pri horných dvoch farbových bitoch prakticky dôjde iba ku spojeniu, keďže predloha znaku má tieto bity vždy nulové.

Za zmienku ešte stojí poukázať na spôsob prechodu na ďalší mikroriadok. Keďže v danom mieste máme okrem akumulátora "obsadené" všetky registre, použili sme konštrukciu, ktorá je rýchlejšia, ako použitie zásobníka pre dočasné odloženie niektorého registra. V danom prípade k registru L pripočítame 40h, čo je rozdiel medzi dvoma mikroriadkami. Ak však L "pretečie", inkrementujeme ešte H.

Na záver rutiny sa ešte obnovia všetky registre uložené na zásobník a inkrementuje sa HL ako prechod na ďalšiu znakovú pozíciu. Ak sa ale zistí, že sme dosiahli pravú stranu obrazovky, tak sa v nižšom byte L adresy VRAM vynulujú bity určujúce pozíciu v radku a vyšší byte H sa zvýši o 3, čo je práve 12 mikroriadkov (12 * 40h = 3 * 100h).

Upozorním ešte na popis pri oboch rutinách, ktorý obsahuje "legendu" (I:, O: a M:) informujúcu o vstupných a výstupných parametroch a zmenených registroch. Je to veľmi užitočný zvyk písať si k rutinám tieto informácie a mať tak neskôr okamžitý prehľad o používaných registroch v danej rutine. Samozrejme, nie vždy to má zmysel, ale mnoho "pomocných" rutín si to priam žiada.

Na konci programu je už iba skôr spomenuté vloženie fontu do kódu pomocou pseudoinštrukcie binclude.

Výpis znaku s diakritikou

Pre zobrazovanie znakov s diakritikou by sme si mohli jednoducho vytvoriť font, ktorý by priamo obsahoval okrem základnej sady znakov aj znaky s diakritikou. Prakticky by sa v našej rutine zmenil iba test povoleného rozsahu kódov znakov a výpočet adresy predlohy daného znaku. Toto riešenie má len to negatívum, že by font zaberal omnoho viac miesta.

Skúsime ísť inou cestou, na ktorú sme sa už čiastočne pripravovali. Font použijeme ten základný a pri vykresľovaní znaku s diakritikou, nad základný znak vykreslíme príslušné diakritické znamienko. Samozrejme, samotná rutina na vykreslenie znaku sa mierne skomplikuje a predĺži, ale určite bude spolu so základným fontom zaberať menej miesta, ako by zaberal úplný font so znakmi s diakritikou. A na druhej strane to možno bude pekné programátorské cvičenie.

Nebudeme ale veľmi vymýšľať a použijeme algoritmus, ktorý je v rozšírenom Monitore Zbrojováčka pre CP/M BIOS. Len si ho prispôsobíme pre naše potreby. Z toho teda vyplýva, že znaky s diakritikou budú v kódovaní KOI-8čs - kódy 0C0h až 0FFh (v rovnakom kódovaní sú znaky s diakritikou aj na PMD 85-3, viď návod k PMD 85-3). A ako dosiahneme, aby texty napísané v PC editore boli nakoniec v tom správnom kódovaní, si povieme v ďalšej kapitole.

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

		cpu	8080
		page	0
		title	"ASM Program 1C"

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

		org	0		; program začne od adresy 0

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

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

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

; Ukážka výpisu znakov vlastnými rutinami
		call	Cls		; zmaž obrazovku

		; vypísanie tabuľky znakov
		lxi	h,AV(1,8)	; 1. riadok, 8. stĺpec
		lxi	b,10*256+' '	; 10 skupín, začneme medzerou
		mvi	e,0		; atribút
PrtChrTblDiaL2:	mvi	d,16		; 16 znakov v riadku
		mov	a,e		; atribút pre tento riadok
		sta	PrtCharDiaC+1	; ulož do inštrukcie
PrtChrTblDiaL1:	mov	a,c		; vypisovaný znak do A
		call	PrtCharDia	; vypíš ho
		mvi	a,' '		; medzi znakmi budú medzery
		call	PrtCharDia
		inr	c		; inkrementuj kód znaku
		dcr	d		; zníž počítadlo znakov v riadku
		jnz	PrtChrTblDiaL1	; opakuj pre celý riadok
		call	PrtCharDiaCRLF	; odriadkuj
		call	PrtCharDiaCRLF	; a ešte raz
		mov	a,c
		cpi	128
		jnz	PrtChrTblDiaC
		mvi	c,192
PrtChrTblDiaC:	mvi	l,8		; opäť 8. stĺpec
		mov	a,e		; ďalšia "farba"
		adi	40h
		mov	e,a
		jnz	PrtChrTblDiaR	; prvé 4 riadky neinvertovane
		mvi	e,3Fh		; ďalšie riadky invertovane
PrtChrTblDiaR:	dcr	b		; zníž počítadlo skupín
		jnz	PrtChrTblDiaL2	; opakuj pre všetky skupiny

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

;------------------------------------------------------------------------------
; Vypísanie znaku, ktorého kód je v A do VRAM od adresy v HL.
; 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: HL=adresa VRAM, A=znak <32, 127> a <192, 255>
; O: HL=adresa VRAM +1
; M: AF, HL
PrtCharDia:	cpi	32		; ak je to riadiaci kód, navrat
		rc			; neplatný kód znaku, ihneď sa vrat
		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		; odpamataj registre
		push	d
		push	b
		push	psw		; odpamataj kod znaku aj CY
		xchg			; adresa VRAM do DE
		jc	PrtCharDiaN1	; skoč, ak je to znak bez diakritiky
		ani	3Fh		; odmaskuj poradie znaku s diakritkou
		mov	c,a		; v tabuľke a ulož do BC
		mvi	b,0
		lxi	h,CsChar	; tabuľka zodpovedajúcich 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ákladné znaky skoč
		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. znamienka

		xchg			; DE=predloha dia, HL=adresa VRAM
PrtCharDiaC:	mvi	c,0		; atribut 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 další 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
		pop	b		; obnov registre
		pop	d
		pop	h
		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
		rc			; nie, návrat
PrtCharDiaCRLF:	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
		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čen nad malým písmenom
M3		equ	(FontDia+12)&255 ; mäkčen 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čen 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

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

		end

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

Výpis tabuľky znakov sa od predošlej verzie líši iba v tom, že vypisujeme až 10 skupín znakov, aby sme zahrnuli aj znaky s diakritikou a hlavne, po dosiahnutí kódu znaku 128 prejdeme priamo na kód 192, čo je začiatočný kód znakov s diakritikou.

Samotná rutina pre výpis znaku PrtCharDia má už trošku viac odlišností. Na úvod sa opäť overuje rozsah podporovaných kódov znakov a pri nepodporovaných kódoch sa z rutiny hneď vrátime a daný kód sa odignoruje. Pre znaky bez diakritiky sa nastaví príznak CY a odpamätá sa. Podľa tohto príznaku sa potom riešia odlišnosti medzi znakom bez diakritiky a s diakritikou.

Pre znaky s diakritikou sú zadefinované dve tabuľky: Prvá tabuľka CsChar definuje základné znaky korešpondujúce so znakom s diakritikou. Ak sa z kódu znaku s diakritikou odmaskuje 6 spodných bitov, dostaneme offset (0 až 63) do tejto tabuľky a tak príslušný základný znak, nad ktorý sa pripojí dané diakritické znamienko.
Druhá tabuľka CsDiaChar obsahuje nižšie byty adresy predlohy príslušného diakritického znamienka. Tabuľka má opäť 64 položiek a zoznam obsahuje iba neskôr zadefinované symboly pomocou pseudoinštrukcie equ, aby sa zápis sprehľadnil. Predlohy diakritických znamienok sú, rovnako ako základný font, vložené do kódu pomocou pseudoinštrukcie binclude - návestie FontDia.

Odlišnosti medzi znakom s diakritikou a bez sa riešia na dvoch miestach. Pred návestím PrtCharDiaN1 sa vyhľadá základný znak zodpovedajúci znaku s diakritikou v tabuľke CsChar. Druhý prípad je za inštrukciou pop psw, kde sa pre znaky bez diakritiky pripraví do registra L hodnota NN, čo je nižší byte predlohy "bez znamienka". Pre znaky s diakritikou sa v tabuľke CsDiaChar nájde nižší byte predlohy daného diakritického znamienka a uloží do registra L. Nakoniec sa do registra H uloží vyšší byte adresy predlôh diakritických znamienok FontDia/256.

Ako je vidieť, kód predpokladá, že predlohy diakritických znamienok neprekračujú "stránku" pamäte, teda že u všetkých diakritických znamienok je vyšší byte adresy rovnaký. To je zabezpečené podmienenou kompiláciou pomocou pseudoinštrukcie if/endif. Podmienka ($ / 256) <> (($ + 48) / 256) overuje, či sa celý font s diakritickými znamienkami vojde do "stránky" - font diakritických znamienok má veľkosť 48 bytov a symbol $ označuje aktuálnu ukladaciu adresu. Ak je podmienka splnená (font diakritických znamienok sa do "stránky" nevojde), pseudoinštrukciou align 256 sa prevedie "zarovnanie" adresy modulo 256, čím sa dosiahne, že vyšší byte adresy pre každé diakritické znamienko bude rovnaký.

Využívanie skutočnosti, že sú nejaké dáta konkrétne "zarovnané", je veľmi užitočné, pretože to často skracuje kód. Napríklad, ak by sme mali nejakú tabuľku adries, kde by sme predpokladali, že každá adresa začína na párnej adrese, tak by sme pred tú tabuľku adries vložili pseudoinštrukciu align 2. Ak by sa kód pred tabuľkou akokoľvek menil, prekladač nám vďaka pseudoinštrukcii align zabezpečí, že tá tabuľka bude vždy začínať na párnej adrese.

Podobne užitočná je aj podmienená kompilácia pomocou pseudoinštrukcií if/else/endif (existujú aj iné podmienené pseudoinštrukcie, viď dokumentácia k makroassembleru AS). Okrem toho, že počas vývoja môžeme pomocou podmienenej kompilácie jednoducho "odstaviť" niektoré časti kódu, umožňuje to aj vytvárať kód, ktorý sa prekladá pre nejakú konkrétnu "konfiguráciu". Symbol, ktorý sa bude testovať v danej podmienke, môže byť zadaný aj v príkazovom riadku, ako parameter kompilátora. Tým môžeme mať jeden zdrojový kód, z ktorého sa na základe hodnoty nejakého symbolu môže vygenerovať rozdielny výsledný binárny kód.

Na stiahnutie

ZIP archív obsahuje celý projekt, ako bolo popísané v minulej kapitole. V tejto kapitole sme mali 3 príklady, ktoré boli prezentované, ako osobitné súbory, avšak v projekte sú súčasťou jediného zdrojového súboru, kde sú v "hlavnom programe" volané postupne 3 rutiny s uvedenými troma príkladmi.


<< Príprava projektu a nástrojov na editáciu zdrojového textu a jeho kompilovanie Píšeme texty >>