Blog:Programovanie v assembleri i8080 pre PMD 85 (8)
Z PMD 85 Infoserver
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 00h až 1Fh - nevyužité
- 0C0B2h : kódy 20h až 3Fh - medzera až otáznik
- 0C0B4h : kódy 40h až 5Fh - veľké písmená
- 0C0B6h : kódy 60h až 7Fh - malé písmená
- 0C0B8h : kódy 80h až 9Fh - nevyužité
- 0C0BAh : kódy 0A0h až 0BFh - nevyužité
- 0C0BCh : kódy 0C0h až 0DFh - nevyužité (na PMD 85-3 malé písmená s diakritikou)
- 0C0BEh : kódy 0E0h až 0FFh - 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 >>