# PC-pelintekijän opas

Another great release from Herr 'Dirk Gently' Werner

# Johdanto

PC:llä pelaaminen on lisääntynyt huomattavasti muutaman viimeisen vuoden aikana. Saatavilla olevien pelien määrä on moninkertaistunut ja samalla myös pelien taso on noussut huimasti.

Ei ihme, että PC tänään on yksi parhaimmista pelikoneista. Koneiden paljous ja lisäksi yhä nopeammat ja paremmat PC:t mahdollistavat hienojen pelien tekemisen.

Monella tietokoneen käyttäjällä on unelma, että he jonain päivänä tekisivät pelin. Tämä kirja on tehty juuri heitä varten.

Markkinoilla on paljon myös suomenkielisiä PC-kirjoja. Varsinaiseen peliohjelmointiin tarkoitettuja kirjoja ei vielä kuitenkaan ole ollut. Joitakin artikkeleita on julkaistu aiheesta, mutta niiden avulla on vaikeaa muodostaa kokonaiskuvaa pelintekemisen luonteesta. Tässä kirjassa kaikki nämä tärkeät seikat on yhdistetty.

PC:n hardware eli kovo on vuosien aikana muuttunut huimasti. Uusi tekniikka on syrjäyttänyt vanhan. Siksi jätämmekin vanhan hardwaren omaan rauhaan ja kerromme, miten uutta tekniikkaa voi hyödyntää. Kirja on suunniteltu 486/Pentium-koneita ja VGA/SVGA-näyttöä varten.

Kirja ei opeta mitään ohjelmointikieltä, mutta joitakin ohjelmointivinkkejä annamme koodin optimoimiseksi. Kirjan esimerkit on toteutettu C++ -kielen ja assembler-kielen avulla.

Alussa kerromme yleisiä asioita pelin suunnittelusta ja markkinoinnista. Sitten tulee pikakurssi C-kielestä ja assemblerista niille, jotka tuntevat kielet heikommin. Sen jälkeen päästäänkin jo ensimmäiseen polttavaan aiheeseen: PC:n muistiongelmaan (onko sitä?). Sen jälkeen pohditaan keskeytyksiä ja rakennetaan linkkiä pelaajan ja koneen välille, eli kerrotaan hiirestä ja näppäimistöstä. Jottei ruutu jäisi aivan tyhjäksi perehdymme myös VGA-kortin sisimpään sielunelämään rekisteritasolla. Naapurien aktiivisuuden ylläpitämiseksi kerromme viimeisessä luvussa, kuinka teet nykyaikaisimmatkin teknorenkutukset omalla äänikortillasi. Lopun liitteissä on tärkeimmät rekisterit ja keskeytykset.

# 1. Pelin suunnittelu

Kaikessa ohjelmoinnissa, mutta erityisesti PC- ohjelmoinnissa suunnittelu on tärkeää. Aikoinaan kun pelejä tehtiin esimerkiksi Commodore 64:lle, voitiin huoletta keksiä mitä kummallisimpia kikkoja koneen hallitsemiseksi. PC:llä tilanne on toinen, sillä koneita valmistavat kymmenet yritykset, mikä tuo mukanaan erilaisia prosessoreita, BIOS- piirejä, väyliä... Tämän laitekirjavuuden huomioonottamista käsittelemme tässä luvussa. Lisäksi kerromme pelien suunnittelusta ja markkinoinnista.

### Pelikone PC

Käsitteenä PC-pelikone on aika uusi. Pelejä on kyllä tehty koko PC:n historian ajan, mutta vasta tällä vuosikymmenellä asiaan todella paneuduttiin. Lode Runner, King's Quest, Super Zaxxon, Gato, Microsoft Flight Simulator ja Silent Service olivat yksinkertaisia mutta mielenkiintoisia pelejä, joita on pelattu paljon.

Kun vertaamme vanhoja pelejä nykyaikaisiin peleihin, huomaamme monia eroja. Uusissa peleissä grafiikka on kaunista, äänet hyviä ja pelit nopeita. Kiintolevy täyttyy peleistä helposti, vaikka vielä vuonna -84 sanottiin, että 40-megatavuista kiintolevyä ei voi saada täyteen.

Nykyiset pelit ovat graafisesti hienoja. Äänet ovat digitoituja, yleensä aidon tuntuisia. Peleissä on usein hienoja animaatioita, jotka pyörivät reaaliajassa kuvaruudulla. Paksut ohjekirjat ovat täynnä asiaa pelistä ja muusta siihen liittyvästä. Silti itse peli saattaa olla monimutkainen tai tylsä. Sen vuoksi kannattaakin keskittyä pelin juoneen ja yrittää tehdä siitä mahdollisimman mielenkiintoinen. Hienoa, muttei suinkaan välttämätöntä, on myös, jos peliin pääsee sisälle heti lukematta paksua ohjekirjaa.

Miksi sitten PC ei saavuttanut nopeammin pelikoneen mainetta? Tämä juontuu selvästi koneen käyttötarkoituksesta. PC oli yrityskone, ei pelikone. Sitä paitsi koneen valmistajalla eli IBM:llä ei ollut pelikoneiden valmistajan mainetta. Koneesta kehittyi kuitenkin myös pelaamiseen sopiva väline. Uusien prosessorien, nimenomaan 80386:n teho sekä 256-värinen VGA saivat valmistajat kiinnostumaan pelien tekemisestä. Nyt voikin sanoa, että peli kannattaa aina tehdä PC-yhteensopiville koneille, sillä PC-koneita on myyty paljon.

Tärkeää on kuitenkin muistaa, että vieläkin koneet kehittyvät nopeasti, joten peliohjelmoijan on pysyteltävä ajan hermolla. Esimerkiksi äänikortit ovat kehittyneet pienessä ajassa huimasti. Pelien tulisikin toimia kaikissa PC-koneissa ja kaikenlaisten lisäkorttien kanssa, mikäli vain muistia on tarpeeksi vapaana.

Pelin suunnittelussa kannattaa tietysti ottaa huomioon myös markkinat. Ohjelmoijan on päätettävä, millaisen koneen peli tarvitsee. 8086, 8088 ja 286 ovat jo historiaa, 386 potkii vielä jonkin verran, 486 on vähän parempi, mutta Pentium on se millä pelataan.

### Suunnittelun perusteet

Pelin huolellinen suunnittelu on ensiarvoisen tärkeää. Hyvin suunniteltu on puoliksi tehty pätee myös pelien ohjelmoinnissa, vaikka joskus tuntuu, että itse ohjelmointi vaatii vähintään 90 prosenttia tarvittavasta työstä. On kuitenkin selvää, että ohjelmointityön määrä vähenee hyvän suunnittelun johdosta, koska turhan työn tekeminen jää pois. Esimerkiksi hyvin suunniteltu ohjelmarutiini käy moneen paikkaan, jolloin samaa rutiinia ei tarvitse kirjoittaa moneen kohtaan pieniä muutoksia tehden, vaan se käy suoraan muutoksitta. Työtä säästyy ja ohjelma lyhenee.

Suunnitteluvaiheessa tulee ottaa huomioon kaikki mahdolliset asiat niin kovo- kuin pehmopuoleltakin. Tietokoneet ovat erilaisia ympäristöltään, vaikka lähes kaikissa käytetäänkin Intelin suorittimia. Nopeasti huomaakin kuinka sotkuinen ja epäyhteensopiva PC-maailma on. Esimerkkinä tärkeimmistä eroavaisuuksista ovat muistin määrä, äänikortit, videokortit ja tietysti itse prosessori.

Jo alkuvaiheessa on päätettävä, minkälaisen ympäristön peli tarvitsee. Tärkeitä kysymyksiä ovat: Paljonko tarvitaan muistia? Minkälaista muistia tarvitaan (EMS, XMS, perusmuisti)? Riittääkö 286-prosessori vai tarvitaanko vähintään 386-prosessori? Mitä äänikortteja peli tukee? Onko tarvetta SVGA-tilojen käyttöön? Ja niin edelleen. Näiden asioiden miettimiseen kannattaa uhrata muutama tunti, sillä jälkeenpäin on hankalaa tehdä muutoksia. Pelin tyyppi ratkaisee, millaiseen ympäristöön se suunnitellaan.

286-prosessorista saattaa yllättäen loppua teho, se kannattaakin nykyisin jo unohtaa. Usealle äänikortille on oltava tuki, mutta SVGA-tilojen käyttö ei vielä tällä hetkellä ole tarpeellista. Yli 600 kiloa perusmuistia on liikaa vaadittu ja peli saisi käyttää mieluiten alle kaksi megatavua lisämuistia.

### Ideointi

Ideointi on tärkeä osa pelin suunnittelua. Idea saattaa syntyä melkein mistä vaan, esimerkiksi jostakin toisesta pelistä. Toinen mahdollisuus on, että ensin on tehty esimerkiksi nopea ja sulava vieritysrutiini, jonka pohjalle peliä aletaan muodostaa.

Idean tulee olla hyvä ja omaperäinen, jotta peli jaksaa kiinnostaa pelaajaa, ohjelmoijaa ja myös tulevaa julkaisijaa. Usein nimittäin käy niin, että ohjelmoija itse kyllästyy omaan peliinsä ja jättää projektin makaamaan pyödän nurkkaan, tai peliä ei voida julkaista, koska se matkii liiaksi toista peliä.

Idean jälkeen aloitetaan hahmottelu paperilla. Paperille kannattaa kirjata kaikki pelissä esiintyvät tilanteet. Myös mahdolliset ongelmakohdat (esimerkiksi prosessoritehon loppuminen) on otettava huomioon. Samaan aikaan on hyvä kirjata jonkinlainen perusrunko, lohkokaavion tapainen esitys koko pelistä, mihin kirjataan ohjelmarutiinien nimet ja niiden toiminta. Tällöin on helppo jälkeenpäin tutkia paperilta, miten mikäkin ohjelman osa toimii. Lohkokaavio helpottaa muutenkin huomattavasti koko pelin toiminnan ymmärtämistä.

Hyvän suunnittelun rinnalle voidaan asettaa dokumentointi, millä tarkoitamme ohjelmakoodin sekaan laitettavia kommentteja. Ne helpottavat ja selkeyttävät huomattavasti koodin lukemista myöhemmässä vaiheessa. Jo puolen vuoden kuluttua itsekin tehty koodi tuntuu monimutkaiselta, eikä siitä saa selvää. Tällöin kommenttirivit ovat suureksi avuksi.

Ohjelmoijalla on nyt mielessään, tai mieluiten jo paperilla luonnostelma pelistä. Samoin ohjelmoijan pitää olla varma, että pystyy ohjelmoimaan suunnitelmansa. Mikään ei ole sen puuduttavampaa kuin huomata, että peli epäonnistuu, koska ohjelmointitaito ei riitä.

Pelien ohjelmointi kannattaa aloittaa yksinkertaisista peleistä. Niissä huomaa, miten suuri projekti pelin ohjelmointi itse asiassa onkaan. Ei kuitenkaan pidä heti lannistua, jos kaikki ei suju toivotulla tavalla, harjoitus tekee mestarin.

### Pelityypit

Pelit voidaan jakaa eri tyyppeihin, mutta rajat ovat usein epätarkat. Seuraavassa on lueteltu eri tyypit.

- Autopelejä on kahta erilaista tyyppiä: ajajan näkökulmasta ja ylhäältä päin kuvatut. Nykyään lähes kaikki autopelit on toteutettu vektorigrafiikalla, ja ne vaativat mieluusti nopean koneen. Vanhemmat autopelit, joissa vektorigrafikkaa ei ole käytetty, on helppo toteutettaa, mutta ne tarvitsevat paremman grafikan. Pelit ovat idealtaan samanlaisia, joten ohjelmoijan on paras keskittyä nopeaan ja sulavaan vieritykseen.
- Urheilupelejä on lähes lajista kuin lajista, päätarkoituksena on tietenkin yrittää saada mahdollisimman hyvä tulos aikaan. Ohjelmakoodin ei tarvitse olla huippunopeata, koska aikakriittisiä osia on harvoin. Idea on peleissä sama, joten kannattaa keskittyä pelin tekniseen toteutukseen.
- Räiskintäpelit eli shoot 'em upit ovat vauhdikasta ammuskelua. Tyypin perusteella ne voidaan jakaa kolmeen alaryhmään: lentävä kone (Raptor), maassa liikkuva hahmo (Chaos Engine) ja muut. Tarkoituksena on tuhota kaikkea liikkuvaa mahdollisimman paljon ennen kuin aika loppuu tai itse tuhoutuu. Ohjelmakoodin pitää olla varsin tehokasta ja vieritysrutiinin täytyy olla nopea. Myös grafikan on oltava hyvää.
- Tasohyppelypeleissä tarkoituksena on kerätä ruudulla olevia tavaroita tai vain edetä eteenpäin. Pelit ratkaistaan kuvaruutu kerrallaan, joten vieritysrutiineja ei välttämättä tarvita. Idea on tärkein.
- Roolipeleissä usein pääasiana ovat keskustelut ja esineiden etsintä. Pelin ratkaisuun tarvitaan monesti suuri määrä pieniä asioita, kuten juuri tavaroita. Lisäksi roolipeleissä päähahmo tai -hahmot kehittyvät taistelujen myötä. Ohjelmointityö pelissä on valtava, idea ja juoni kuitenkin tärkeämpiä.
- Seikkailupelien ja roolipelien raja on häälyvä. Molemmat ovat laajoja ja vaativat näin useita megatavuja levytilaa. Uutuutena peleihin ovat tulleet animaatiot, jotka tekevät peleistä elokuvamaisen. Ohjelmointityö kestää kauan ja on vaativaa. Grafiikalla on suuri merkitys, mutta seikkailuissakin juoni ja mielenkiinnon säilyttäminen ovat ensiarvoisessa asemassa.
- Strategiapelit tarjoavat mahdollisuuden taktiseen pelaamiseen. Tarkoituksena on saada vallattua itselleenjotakin, esimerkiksi maata. Pelaaminen saattaa kestää useita tunteja. Pelit ovat yleensä varsin laajoja, ja sisältävät paljon pieniä yksityiskohtia, jotka tekevät ohjelmointityöstä mittavan. Samoin peli tarvitsee hyvin suunnitellun tekoälyrutiinin niitä varten, jotka haluavat ottaa mittaa tietokoneesta.
- Pulmapeleissä tarkoituksena on selvittää annettu ongelma, esimerkiksi siirtääjokin esine tiettyyn kohtaan, jotta pääsee eteenpäin. Pelit on yleensä toteutettu yhdellä kuvaruudulla. Ohjelmointi on helppoa (pelin pystyy tekemään parissa kuukaudessa), mutta hyvän idean keksiminen on vaikeaa.
- Simulaattorit ovat yleensä vektorigrafiikalla toteutettuja pelejä, joissa tarkoituksena on simuloida jotakin aitoa laitetta, esimerkiksi lentokonetta. Pelien toteutukseen vaaditaan paljon tietoa simuloitavasta laitteesta. Pelistä pitää saada aidon tuntuinen ja se tekeekin simulaattoreista vaikeimman peliohjelmoinnin alueen.

### Pelin suunnittelua

Tähän mennessä olemme puhuneet ideoista, suunnittelustaja lohkokaavioista. Nyt teemme ideasta pelin.

Tehdään yksinkertainen räiskintäpeli, jossa ei ole vierivää kuvaruutua eikä muutakaan monimutkaisempaa toimintaa (kuvassa 1.1 on pelin ruutu). Pelissä on kerrallaan kolme liikkuvaa esinettä, joista yhtä liikutetaan näppäimistöltä. Toiselle esineelle lasketaan liikerata ja kolmas liikkuu taulukon mukaan.

Kuvan 1.1 ruudun alareunassa on tykki, joka ampuu yläreunassa liikkuvia vihollisen aluksia. Tykkiä ohjataan nuolinäppäimillä ja liike tapahtuu ainoastaan oikealle tai vasemmalle. Piippu suuntautuu näppäimien mukaan yhden pikselin välein, joten sillä on 320 erilaista asentoa (kuvaruudun leveys on 320 pikseliä). Tykki voi ampua yhden ammuksen kerrallaan eli seuraava ammus lähtee vasta, kun vanha on poistunut näkyvistä (tällöin ammuksille ei tarvita erityistä taulukkoa). Vihollisaluksen liikettä varten tarvitaan

###### Kuva 1.1. Pelin kuvaruutu

oma taulukko, josta luetaan minne aluksen tulee seuraavaksi suunnata kulkunsa. Näin saadaan helposti aikaiseksi järkevä liikerata.

Ammuksen osuessa viholliseen se räjähtää (animaatio), ja alus ja ammus häviävät näytöltä. Samalla asetetaan uusi alus yläreunaan ja aloitetaan liikeratataulukon luku alusta.

Peli on erittäin yksinkertainen, ja sen pelattavuuskin on niukka, koska tähtäintä ei ole. Tämänkin tyyppisessä pelissä on kuitenkin paljon ohjelmoitavaa, kuten kohta näemme.

Jokainen peli tarvitsee nipun alustustoimenpiteitä. MS- DOSin tekstinäytön tilalle asetetaan 256-värinen grafikkatila, keskeytykset varataan omaan käyttöön ja niin edelleen. Pelin alustustoimenpiteistä kerrotaan myöhemmin.

Aloitetaan kuvaruudusta. Kuvaruutu ei vieri, jolloin ei myöskään tarvita vieritysrutiineita ja sen hallinta on yksinkertaista. Ruutuna voidaan käyttää esimerkiksi valmiiksi piirrettyä kuvaa, joka on pelin tausta ja jossa on paikat pistelaskureille. Mutta kuinka saadaan hallittua näytöllä liikkuvat esineet? Ja miten ne saadaan liikkumaan tasaisesti?

Esine, olipa se sitten vihollisen alus, ammus tai tykki, piirretään ruudulle jokaisen näytönpäivityksen yhteydessä uudestaan. Yleensä tämä tapahtuu 50-70 kertaa sekunnissa. Kun aluksen koordinaattia ensin siirretään ja piirretään se seuraavan kuvaruudun päivityksen aikana uusien koordinaattien mukaisesti, alus liikkuu.

Ennen piirtämistä vanha alus on kuitenkin poistettava näytöltä. Yksi tapa on palauttaa tausta pelkästään aluksen kohdalle. Toinen tapa on palauttaa aina koko kuvaruutu, ja piirtää vasta sen jälkeen esineet. Jälkimmäinen tapa sopii silloin, kun ruudulla on paljon liikkuvaa.

Aluksen piirtäminen näytölle ei ole sekään yksinkertaista. Jos alus piirretään uudelleen minä hetkenä hyvänsä, näyttö välkkyy ikävästi. Tämä poistetaan kaksoisbufferin ja kuvaruudun tahdistuksen avulla.

Käydään seuraavaksi läpi tilanteita, joita syntyy pelin aikana.

Vihollisen alus liikkuu taulukon arvojen mukaisesti, joten se ei pääse harhautumaan ruudun ulkopuolelle. Mutta ammus saavuttaa ruudun reunat, mikäli se ei ole törmännyt alukseen. Siksi sen liikkuminen pitää pysäyttää. Samalla tykki voi ampua uudestaan.

Kun ammus osuu alukseen, näytetään animaatio räjähdyksestä (animaatio on kuvasarja, joka toistetaan nopeasti niin, että se näyttää liikkeeltä). Helpoiten animaation esittäminen käy valmiiksi piirrettyjen kuvien avulla, jotka piirretään ensin jollakin piirto-ohjelmalla ja ladataan sen jälkeen peliin.

Miten sitten tutkitaan ammuksen ja aluksen törmäys? Niiden koordinaatteja ei voi verrata suoraan toisiinsa, koska kumpikaan ei ole pelkkä piste. Mitään erityistä törmäys- rekisteriäkään ei ole olemassa, joten ainoa vaihtoehto on tietää, missä kohdissa aluksen ja ammuksen rajat kulkevat.

Vielä ei ole selvitetty, miten pelin runko yleensä muodostuu. Ensiksi tarvitaan pelin alustusosa, joka suoritetaan yleensä vain yhden kerran. Itse peli toimii yhdessä suuressa silmukassa, jota toistetaan kuvaruudun piirtoon tahdistettuna eli 50-70 kertaa sekunnissa. Ohjelmoija voi tehdä silmukasta keskeytysrutiinin (koko peli toimii keskeytyksen aikana) tai vain tahdistaa silmukan aloituksen keskeytyksellä.

Tämä oli pieni kuvaus ohjelmointityöstä. Vieläkin jäi paljon tehtävää, ennen kuin peli on valmis pelattavaksi, eikä saa myöskään unohtaa pelin pelattavuuden ja mielenkiinnon parantamista. Kuvassa 1.2 on lohkokaavion tapainen esitys pelin kulusta.

###### Kuva 1.2. Kaavio pelistä.

          Tunnistukset
                 VGA-näyttö
                 äänikortti
          Alustus
                 kuvaruutu
                 keskeytykset
                 äänikortti
          Silmukka eli pelin runko-osa
                 törmäyksen tutkiminen
                 ammuksen liikkeen tutkiminen
                 näppäimistön lukeminen
                 ammuksen, aluksen ja tykin piirtäminen ja niin edelleen
          Pelin lopetus
                 koneen palauttaminen normaalitilaan (keskeytykset, kuvaruutu...)
                 paluu DOSiin

### Prosessorit

PC:n kehitys on ollut pitkäaikainen prosessi, nyt on eletty jo monta eri prosessorisukupolvea. Antiikkisen 8088- prosessorin maksimimuisti oli yksi megatavu, 286-prosessori on miltei samanlainen muisto menneisyydestä. Siinä on kuitenkin suojattu tila, jolloin muistia on käytettävissä 16 megatavua. Tämän muistin käyttö on kuitenkin hidasta, koska prosessorissa ei ole niin sanottua sivutustekniikkaa. Nykypäivän peliohjelmoinnissa voidaan onneksi 286- koneet unohtaa kokonaan.

386-prosessori on 32-bittinen prosessori, joten muistia voidaan osoittaa neljä gigatavua (gigatavu = 1000 megatavua), mikä tosin onnistuu vain suojatussa tilassa. Normaalisti prosessori käynnistyy reaalitilaan, jolloin käytössä on tuttu yksi megatavu. 386-prosessorille tehty koodi toimii kaikissa uudemmissa prosessoreissa, 286- koneissa se ei sen sijaan toimi.

Mitä hyötyä muistiavaruudesta sitten on? Kuten myöhemmissä luvuissa todetaan, PC:n muistinhallintaohjelmat toimivat suojatussa tilassa. Tällöin perusmuistiin voidaan siirtää tietoa ylämuistista (EMS ja XMS) salamannopeasti. 386- prosessorissa on myös 32-bittiset rekisterit, joten ohjelmointi konekielitasolla helpottuu ja nopeutuu huomattavasti.

386SX on aidosta 386:sta hieman riisuttu malli. Se on sisäisesti 32-bittinen, mutta ulkoisesti osoiteväylä on 24- bittinen, joten muistia voidaan osoittaa 16 megatavun verran. Sisäisestä 32-bittisyydestä johtuen aidolle 386- prosessorille kirjoitettu ohjelma toimii myös 386SX:ssä. Myös suojatun tilan ohjelmat toimivat.

486DX ja Pentium ovat tämän hetken perusmikron sydämet. Ne ovat lähellä 386- prosessoria, mutta niissä on huomattavia parannuksia. Samaan piiriin on integroitu myös matematiikkaprosessori, mutta sen käyttö on hitaampaa kuin esimerkiksi taulukoidut 32-bittiset kokonaisluvut. Huomattavasti nopeamman 486DX:stä tekee sen sisäinen kahdeksan kilotavun välimuisti. 486:ssa on myös niin sanottu liukuhihnatekniikka, mikä mahdollistaa kahden konekielikäskyn limittäisen suorituksen. 386- ja 486DX-/Pentium- prosessorin ohjelmoinnit eivät poikkea toisistaan muuten kuin optimoinnin osalta.

486DX2 poikkeaa 486DX:stä vain siten, että sen sisäinen kello toimii kaksinkertaisella nopeudella ulkoiseen kelloon verrattuna. Näin esimerkiksi 486DX2-66 toimii ulkoisesti 33 MHz:llä kuten 486DX-33, mutta sen sisäiset toiminnot tapahtuvat 66 MHz:llä. 486DX4 toimii nimestään huolimatta sisäisesti kaksin- tai kolminkertaisella nopeudella (tällä hetkellä). Näin saavutetaan jopa 120 MHz:n sisäinen kellotaajuus, ulkoisen ollessa 40 tai 60 MHz. Erona 486DX- prosessoriin on myös sisäinen 16 kilotavun välimuisti, jolla saavutetaan hieman lisää nopeutta. Näiden ohjelmointi ei poikkea mitenkään 486DX-prosessorista.

486SX on 486DX ilman matematiikkaprosessoria. Yleensä 486SX toimii 25/33 MHz:llä, joten se on myös sen takia hitaampi kuin aito 486DX.

Pentium on edelleen viritetty 486. Sen liukuhihnatekniikka on edistyksellistä: kaksi käskyä voidaan suorittaa yhden kellojakson aikana. Ohjelmoijalle tämä tuo haasteita etenkin optimoinnissa. Ulkoinen muistiväylä on 64-bittinen, joten tieto liikkuu vauhdikkaasti prosessorin ja muistin välillä. Myös kaikki liukulukulaskut ovat todella nopeita. 386- ja 486-prosessoreille tehdyt ohjelmat toimivat kuitenkin myös Pentium-koneessa.

Kuten huomataan, prosessorien kirjo on melkoinen. Lisäksi samaa prosessoria tekevät monet eri valmistajat, kuten Intel, AMD ja Cyrix. Ainakin Cyrixin 486DLC on hitaampi kuin Intelin samalla kellotaajuudella toimiva ratkaisu. Yksi syy on sen pienempi sisäinen välimuisti.

Peliä pitäisi päästä testaamaan eri prosessoreissa, sillä nopeusero 40 MHz:n 386:n ja 33 MHz:n 486:n välillä on yllättävä. Kun pelipaketin kylkeen laitetaan "vaatii 386- prosessorin", se ei takaa, että peli toimisi joustavasti kaikkien koneissa. Kun koodi tehdään 386-yhteensopivaksi, sitä voidaan ajaa sekä huippunopealla Pentiumilla tai hitaammassa 16 MHz:n 386SX:ssä. Nykyään peleissä ilmoitetaan sekä minimivaatimus että suositeltava kokoonpano.

###### Kuva 1.3. Prosessorien ominaisuuksia.

             Dataväylä/     Osoiteväylä/ Tyypillinen             Muisti/  Prosessorin
             bittiä         bittiä       kello/MHz   Mt                   Sis.välimuisti
                                                                          /kt
    8088,    8              20           4,7/8/10/12             1        -
      (8086,NEC V20,NEC V30)
    80286    16             24           8/10/12/16/20/24/32     16       -
    80386SX  16             24           16/20/25/33             4000     -
    80386    32             32           16/20/25/33/40          4000     -
    80386SCL 32             32           33                      4000     -
    AMD386   32             32           33/40                   4000     -
    Cy486SLC 16             24           25/33/40                16       1
    80486SX  32             32           25/33                   4000     8
    Cy486DLC 32             32           40                      4000     1
    80486DX  32             32           33                      4000     8
    80486DLC2 32            32           40                      4000     16
    80486DX2 32             32           33/66,50/100            4000     8
    80486DX4 32             32           33/100,40/120           4000     16
    AMD586   32             32           40/133                  4000     16*
    Pentium  64             32           60/66/75/90/100/120/133 4000     16

*: Sisäinen kello voi olla jopa 160 MHz, silläkin se toimii kunnolla. Sen lisäksi, tämä prosessori ei tarvitse kallista Pentium-emoa.

### Näytönohjaimet

Näytönohjaimella on erittäin suuri merkitys pelin nopeuteen. Tyypillinen näytönohjain on ISA-väylään liitetty SVGA-kortti, jonka nopeus ei useinkaan päätä huimaa. Sen sijaan paikallisväylään (VLB) tai nykyisin PCI-väylään liitetty kortti on kohtuullisen nopea.

Markkinoilla on mitä erilaisempia kiihdytinpiirejä, kuten Cirrus ja varsinkin S3. Nämä kiihdytinpiirit ovat käytössä pääasiassa Windows-ympäristössä, harvat pelit tukevat niiden ominaisuuksia. Kiihdytinpiireillä saadaan aikaan muun muassa nopeita videomuistin kopiointeja (vertaa ikkunan siirto ruudulla), mutta pelien nopeus riippuu lähes yksinomaan kortin kyvystä vastaanottaa tietoa muistista niiden omaan videomuistiin. VLB/PCI-kiihdytinkortit voivat olla tavallisessa DOS-käytössä hitaampia kuin nopea ISA-väylään liitettävä kortti.

Edellä puhuttiin vain Super VGA -korteista, mutta VGA- kortteja on käyttäjillä vielä jonkin verran. Monet tämän hetken pelit toimivat normaalilla VGA-kortilla (320x200, 256 väriä), tosin VGA-kortit ovat usein hitaampia kuin uudemmat SVGA-kortit. Kaikissa SVGA-korteissa on myös normaali VGA- tila, joten ohjelmoija voi vapaasti käyttää kiinteää videomuistiosoitetta ja VGA-kortin kovorekistereitä. SVGA- tilojen ohjelmointi taas on parasta toteuttaa VESA- standardin mukaisilla rutiineilla. EGA-kortit voidaan unohtaa kokonaan.

### Äänikortit

PC:n peruspiipperin ääniä tuskin kukaan jaksaa kuunnella. Markkinoilla onkin kymmeniä erilaisia äänikortteja. Hyvä asia tässä kilpailussa on korttien laadun parantuminen ja samalla hintojen romahdus.

Ohjelmoijalle korttien määrä on kuitenkin eräänlainen vitsaus. Kaikki kortit toimivat eri tavalla ja niiden kovotason ohjelmointia käsitteleviä kirjoja on erittäin vaikea saada käsiinsä. Monet pelitalot tyytyvätkin tukemaan vain muutamia yleisimpiä kortteja, kuten Sound Blaster - sarja, Gravis Ultrasound, AdLib, Pro Audio Spectrum ja General Midi. Lähes kaikki kortit ovat Sound Blaster - yhteensopivia, joten standardin mukainen Sound Blasterin ohjelmointi on erittäin tärkeää.

Äänikortteja ei yleensä kannata yrittää tunnistaa millään "konekielijipoillä", vaan koneen ympäristömuuttujat tutkimalla. Esimerkiksi Sound Blasterin asennuksen yhteydessä koneeseen lisätään BLASTER-muuttuja, josta löytyy käytetyt asetukset. Tämä siksi, että koneessa saattaa hyvinkin olla muita kortteja, jotka sotkevat äänikortin keskeytyksiä tai DMA-linjoja.

### Muut oheislaitteet

Koska peli saa vaatia 386-koneen, on ilmeistä, että koneessa on myös uusi 102-näppäimistö, jolle näppäimistörutiinit kannattaa tehdä. Vanhemmat 83- ja 84- näppäimistöt ovat jo lähes historiaa.

Hiirirutiineja ei ole syytä tehdä itse, sillä hiiriä on moneen lähtöön. Yleisimmät ovat sarjahiiri ja väylähiiri. Useimmat hiiret ovat kuitenkin Microsoft-yhteensopivia ja varmin tapa on käyttää valmiita BIOS-kutsuja. Hiiri vaatii aina ajurin config.sys- tai autoexec.bat- tiedostoon, minkä vuoksi ohjelmoijan on ehdottomasti tutkittava BIOS- funktiolla, onko hiiri asennettu.

Kaikki kiintolevyrutiinit tehdään esimerkiksi C-kielen valmiilla funktioilla. Ylämuisti jätetään muistinhallintaohjelmien huoleksi. Näin taataan yhteensopivuus mahdollisimman monessa eri kokoonpanossa.

### Apuohjelmat

Pääohjelma pelin tekemisessä on tietysti C-kääntäjä. Tämän kirjan ohjelmat on tehty Borlandin C:n versiolla 3.1. Konekieliohjelmat on tehty Borlandin Turbo Assemblerilla. Toinen suosittu kääntäjä on Microsoft C ja Assembler. Huippuluokan ohjelmoinnissa pitää käyttää jotakin 32-bittistä kääntäjää, kuten Watcom C++.

C- ja assembler-kääntäjien lisäksi pelin tekemisessä tarvitaan apuohjelmia. Grafiikan tekemiseen tarvitaan jokin piirto-ohjelma, esimerkiksi perinteinen Deluxe Paint, jolla kuva voidaan tallentaa joko LBM- tai PCX-muodossa. Kummatkin formaatit sisältävät kuvan pakatussa muodossa. Jotta kuvia voidaan käyttää pelissä, pitää kuva purkaa raakakuvaksi. Itse ohjelmoitu konvertteri-ohjelma mahdollistaa kuvan tallennuksen halutussa muodossa. Tästä on kerrottu lisää Grafiikka-luvussa.

Äänet voidaan digitoida esimerkiksi äänikortin mukana tulevalla ohjelmistolla. Yleensä ohjelmat toimivat Windows- ympäristössä, jolloin yleinen formaatti on WAV. Mikäli mahdollista, äänet tulee aina digitoida 16-bittisellä kortilla, sillä tällöin äänen laatu on huomattavasti parempi kuin 8-bittisellä kortilla digitoidut. Pelin asennuslevykkeillä äänet kannattaa pitää esimerkiksi 16- bittisinä 44 kHz:llä digitoituina. Asennuksen mukaan äänet kovertoidaan esimerkiksi 22 kHz:n 8-bitti ääniksi. Tällöin huippukortin omistajat saavat kaiken irti äänistä. Jos äänet konvertoidaan vasta asennusvaiheessa, pitää konvertteri tehdä itse. Bittisyyden tai taajuuden laskeminen esimerkiksi puoleen on helppo toteuttaa.

Pelin tekemisen aikana tulee tarve räätälöidä mitä kummallisimpia apuohjelmia. Karttaeditori pelialueen suunnitteluun tarvitaan lähes pelissä kun pelissä. Lisäksi saatetaan tarvita erilaisten taulukoiden luontiin sopivia rutiineja, tekoälyn/keskustelun suunnitteluohjelmia, liikeratojen editoreja ja niin edelleen. Hyvin tehdyt apuohjelmat nopeuttavat ja helpottavat pelin kehittelyä. Jos ohjelma tehdään joustavaksi, sitä voidaan ehkä käyttää vielä seuraavassakin pelissä.

### Markkinointi

Vaikka pelin ohjelmointi saattaa välillä tuntua vaikealta ja aikaa vievältä prosessilta, markkinointi vasta on vaikeaa ja aikaa vievää. Pelkkää peli-ideaa ei voi markkinoida, ympärillä pitää olla melkoisesti koodia.

Jos tiedät tehneesi jonkun erityisen hyvän yksittäisen rutiinin (esimerkiksi 3D-maailman täydellinen hallinta), sitäkin voi yrittää kaupata pelitaloille. Tosin tällöinkin rutiinista pitää olla tehtynä näyttävä demo, joka näyttää mihin se pystyy. Kun kaupataan "valmista" peliä, pitää peli-idean olla hyvin suunniteltu, sillä pelitaloja kiinnostaa pelin toimivuus enemmän kuin hienot rutiinit yksinkertaisen pelin ympärillä.

Yksi tapa yrittää myydä peliään on lähettää demoversioita useille eri pelitaloille. Pelkkä disketti ei riitä, vaan mukaan on syytä laittaa siisti kirje, jossa kerrotaan tekijöistä: mitä on ohjelmoinut ennen, ikä, tämänhetkiset mahdollisuudet ohjelmoida (opiskelu/työ) ja niin edelleen. Myös tekniset tiedot sekä mahdollisimman tarkasti selvitetty pelin kulku on syytä laittaa mukaan.

Jos peliään ei halua julkaista kaupallisena, voi sen aina levittää esimerkiksi sähköpostien kautta sharewarena. Versiossa voi olla esimerkiksi vain yksi kenttä ja loput saa lähettämällä tietyn summan rahaa pelintekijälle. Tämän vuoksi pelissä pitää olla selkeät yhteystiedot. Rikkaaksi sharewarella ei tosin tule.

Jos ja kun jokin pelitalo tekee sopimuksen pelisi julkaisemisesta, kannattaa olla varovainen ja pitää mahdollisimman paljon oikeuksia itsellään. Huono idea ei ole palkata asianajaja selvittämään monisivuisen sopimuksen saloja.

# 2 Ohjelmoinnin perusteet

Tässä luvussa käymme läpi muutamia kielien käyttöön ja optimointiin liittyviä asioita. Perehdymme ohjelmoinnin perusteisiin, mutta emme opeta ohjelmointikieltä alusta alkaen. Ohjelmointikielien opetteluun on olemassa paljon kirjallisuutta.

### Assemblerin käyttö

Assembler-kielen eli symbolisen konekielen käyttö on pelien teossa vähäistä, sillä pelin tekeminen on hidasta ja monimutkaista. Mihin assembleria sitten oikein tarvitaan?

C-kielellä ei voi tai ei oikeastaan kannata tehdä kaikkia rutiineja. Lisäksi C:n tuottama koodi on joissakin tapauksissa liian hidasta, joten tarvittavat rutiinit on ohjelmoitava assemblerilla. Yleistyksenä voisi sanoa, että kaikki grafiikan piirtoon liittyvät rutiinit pitää tehdä assemblerilla. Toisaalta, jos assemblerilla ei tarvitse ohjelmoida pitkiä rutiineja, sen käyttö on järkevää. Tällöin myöskään pelin pelaamiseen ei tarvita mitään "tehomyllyjä", joten ainakin pelaajat ovat ratkaisuun tyytyväisiä. Ohjelmoijalta se vaatii ylimääräistä ponnistelua, mutta on varmasti vaivan arvoista.

Intelin mikroprosessoreissa, erityisesti 386-prosessorissa ja ylemmissä, on valtavasti erilaisia käskyjä. Pelintekijä ei tarvitse niitä kaikkia. Ennen varsinaisia käskyjä kertaamme vähän 386-prosessorin sisäistä toimintaa.

### Rekisterit

Prosessorin rekistereistä jokaisella on oma tehtävänsä. Tämä vaikeuttaa huomattavasti ohjelmointia, sillä pitää tarkkaan tietää, mitä rekisteriä kannattaa missäkin kohdassa käyttää. Toinen ongelma on rekisterien vähäisyys, mikä vaikeuttaa tietojen pitämistä rekistereissä. Kuitenkin on pyrittävä siihen, että aina kun vain on mahdollista pitää käyttää jotakin yleiskäyttöisistä rekistereistä.

Muistin käyttö on aina hidasta, joten ensimmäinen optimointi eli nopeuttamisoperaatio on juuri rekistereiden käyttö. Tämä periaate toimii hyvin ainakin silmukoissa (loop), koska silloin arvot saadaan nopeasti luettua. Muistin nopeutta voidaan hieman säätää laittamalla sanat (16-bit) parillisiin osoitteisiin ja kaksoissanat neljällä jaollisiin.

###### Kuva 2.1. 80386-rekisterit.

Yleisrekistereitä, joita ovat EAX, EBX, ECX ja EDX, käytetään usein tarvittavien tietojen tallennukseen sekä niiden tehtävän mukaiseen käyttöön. Esimerkiksi vain akussa (EAX) voidaan suorittaa kertolasku.

Yleisrekistereiden 16 alinta bittiä voidaan jakaa kahteen 8-bittiseen osaan ja kumpaakin osaa voidaan osoittaa erikseen (kuva 2.1). Turhia bitin pyörityksiä ei tarvita osoitettaessa esimerkiksi AX-rekisterin kahdeksaa ylintä bittiä (AH). Toisin käy 32-bittisellä rekisterillä (esimerkiksi EAX), jossa ylimpää 16 bittiä ei ole mahdollista osoittaa erikseen.

Vaikka 286- ja 386-prosessorilla ei olekaan suurta nopeuseroa, näkyy 386:n paremmuus selvästi 32-bittisissä rekistereissä, sillä niihin voidaan tallentaa paljon suurempia lukuja kuin 16-bittisiin. Rekistereiden määrä myös näennäisesti "kaksinkertaistuu", koska 16-bittisiä osia voi käsitellä erikseen. Esimerkiksi rekisteriä EAX pystytään käsittelemään kahtena 16-bittisenä rekisterinä pyörittämällä sitä 16 bittiä oikealle käskyllä ROR EAX,16. Näin ylä- ja alapuolisko vaihtavat paikkaa. Nyt voidaan osoittaa entisiä 16:tta ylimmäistä bittiä normaalisti rekistereillä AH ja AL. Samaa menetelmää voidaan käyttää myös indeksirekistereihin.

Indeksirekistereitä käytetään yleensä segmenttirekisterien parina 20-bittisessä muistinosoituksessa. Tämä on erittäin nopea tapa hakea muistista tietoa prosessorille. Tapa sopii esimerkiksi taulukon käsittelyyn. Huomattavaa on, että normaalissa DOS-tilassa voidaan käyttää vain indeksirekistereiden 16:tta alinta bittiä, jotka rajoittavat tiedon määrän 64 kilotavuun. Tämä on se kuuluisa DOSin segmenttiraja. Myös BX-rekisteriä voidaan käyttää indeksinä muistinosoituksessa. Näillä kahdella tavalla ei ole nopeuseroa, joten ohjelmoijan tulee itse päättää kumpaa käyttää.

Uudemmat C-kielet, kuten Watcom C, toimivat suojatussa tilassa, jolloin koko muisti voidaan osoittaa pelkästään 32- bittisellä indeksirekisterillä. Muistin rakenteeseen ja suojattuun tilaan palaamme tarkemmin luvussa PC:n muistiongelma.

###### Esimerkki 2.1. Muistiosoitus segmentti- ja indeksirekisterillä.

      mov ax,0a000h            ;VGA-muistin alkuosoite
      mov es,ax                ;lataa segmenttirekisteri videomuistin alulla
      mov di,0                 ;nollaa indeksirekisteri
      mov cx, 320              ;aseta laskuri
    loop1:
      mov [es:di],0            ;nollaa muistipaikka a000h:di
      inc di                   ;lisää indeksiä
      dec cx                   ;vähennä laskuria
      jnz loop1                ;hyppy, jos ei nolla
      ret                      ;palaa ohjelmasta

Ohjelmoijalle vapaasti käytettävät indeksirekisterit ovat ESI ja EDI. Esimerkissä 2.1 käytettiin DI-rekisteriä, mutta yhtä hyvin olisi voinut käyttää myös SI-rekisteriä. (E)SP osoittaa koneen pinoon, joten sitä ei ole syytä käyttää. Tietokone menee helposti sekaisin, jos tätä rekisteriä asettelee oman mielensä mukaan. C-kielessä ei ole mielekästä sotkea rekisterin sisältöä, koska kutsuttaessa C-kielestä funktiota laitetaan välitettävät parametrit pinoon. Samalla sinne tallennetaan muutakin informaatiota.

Kutsutun funktion alussa BP-rekisteri laitetaan yleensä osoittamaan pinoa, jolloin paikalliset muuttujat löytyvät sen kautta. Jos funktiossa käytetään konekieltä, ei EBP:n käyttö omiin tarkoituksiin ole sallittua ilman sen tallennusta ja palauttamista alkuperäiseen arvoonsa. BP:n käytöstä kerrotaan tarkemmin myöhemmin.

Komentorekisteri EIP ilmoittaa indeksin missäpäin muistia senhetkistä ohjelmakoodia ollaan suorittamassa. Täten sen arvoa ei saa muuttaa.

Segmenttirekistereitä ovat CS, DS, ES, FS, GS ja SS. Nimestäkin päätellen ne toimivat aina 64 kilotavun rajoissa. Näistä vapaasti käytettäviä segmenttirekistereitä ovat ES, FS ja GS. Ne ovat tarkoitettu datan sijoittamista varten, jolloin dataa saadaan helposti ja nopeasti käyttöön esimerkiksi rekisteriparilla GS:DI. On huomattava, että FS- ja GS-rekisterit ovat käytössä vasta 386-prosessorista ylöspäin.

CS on varattu osoittamaan missäpäin muistia suoritettava ohjelmakoodi sijaitsee. Sitä ei voi käyttää omiin tarkoituksiin. DS sisältää senhetkisen datasegmentin sijainnin. Datasegmentissä sijaitsevat muun muassa kaikki globaalit muuttujat. Voit käyttää omissa konekielisissä rutiineissa datasegmenttiä silloin, kun et käytä muuttujia. Muista, että ennen datasegmentin muuttamista pitää se tallentaa pinoon ja käytön jälkeen arvo pitää palauttaa pinosta. SS osoittaa pinon segmentin, joten tämänkään rekisterin sisältöä ei saa muuttaa.

##### Esimerkki 2.2. Datasegmentin käyttö.

      mov bl,[arvo]            ;tässä voidaan käyttää muuttujia
      push ds                  ;tallenna DS pinoon
      mov ax,0a000h
      mov ds,ax                ;tämän jälkeen globaaleja muuttujia ei voi
                               ; käyttää
      mov di, 0
      mov [ds:di],bl           ;arvo muistipaikkaan ds:di
    ;edellinen rivi voidaan korvata myös lyhyemmin:
    ;mov [di],bl
    ;sillä ilman segmentin määritystä kone käyttää oletuksena DS:ää.
      pop ds                   ;palauta DS pinosta
      ret

### Optimointia

Konekielikäskyjä käytettäessä on katsottava, miten paljon käsky kuluttaa aikaa (oletamme, että konekieltä käytetään tilanteisiin, joissa vaaditaan suurta nopeutta). Kannattaa myös tarkistaa, voiko saman asian ajaa eri käskyllä ja vertailla näin eri käskyjen suoritusnopeutta. On myös tarkistettava, mitä lippuja käsky asettaa, sillä lippujen avulla tehdään kaikki haarautumiset. Tällöin voidaan välttyä yhdeltä ylimääräiseltä vertailukäskyltä (CMP).

Niinkin yksinkertaista asiaa kuin rekisterin nollausta voidaan nopeuttaa. Esimerkissä 3.3 on esitetty kaksi eri tapaa nollata rekisteri EAX. Rivin alussa on assembler- käskyt muutettuina konekoodeiksi. Kuten esimerkistä huomataan perinteinen MOV-käsky vaatii kaksinkertaisen määrän tavuja suoritukseen, eli prosessorilta menee pidempi aika koodien hakemiseen muistista itselleen.

##### Esimerkki 2.3. Rekisterin nollaus:

    66 b8 00 00 00 00             mov eax,00000000H
    66 33 c0                      xor eax,eax

Myös prosessorin tyyppi vaikuttaa käskyjen suoritusnopeuteen. 386 on hieman nopeampi kuin vastaavalla kellotaajuudella toimiva 286, ja 486 on huomattavasti nopeampi kuin 386. 486-prosessorin sisäinen välimuisti nopeuttaa monia muistia käsitteleviä silmukoita. Samoin monien käskyjen suoritusnopeus on 486-prosessorissa laskettu yhteen kellojaksoon, joten se tuplaa tehon tietyissä käskyissä.

486-prosessorissa on myös pipelining-tekniikka, jolloin samanaikaisesti suoritetaan kahta käskyä limittäin. Tämä aiheuttaa sen, että käskyjen järjestyksen vaihdolla lähdekoodissa voidaan saada aikaan suuriakin nopeuseroja. Työ on lähes salatiedettä, joten parhaat tulokset saadaan kokeilemalla eri vaihtoehtoja.

Optimointi tapahtuu aina joko muistin tai ajan suhteen. Käytännössä käy niin, että kun toista optimoidaan niin toinen kärsii. Esimerkiksi mikäli halutaan lisää nopeutta, kuluu muistia enemmän. Muistia ei ole käytettävissä rajattomasti, joten voidaan ajatella, että optimointi on täysin riippuvainen tilanteesta.

Kerrataan muutamia perusasioita: AND-käskyllä voi helposti nollata bittejä, OR-käskyllä asettaa bittejä ja NOT kääntää bitit. MUL- ja DIV-käskyt voidaan korvata SHL- ja SHR- käskyillä, mikäli lukua pitää kertoa/jakaa jollakin kahden potenssilla. ADD-ja SUB-käskyt korvataan nopeammilla INC- ja DEC-käskyillä, jos on lisättävä tai vähennettävä yksi. Ainoastaan 386- ja paremmissa prosessoreissa on yksittäisiin bitteihin kohdistuvat käskyt. Tällöin voidaan AND- ja CMP- käskyn sijasta käyttää BT-käskyä (bit test).

20-bittisen osoitteen asettaminen segmentti- ja indeksirekisteriin tehdään yleensä kahdella MOV-käskyllä, mutta käskyt voidaan myös korvata käyttämällä Lxx-käskyä. Tämän käskyn etuna on se, että samalla saadaan haluttuun rekisteriin myös OFFSET. Esimerkki 2.4 kuvaa tätä asiaa.

##### Esimerkki 2.4. 20-bittinen osoitteen asettaminen.

    ; normaali tapa
      mov ax,SEG osoite            ;segmentti->ax
      mov es, ax
      mov si,OFFSET osoite         ;offset->si
    ; vaihtoehtoinen tapa
      les si,osoite                ;segmentti->es ja offset->si
      ret
    osoite : dd 0

Ohjelmasilmukoiden käyttöön on valmiina käskyjä, esimerkiksi LOOP. Yksi LOOP-käsky tekee itseasiassa kolmen käskyn verran töitä. Se korvaa käskyt SUB, CMP ja JNZ. Näistä CMP-käskyä ei tarvita, koska SUB-käsky osaa itse asettaa Z-lipun tuloksen tullessa nollaksi. SUB-käsky korvataan DEC-käskyllä, joka osaa suorittaa samat toimenpiteet nopeammin. LOOP-käsky kuluttaa 11+m kellojaksoa, DEC-käsky ja JNZ-käsky yhdessä kuluttavat 9+m kellojaksoa. Tuloksena on todistettavasti nopeampi käsky- yhdistelmä. Tässä on yksi syy siihen, miksi on tärkeää katsoa, kuinka paljonmikäkin käsky kuluttaa aikaa.

Tämä ei ole ainoa tapa optimoida silmukkaa. Esimerkeissä 2.5 ja 2.6 on toinen tapa. Silmukoiden koodi sinänsä ei tee mitään järkevää.

##### Esimerkki 2.5. Hidas silmukka.

      mov cx,1000            ; Silmukka suoritetaan 1000 kertaa
    silmu:
      mov [es:di],0
      inc di
      dec cx
      jnz silmu
      ret

##### Esimerkki 2.6. Nopeampi silmukka.

      mov cx,200             ; Silmukka suoritetaan vain 200 kertaa
      mov al,0
    silmu:
      mov [es:di],al
      inc di
      mov [es:di],al
      inc di
      mov [es:di],al
      inc di
      mov [es:di],al
      inc di
      mov [es:di],al
      inc di
      dec cx
      jnz silmu
      ret

Silmukan toistokertoja vähentämällä vähennetään myös suorittamiseen kuluvaa aikaa, koska myös DEC-käsky ja JNZ- käskyt kuluttavat aikaa. Helpon laskutoimituksen tuloksena saadaan jälkimmäisen esimerkin säästämät kellojaksot 386- koneessa: 800 x (9 + 2) = 8800.

Monessa tapauksessa on hyvä käyttää käskyjä, jotka suorittavat enemmän kuin yhden toimenpiteen. Useimmiten ne suoriutuvat tehtävästä nopeammin kuin erilliset käskyt. Tällaisia käskyjä ovat esimerkiksi kaikki merkkijonokäskyt ja REP. Esimerkit 2.7 ja 2.8 tekevät täsmälleen saman asian, mutta jälkimmäinen suoriutuu tehtävästä huomattavasti nopeammin. Huomaa, että esimerkissä 2.8 muistiinosoitus tehdään 32-bittisenä, jolloin toistokertojen määrä putoaa neljännekseen.

##### Esimerkki 2.7. Muistin täyttö perinteisellä tavalla.

      les di,muisti              ;lataa muistiosoite pariin ES:DI
      mov al,10                  ;tavu, jolla täytetään
      mov cx,1000                ;täytettävä alue 1000 tavua
    silmu:
      mov [es:di],al             ;tavu muistiin
      inc di                     ; lisää indeksiä
      dec cx                     ; vähennä laskuria
      jnz silmu                  ;hyppää, jos <> 0
      ret

##### Esimerkki 2.8. Mulstin täyttö nopealla tavalla.

      les di,muisti              ;lataa muistiosoite pariin es:di
      mov eax,0a0a0a0ah          ;tavut, joilla täytetään
      mov cx,250                 ;täytettävä alue 1000 tavua
      cld                        ;nollaa direction-lippu,
                                 ;jolloin STOS-käsky lisää DI:tä.
                                 ;Jos suoritetaan STD-käsky
                                 ;(lipun asetus), STOS-käsky
                                 ;vähentää DI:n arvoa.
      rep stosd                  ;STOSD: siirtää EAX:n osoitteeseen
                                 ;ES:DI ja lisää DI:tä 4:llä.
                                 ;REP : tekee STOSB-käskyä ja
                                 ;vähentää CX:ää, kunnes CX=0.
      ret

### TASMin käyttö

Tämän kirjan esimerkit on tehty Borlandin Turbo Assemblerin versiolla 3.1. Myös vanhempien versioiden pitäisi toimia moitteetta, kunhan ne tukevat 386-käskyjä. Itse konekielikäskyt toimivat lähes jokaisessa 386-prosessoria tukevassa assemblerissa, kuten Microsoftin MASMissa. Sen sijaan assembler-tiedoston alussa tarvittavat segmentti- ja muut määrittelyt saattavat erota eri kääntäjien välillä.

Esimerkissä 2.9 on tiedosto, joka voidaan kääntää TASM- kääntäjällä. Tiedoston funktiot on tarkoitettu kutsuttavaksi C-kielestä, minkä kertoo lause "MODEL LARGE,C". Tällöin parametrit funktioihin välittyvät pinon avulla, kuten edellä on kerrottu.

##### Esimerkki 2.9. Tyypillinen TASM-tiedosto.

    IDEAL              ;vaihda TASM ideal-tilaan. Tällöin kääntöaika
                       ;nopeutuu,
                       ;tosin koodi ei ole täysin MASM-yhteensopivaa.
    MODEL LARGE,C      ;muistitilaksi LARGE, funktioita kutsutaan
                       ;C-kielestä.
    P386               ;mahdollistaa 386-käskyt.
    DATASEG            ;alustetaan datasegmentti

    muuttuja dw 100    ;alustetaan muuttuja

    CODESEG            ;alustetaan koodisegmentti
    PUBLIC rutiinil,rutiini2 ;esitellään kaksi funktiota

    ;rutiini1 alkaa, saatuja parametrejä on kaksi (16- ja 8-bittinen)
    PROC rutiini1 parametri1:WORD,parametri2:BYTE

      mov ax,[parametri1]
      mov bl,[parametri2]
      ret

    ENDP ;rutiini1 loppuu

    ;rutiini2 alkaa
    PROC rutiini2 parametri1:WORD

      mov ax,[muuttuja]
      add ax,[parametri1]
      ret

    ENDP ;rutiini2 loppuu

    END ;koko ohjelma loppuu

Jos taas tehdään assembler-tiedosto, joka halutaan yksinään EXE-tiedostoksi, muuttuvat alun määrittelyt hieman. Ohjelmalle pitää varata pino .STACK- komennolla. Ohjelman alussa datasegmentti pitää muuttaa oikeaksi @data- symboolilla. Ohjelmasta ei voi poistua RET-komennolla, sillä funktio ei ole alirutiini. DOSiin voidaan poistua DOS- keskeytyksellä 21h.

Esimerkissä 2.10 on TASM-tiedosto, joka voidaan kääntää seuraavasti: tasm esim.asm ja sitten linkata: tlink esim.obj. Nyt voit ajaa esim.exe-tiedoston.

##### Esimerkki 2.10. TASM-tiedosto, joka voidaan ajaa yksinään.

    MODEL LARGE
    P386
    . STACK ; pino
    IDEAL

    ;Seuraavat muuttujat datasegmenttiin
    DATASEG
    muuttuja dw 0

    CODESEG

    PROC koodi

      mov ax,@data
      mov ds,ax                 ;asetetaan oikea datasegmentti
      mov ax,1
      mov [muuttuja],ax

      mov ah,04ch               ;palaa systeemiin
      int 021h

    ENDP
    END

### C-kielen käyttö

Pelin ohjelmoinnissa C-kieli on ylivoimainen ykkönen. Siihen on helppo liittää esimerkiksi assembleria. Olemme käyttäneet esimerkeissämme Borlandin C++ v3.1-kääntäjää.

Aluksi on syytä katsoa muutamia kääntäjän asetuksia, jotka on hyvä olla päällä ennen kuin aloitetaan varsinainen ohjelmankirjoitus. Peliohjelmoinnissa muistityypiksi (memory model) on hyvä valita "large", tällöin dataa ja koodia voi olla maksimissaan 1 megatavu. Kuitenkaan yksittäinen taulukko ei voi olla suurempi kuin 64 kilotavua. Kaikki kirjan C- ja assembler-esimerkit on tehty käyttäen tätä tilaa.

Optimointi-asetukset on syytä asettaa nopeimmalle koodille. "Heap size" asetetaan osoittamaan 640 kilotavua. Mikäli kääntäjäsi sallii 386-koodin generoinnin, aseta se päälle. Aseta myös tulostiedostojen hakemistopolku haluamaksesi, esimerkiksi RAM-asema on kätevä. Kun C-kääntäjälle annetaan komento "käännä", se muuttaa C-kielisen lähdekoodin konekieleksi. Ohjelmoija voi vaikuttaa tuloskoodin nopeuteen käyttämällä oikeanlaisia rakenteita. Samoin tuloskoodiin voi tehdä itse muutoksia, mikäli ei pidä sattumalta C-kääntäjän tuottamasta koodista.

Olemme edellä jo muutaman kerran tähdentäneet, ettei tässä kirjassa opeteta ohjelmointikieltä, mutta haluamme kuitenkin lähestyä alkavaa ohjelmoijaa. Opetamme tässä luvussa miten C-kielellä kannattaa ohjelmoida, ts. minkälaisia rakenteita on syytä käyttää tuloskoodin nopeuden kannalta. Yllättäen huomaa, kuinka jopa 486-prosessori tahmaa yksinkertaisissa asioissa.

Lopulta pienen optimoinninjälkeen sama rutiini toimii puhtaasti 386-prosessorillakin. Näin käy helposti esimerkiksi grafiikkaan liittyvissä asioissa. Jonkinlaisena pääsääntönä C-ohjelmoijalle voi sanoa, että valmiit funktiot, esimerkkinä mainittakoon printf-funktio, ovat luvattoman hitaita. Samalla nopeudella, millä printf-funktio tulostaa kirjaimia merkkinäytölle, itse tehty tulostaa grafiikkanäytölle VGA-tilassa.

Tämä oli vain yksi esimerkki lukuisien muiden esimerkkien joukosta. Kannattaakin opiskella konekieltä ja kirjoittaa omia funktioita C:n hitaiden funktioiden tilalle.

Jotta saataisiin hieman käsitystä siitä, millaista tuloskoodia C-kääntäjä tuottaa, katsotaan asiaa muutamien esimerkkien valossa. Aloitamme hyvin yksinkertaisesta asiasta, summauksesta. Seuraava rivi summaa muuttujan aa:n sisältöön yhden.

       aa=aa+l;

Summaus tehtiin konekielitasolla "sijoittamalla". Tällöin asian suorittamiseen tarvittiin kaiken kaikkiaan kolme eri käskyä. Sama asiahan voitaisiin tehdä myös yhdellä käskyllä; INC. C-kääntäjän saa tekemään samoin, kun kirjoittaa äskeisen rivin tilalle

       aa++;

Haittana jälkimmäisessä on, että lisäys tapahtuu aina yhdellä. Isompia lukuja voidaan muuttujaan lisätä seuraavalla rivillä. Kääntäjä tekee seuraavan lisäyksen yhtä käskyä käyttäen: ADD-käskyä.

       aa+=2

Tehdään seuraavaksi aivan yksinkertainen C-ohjelma, jotta tiedetään, miten C-kielen muuttujat toimivat konekielitasolla.

Esimerkissä 2.11 käytetään kahta eri tyypin muuttujaa, paikallista ja globaalia.

##### Esimerkki 2.11. C-ohjelman rakenne.

    int aa;                     //globaali muuttuja

    void main(void)
    {
      int bb;                   //paikallinen muuttuja
      void funktio(int);

      bb=aa;                    //molempia voidaan käyttää
      funktio(1);               //kutsutaan funktiota "funktio"
    }
    void funktio(int dd)
    {
      int cc; //paikallinen muuttuja

      cc=dd;
      aa=cc; //molempia voidaan käyttää
    }

Edellä oli puhetta PC:n rekistereistä ja segmenteistä. Tutkitaan miten kääntäjä käyttää niitä. Aloitetaan muuttujasta aa. Se on ns. globaalimuuttuja, eli sitä voidaan käyttää sekä main-funktiossa että kaikissa muissakin funktioissa. Kaikki globaalit muuttujat sijaitsevat aina datasegmentissä (DS). Tämän vuoksi ne ovat nopeita käyttää ja usein tarvittavat muuttujat kannattaakin laittaa globaaleiksi. Haittana on se, että tällaisia muuttujia ei voi olla määriteltynä kuin 64 kilotavua. Mikäli datasegmentti täyttyy, kääntäjä antaa siitä virheen.

Paikallisia muuttujia (bb, cc ja dd) C-kieli käyttää BP:n eli pinon kautta. Nämä muuttujat ovat huomattavasti hitaampia kuin globaalit. Kaiken nopein tapa käyttää muuttujia on niiden laittaminen suoraan johonkin prosessorin rekisteriin. C-kielessä se tapahtuu antamalla tyypiksi "register". Valitettavasti kääntäjä itse päättää, voidaanko muuttuja asettaa rekisteriin.

Samaisesta esimerkistä voidaan tutkia myös C-kielen toimintaa funktion kutsussa. Aina, kun kutsutaan jotakin funktiota, parametrit tallennetaan pinoon (PUSH) ja hypätään funktioon (CALL). Funktion alussa BP:n arvo tallennetaan pinoon ja BP:n arvoksi asetetaan SP:n arvo. Jotta asia näkyisi käytännössä, esimerkissä 2.12 on esimerkin 2.11 listaus disassembloituna, eli assembleriksi käännettynä.

##### Esimerkki 2.12. C-kieltä vastaava assembler-listaus (vertaa esimerkki 2.11).

    _main
      push bp
      mov bp,sp
      sub sp,0002H
      mov ax,_aa
      mov [bp-2],ax                     ;bb=aa
      push 0001H
      call FAR funktio                  ;funktio(1)
      add sp,0002H
      leave
      retf

    funktio
      push bp
      mov bp, sp
      sub sp,0002H
      mov al,[bp+6]
      cbw
      mov [bp-2],ax                     ;cc=dd
      mov _aa, ax                       ;aa=cc
      leave
      retf

Muuttujia hieman monimutkaisempia ovat osoittimet ja taulukot. Niiden ymmärtäminen on ensiarvoisen tärkeää C- ohjelmoinnissa. Osoittimet ja taulukot voivat olla muuttujien tapaan joko globaaleja tai paikallisia. Usein peleissä tarvitaan esimerkiksi grafiikkaa varten suuria muistialueita, jolloin ei voida käyttää globaaleja taulukoita, sillä pian datasegmentistä (vain 64 kt käytössä) loppuisi tila. Parempi on käyttää osoittimia, jotka osoittavat muualle muistiin. Ne kertovat mistä grafrikkaa löytyy tarvittaessa.

Toinen tapa on käyttää esimerkiksi EMS-muistia, siihen palataan luvussa PC:n muistiongelma.

Esimerkissä 2.13 on lyhyt ja yksinkertainen esimerkki taulukoiden ja osoittimien käytöstä. Esimerkissä varataan kahdella eri tavalla tilaa keskusmuistista. Huomaa, että ennalta määritelty taulukko lisää saman verran käännetyn ohjelman pituutta.

##### Esimerkki 2.13. Taulukot ja osoittimet.

    #include <alloc.h>

    char far *osoitin;                   //osoitin
    char taulukko[4000];                 //4000 tavua pitkä taulukko

    void main(void)
    {
      //varataan 64000 tavua tilaa, "osoitin" osoittaa alueen alkuun.
      osoitin=(char far *)farmalloc(64000);

      //molempia voidaan käyttää samalla tavalla.
      osoitin[999]=1;
      taulukko[999]=2;
    }

Molempien taulukoiden käsittely C-kielessä näyttää aivan samalta. Pelkällä C-kielellä ohjelmoija ei huomaa mitään eroa, mutta peliohjelmoijalle on tärkeää tietää, kumpaa tapaa on parempi käyttää. Katsotaan jälleen käännettyä konekoodia. Taulukoiden käyttö näyttää aivan erilaiselta konekielitasolla, kuten esimerkistä 2.14 nähdään.

##### Esimerkki 2.14. C-kieltä vastaava assembler-listaus (esimerkistä 2.13).

      push      0000fa00H
      call      FAR _farmalloc
      add       sp,0004H
      mov       L1, dx
      mov       _osoitin,ax
      les       bx, dword ptr _osoitin
      mov       [byte ptr es:bx+3e7h],01H        ;osoitin[999]=1
      mov       byte ptr L2,02H                  ;taulukko[999]=2
      retf

Osoittimet ovat siis "large"-tilassa aitoja 20-bittisiä osoittimia, joten niitä käytetään tutulla segmentti-indeksi parilla (es:bx). On samantekevää, onko osoitin paikallinen vai globaali, konekielitasolla niitä käytetään samalla tavalla. Globaalit taulukot taas sijaitsevat kiinteästi datasegmentissä, joten niiden käyttö onnistuu yhdellä konekielikäskyllä. Vastaavasti paikalliset taulukot löytyvät BP:n kautta yhdellä käskyllä.

Jos peräkkäisillä C-kieli-riveillä käytetään eri osoittimia [esimerkki 2.15), konekoodista tulee melko hidas, sillä uusi osoitin pitää aina alustaa Lxx-käskyllä. Parempi tapa (tosin sekavampi) on varata yksi suuri taulukko, jota sitten käytetään moneen tarkoitukseen lisäämällä alkuun jokin indeksi. Jos osoittimilla tehtyjä taulukoita käytetään omissa konekielirutiineissa, saadaan niistä yhtä nopeita kuin taulukoista järkevällä koodin suunnittelulla.

##### Esimerkki 2.15. Osoittimien käyttö.

    #include <alloc.h>

    #define TAULUKOKO 20000
    void main(void)
    {
      char far *oso1
      char far *oso2;
      char far *oso3;

      osol=(char far *)farmalloc(TAULUKOKO);
      oso2=(char far *)farmalloc(TAULUKOKO);
      oso3=(char far *)farmalloc(TAULUKOKO*2); //varataan yksi suuri
                                               //taulukko
      oso1[1000]=1;                  //tässä kone suorittaa Lxx-käskyn
      oso2[1000]=2;                  //tässä kone suorittaa Lxx-käskyn
      oso1[2000]=3;                  //tässä kone suorittaa Lxx-käskyn

      oso3[1000]=1;                  //vain tässä kone suorittaa Lxx-käskyn
      oso3[1000+TAULUKOKO]=2;
      oso3[2000]=3;
    }

### Kielien yhdistäminen

Yhteistyö on aina valttia, sama pätee myös ohjelmoinnissa. Tuskin kukaan viitsii ohjelmoida peliä kokonaan assemblerilla. C:llä se onnistuu, mutta viisas yhdistää kummankin kielen edut yhdeksi paketiksi.

Lähtökohta on, että pelin runko tehdään C:llä, kriittiset tilanteet assemblerilla. Miten assembler-koodi liitetään C:hen riippuu C-kääntäjästä. Helpoin tapa on C++- ohjelmointikielen tukema ASM-käsky.

Kääntäjä tukee suoraan inline-assembleria, kun siitä vain kerrotaan sille. Ainakin Borlandin C++:ssa aivan listauksen alkuun pitää laittaa komento "#pragma inline", jotta saadaan käyttöön myös 386-prosessorin käskyt. Esimerkki 2.16 liittää assembleria C-sourcen joukkoon.

##### Esimerkki 2.16. C++ ja assembler

    #pragma inline

    void main(void)
    {
      for (i=O;i<5;i++)
      {
      printf ( "Arvo on %d", i ) ;
      }
      asm{
      sub eax,eax
      }
    }

Ohjelma on yksinkertainen esimerkki, eikä se tuota mitään järkevää. Siitä kuitenkin näkee miten liittäminen tapahtuu.

Assembler-koodi on samalla kohdalla myös tuloskoodissa. Mutta miten assembler-koodilla voidaan käsitellä muuttujia? Helposti, sillä samoja muuttujia voidaan käyttää kummassakin kielessä. Esimerkki 2.17 havainnollistaa asiaa.

##### Esimerkki 2.17. Muuttujien käyttö.

    #pragma inline
    void main(void)
    {
      int i ;
      long e=0;

      for (i=O;i<5;i++)
      {
        asm{
        inc      [e]
        }
      }
    }

Inline-assemblerin käytöstä on myös haittaa. Ensinnäkin käännösaika pitenee, mutta pahempi asia on C-kääntäjän optimointien heikentyminen niissä funktioissa, joissa inline-assembleria on käytetty. Hyvä sääntö olisikin tehdä funktio kokonaan C:llä tai inline-assemblerilla. Tällöin on tosin parempikin tapa liittää assembleria C-kieleen: C- listaus käännetään C(++)-kääntäjällä ja assembler-listaus assembler-kääntäjällä, lopuksi ne linkataan yhteen. Tähän tarjoaa mahdollisuuden Borlandin projektisysteemi. Ohjelmat voidaan kääntää myös komentorivikääntäjillä ja linkata esimerkiksi tlink-ohjelmalla.

Kun ohjelma käännetään joko C- tai assembler-kääntäjällä, syntyy siitä .OBJ-päätteinen tiedosto. Nämä objektitiedostot liitetään yhteen linkkerillä, jolloin syntyy yksi .EXE tiedosto. Seuraavassa on kaksi listausta, joista ensimmäinen käännetään C++-kääntäjällä ja jälkimmäinen assembler- kääntäjällä. Saadut .OBJ tiedostot linkataan yhdeksi ajettavaksi ohjelmaksi.

##### Esimerkki 2.18. Kokonaisuuden C-kielinen listaus.

    //
    // Ohjelma : testil.cpp
    //
    #include <conio.h>

    extern"C"{                       //funktio pitää esitellä C-tyyppisenä
    void teksti(char);               //parametri on 8-bittinen
    }

    void main(void)
    {
      clrscr();

      teksti('A');                   //kutsu assembler-rutiinia

      getch();
    }

##### Esimerkki 2.19. Kokonaisuuden assembler-kielinen listaus.

    ;
    ; Ohjelma : testi2.asm
    ;
    IDEAL
    MODEL LARGE,C                 ;Funktiot saavat parametrit C-tyyppisenä
                                  ;(pinossa)
    P386

    DATASEG

    muuttuja dw 0                 ;tässä voit määritellä muuttujia
    CODESEG
      PUBLIC teksti               ;esitellään funktiot

    ;
    ; Tulostaa merkin ruudun yläreunaan
    ;

    PROC teksti merkki:BYTE
    ;"teksti"-funktio, saa 8-bittisen parametrin

      push ax
      push es
      push di
      mov ax,0b800h
      mov es, ax
      mov di, 0
      mov al,[merkki]
      mov [es:di],al
      pop di
      pop es
      pop ax
      ret

    ENDP

    END

Jos käytössäsi on Borlandin C++ kääntäjä v3.1, menettele seuraavasti: Kirjoita kummatkin esimerkit (2.18 ja 2.19) eri tiedostoihin. Valitse ylävalikosta Project / Open Project ja kirjoita projektille nimi "testi". Valitse ylävalikosta Window / Project. Lisää projektiin äsken tallentamasi tiedostot "testi1.cpp" ja "testi2.asm". Nyt vain käynnistät ohjelman painamalla CTRL+F9. Kääntäjä valitsee tarkentimen (.CPP tai .ASM) perusteella itse joko C- tai assembler-kääntäjän kuhunkin tiedostoon.

# 3. PC:n muistiongelma

Aloittelevien ohjelmoijien saattaa tuntua vaikealta ylittää tuota maagista 640 kilotavun muistirajaa. PC:n vanhuudesta johtuen tätä ongelmaa on mahdoton poistaa DOSin alaisuudessa. Se voidaan kuitenkin kiertää myöhemmin esitettävillä muistinhallintaohjelmilla.

Uudet DOS-versiot ovat tulleet auttamaan monia koneen käyttäjiä jättämällä yhä enemmän työmuistia käyttöön. Hyödyntämällä uudet ominaisuudet hyvin, on mahdollista jättää vapaata muistia jopa 620 kilotavua. Täytyy kuitenkin todeta, että edes täydet 640 kiloa ei riitä kunnon pelin pyörittämiseen.

Pelit vaativat muistia mielellään parikin megatavua, jolloin avuksi tulevat muistinhallintaohjelmat. Onneksi niiden käyttäminen on helppoa, eikä kenenkään tule jättää niitä hyödyntämättä siinä uskossa, että ne olisivat liian monimutkaisia.

### Segmentointi

Segmentointi oli aikanaan hyvä keksintö, tarkemmin sanoen 8-bittisten prosessorien vallankumouksen aikana - aikana, jolloin 16- bittinen teknologia oli liian kallista hyödyntää. Silloin oli segmentoinnin avulla mahdollisuus kasvattaa prosessorin muistiavaruuden määrää 64 kilosta yhteen megatavuun. Megatavu oli silloin käsittämätön muistimäärä, jota ei voinut saada täyteen mitenkään.

Kun segmentointi mahdollisti tämän suuren kapasiteetin, ei sen tuomia haittoja katsottu olevan olemassa. Palataan hetkeksi vielä segmentoinnin toimintaan ja myöhemmin muistin määrän kasvattamiseen.

16-bittinen prosessori, jolla on samanlevyinen osoiteväylä, ei pysty suoraan osoittamaan muistia kuin 64 kilotavua. 8086- ja 8088-prosessoreissa ongelma on ratkaistu antamalla niille 20-bittinen väylä 16-bittisen sijasta, jolloin muistiavaruus laajentuu yhteen megatavuun. Dataväylä on kuitenkin niissä 16-bittinen, jolloin yhdellä kerralla voidaan osoittaa vain 64 kilotavua muistia. Kahdella 16-bittisellä luvulla pystytään käymään koko muistialue läpi. Näin syntyi segmenttiosoite ja siirrososoite.

Segmenttiosoite pystytään valitsemaan 16-tavun välein ja siirrososoite sisältää siirroksen segmentin sisällä.

Todellinen osoite muodostetaan prosessorissa summainpiirillä. Tuloksena saadaan 20-bittinen luku. Kuvassa 3.1 on esitetty oikean muistiosoitteen muodostuminen.

##### Kuva 3.1. Segmentin ja siirroksen yhteenlasku.

    Segmentti      Siirros       Laskenta       Muistiosoite
    c0h            1ch           c0h x 16+1ch   c1ch

Todellisuudessa koko muistiavaruus on lineaarisesti jatkuvaa, mutta sitä ei voi niin hyödyntää, ellei ohjelmoi suojatussa tilassa. Kuvassa 3.2 on PC:n muistikartta. Siitä selviää hyvin segmentointi sekä muistin lineaarinen jatkuvuus.

##### Kuva 3.2. PC:n muistikartta.

    Alkuosoite   Tehtävä
    0000:0000    Keskeytysvektorit
    0000:0400    ROM-BIOSin tietoalue
    0000:0500    Perusmuisti
    A000:0000    Näyttömuisti
    C000:0000    EMS,devicet ym.
    F000:0000    ROM-BIOS
    10000:000    HMA-alue.(286-,386-prosessori)*
    11000:000    Extended-muisti (vain suojatussa tilassa)

* HMA muodostetaan osoitelinjan A20 avulla. Se puuttuu 8086- ja 8088-prosessoreista kokonaan.

Mitä haittaa segmentoinnista on peleissä? Hyvin vähän, C- kääntäjä nimittäin huolehtii segmenttien vaihdoista automaattisesti. Yleensä itse tehdyt konekielirutiinit ovat niin lyhyitä, että ne mahtuvat hyvin yhteen segmenttiin. Ongelmia saattaa syntyä grafiikan kanssa. Kaikki palaset eivät välttämättä mahdu yhteen segmenttiin, jolloin ei ole muuta mahdollisuutta kuin ottaa käyttöön lisää segmenttejä. Palojen kopioiminen muistista monimutkaistuu vähän, mutta pätevälle ohjelmoijalle se ei tuota vaikeuksia. Yleensä myöskään digitoitu ääni ei mahdu yhteen segmenttiin.

Ongelmat on ohitettavissa, mutta segmentoinnista on myös todellista haittaa. Segmentointi hidastaa koodin suorittamista, koska välillä pitää laskea uusi segmentti. Näin tapahtuu usein C-kielisessä koodissa. Esimerkkinä tästä voidaan mainita datan hakeminen perusmuistista videomuistiin.

Toinen haitta esiintyy DMA:ta käytettäessä: DMA lopettaa toimintansa aina, kun osoiteväylän 16 alimmaista linjaa ovat loogisessa ykköstilassa (FFFF). Tällainen osoite on esimerkiksi 3555:AAAF, vaikka tässä indeksi ei olekaan vielä FFFF. Mutta kun kyseinen arvo annetaan osoiteväylälle, sen arvoksi tuleekin 3FFFF.

### Muistin kasvattaminen

640 kiloa ei enää riitä peleihin, koska pelkkä grafiikka hotkaisee sen helposti kokonaan. Jostakin pitää ottaa lisää muistia käyttöön.

Yhden megan alapuolelta se on mahdotonta, koska se on tungettu täyteen kaikenlaisia ajureita ja muita tärkeitä ohjelmia. Sitäpaitsi muistialueet, jotka saadaan käyttöön megan alapuolelta, ovat kovin pieniä. Parempi onkin käyttää muistia ensimmäisen megan yläpuolelta, jolloin on mahdollista saada isompia muistialueita käyttöön.

Alue saadaan käyttöön kahdella eri tavalla: EMS- tai XMS- muistinhallintaohjelmilla. (Emme suosittele luomaan vastaavia omia rutiineja, koska yhteensopivuus kärsii eikä peli toimi kaikissa koneissa).

EMS-muistin käyttö 286-koneessa ei ole järkevää, koska se on liian hidasta. 386 (ja paremmat)-koneissa on jo suunnitteluvaiheessa kyseisen muistin käyttö on otettu huomioon. XMS-muisti sen sijaan soveltuu kumpaankin koneeseen, tosin hidasta sekin on 286-koneessa.

Jos PC olisi aikoinaan suunniteltu järkevämmin, olisi perusmuistia saatu helposti käyttöön enemmän. Kiinteät osoitteet ovat huono asia, sillä ne rajoittavat aina muistin alkamaan ja päättymään tiettyyn osoitteeseen. Tästä esimerkkinä mainittakoon VGA-kortin muistiosoite, joka alkaa aina osoitteesta A000:0000. Tämä rajoittaa perusmuistin maksimikooksi juuri tuon 640 kt. Jos kortin osoitetta voisi muuttaa, saataisiin muistia helposti 128 kilotavua lisää.

Nykyisetkään prosessorit eivät siis pysty osoittamaan muistia ilman suojattua toimintatilaa yhtä megaa enempää.

### Muistinhallintaohjelmat

XMS-muisti saadaan käyttöön esimerkiksi config.sys- tiedostoon sijoitettavalla HIMEM-ajurilla. EMM386-ajurilla saadaan käyttöön sekä EMS- että XMS-muistit, tosin EMS- muisti voidaan kytkeä pois "noems"-valinnalla.

Kummankin toiminnassa on muutamia yhteisiä piirteitä - ne kopioivat dataa joltakin muistialueelta jollekin ennalta määrätylle muistialueelle. Kohteen tulee siten aina sijaita ensimmäisen megan alapuolella, jos muistia halutaan käyttää DOSin alaisuudessa.

Toinen yhteinen piirre on se, että muistin käyttö rajoittuu pelkästään tietojen taltiointiin, ohjelmia ei voida ajaa ylämuistissa. Kukaan ei tietystikään kiellä, etteikö lisämuistiin voisi tallentaa myös ohjelmakoodia. Silloin tarvitsee ensiksi siirtää ohjelma normaalille osoitealueelle ja vasta sitten suorittaa se. Nämä rajoitukset johtuvat siitä, että muistinhallintaohjelmat siirtävät prosessorin vain hetkeksi suojattuun tilaan ja palauttavat normaalin toimintatilan datan siirron jälkeen.

Esittelemme seuraavaksi kummankin muistinhallintaohjelman käytön. Kummallakin on omat hyvät ja huonot puolensa. Suosittelemme näistä kahdesta käytettäväksi EMS-muistia, koska se on jonkin verran nopeampaa käyttää 386-koneessa.

### XMS-muisti

XMS-muisti eli jatkettu muisti tarkoittaa käytännössä ensimmäisen megatavun jälkeen lineaarisesti jatkuvaa muistia. Tämä on tavallaan "oikeaa muisti", koska muisti on sopusoinnussa prosessorin osoiteinformaation kanssa. Suojatussa tilassa muistin käyttö on nopeaa, koska sitä ei tarvitse siirtää mistään vaan se näkyy suoraan prosessorille. Nykyisissä koneissa (useimmat 386/486/Pentium-koneet) on lisäksi panostettu muistin nopeuteen liittämällä se suoraan prosessorille (esimekiksi SIMM-muistipalat), eikä liittämällä muistia AT-väylän kautta prosessorille (286- muistinlaajennukset).

XMS käyttää keskeytysten sijasta suoria hyppyjä laiteajuriin. Keskeytyksistä ei kuitenkaan ole päästy ihan kokonaan eroon, sillä laiteajurin tunnistaminen ja XMS- perusosoite selvitetään niiden avulla. Tunnistus (detektointi) on erittäin tärkeä toimenpide ja se pitää tietysti suorittaa ensimmäisenä. Kummatkin toiminnot suoritetaan multiplex-keskeytyksen avulla, jonka keskeytysnumero on 2Fh. Esimerkin 3.1 alussa on esitetty nämä toimenpiteet.

Kuvassa 3.3 on esitetty ne kaksi keskeytysrutiinia, joita tulee käyttää ennen muita rutiineja. Muut rutiinit ovat kuvassa 3.4. Kuvan 3.4 rutiineja käytetään siten, että rekisterin AH välityksellä kerrotaan, mikä näistä rutiineistä pitää suorittaa. Esimerkiksi rutiini numero 0 (hakee versionumeron) käynnistetään kirjoittamalla rekisterin AH arvoksi nolla ja hyppäämällä (CALL-käsky) XMS- ajurin perusosoitteeseen. Osoite saadaan keskeytyksen 2Fh avulla, kun parametriksi annetaan luku 4310h. Kuvaan 3.4 ei ole merkitty virhekoodia, joka palautetaan rekisterissä BL. Virhekoodit löytyvät liitteistä.

##### Kuva 3.3. XMS-ajurin keskeytykset.

    INT 2Fh, AX=4300h
      XMS-ajurin tunnistus. Keskeytys palauttaa rekisterissä AL 80h,
      jos XMS on asennettu.
      HUOM! Tätä pitää käyttää ennen yhtäkään muuta rutiinia.

    INT 2Fh, AX=431 Oh
      XMS-ajurin perusosoite. Keskeytys palaunaa rekisteriparissa ES:BX
      osoitteen, joka kertoo mihin osoitteeseen tulee hypätä, kun XMS-rutiineja
      aletaan käyttää.

##### Kuva 3.4. XMS-ajurin rutiinit.

    Nro     Toiminta

    0       Ota XMS-versionumero. Tulos tulee BCD-koodina, joten versio 3.0
            esitetään seuraavasti: 0300. Rutiini kertoo myös HMA-alueen olemassaolosta.
            Palauttaa: AX=versio numero.
                       BX=sisäinen päivitysnumero
                       DX=tieto HMA:sta. 1, jos olemassa

    8       Ota XMS-muistin määrä (HMA:ta ei oteta huomioon). Palauttaa
            vapaana olevan koko muistin määrän sekä suurimman yhtenäisen
            muistialueen.
            Palauttaa: AX=suurin yhtenäinen alue
                       DX=kokonaismäärä

    9       Varaa XMS-muistia. Haluttu muistimäärä voidaan asettaa suoraan
            kilotavuina. Rutiini palauttaa myös ns. handlen. Sitä tarvitaan myöhemmin
            lähes kaikkien muiden rutiinien yhteydessä.
            Annetaan: DX=muistimäärä kilotavuina
            Palauttaa: AX=1 tai 0 eli onnistui tai ei onnistunut
                       DX=handle

    10      Poistaa handlen käytöstä ja samalla vapauttaa varatun muistin.
            Tätä rutiinia käytetään lopettamaan XMS-muistin käyttö omassa ohjelmassa.
            Annetaan: DX=handle
            Palauttaa: AX=1 tai 0 eli onnistui tai ei onnistunut

    11      Siirtää (kopioi) muistin sisältöä perusmuistin ja XMS-muistin välillä.
            Myös kopiointi pelkästään XMS-muistin tai perusmuistin sisällä on
            mahdollista. Parametrina annetaan taulukko, jossa määritellään lähde
            ja kohde sekä siirrettävän muistin määrä. Taulukon osoite pitää sijaita
            DS:SI-rekisteriparin osoittamassa osoitteessa.
            Annetaan: DS:SI=taulukko (esitetty kuvassa 3.5)
            Palauttaa: AX=1 tai 0 eli onnistui tai ei onnistunut

    12      Lukitsee handlen. Lukitsemisen jälkeen kyseiselle handlelle varatun
            muistin sijainti ei muutu missään olosuhteessa. Rutiini palauttaa varatun
            muistin aloituskohdan absoluuttisen osoitteen 32-bittisenä.
            Annetaan: DX=handle
            Palauttaa: AX=1 tai 0
                       DX:BX=32-bittinen alkuosoite

    13      Vapauttaa edellisellä rutiinilla tehdyn lukituksen.
            Annetaan: DX=handle
            Palauttaa: AX=1 tai 0

    14      Tulostaa tietoja handlesta, tärkein tieto on sen kuluttama
            muistimäärä. Lisäksi se kertoo vapaat handlet (niiden lukumäärän),
            joita voidaan käyttää.
            Annetaan: DX=handle
            Palauttaa: BH=handlen lukitus (0=vapaa)
                       BL=vapaat handlet
                       DX=handlen varaama muistimäärä kilotavuina

##### Kuva 3.5. Taulukko datan siirtoa varten (rutiini 11).

    DWORD   Siirrettävän muistialueen koko, oltava jaollinen kahdella.
    WORD    Lähteen valinta. Arvoksi asetetaan handlen arvo. Mikäli halutaan kopioida
            perusmuistista, asetetaan kenttä nollaksi.
    DWORD   Lähteen siirrososoite (handle) tai muistiosoite (offset:segment).
    WORD    Kohteen valinta. Asetetaan handlen arvo tai nolla (perusmuistiin).
    DWORD   Kohteen siirrosoite (handle) tai muistiosoite (offset:segment).

EMS-muistin ohjelmoinnissa käytössä on aina yksi segmentti, johon kaikki viittaukset kohdistuvat. XMS-standardi poistaa tämän rajoittuneisuuden - muistiosoitteet ja muistinkoon voi vapaasti valita. Se ei kuitenkaan voi olla pariton luku, jotenjos siirron pituus on FFFFh siirtoa ei tapahdu. Tällöin kooksi tulee asettaa 10000h (65536).

Jos muistialueen kooksi asetetaan 500 kilotavua ja alkuosoitteeksi vaikkapa 2000:0000 saadaan perusmuistin rinnalle näennäisesti toinen muisti, mutta vain 386- koneessa. 286-koneessa pitää ottaa huomioon, että tiedon siirto perusmuistin ja XMS-muistin välillä on paljon hitaampaa, koska tiedot kopioidaan ylämuistista. Toisaalta ongelma voidaan osittain kiertää siten, että siirretään tietoa XMS-muistin ja perusmuistin välillä hetkenä, jolloin aikaa on riittävästi.

XMS on nyt asennettu ja perusosoite on tiedossa. Seuraavaksi voidaan varata muistia käyttöön. Se käy helposti rutiinin yhdeksän avulla, eli DX-rekisterin sisällöksi annetaan esimerkiksi 50, jolloin muistia varataan 50 kiloa. Samalla saadaan handle. Se on tärkeä tieto, jota ei saa kadottaa missään vaiheessa ennen kuin muisti on taas vapautettu. Osoitteesta, jonka handle kertoo, ei tarvitse välittää.

XMS-ajuri huolehtii siitä, että tiedot tallentuvat oikeisiin muistipaikkoihin. Nyt voidaan esimerkiksi ladata kiintolevyltä johonkin päin muistia esimerkiksi 50 kilotavua grafikkaa. Rutiinin 11 avulla siirretään data XMS-muistiin. Kuvan 3.5 osoittaman taulukon mukaisesti asetetaan muistialueen kooksi 50 kiloa, lähteeksi 0 (eli perusmuisti) ja osoitteeksi se muistiosoite, johon tiedot ladattiin. Lopuksi kohteeksi ilmoitetaan handlen arvo ja siirrokseen kirjoitetaan nolla. Tämän jälkeen hypätään rutiiniin.

Samalla periaatteella saadaan data takaisin perusmuistiin. Tällöin lähteeksi annetaan handle ja kohteeksi jokin muistiosoite.

Lopuksi lyhyt konekielinen esimerkki XMS-muistin käytöstä.

##### Esimerkki 3.1. XMS-muistin käyttö.

    ;
    ; Lyhyt esimerkki XMS:n käytöstä. Tehty TASM-kääntäjälle.
    ; Ohjelma tutkii ensin, onko XMS aktivoitu. Sitten otetaan
    ; kutsuosoite. Tämän jälkeen voidaan aloittaa itse
    ; XMS-muistin käyttö. Esimerkki kopioi keskusmuistiin
    ; kirjoitetun tiedon XMS-muistiin mutta ei sieltä takaisin.
    ; HUOM! Pinossa pitää olla riittävästi tilaa, sillä XMS ei
    ; käytä omaa pinoa. Ohjelmassa käytetään myös DOSin
    ; tulostusta, jotta käyttäjä tietää, onnistuivatko
    ; suoritetut rutiinit.
    ;

    MODEL LARGE
    P386
    . STACK     ; pino
    IDEAL

    ;Seuraavat muutujat datasegmenttiin
    DATASEG

    text1 db 'XMS OK!',13,10,'$'
    text2 db 'Muisti varattu!',13,10,'$'
    text3 db 'Muisti siirretty!',13.10,'$'
    text4 db 'Muisti vapautettu!',13,10,'$'

    handle dw 0
    kut su dd 0

    ;Taulukko datan siirtoa varten
    koko      dd 0       ; koko
    lahde     dw 0       ;0 = muisti tai handle
    osoite1   dd 0       ;muistiosoite
    kohde     dw 0       ;0= muisti tai handle
    osoite2   dd 0       ;muistiosoite

    CODESEG

    PROC exemes

      mov ax,@data
      mov ds,ax                 ;asetetaan oikea datasegmentti

    ;Tutki onko XMS asennettu
      mov ax,04300h
      int 02 fh
      cmp al,080h
      jne virhe                 ;XMS ei asennettu
      mov ah, 9
      mov dx,offset text1
      int 021h                  ;tulosta teksti

    ;Ota kutsuosoite
      mov ax,04310h
      int 02 fh
    ;Talleta osoite
      mov [word ptr kutsu],bx   ;OFFSET
      mov [word ptr kutsu+2],es ;SEGMENT

    ;Varaa muistia
      mov ah, 9
      mov dx,10                 ;10 kilotavua
      call [kutsu]
      cmp ax,0                  ; virhe ?
      je virhe
      mov [handle],dx           ;tallenna handle
      mov ah, 9
      mov dx,offset text2
      int 021h

    ;Siirrä muistia
      mov [koko],1000           ;1000 tavua
      mov [lahde],0             ;lähde=perusmuisti
      mov ax,0l00h              ;tässä asetetaan vain jokin
      mov es,ax                 ;osoite perusmuistista
      mov si, 0
      mov [word ptr osoite1],si
      mov [word ptr osoitel+2],es
      mov ax,[handle]           ;kohde=XMS
      mov [kohde],ax
      mov [osoite2],0           ;siirros XMS:ssä
      mov ah,11
      mov si,offset koko        ;taulukon osoite DS:SI-rekisteriin
      call [kutsu]              ;kutsu rutiinia
      cmp ax, 0
      je virhe
      mov ah, 9
      mov dx, offset text3
      int 021h

    ;Vapauta muistia
      mov ah,10
      mov dx, [handle]
      call [kutsu]
      mov ah, 9
      mov dx,offset text4
      int 021h

    virhe:
      mov ah,04ch               ;palaa systeemiin
      int 021h

    ENDP

    END

### EMS-muisti

EMS-muisti eli laajennettu muisti toimii periaatteessa normaalin muistin rinnalla, eikä siis ole ensimmäisen megan jälkeen lineaarisestijatkuvaa. EMS-muisti toimii ikkuna- periaatteella, jolloin ikkunan tietoja osoitettaessa osoitetaan jotakin kohtaa varsinaisesta EMS-muistista. Ikkunan kohdeosoitetta EMS-muistissa muuttamalla käydään läpi koko muistin sisältö.

EMS kehitettiin aikoinaan 8088- ja 8086-prosessoreita varten, mistä syystä sitä pidetään hitaana. Vanhoissa koneissa se olikin hidas, koska koneet olivat hitaita ja muisti liittyi prosessoriin AT-väylää pitkin. Nykyään muistit liittyvät suoraan prosessoriin ja XMS-muistin voi vaihtaa EMS-muistiksi ja päinvastoin.

386-prosessorilla 64 kilon palasen kopioiminen kestää alle 10 mikrosekuntia. Ratkaisu piilee siinä, että tuota aluetta ei tarvitse kopioida oikeasti, riittää kun kerrotaan prosessorille, minkä alueeen EMS-muistista tulee näkyä. Jos peli tehdään pelkästään 386-prosessoria silmällä pitäen, kannattaa käyttää EMS-muistia.

Muistinhallintaohjelman käyttö poikkeaa XMS-muistin käytöstä, koska EMS käyttää normaaliin tapaan keskeytyksiä. Ennen yhtäkään keskeytyskutsua on tarkastettava, onko EMS asennettu.

EMS-muistin tarkistus tapahtuu seuraavasti: Luetaan osoitteesta 0:414 segmentin arvo. Kun tämän segmentin offsetiksi asetetaan OOOA, pitäisi sen osoittamassa osoitteessa lukea teksti: EMMXXXX. Käytännössä riittää, kun luetaan kolme ensimmäistä merkkiä (EMM).

Kuvassa 3.6 on muutamia tärkeimpiä EMS-rutiineja. AH- rekisteri ladataan "arvo":lla, jonka jälkeen kutsutaan keskeytystä 67h.

##### Kuva 3.6. EMS-keskeytyksen rutiineja.

    Arvo   Toiminta
    41h    Lue EMS-ikkunan sijaintiosoite keskusmuistissa. Huomaa, että rutiini
           palauttaa vain segmentin, offsetin arvo on riippuvainen käsiteltävästä sivusta.
           Palauttaa: BX=ikkunan osoitteen segmentti.

    42h    Lue EMS-muistin koko. Rutiini palauttaa yhdellä kertaa kaksi tarpeellista
           tietoa: vapaiden sivujen määrän ja kaikkien sivujen määrän.
           Palauttaa: BX=vapaiden sivujen määrä
                      DX=kaikkien sivujen määrä.

    43h    Ota EMS käyttöön ja varaa samalla muistia. Tätä rutiinia tulee kutsua
           ensimmäisenä, kun halutaan käyttää muistia.
           Annetaan: BX=varattavien sivujen määrä
           Palauttaa: DX=handle. Tätä arvoa pitää käyttää kaikkien muiden rutiinien
                      käytön yhteydessä.

    44h    Asettaa halutut loogiset sivut fyysisiksi sivuiksi ts. asettaa jonkun EMS-sivun
           näkymään keskusmuistissa. Tietysti vain varatuista sivuista voidaan
           tehdä fyysisiä.
           Annetaan: AL=fyysisen osoitteen sivu 1-3
                     BX=Looginen sivu (0 - viimeinen varattu sivu miinus yksi)
                     DX=handle

    45h    Poistaa handlen käytöstä. Rutiini vapauttaa samalla varatun muistin.
           Tätä rutiinia tulee käyttää aina ennen ohjelmasta poistumista, mikäli EMS
           on otettu omaan käyttöön.
           Annetaan: DX=handle

    46h    Lue EMS-versionumero. Tulos palautetaan BCD-koodina, esimerkiksi
           versionumero 4.0 on 0400.
           Palauttaa: AL=versio numero

    47h    Tallentaa fyysiset sivut loogiseksi. Tämän rutiinin avulla voi joku ohjelma
           (esim. TSR) saada välillä EMS-ajurin omaan käyttöönsä tekemättä
           haittaa ohjelmalle.
           Annetaan: DX=handle

    48h    Palauttaa loogiset sivut fyysiseksi. Tätä rutiinia pitää kutsua aina ennen
           (esim. TSR) ohjelman lopettamista, jos rutiinia 47H on kutsuttu.
           Annetaan: DX=handle

    51h    Muutetaan varattujen loogisten sivujen määrää. Tämä rutiini ei kuitenkaan
           poista handlea, joten sillä ei voi lopettaa EMS:n käyttöä vaikka kaikki sivut
           vapautettaisiin.
           Annetaan: BX=uusi sivujen määrä
                     DX=handle

Kuvaan ei ole merkitty yhtä tärkeää palautusarvoa keskeytyksen jokaiselle rutiinille. Tuo arvo kertoo suorituksen onnistumisesta ja se palautetaan rekisterissä AH. Jos sisältö on nolla, onnistui rutiinin suorittaminen, muussa tapauksessa palautetaan virheen numero. Katso liitteistä numeroita vastaavat virheilmoitukset.

EMS laajentaa siis koneen muistikapasiteettia, mutta miten se toimii? Perusidea on se, että ulkopuolinen muisti, jota kutsutaan loogiseksi muistiksi on jaettu 16 kilotavun sivuihin, eli jos kokonaismuisti on 128 kilotavua, se sisältää 8 sivua. Kerralla koneen keskusmuistissa (fyysisessä) voi olla neljä tällaista sivua, yhteensä siis 64 kilotavua. Loogisesta muistista voidaan varata tarpeellinen määrä sivuja, joita myöhemmin voidaan käyttää fyysisenä muistina. Jotta tiedettäisiin mikä ohjelma on varannut minkäkin verran muistia, käytetään handleja. Handleja voi olla kerralla käytössä useampia kuin yksi.

Kun tiedetään, että EMS on asennettu ja tunnetaan kaikki tarpeelliset käskyt sekä niiden toiminta, voidaan alkaa käyttää sitä. Ensimmäinen tärkeä tieto on EMS-sivujen osoite perusmuistissa. Se saadaan rutiinilla 41h. Koneesta riippuen tuloksena tulee jokin segmentti 640 kilon yläpuolelta, esimerkiksi D000h. Tällöin osoitettaessa aluetta D000:0000- D000:FFFF, osoitetaan EMS-muistia. Ennen kuin tuohon osoitealueeseen voi vapaasti kirjoittaa, pitää varata ensiksi vähintään 4 sivua muistia ja asettaa (kartoittaa) ne tuolle alueelle. Muistia varataan rutiinilla 43h, jossa rekisteriin BX asetetaan varattavien sivujen määrä. Seuraavaksi pitää tehdä näistä loogisista sivuista fyysisiä sivuja, jotta tuo varattu muisti saadaan käyttöön. Se tehdään, kun rekisterin AH arvoksi asetetaan 44h ja AL asetetaan vastaamaan fyysistä sivua ja BX loogista sivua. Huomaa, että rutiini muuttaa vain sivun kerrallaan, joten jos koko segmentin sisältö muutetaan, pitää rutiinia kutsua neljä kertaa.

Esimerkissa 3.2 on konekielellä toteutettu ohjelmaesimerkki.

##### Esimerkki 3.2. EMS-muistin käyttö.

    ;
    ; Lyhyt esimerkki EMS:n käytöstä. Tehty TASM-kääntäjälle.
    ; Ohjelma tutkii ensin, onko EMS aktivoitu.
    ; Sitten varataan 4 sivua (64 kt) muistia, ja mapataan ne.
    ; Lopuksi muisti vapautetaan.
    ; Ohjelmassa käytetään myös DOSin tulostusta, jotta käyttäjä
    ; tietää, onnistuivatko suoritetut rutiinit.
    ;

    MODEL LARGE
    P386
    . STACK     ; pino
    IDEAL

    ;Seuraavat muuttujat datasegmenttiin
    DATASEG

    emmname db 'EMM'
    text1 db 'EMS OK!',13,10,'$'
    text2 db 'Muisti varattu!',13,10,'$'
    text3 db 'Muisti vapautettu!',13,10,'$'

    base      dw 0
    handle    dw 0

    CODESEG

    PROC ems

      mov       ax,@data
      mov       ds,ax         ;asetetaan oikea datasegmentti

    ;Tutki onko EMS asennettu
      mov       ax,0
      mov       es,ax
      mov       ax,[es:414]
      mov       es,ax
      mov       si,10
      mov       di,offset emmname
      mov       cx,3
    testi:
      mov       al,[es:si]
      cmp       al,[ds:di]    ;vertaa EMM-tekstiä muistiin
      jne       virhe
      inc       si
      inc       di
      loop      testi

      mov       ah,9
      mov       dx,offset textl
      int       021h          ; tulosta teksti

      mov       ah,041h
      int       067h
      mov       [base],bx     ;tallenna fyysisten sivujen segmentti

      mov       ah,043h
      mov       bx,4
      int       067h          ;varaa neljä sivua muistia
      cmp       ah,0
      jne       virhe
      mov       [handle],dx   ;tallenna handle

      mov       ah,9
      mov       dx,offset text2
      int       021h

    ;mapataan kaikki 4sivua
      mov       ah,044h
      mov       al,0          ;fyysinen sivu 0
      mov       bx,0          ;looginen sivu 0
      int       067h          ;aseta looginen sivu fyysiseksi
      mov       ah,044h
      mov       al,l          ;fyysinen sivu 1
      mov       bx,l          ;looginen sivu 1
      int       067h          ;aseta looginen sivu fyysiseksi
      mov       ah,044h
      mov       al, 2
      mov       bx, 2
      int       067h
      mov       ah,044h
      mov       al, 3
      mov       bx, 3
      int       067h

      mov       ah,045h
      mov       dx,[handle]
      int       067h          ; vapauta muisti

      mov       ah, 9
      mov       dx,offset text3
      int       021h

    virhe:
      mov       ah,04ch       ;palaa systeemiin
      int       021h

    ENDP

    END

Segmentin tullessa täyteen tietoa pitää uutta tilaa saada jostain käyttöön. Mikäli sivuja on varattu heti alkuun tarpeeksi (mikä on suotavaa) käy se kädenkäänteessä jo opitulla rutiinilla 44h. Sille annetaan vain uudet sivut, jotka se muuttaa fyysiseksi. Samalla segmenttiin jo kirjoitetut tiedot piiloutuvat jonnekin EMS-muistin kätköihin ja ovat sieltä palautettavissa keskusmuistiin rutiinilla 44h.

### Suojattu tila

Normaalin tilan ja suojatun tilan ohjelmoinnissa ei ole mitään yhteistä. Normaali tila on täynnä kaikenlaisia rajoituksia, (muistiavaruus 1 Mt) mutta suojattu tila on melko vapaa. Uppoudutaan hetkeksi suojatun tilan ihmeellisyyteen.

286- ja 386-prosessorien välillä on suuri ero. 286 sallii suuremman lineaarisen muistikapasiteetin, joka on 24 bitillä toteutettuna 16 megatavua. Koska sama muisti saadaan XMS- ajurin avulla käyttöön, suojatun tilan ei pitäisi olla tavoittelemisen arvoinen, mutta 386:n arkkitehtuurin ansiosta on.

386:n yksi suurimmista muutoksista entisiin prosessoreihin verrattuna oli segmentoinnin poistuminen. Koska segmenttejä ei ole ja muistiavaruus 32-bittisessä prosessorissa on 4 gigatavua, koko muistiavaruus on käytettävissä yhdellä 32- bittisellä osoittimella. Myös segmentoitua muistia voidaan tarvittaessa käyttää ja sen koko voidaan vapaasti määrittää.

Toinen muutos on sivutus (paging). Se mahdollistaa edellä esitetyn nopean "kopioinnin", josta kerrottiin XMS- ja EMS- muistien yhteydessä. Tämä perustuu siihen, että prosessori voi asettaa tietyn muistialueen näkymään missä päin 4 gigatavun muistiavaruutta tahansa. Se hankaloittaa ohjelmointia, koska ei voi tietää tarkkaan mikä muistipaikka on milläkin kohtaa todellisessa muistissa. Sivutustoiminnon voi haluttaessa kytkeä pois päältä.

### Suojatussa tilassa ohjelmointi

Suojatun tilan ihmeellisyyttä ja paremmuutta on ylistetty, joten miksi sitä sitten ei käytetä peleissä? Koska se vaatii ohjelmoijalta todella paljon taitoja ja vaivaa.

Ensinnäkin kaikkien segementtirekistereiden tehtävä muuttuu. Ne eivät enää suoraan osoita muistia vaan erilaisia taulukoita, joita nimitetään kuvaajiksi (descriptor). Kuvaajia on todella paljon, esimerkiksi koko muistiavaruus (4 Gt) on kartoitettu taulukoilla. Taulukot ovat periaatteeltaan kaikki samanlaisia, mutta niitä käytetään eri tarkoituksiin. Kolme tärkeintä taulukkoa ovat GDT, LDT ja IDT, joiden käyttötarkoitukset tulee hallita hyvin.

Konekielikäskyjen käytössä on muutoksia. Tavallista real- tilaan tehtyä ohjelmaa ei voi suorittaa. Suojattu tila antaa uusia ominaisuuksia käskyihin, esimerkiksi kertolasku voidaan korvata LEA- käskyllä, joka tietysti kasvattaa koodin suoritusnopeutta.

Ongelmallisin tilanne syntyy silloin, kun tarvitaan käyttöjärjestelmän keskeytyksiä. Niitä ei nimittäin voi käyttää suojatusta tilasta, vaan on siirryttävä real-tilaan ja sen jälkeen takaisin suojattuun tilaan. On kuitenkin mahdollista uudelleen koodaamalla laittaa tarvittavat keskeytykset (esim. IRQ:t) toimimaan myös suojatussa tilassa.

Jos ohjelmoija tuntee prosessorin toiminnan tilassa kuin tilassa, mikään ei estä ohjelmoimasta peliä suojattuun tilaan. Mutta valitettavasti kirjassa käytetty Borlandin C++-kääntäjä ei pysty tekemään koodia tähän tilaan. Onko peli siis tehtävä kokonaan konekielellä? Onneksi ei, sillä Watcomin C-kääntäjä on tarkoitettu ja luotu pelkästään suojattua tilaa varten.

# 4. Keskeytykset ja ajastimet

Keskeytykset ovat helppo ja vaivaton tapa kommunikoida muiden koneen osien (piirien) kanssa. Keskeytysten käyttöä kuvaa, että vaikka kone ei tee mitään (ruudulla on vain DOSin kehote), keskeytyksiä tulee noin 18 kertaa sekunnissa. Jos näppäilee jotain ruudulle, keskeytyksiä tulee useammin. Keskeytykset näyttelevät siis lähes pääosaa tietokoneen toiminnassa.

Jos näppäimistö haluaa ilmoittaa prosessorille, että se voi lukea käyttäjän painaman näppäimen, tapahtuu se keskeytyksen avulla. Näppäimistö ja mikroprosessori eivät kommunikoi suoraan toistensa kanssa eli niiden välissä on tulkki. Tällaisena tulkkina PC:ssä toimii piiri 8259 eli keskeytyspiiri.

Tulkin tehtävänä on vastaanottaa näppäimistön lähettämä keskeytys ja siirtää se prosessorille. Ennen kuin prosessori alkaa suorittaa keskeytystä, on pysäytettävä senhetkisen ohjelman ajo, haettava keskeytysvektoritaulukosta suoritettavan keskeytyksen osoite, jonka jälkeen vasta itse keskeytysohjelma suoritetaan. Kun keskeytysohjelma on päätetty, palaa prosessori jälleen takaisin alkuperäiseen ohjelmaan. Kaikki tämä tapahtuu niin, että käyttäjä ei huomaa mitään.

Keskeytysvektoritaulukosta saadaan keskeytyksen osoite eli paikka, jossa suoritettava rutiini sijaitsee. Ennen osoitteen saamista on prosessorilla tiedossa vain keskeytyksen numero. Tämä kerrotaan neljällä ja näin saadaan ensimmäinen osoite. Tämän osoitteen sisältö kertoo, missä päin muistia keskeytysrutiini sijaitsee. Taulukossa on kaiken kaikkiaan tiedot 256 keskeytystä varten. Taulukon pituus on siten 1024 tavua, koska jokaista osoitetta varten tarvitaan neljä tavua.

Jos esimerkiksi annetaan käsky INT 21H, haetaan osoitteesta 21 x 4 = 84 uusi segmentti, joka ladataan CS- rekisteriin. Osoitteesta 86 saadaan IP-rekisterille uusi arvo.

##### Esimerkki 4.1. DOS-keskeytys.

      mov ax, 9
      int 21H

    ;prosessori hyppää tässä kohtaa keskeytysrutiiniin,
    ;joka sijaitsee osoitteiden 84:86 osoittamassa osoitteessa.
    ;Keskeytyksen loputtua jatketaan ohjelman suoritusta seuraavasta
    ;käskystä
      mov ax,[bx]

      ret

### Keskeytysten tyypit

PC-yhteensopivissa koneissa on keskeytyksillä suurempi merkitys kuin 68000-pohjaisissa. Esimerkiksi tietokoneen BIOS-rutiineja käytetään keskeytyksien avulla. Näitä kutsutaan nimellä software interrupt, ohjelmistokeskeytykset. Muista keskeytyksistä käytetään nimeä hardware interrupt eli kovopohjainen keskeytys.

Ohjelmistokeskeytykset luodaan itse (esimerkiksi INT 21H), kovopohjaiset keskeytykset tulevat automaattisesti koneen piireiltä prosessorille. (Ainut tapa, miten käyttäjä voi vaikuttaa hardwarekeskeytyksiin on niiden estäminen.)

Kovopohjaiset keskeytykset on jaettu kahteen ryhmään, IRQ ja NMI. IRQ-keskeytykset kommunikoivat muiden osien kanssa. NMI-keskeytystä käytetään virhetilanteisiin. Lisäksi on olemassa prosessorin itse luoma keskeytys, jota käytetään yksinomaan prosessorin virhetilanteiden korjaamiseen. Keskeytysluettelo eri tyyppien perusteella on seuraavanlainen:

- ohjelmistopohjaiset keskeytykset
- kovopohjaiset keskeytykset
- prosessorin omat keskeytykset

  Kuvaan 4.1 on koottu osa PC:n keskeytystaulukkoa. Kun keskeytyksen numero kerrotaan neljällä saadaan todellinen osoite, jonka perusteella rutiiniin hypätään. Kuvaan on myös merkitty, mikä on keskeytyslähde (CPU, IRQ vai BIOS).

  ##### Kuva 4.1. Osa keskeytystaulukkoa.

      Keskeytys    Toiminta
      00           CPU             Jako nollalla
      01           CPU             Prosessori STEP-moodissa
      02           CPU             NMI-keskeytys
      03           CPU             Breakpoint
      04           CPU             Ylivuoto
      05           BIOS            Print Screen
      07           CPU             Matematiikkaprosessori puuttuu (286, 386)
      08           IRQ0            Ajastin
      09           IRQ1            Näppäimistö
      0A           IRQ2            8259-orjapiiri

  ### Keskeytysten käyttö

  Peleissä on erittäin paljon käyttöä keskeytyksille. Ensimmäinen tärkeä keskeytys on ajastimen tuottama keskeytys, joka tottelee nimeä IRQ0. Se on kuvassa 4.1 (keskeytysnumero 08). Ajastimen avulla voi luoda pelin, joka toimii koneessa kuin koneessa samalla nopeudella, sillä se ei ole riippuvainen prosessorin kellotaajuudesta. Ajastimesta kerrotaan enemmän luvun loppupuolella.

  Muiden IRQ-keskeytyksien käyttö on vähäisempää, mutta lähes yhtä tärkeää. Esimerkiksi hiiri ja äänikortit toimivat keskeytyksien varassa. Tärkeää onkin tietää, miten näitä IRQ-keskeytyksiä saa käyttää.

  Prosessori pystyy yksin luomaan keskeytyksiä. Näistä käytetään nimitystä exception. Exception toimii samalla tavalla kuin normaalikin keskeytys ja sillä on oma paikkansa keskeytysvektoritaulukossa.

  Tyypillinen tapaus exception-rutiinin suorittamiseen on, kun prosessori törmää laittomaan käskyyn tai binäärikoodiin, jota ei ole määritelty. Tarkastellaan kahta eri tapausta.

  Matematiikkaprosessorille tarkoitettu käsky aiheuttaa keskeytyksen, mikäli kyseistä prosessoria ei ole asennettu. Tästä toiminnasta on hyötyä peleissä, jotka tarvitsevat paljon apua matematiikkaprosessorilta. Jos ei tiedetä, onko matematiikkaprosessori asennettu, pelin pitäisi ottaa se selville. Mutta tällöin jokaisen prosessorille tarkoitetun käskyn edessä pitäisi olla vertailu, voidaanko kyseinen käsky suorittaa. Se kuluttaisi turhaan koneen tehoa sekä toisi pituutta ja sekavuutta lähdekoodiin.

  Keskeytyksen ansiosta ongelma voidaan helposti kiertää. Kun CPU kohtaa matematiikkaprosessorille tarkoitetun käskyn, se suoritetaan, mikäli prosessori on asennettu. Jos prosessori ei ole asennettu, suoritetaan keskeytys, jossa käsky emuloidaan.

  Toinen exception-rutiinin käyttökohde on uuden käskyn luominen prosessorille. Kun prosessori kohtaa käskyn, jota se ei tunne, se luo keskeytyksen (INT 06). Kun sinne laitetaan koodi, joka suoritetaan aina keskeytyksen tullessa, on kyseisen käskyn ohjelmoiminen valmista. Tällä saadaan esimerkiksi lisättyä yhteensopivuutta 286- ja 386-prosessoreilla.

  ### PIC-piiri

  Kaikkia IRQ-keskeytyksiä ohjaa piiri 8259 (Programmable Intenupt Contoller) eli lyhyesti PIC. Yksi piiri pystyy käsittelemään kahdeksan keskeytystä. Vanhoissa PC-koneissa (PC, XT) oli käytössä vain yksi piiri, uudemmissa (AT, 386, 486, Pentium) on kaksi piiriä. Kaksi piiriä mahdollistaa 15 keskeytystä, koska yksi keskeytyslinja tarvitaan piirien yhdistämiseen. Valitettavasti uusien prosessoreiden asentaminen ei ole sujunut ongelmitta, sillä nyt muutamat keskeytykset menevät päällekkäin, katso kuva 4.2.

  ##### Kuva 4.2. Päällekkäin olevat keskeytykset.

      Keskeytys        Lähde
      05               BIOS ja CPU
      08-0FH           IRQ ja CPU
      10H              BIOS ja CPU

  Piiri kerää sille tulevat keskeytykset ja päättää, mikä keskeytys annetaan prosessorille. Tämä päätös tehdään prioriteetin eli tärkeysjärjestyksen mukaan. Korkein prioriteetti on IRQ0-keskeytyksellä, matalin IRQ7- keskeytyksellä. Toinen 8259 on orjapiiri, joten se ei voi ilman lupaa keskeyttää prosessoria (luvan se saa ensimmäiseltä piiriltä).

  Kun prosessori lopettaa keskeytyksen suorittamisen sen pitää lopuksi ilmoittaa tästä PIC-piirille. Tämä tapahtuu antamalla End Of Interrupt -komento kirjoittamalla osoitteeseen 20H tai osoitteisiin 20H ja AOH luku 20H. Käyttöjärjestelmän rutiineissa tämä tehdään aina, mutta on tärkeää tehdä samoin OMISSAKIN keskeytysrutiineissa. Jos näin ei tehdä, muitakaan keskeytyksiä ei lähetetä prosessorille ja systeemi kaatuu.

  PIC-piirit näkyvät ohjelmoijalle porteissa 20H (AOH) ja 21H (A1H). Piirien tiedot on asetettu oikein koneen käynnistyessä, eikä niiden asetusarvoihin tarvitse koskea. Yhteensopivuuden säilyttäminen on tärkeää. Tietysti EOI pitää lähettää piirille, mutta se ei sotke asetusarvoja.

  IRQ-keskeytysten sallinnan ja poiston avulla voi estää halutut keskeytykset erikseen. Pelistä poistuttua on syytä asettaa kaikki alkuperäiseen tilaan. Vaikka piirillä ei ole kuin kaksi porttia, jolla sitä ohjelmoidaan, kätkeytyy silti niihin useampi toiminto. Se tuntee neljä erilaista komentosanaa (Command Word) ja kolme kontrollisanaa (Control Word).

  ### NMI-keskeytys

  NMI-keskeytyksellä kontrolloidaan mahdollisia virhetilanteita. Esimerkiksi jos muistin pariteettisuus heittää, annetaan prosessorille NMI-keskeytys. NMI- keskeytyksen tultua kone yleensä kaatuu saman tien. Korkein prioriteetti on NMI-keskeytyksellä, seuraavaksi korkein INT- käskyllä. Alin prioriteetti jää IRQ-keskeytyksille.

  NMI-keskeytyksen voi estää tulemasta nollamaalla bitti 7 portista 70H.

  ### Ajastimet

  Usein pelissä on tärkeää saada tietyn pituisia viiveitä. Joidenkin äänikorttien resetointisignaalien välissä tarvitaan useiden millisekuntien (esimerkiksi noin 200 ms) viiveitä. Tällöin viive tehdään helpoiten ajastimella.

  Jos esimerkin 4.2 mukainen silmukka tehdään prosessorilla, se vie 286:ssa paljon enemmän aikaa kuin 486:ssa. Jos halutaan tehdä vielä pidempiä viiveitä, ero kasvaa eri prosessoreiden välillä.

  ##### Esimerkki 4.2. Prosessoriviive.

        mov cx,65535
      silmu:
        nop                            ; 3 jaksoa
        loop silmu                     ; 10 jaksoa (286)
                                       ; 13 jaksoa (386)
        ret

  Tällaiset ongelmatilanteet voi poistaa, kun käytetään tavanomaisen prosessorisilmukan sijasta ajastinta. Ajastin on periaatteessa eräänlainen kello. Sille annetaan joku luku väliltä 1-65536, joka merkitsee samalla tiettyä aikaa. Ajastimen toiminta pyörii sille syötetyn signaalin taajuuden tahdissa ja taajuus on vakio riippumatta koneen kellotaajuudesta. Taajuusarvo on 1,19 MHz, joka on perintöä ensimmäisestä PC-koneesta.

  ### Ajastimen toiminta

  AT:ssä (286, 386, 486, Pentium) on uudempi ajastinpiiri nimeltään 8254, jossa on kolme erillistä ajastinta. Jokainen ajastin sisältää yhden 16-bittisen laskurin ja yhden yhteisen asetusrekisterin. Ajastin toimii siten, että jokaisella kellojaksolla laskurin arvoa vähennetään yhdellä. Tämä tapahtuu jokaisessa kolmessa ajastimessa yhtä aikaa, vaikka ne ovatkin toisistaan riippumattomia. Kellojaksojen välinen aika voidaan laskea, kun tiedetään, että ajastinpiirille tuleva taajuus on 1,19318 MHz. Kellojaksot tulevat siis noin 0,83 mikrosekunnin välein, jonka perusteella taas voidaan laskea, kuinka kauan laskeminen kestää.

  Kun laskuri lopulta saavuttaa nollan, samalla hetkellä syntyy keskeytys. Sen jälkeen laskuriin ladataan uusi arvo lukkopiireistä (tapahtuu automaattisesti) ja laskenta aloitetaan uudelleen. Tämä toistuu niin kauan kunnes laskenta pysäytetään kirjoittamalla asetusrekisteriin. Tämä on yksi mahdollisuus kuudesta, ajastimen moodi voidaan vapaasti valita.

  Laskurille voidaan antaa arvot 1-65536, siitä saadaan laskettua maksimi- ja minimiarvot, kuinka kauan kestää nollan saavuttaminen. Pienin aika on 0,83 mikrosekuntiaja suurin 0,55 millisekuntia. Pienin teoreettinen aika on suurempi, koska laskurin lataus ja keskeytyksen anto prosessorille (ei tapahdu saman kellojakson aikana) kuluttaa aikaa.

  16-bittisellä luvulla pystytään esittämään vain luvut 0- 65535. Arvo 65536 saadaankin asettamalla laskurin arvoksi nolla. Asetettaessa ajastimen arvoa pitää haluttu 16- bittinen luku kirjoittaa kahdessa eri osassa. Lopullisen kirjoitusjärjestyksen määrää asetusrekisteri.

  ##### Kuva 4.3. Ajastimien portit.

      Portti        Toiminta
      40H           Ajastin 0 (laskuri)
      41H           Ajastin 1 (laskuri)
      42H           Ajastin 2 (laskuri)
      43H           Asetusrekisteri (ajastimet 0,1,2)

  ### Ajastimien käyttökohteet

  Ajastimien käyttöä hankaloittaa niiden vähyys. Toiseksi jokaiselle on jo ennestään keksitty käyttöä. Kuvassa 4.4 on eritelty ajastimet ja toiminnot.

  ##### Kuva 4.4. Ajastimien tehtävät.

      Ajastin       Toiminta
      0             Käyttöjärjestelmän ajastin
      1             RAM-virkistys
      2             Kaiuttimen äänigeneraattori

  Ajastinta 1 ei saa kytkettyä omiin ohjelmiin. Se virkistää DRAMia 15 mikrosekunnin välein. Käyttöön jää enää vain kaksi. Ajastin 0 on kytketty IRüO-keskeytyslinjaan ja näin ollen se käyttää INT 08 -vektoria (osoitteessa 32). Ajastimen arvoksi on asetettu nolla, joten keskeytysrutiiniin hypätään 18,2 kertaa sekunnissa. Ajastinta käytetään ylläpitämään koko käyttöjärjestelmää, toisin sanoen kaikki toiminnot pyörivät tämän keskeytyksen varassa (levyasemat, kello, jne). Jos käyttöjärjestelmän rutiinin poistaa kokonaan, pitää peleissä tehdä itse kaikki tiedostojen lataus- tai tallennusrutiinit suoraan hardwarella. Ei siis ole kovin järkevää ottaa ajastinta käyttöön, ellei keksi keinoa, jolla ongelma poistetaan.

  Ongelmaan on kuitenkin kaksi ratkaisua: laitetaan oma rutiini keskeytysvektoriin 1CH tai ohjelmoidaan koko 08H- keskeytysrutiini uudelleen. Ensiksi esitetty soveltuu vain, jos pelille riittää 18,2 kertaa sekunnissa (0,55 ms:n välein) tapahtuva keskeytys. Jälkimmäinen vaihtoehto on parempi, mutta vaikeammin toteutettavissa. Ajastimen käyttöön pureudutaan luvun loppupuolella tarkemmin.

  Ajastin 2 liittyy kaiuttimen ohjaukseen. Kun kaiutinta ei käytetä, voidaan ajastimesta tehdä laskuri, joka laskee annetusta luvusta alaspäin. Keskeytystä (IRQ) ei saada prosessorille, joten on itse tutkittava laskurin tilaa. Tämä siis rajoittaa laskurin käytön vain viiveiden mittaamiseen. Ajastimelta saadaan tieto siitä, milloin se on saavuttanut nollan. Tieto luetaan portista 61H. Kaiuttimen ohjaus tapahtuu samaisesta portista. On varottava koskemasta portin muihin bitteihin.

  Kaiuttimen asetukseen kannattaa käyttää AND- ja OR- operaatioita. Kuvaan 4.5 on koottu portin 61H tärkeimpiä toimintoja.

  ##### Kuva 4.5. Ajastimen 2 käyttö.

      Bitti          Toiminta
      05             Ajastimen 2 lähdön tila (Huom! Ei voi kirjoittaa)
      01             Kaiuttimen ohjaus.
                             1 = päällä
                             0 = pois päältä
      00             Ajastimen 2 ulostulo.
                             1 = päällä
                             0 = ei päällä

  ### Ajastimien käyttöönotto

  Ajastimen toiminta aloitetaan alustamalla asetusrekisteri. Se määrää mikä on ajastimen moodi ja tyyppi. Samalla päätetään mitä ajastinta käytetään ja miten se alustetaan. Kuvassa 4.6 on rekisterin sisältö.

  ##### Kuva 4.6. Asetusrekisteri (portti 43H).

      Bitit          Selitys
      07-06          Valitsee ajastimen.
                           00 = ajastin0
                           01 = ajastin1
                           10 = ajastin2
                           11 = lue takaisin -komento
      05-04          Luku/kirjoitus tai lukitus.
                           00 = lukitsee laskurin arvon
                           01 = R/W MSB
                           10 = R/W LSB
                           11 = R/W LSB sitten MSB*
      03-01          Ajastimen moodi. Viisi eri moodia käytettävissä.
                           00 = Laskurin tyyppi.
                           0  = 16-bit binäärilaskuri
                           1  = BCD-laskuri (luvut 0-9)

            *R/W = luku tai kirjoitus
            MSB = enemmän merkitsevä tavu
            LSB = vähemmän merkitsevä tavu

  Tärkeimmät moodit ovat seuraavat:

- moodi 0: Laskee nollaan (OUTPUT=LOW), keskeytys. Pysähtyy odottamaan uutta moodia tai laskurin arvoa (OUTPUT=HIGH).
- moodi 1: Laskee nollaan (OUTPUT=LOW), keskeytys. Odottaa uutta moodia ja laskurin arvoa (OUTPUT=HIGH)
- moodi 2: Tahtigeneraattori. Laskee nollaan (OUTPUT=HIGH), ulostulo tekee nopean pulssin (nolla ja heti ykkönen). Automaattinen lataus.
- moodi 3: Kanttiaaltogeneraattori. Laskee puolet arvosta (OUTPUT=HIGH), sen jälkeen loput (OUTPUT=LOW). Automaattinen lataus.

  HUOM! Jokainen ajastimen moodi luo keskeytyksen.

  Koneen käynnistyessä laskurin 0 moodiksi asetetaan 3 ja laskurin 1 moodiksi 2. Moodien yhteydessä kerrottiin ajastimen ulostulolinjan tila. OUTPUT=HIGH tarkoittaa, että linjan tila on looginen ykkönen, eli se on aktiivinen. OUTPUT=LOW taas tarkoittaa, että linjan tila on looginen nolla. Jos halutaan alustaa ajastin 2 moodiin 3 (tyyppi 0) tehdään seuraavasti: Kirjoitetaan porttiin 43H arvo 101101 lOB, jonka jälkeen kirjoitetaan porttiin 42H luku OOH ja samaan porttiin uudestaan luku 80H. Tällöin ajastimen arvoksi on asetettu 32768, joten laskuri saavuttaa nollan 36,4 kertaa sekunnissa. Samainen toiminta on esitetty esimerkissä 4.3.

  ##### Esimerkki 4.3. Ajastimen 2 asetus.

      cli
      mov   al,10110110B   ; Ajastin 2,LSB sitten MSB
      out   43H,al         ; aseta se
      mov   al,00H         ; Ajastimen arvoksi 8000H
      mov   al,00H         ; viive
      out   40H,al         ; LSB
      mov   al,80H
      mov   al,80H         ; viive
      out   40H,al         ; MSB
      sti

  ### Ajastimen lukeminen

  On tilanteita, joissa tarvitaan tietoa ajastimesta. Kuvassa 4.6 on mainittu lukitus, jonka avulla voi lukita senhetkisen laskurin arvon. Senjälkeen arvo voidaan lukea portista. Tällä tavalla voi esimerkiksi mitata, kuinka paljon aikaa kului konekielikäskyjen suorittamiseen. Kun laskuri halutaan lukita, kirjoitetaan vain laskurin numero ja toiminto lukitusporttiin 43H. Ajastimen moodia ei siis tarvitse tietää.

          mov al,l0000000B ; ajastin 2
          out 43H,al

  Ajastimen asetuksista ei saada pelkällä lukituksella tietoja. Lue takaisin -komennolla saadaan loputkin tiedot käyttöön (HUOM! komento toimii vain 8524-piirissä, eikä se siis toimi vanhoissa PC:issä). Komennossa määritellään luettava ajastin, sen jälkeen kirjoitetaan porttiin 43H. Kuvassa 4.7 on esitetty lue takaisin -komennon sisältö.

  ##### Kuva 4.7.Lue takaisin -komento.

      Bitti   Toiminta
      07      Aina yksi.
      06      Aina yksi.
      05      0= Lukitse valitun laskurin arvo.
      04      0= Lukitse laskurin tila.
      03      Valitse laskuri 2.
      02      Valitse laskuri 1.
      01      Valitse laskuri 0.
      00      Aina nolla.

  Esimerkki komennon kirjoittamisesta

      mov al,l l0010008 ; laskuri 2
      out 43H,al

  Nyt voidaan tietoa lukea suoraan ajastimen laskurista. Kuvaan 4.8 on koottu ne tiedot, jotka komento antaa luettavaksi.

  ##### Kuva 4.8. Ajastimen tiedot.

      Bitti Toiminta
      07    Ulostulolinjan tila.
                    0 = LOW
                    1 = HIGH
      06    Laskurin arvo asetettu lukko piiriin.
                    0 = on asetettu
                    1 = ei ole
      05-04 Luku/kirjoitus tai lukitus.
      03-01 Laskurin moodi. Moodi on 0-5.
      00            0 = 16-bittinen binäärilaskuri
                    1 = BCD-laskuri

  Lukeminen tapahtuu siis seuraavasti:

          in 42H,al ; laskuri 2

  Tuloksena saataisiin luku B6H eli bittisarja 1011 0110. Siitä saadaan käyttöön seuraavanlainen tieto: Ulostulolinjan tila on 1, laskurin arvo on asetettu lukkopiiriin ja ajastin toimii moodissa 3. Huomaa, että bitistä 07 saatava tieto on sama kuin portin 61H bitistä 5.

  ### Ajastimen 0 koukutus

  Edellä todettiin, että on paras tehdä koko 08H- keskeytysrutiini uudestaan. Kuitenkin pohjana uudelle keskeytykselle voidaan käyttää vanhaa. Kun vanhaan keskeytykseen hyppää 18,2 kertaa sekunnissa, se riittää käyttöjärjestelmälle.

  Tosiasiassa DOS ei edes tiedä, että rutiinia on muutettu (pelistä poistumisen jälkeen vanha rutiini tietysti asetetaan omalle paikalleen). Aluksi kopioidaan 08H-rutiinin osoiteja kielletään keskeytykset. Asetetaan ajastimen arvo sekä moodi, kirjoitetaan uuden keskeytysrutiinin osoiteja lopuksi sallitaan keskeytykset. Nyt keskeytys on omassa käytössä, mutta se ei vielä riitä. Omassa keskeytysrutiinissa pitää huolehtia siitä, että se hyppää tarvittavat 18,2 kertaa sekunnissa vanhaan keskeytysrutiiniin (näin saadaan konetta huiputettua).

  Kun keskeytysrutiiniin hypätään, on tärkeää, ettei se sotke suoritettavaa ohjelmaa. Kaikki käytettävät rekisterit pitää tallentaa pinoon ja palauttaa takaisin ennen rutiinista poistumista. 18,2 kertaa sekunnissa tapahtuva keskeytys voidaan toteutettaa esimerkiksi siten, että laskurin jokaisella kierroksella hypätään kerran alkuperäiseen rutiiniin. Tämä käy helposti, jos laskuriin asetettava luku onjaollinen 65536:lla.

  Yleensähän TSR-ohjelmat linkittyvät juuri IRüO-vektoriin (myös IRQ 1-vektoriin, joka on näppäimistö), joten mikäli keskeytystä ei näennäisesti suoriteta, ei myöskään TSR- ohjelmaa suoriteta. On olemassa kuitenkin yksi tapa laskurin käyttöön, jonka avulla päästään kaikista TSR-ohjelmista eroon: Linkitetään keskeytysvektori vain omaan käyttöön eikä hypätä koneen omaan keskeytysvektoriin lainkaan.

  Kello menee sekaisin, mikäli koneen omaa IRüO-keskeytystä ei suoriteta 18,2 kertaa sekunnissa. Tämä ei ole sinänsä ongelma, koska koneen paristovarmennetusta kellosta voidaan helposti palauttaa oikea aika pelistä poistuttaessa. Tärkeää on vain muistaa palauttaa myös pelin aikana oikea keskeytysrutiini, mikäli käytetään levytoimintoja.

  ##### Esimerkki 4.4. Ajastimen 0 käyttö C-kielellä ja kellon ajan palautus.

      #include <dos.h>
      #include <alloc.h>
      #include <stdlib.h>

      #define INT_TIMER 0X08
      #define _ _CPPARGS ...

      void interrupt ( *oldhandler)(_ _CPPARGS);
      void interrupt timer _handler(_ _ CPPARGS);

      //-
      //-Pääohjelma
      //-
      void main(void)
      {
      int restore_time(void);
      unsigned int timer;
      unsigned long rate=22050;

      oldhandler = getvect(INT TIMER);

      timer=1193180/rate;
      setvect(INT_TIMER,timer handler);
      asm cli;
      //Ajastin 0
      asm{
      push        ax
      push        cx
      //bitit 76=00   - counter 0
      //bitit 54=11   - LSB & MSB
      //bitit 321=011 - square wave
      //bitit 0=0     - 16 binary counter
      mov         al,0x36
      out         0x43,al
      pusha
      popa
      mov         cx, [timer]
      mov         al, cl
      out         0x40,al
      pusha
      popa
      mov         al, ch
      out         0x40,al
      pusha
      popa

      pop      cx
      pop      ax
      sti
      }

      getch();

      //Ajastin 0

      asm{
      mov      al, 54
      out      0x43,al
      pusha
      popa
      //Ajastimen 0 vakioarvo=65536(=0)
      mov      cx, 0
      mov      al, cl
      out      0x40,al
      pusha
      popa
      mov      al, ch
      out      0x40,al
      }

      setvect(INT_TIMER,oldhandler);
      restore_time () ;
      }

      //-
      //-Palauttaa oikean ajan DOSille
      //-
      int restore_time(void)
      {

        asm{
        mov    ah, 4
        int    0x1a
        jc     virhe
        mov    ah, 2
        int    0x1a
        jc     virhe

        mov    bl,10                           //kerroin

        mov    al,ch                           //BCD to binary
        and    ch,15
        shr    al,4
        mul    bl
        add    ch,al
        mov    al,cl                           //BCD to binary
        and    cl,15
        shr    al,4
        mul    bl
        add    cl, al

        mov    al,dh                           //BCD to binary
        and    dh,15
        shr    al, 4
        mul    bl
        add    dh, al

        mov    dl, 0
        mov    ah,0x2d
        int    0x21
        cmp    al,0
        jnz    virhe
        }
        return 1;
      virhe:
        return 0;
      }

      //-
      //-Timer-keskeytys
      //-
      void interrupt timer_handler(_ _CPPARGS)

        asm{
        mov    al,0x20
        out    0x20,al
        }
      //omaa koodia
      }

  ### DMA

  Keskeytyksille hyvin läheisen DMA:n toiminnan ymmärtäminen on peliohjelmoijalle tärkeää. Yleisin DMA- sovellus on digitoidun äänen soittaminen äänikortilla. DMA pystyy siirtämään dataa esimerkiksi perusmuistin ja äänikortin välillä. Tätä varten äänikorteilla on oma DMA- kanava. Kun DMA on ohjelmoitu siirtämään tietty tavumäärä, toimii se taustalla itsestään ilman minkäänlaista merkkiä. Toki DMA-siirto vie taustalla ollessaan vähän aikaa väylältä, menetetty aika on riippuvainen siirron nopeudesta (esimerkiksi digitoidun äänen toistotaajuus).

  DMA:ta ohjelmoidessa pitää ottaa huomioon, että 8- bittinen DMA toimii vain fyysisten 64 kilotavun rajojen sisällä. Näitä 64 kilotavun alueita sanotaan sivuiksi. Kuvassa 4.9 on esitetty PC:n sivujako.

  ##### Kuva 4.9. DMA-sivut.

      Sivu         Muistialue
      0            0000:0000-0000:FFFF
      1            1000:0000-1000:FFFF
      2            2000:0000-2000:FFFF
      .                     .
      .                     .
      E            E000:0000-E000:FFFF
      F            F000:0000-F000:FFFF

  Esimerkiksi DMA ei pysty siirtämään muistialuetta 2C00:0000- 2C00:FFFF yhdellä siirrolla, sillä muistialue sijoittuu kahdelle eri fyysiselle sivulle. 16-bittinen DMA sen sijaan toimii 128 kilotavun rajojen sisällä.

  Nykyisin PC:ssä on kahdeksan DMA-kanavaa. Kanavat 0-3 ovat 8-bittisiä, jolloin siirto tapahtuu tavu kerrallaan. Kanavat 4-7 ovat 16-bittisiä, jolloin siirretään kaksi tavua kerralla. Vapaasti käytössä ovat kanavat 0, 1, 3, 5, 6 ja 7.

  8-bittistä DMA:ta ohjataan muutamien porttiosoitteiden kautta. Seuraavaksi esitellään tärkeimmät kontrollirekisterit, joilla esimerkiksi määrätään halutun kanavan suunta. Suluissa on vastaavan 16-bittisen DMA:n porttiosoite.

  ### 0Ah (D4h), Single Mask Register

      Bitti      Käyttö
      7-3        Ei käytössä
      2          1 = estä kanavan käyttö
                 0 = salli kanavan käyttö
      1-0        Kanavan numero

  Tällä rekisterillä asetetaan kanava joko päälle tai pois päältä.

  ### 0Bh (D6h), Mode Register

      Bitti      Käyttö
      7-6        01 = normaali käyttö
      5          0  = osoite kasvaa siirrossa
                 1  = osoite vähenee siirrossa
      4          0  = kertatoiminta
                 1  = auto-init moodi
      3-2        01 = kirjoitus
                 10 = luku
      1-0        Kanavan numero

  ### 0Ch (D8h), Clear FF

  Jos kirjoitat tänne minkä tahansa luvun, DMA:n sisäinen kiikku nollautuu. Tällöin kaikki kaksitavuiset toiminnot nollautuvat siten, että ensin kirjoitetaan alemmat bitit. Tätä käytetään esimerkiksi ennen osoitteen kirjoitusta. Lisäksi jokaiselle kanavalle on omia rekistereitä, joilla määrätään kanavakohtaisia asioita, kuten siirron pituus.

  ### 00h, Channel 0 Address

  Tähän rekisteriin kirjoitetaan siirros sen sivun sisällä, mistä DMA-siirto aloitetaan. Ensin kirjoitetaan alemmat kahdeksan bittiä, sitten ylemmät kahdeksan bittiä. Katso myös portin 0Ch selitys. Vastaava rekisteri on kaikille kanaville:

      Kanava       Porttiosoite
      0            0
      1            2
      2            4
      3            6
      4 (16-bit)   COh
      5 (16-bit)   C4h
      6 (16-bit)   C8h
      7 (16-bit)   CCh

  ### 01h, Channel 0 Count

  Tähän rekisteriin kirjoitetaan DMA-siirron pituus tavuina -1. Ensin kirjoitetaan alemmat kahdeksan bittiä, sitten ylemmät kahdeksan bittiä. 16-bittisessä DMA:ssa pituus on sanoja, eli jos siirrettävä pituus on 1000 tavua kirjoitetaan rekisteriin 500-1. Katso myös portin OCh selitys. Vastaava rekisteri on kaikille kanaville:

      Kanava       Porttiosoite
      0            1
      1            3
      2            5
      3            7
      4 (16-bit)   C2h
      5 (16-bit)   C6h
      6 (16-bit)   CAh
      7 (16-bit)   CEh

  ### 87h, Page Register

  Rekisteriin kirjoitetaan halutun fyysisen sivun numero (0-15), josta/jonne siirto tapahtuu. Vastaava rekisteri on kaikille kanaville:

      Kanava        Porttiosoite
      0             87h
      1             83h
      2             81h
      3             82h
      5 (16-bit)    8Bh
      6 (16-bit)    89h
      7 (16-bit)    8Ah

  Esimerkissä 4.5 on 8-bittisille DMA-kanaville soveltuva ohjelmointiesimerkki. Kutsu on tarkoitettu tapahtuvan C- kielestä. Esimerkki käyttää kanavaa yksi. Siirron alku oletetaan fyysisen sivun alkuun.

  ##### Esimerkki 4.5. 8-bittisen DMA:n ohjelmointi.

      ;
      ; Esimerkki 8-bittisen DMA:n ohjelmoinnista
      ;
      ; Prototyyppi C-kielessä (large-model):
      ; void dmaesim(char sivu, int pituus);
      ;

      IDEAL
      P386
      MODEL LARGE,C

      CODESEG

        PUBLIC dmaesim

      PROC dmaesim sivu:BYTE, pituu5:WORD

        cli
      ; dma pois
        mov    al,04h
        or     al,1                            ; kanava 1
        out    0ah,al
      ;tyhjennä kiikku
        mov    al,00h
        out    0ch,al
      ;aseta moodi-rekisteri
      ;bitti6=1:normaali toiminta
      ;bitti3=1:luku
        mov    al,048h
        or     al,1                            ; kanava 1
        out    0bh,al
      ;sivurekisteri (kanava 1)
        mov    dx,083h
        mov    al, [sivu]
        out    dx, al
      ;sivun siirros nolla.
        mov    bx, 0
      ;kanavan 1 siirros-rekisteri
        mov    dx, 2
        mov    al, bl
        out    dx,al                           ; off l
        mov    al, bh
        out    dx, al                          ; off h

        mov    bx,[pituus]
        dec    bx                              ; dmasize=dmasize-1
      ;kanavan 1 pituus-rekisteri
        mov    dx,03h
        mov    al,bl                           ; size l
        out    dx, al
        mov    al, bh                          ; size h
        out    dx, al
        mov    al,1                            ; kanava 1
        out    0ah,al
        sti
        ret
      ENDP

      ENDS

      END

  # 5. Pelin kontrollointi

  Pelin tekemiseen ei riitä pelkästään grafiikan ja äänien yhdistäminen kokonaisuudeksi. Demot, joita PC:lle on alkanut ilmestyä lähiaikoina, on tehty esittämään koneen kykyjä. Samaa tekevät luonnollisesti pelit, mutta niissä otetaan huomioon myös pelaaja.

  Koska pelaaja pystyy vaikuttamaan pelin kulkuun, suunnitteluvaiheessa ei voi tarkkaan määritellä missä järjestyksessä peli tulee kulkemaan. (Pelaaja voi esimerkiksi löytää esineet eri järjestyksissä.) Demoissa ei katsoja voi muuttaa esitysjärjestystä ja siksi demojen tekeminen onkin helpompaa. Yleensä peli syntyy jostakin käyttökelpoisesta ideasta tai rutiinista, ja kun rutiinia kokeillaan käytännössä grafiikan kera, syntyy demo. Tästä eteenpäin demon kehittäminen saattaa jatkua pelin kehittelynä, mikäli ohjelmoija on tyytyväinen tulokseen.

  Pelissä täytyy antaa pelaajalle mahdollisuus valita peliväline. Perinteinen ohjaustapa on näppäimistö, joka soveltuu hyvin lähes kaikkiin peleihin. Hiiri on myös hyvä, joskaan ei näppäimistön veroinen. Myös tallennus- ja lataustoiminnot on oltava pelaajan tehtävissä, tietysti pelin tyypistä riippuen. (Roolipelissä tämä on suorastaaan välttämättömyys.)

  ### Näppäimistö

  Käymme läpi erilaisia vaihtoehtoja näppäimistön lukemiseen. Aloitamme ascii-koodeista ja päädymme näppäimistön ohjelmointiin kovotasolla, jolloin peliin saadaan paras mahdollinen näppäimistön tuntuma.

  Merkkejä, jotka tulostuvat ruudulle näppäimiä painettaessa, käsitellään ascii-koodeina. Jokaiselle näppäimistön merkille on annettu vastaava ascii-koodi. Esimerkiksi kirjainta A vastaa luku 65. Taulukosta saadaan muut vastaavat koodit. Koska ascii muodostetaan ohjelmallisesti (koneen BIOS tekee merkit asciiksi), ei suoraan porttia luettaessa saada ascii-koodeja. Ne pitää tarvittaessa muuttaa joko itse tai antaa BIOSin muuttaa ne asciiksi (tapahtuu automaattisesti keskeytystä käytettäessä). Pelissä on kuitenkin parempi käyttää make-koodeja, eli koodeja, jotka näppäimistö lähettää tietokoneelle.

  Ensimmäinen kosketus make-koodeihin saadaan näppäimistöpuskurin avulla, johon on tallennettu sekä make- koodi että asciikoodi (näitä kahta koodia kutsutaan yhteisnimellä scan-koodi). Puskuri alkaa osoitteesta 40H:1EH ja on 32 tavua pitkä, eli siihen mahtuu yhteensä 16 merkkiä. Jotta tiedettäisiin, mikä merkki puskurista pitää milloinkin lukea, käytetään hyödyksi osoittimia. Niitä on käytössä kaksi: toinen osoittaa ensimmäistä merkkiä ja toinen paikkaa, johon seuraava merkki luetaan.

  Valitettavasti näppäimien lukeminen suoraan puskurista ei ole tarpeeksi hyödyllistä, joten emme esittele lukurutiinia. Rutiinista tulisi turhan monimutkainen verrattuna hetken kuluttua esiteltävään keskeytyksen avulla toimivaan rutiiniin. Lisäksi erikoisnäppäimien (shift, alt) tilan joutuu lukemaan erikseen.

  Peleissä tulee usein eteen puskurin haitta: Painetun näppäimen koodia ei saada heti käyttöön (puskurissa nimittäin saattaa olla vielä entisiä merkkejä), joten pelaajan antamaa käskyä ei suoriteta heti, vaan vasta hetken kuluttua.

  Näppäimistöä voi lukea myös BIOS-keskeytyksen avulla. Tähän toimintaan on varattu INT 16H. Se antaa make-koodien ja vastaavien ascii-koodien arvot. Erikoisnäppäinten (shift, alt) tilat ovat saatavissa, mikä on usein hyvin tarpeellista. Keskeytys kykenee käsittelemään kaikki tiedot yhdellä kertaa, minkä vuoksi se on käyttökelpoinen. Huomattava on, että merkki luetaan puskurista. Puskurista lukeminen johtuu siitä, että INT 09, joka käsittelee scan- koodit, kirjoittaa ne aina puskuriin.

  ##### Esimerkki 5.1. Näppäinten lukeminen keskeytyksen 16H avulla.

      ; Keskeytys palauttaa Al-rekisterissä ascii-koodin ja
      ; rekisterissä AH make-koodin.
        sub    ax, ax
        int    16h
        cmp    al,65                           ; onko A-näppäin

  Valmiiden rutiinien käyttö on helppüa ja yksinkertaista. Pelissä ei ole aina käyttöä BIOS-rutiinien tuottamille ascii-koodeille, paitsi high score- eli tulostaulukossa. Miksi siis käyttäisi hitaita rutiineja, kun mikään ei estä uuden näppäimistörutiinin ohjelmoimista?

  Seuraavaksi perehdymme käyttöjärjestelmän ohittaviin toimintoihin, pääasiassa näppäimistön lukemiseen, mutta myös sen ohjelmoimiseen.

  ### Näppäimistön toiminta

  Näppäimistön toimintaan täytyy perehtyä ennen uuden keskeytyksen ohjelmoimista, koska toiminnan ymmärtäminen on ensiarvoisen tärkeää. Luvussa kerrotaan make-koodeista ja lisäksi annetaan joidenkin yksittäisten näppäinten arvot. Kaikkien näppäinten koodit on esitetty liitteessä B. Liitettä tutkimalla huomaa, kuinka uudet näppäimet on lisätty ja miten yhteensopivuus vanhempiin näppäimistöihin on säilytetty. Se on saatu helposti aikaan make-koodien avulla, koska ne eivät ole paikasta riippuvaisia. Olemme ottaneet tähän lukuun vain 102-näppäimistön, koska ainoastaan siinä on näppäintoisto ja -viiveen ohjelmointi.

  Esimerkiksi A-näppäimen make-koodin luku on 1EH. Insert- näppäimen koodiksi on annettu E052H. Luku on kaksitavuinen, ja käsittää siis tavut E0 (lähetetään ensiksi) ja 52. Kaksiosaiset luvut kertovat uudesta näppäimestä, jota ei ole vanhoissa näppäimistöissä. Tässä tavu EO ilmoittaa vain uuden näppäimen, muuta merkitystä sillä ei ole. Tavu 52 on näppäimen make-koodi, huomaa, että samainen koodi löytyy myös erillisen numeronäppäimistön 0-näppäimestä. Uudesta näppäimestä kertoo myös tavulla E1 alkava koodi, esimerkiksi Pause-näppäin antaa tavut E1H ja 1DH. Tällä tavoin voidaan tunnistaa jokainen näppäin.

  Jotta tiedettäisiin, milloin näppäintä painetaan ja milloin se vapautetaan, on jokaiselle näppäimelle annettu kaksi arvoa.

  Näppäintä painettaessa koodi on liitteen mukainen. Kun näppäin päästetään, samalla lähetetään uusi luku, joka on arvoltaan 80H suurempi. Esimerkiksi A:n arvo on tällöin 80H + 1EH = 9EH. Lukittavilla näppäimillä (Caps Lock yms.) toiminta on vastaavanlainen, joten "lukitseminen" toteutetaan ohjelmallisesti (ledien sytyttäminen tehdään erikseen). Käytännössä tulee myös usein vastaan kahden tai useamman näppäimen yhtäaikainen painaminen.

  Otetaan esimerkki näppäimistä Shift ja A. Näppäimistö lähettää tällöin ensiksi shift-näppäimen koodin ja heti perään a-näppäimen koodin. Mikäli näppäimiä olisi ollut useampi painettuna, perään olisi lähetetty myös muiden näppäinten make-koodit.

  Tietokoneen ja näppäimistön välinen tiedonsiirto on toteutettu sarjamuotoisena binäärisignaalina. Näppäimistöön on asennettu oma prosessori, joka osaa tutkia mitä näppäintä painetaan. Se muuttaa make-koodin sarjamuotoon, lähettää sen yhteen I/O-osoitteeseen, josta pääprosessori voi sen noutaa. Mahdollinen vaaratilanne (merkkiä ei ehdittäisi lukea ennen toisen merkin saapumista) on poistettu puskurin avulla, jonne uusi merkki tallennetaan (uutta merkkiä ei lähetetä ennen kuin vanha on luettu). Tätä puskuria ei pidä sotkea aikaisemmin kerrottuun puskuriin, koska ne ovat kaksi eri asiaa. Nämä monimutkaiset toiminnot, jotka näppäimistön prosessori joutuu tekemään, ovat täysin näkymättömiä ohjelmoijalle.

  ### Näppäimistön lukeminen

  Painetun näppäimen make-koodi luetaan portista 60H. Se pitäisi tietysti lukea silloin, kun näppäimistö on sen lähettänyt. Keskeytyksen (INT 09) avulla tämä on kaikkein helpointa, koska se annetaan prosessorille aina uuden merkin saapuessa porttiin. Kun tämä keskeytys otetaan omaan käyttöön, päästään kaikista edellä mainituista ongelmista eroon. Se on vain muistettava, että DOS ei kykene sen jälkeen lukemaan merkkejä. Tämä näkyy esimerkiksi Ctrl- Alt-Del-näppäinyhdistelmän toimimattomuutena. Myös kaikki muutkin erikoistoiminnot ovat poistuneet, joten ne pitää luoda itse tai käyttää BIOSin valmiita rutiineja, mikäli niitä haluaa käyttää.

  Seuraavaksi ohjelmoidaan uusi keskeytysrutiini. Aluksi tehdään yksinkertainen keskeytysrutiini, joka lukee näppäimistön lähettämät make-koodit. Tällainen ohjelma on esitetty esimerkissä 5.2. Se on konekielinen ohjelma, jota ohjataan Esc-näppäimen avulla. Ensi kerran painettaessa kyseessä olevaa näppäintä ei tapahdu mitään, mutta vapautettaessa näppäin sytytetään Scroll Lock -ledi. Painettaessa uudelleen Esc-näppäintä ohjelma palaa DOSiin.

  Erikoisuutena ohjelmaan on lisätty rivi, jolla tulostetaan painetun näppäimen make-koodi ruudun yläreunaan.

  ##### Esimerkki 5.2. Koukutettu INT 9-keskeytys.

      ; Näppis.asm-tiedosto, joka voidaan
      ; kääntää sellaisenaan.
      ; Käännä: tasm näppis.asm
      ; Linkkaa: tlink näppis.obj

      ; Varaa pino ohjelmalle
      pino     segment stack
               db 200 dup ("pino")
      pino ends

      ; Varaa muuttujia
      dseg     segment "data"
      old      dd 0 ; tähän tallennetaan keskeytyksen osoite
      merkki   dw 0
      dseg     ends

      ; Tästä alkaa ohjelmakoodi
      cseg     segment "code"
      start    proc far
               assume cs:cseg,ds:dseg,ss:pino
               push      ds
               sub       ax, ax
               push      ax

      ; Normaalit alustukset tehty! Oma ohjelmamme alkaa.
               mov       ax,dseg ; aseta segmentit
               mov       ds, ax
               sub       ax, ax
               mov       es,ax   ; aseta ES nollaksi!
      ; Ota vanha keskeytysosoitin talteen
               cli
               mov       ax,word ptr es:[36] ; osoite: 0000:0036
               mov       ds:[0],ax
               mov       ax,word ptr es:[38]
               mov       ds:[2],ax
      ; Aseta uusi pointteri
               lea       ax,intr
               mov       word ptr es:[38],cs
               mov       word ptr es:[36],ax
               sti

      ; Asettamamme keskeytysrutiini on nyt käytössä!
      ; Se asettaa merkki-muuttujaan
      ; painetun näppäimen MAKE-koodin.

      ; dota, että käyttäjä painaa Esc-näppäintä
        ja vapauttaa sen (koodi 129)
      silmu:   cmp       merkki,129
               jne       silmu

      ; Aseta "scroll lock"-ledi osoittamaan
      ; ESC-näppäimen painallusta
               mov       merkki,0
               mov       ax,237      ; EDH eli ledi-komento
               out       60H,al
      silmu1:  cmp       merkki,250  ; odota, kunnes tulee ns. kättely
               jne       silmu1
               mov       ax,1        ; ledin numero
               out       60H,al      ; SYTYTÄ!!!

      ; Seuraavaksi odotetaan, että käyttäjä
      ; painaa Esc-näppäintä.
      silmu2:  cmp       merkki,1
               jne       silmu2

      ; Palautetaan vanha rutiini
      pois:    mov       ax,ds:[0]
               mov       word ptr es:[36],ax
               mov       ax, ds:[2]
               mov       word ptr es:[38],ax
      ; Palaa ohjelmasta
               ret

      ; Tästä alkaa oma keskeytysrutiini
      ; Kaikki rekisterit, joita keskeytysrutiini
      ; käyttää, tallennetaan pinoon:
      intr :   push        ax
               push        ds
               push        es

      ; aseta datasegmentti
               mov         ax,dseg
               mov         ds,ax

               mov         ax,47104 ; B800 eli näytön osoite.
               mov         es,ax
               mov         ax,0
               in          al,60H   ; lue merkki portista
               mov         merkki,ax ; tallenna arvo merkki-muuttujaan
               mov         es:[0],al ; tulosta ruudun yläreunaan merkki.

      ; Keskeytys suoritettu. Kuitataan PIC-piirille.
               mov         al,20H
               out         20H,al       ; kuittaus

      ; Palautetaan rekisterien arvot
               pop         es
               pop         ds
               pop         ax
      ; lopeta keskeytys
               iret

      start    endp
      cseg     ends
               end

  Luvun alussa kerrottiin valmiiden rutiinien tuottamista ongelmista, kuten puskurin tuottama ongelma eli näppäin ei ole heti luettavissa. Ongelma poistui esimerkin 5.2 mukana, mutta nyt on olemassa toinen ongelma, eli yhtäaikaisten näppäinten lukeminen. Esimerkissä 5.3 on tämäkin ongelma poistettu.

  ##### Esimerkki 5.3. Erikoisnäppäinten lukeminen.

      ; Sama runko kuin edellisessä esimerkissä.
      ; Toistuvat osat on karsittu pois, joten ohjelmaa ei
      ; voi kääntää sellaisenaan

      ; Varaa muuttujia
      dseg     segment     "data"
      old      dd 0 ; tähän tallennetaan keskeytyksen osoite
      merkki   dw 0
      shift    db 0
      ctrl     db 0
      dseg ends

      ; Aseta uusi pointteri
               lea         ax,intr
               mov         word ptr es:[38],cs
               mov         word ptr es:[36],ax
               sti

      ; Seuraavaksi odotetaan, että käyttäjä painaa
      ; Esc-näppäintä sekä shift-näppäintä.
      silmu:   cmp         merkki,1
               jne         silmu
               cmp         shift,1 ; testaa onko shift-näppäin pohjassa
               jne         silmu

      ; Palautetaan vanha rutiini
      pois:    mov         ax,ds:[0]
               mov         word ptr es:[36],ax
               mov         ax,ds:[2]
               mov         word ptr es:[38],ax
      ; Palaa ohjelmasta
               ret

      ; keskeytysrutiini

               in          al,60H   ; lue merkki portista
               cmp         al,2aH   ; onko shift-näppäin
               jne         next1
               mov         shift,1  ; aseta muuttuja (näppäin painettu!)
      next1:   cmp         al,1dH   ; onko ctrl-näppäin
               jne         next2
               mov         ctrl,1    ; aseta muuttuja (näppäin painettu!)
      next2:   cmp         al,aaH
               jne         next 3
               mov         shift,0
      next3:   cmp         al,9dH
               jne         next 4
               mov         ctrl,0

      next4:   mov         merkki,ax ; tallenna arvo merkki-muuttuja

      ; Keskeytys suoritettu. Kuitataan PIC-piirille.
               mov         al,20H
               out         20H,al    ; kuittaus

               iret

  Näppäin lähettää kaksi make-koodia, ensimmäisen painettaessa näppäintä ja toisen päästettäessä näppäin. Tutkitaan kolmen yhtäaikaisen näppäimen painallusta, esimerkiksi näppäimet shift, A ja S. Kun vasen shift-näppäin painetaan pohjaan, vastaanotetaan koodi 2AH. Tämä arvo kirjoitetaan johonkin osoitteeseen ylös, jolloin annetaan tieto: shift-näppäintä on painettu. Tämänjälkeen saadaan heti A:n koodi 1EH. A:n jälkeen painetaan vielä S-näppäin, jolloin lähetetään koodi 1FH.

  Jälkimmäisten näppäinten aikana on tiedossa se, että shift-näppäin oli painettuna, koska ylöspäästetyn näppäimen koodia ei tullut. Luonnollisesti ohjelmassa shift-näppäimen tila tarkistetaan muistipaikasta, jonne erikoisnäppäinten tiedot on tallennettu. Muistipaikka tietysti nollataan silloin, kun saadaan koodi 2AH+80H. Näin yksinkertaisella tavalla saadaan käyttöön useampia eri näppäimien tiloja.

  Näppäimistön lukeminen ilman BIOS-rutiineja ei ole vaikeaa, lisäksi oman keskeytyksen avulla päästään kaikista ongelmista eroon. Haittana on make-koodien tekeminen ascii- koodiksi. Se on monimutkaista, mutta esimerkiksi high score -taulukon ajaksi (jolloin tarvitaan ascii-koodeja) voidaan palauttaa BIOSin tuottamat palvelut.

  Nämä edellä esitetyt niksit eivät ole ainoat mahdollisuudet näppäimistön luvussa. Luku voidaan toteuttaa myös siten, että jokaiselle näppäimelle on varattu yksi muistipaikka. Näin muodostuu taulukko, josta voidaan helposti lukea, onko jokin tietty näppäin painettu vai ei. Mahdollisuuksia on monia, joten kannattaa miettiä omaan peliinsä paras ratkaisu.

  ### Näppäimistön ohjelmoiminen

  102-näppäimistö sallii myös ohjelmoinnin. Esimerkiksi näppäimen toistoviive ja -nopeus voidaan asettaa. Myös näppäimistön prosessorin avulla voidaan esimerkiksi osoitelinja A20 lukita tai vapauttaa. Tätähän käyttää esimerkiksi himem.sys salliessaan UMB-muistialueen käytön.

  Toinen tärkeä toiminto on pääprosessorin resetoiminen, jota tarvitaan siinyttäessä suojatusta tilasta takaisin normaaliin toimitilaan. Peliohjelmoijalle näistä toiminnoista on hyvin vähän hyötyä.

  Näppäimistöllä on kaksi porttia, joiden avulla sitä ohjelmoidaan. Ne ovat portti 60H ja 64H. Niiden toiminta ei kuitenkaan rajoitu pelkästään make-koodien lukemiseen. Jälkimmäinen portti toimii näppäimistön prosessorille annettujen käskyjen vastaanottajana. Ohjelmointijakaantuukin kahteen osaan, nimittäin näppäinprosessorin ja näppäimistön ohjelmointiin. Portin 60H kautta ohjataan näppäimistöä ja portin 64H kautta prosessoria. Kuva 5.2 kertaa porttien tehtävät.

  ##### Kuva 5.2. Näppäimistön I/O-portit.

      60H Luku          OUTPUT-rekisteri. Kaikki tiedot, jotka prosessori tai
                        näppäimistö lähettää tulee luettavaksi tähän rekisteriin.
                        Pitäisi lukea vain silloin, kun kontrollitavun bitti 0 on nolla
                        (katso kuva 5.3).

      60H Kirjoitus     Annetaan komentoja näppäimistölle, esimerkiksi nopeus ja
                        viive. Käytetään myös portin 64H käskyjen yhteydessä.
                        Pitäisi kirjoittaa vain silloin, kun kontrollitavun bitti 1 on nolla
                        (katso kuva 5.3).

      64H Luku          Lukee kontrollitavun, jonka sisältö on esitetty kuvassa 5.3.
      64H Kirjoitus     Annetaan komentoja prosessorille. On syytä lukea ennen
                        kirjoittamista portin 60H tiedot, koska ne saattavat tuhoutua.

  ##### Kuva 5.3. Kontrollitavu (portti 64H).

      Bitti             Toiminto
      07                Pariteetti. Asetetaan parittomaksi, eli bitin arvoksi nolla.
      06                Tavun vastaanotto ei onnistunut, jos bitti on ykkönen.
      05                Tavun lähetys ei onnistunut, jos bitti on ykkönen.
      04                Näppäimistö estetty, jos bitti on ykkönen.
      03                Luku 60H- tai 64H-portissa sisältää komennon tai datan 8042:lle

      Komento, jos bitti on ykkönen.
      02                Järjestelmä lippu (asetettu nollaksi).
      01                Portti 60H tai 64H sisältää tietoa 8042-piirille.
      00                Portti 60H sisältää luettavaa tietoa.

  ### Ohjelmointi

  Aloitamme ohjelmoinnin näppäimistön prosessorista. Ohjelmointi tapahtuu antamalla käsky porttiin 64H. Prosessori suorittaa tämän jälkeen annetun tehtävän ja palauttaa tuloksen tarvittaessa. Huomaa, että vastaus kirjoitetaan aina porttiin 60H. Sen sisältö on siis parasta lukea aina ennen käskyä. Jos esimerkiksi käsky ABH kirjoitetaan porttiin 64H, pitäisi porttiin 60H tulla luku 0, jos näppäimistön kello- ja datalinja on kunnossa. Peleissä tuskin näille käskyille on käyttöä.

      Komento   Toiminta
      20H       Kirjoita komentotavu (esitetty kuvassa 5.4)
      60H       Lue komentotavu (esitetty kuvassa 5.4)
      AAH       Testaa koko näppäimistön toiminnan.Palauttaa porttiin 60H luvun 55H,
                jos kaikki on kunnossa.
      ABH       Testaa data- ja kellolinjat.Palauttaa porttiin 60H luvun 0,
                jos kaikki on kunnossa.
      ADH       Estää näppäimistön toiminnan ajamalla kellolinjan pysyvästi alas.
      AEH       Sallii jälleen näppäimistön toiminnan.
      COH       Lukee sisäisen vastaanottoportin tilan,jota CPU ei voi lukea lainkaan.
                Palauttaa porttiin 60H kuvan 5.5mukaiset tiedot.
      DO        Lukee ulostuloportin tilan,jota CPU ei voi lukea.Palauttaa porttiin 60H
                kuvan 5.6mukaiset tiedot.
      D1        Kirjoittaa ulostuloporttiin.Tavu annetaan porttiin 60H.

      Fx        Pulssittaa 6:mikroksi halutut linjat (0-3) nollaksi. Tämän avulla himem.sys
                toteuttaa edellä puhutut toiminnot.Linjat ovat samat kuin kuvassa 5.6.

                Linja annetaan käskyn yhteydessä,joten käsky F6 (eli 0110) pulssittaa
                linjat 0 ja 3 nollaksi.

  ##### Kuva 5.4. Komentotavun sisältö.

      Bitti     Toiminto
      07        Aina nolla
      06        MAKE-koodin tulkinta.AT:ssa bitti on nolla.
      08        8042= 8255
      04        Estä näppäimistön toiminta.
      03        Ohita näppäimistön Iukitus.1= ohita.
      02        Asettaa tai nollaa järjestelmälipun.
      01        Aina nolla.
      00        INT 09-keskeytys toiminta.1= päällä 0= estetty.

  ##### Kuva 5.5.Vastaanottoportin tila.

      Bitti     Toiminto
      07        Näppäimistön esto.0= estetty
      06        Näytön tyyppi.1= MDA 0= CGA
      05        Valmistajan jumpperi asennettu.0= asennettu.
      04        Sallii toisen 256 kilotavun keskusmuistin, jos bitti 1 asetettu.
      03-O1     Määrittelemätön.

  ##### Kuva 5.6.Ulostuloportin sisältö.

      Bitti     Toiminto
      07        Datalinjan tila.
      06        Kellolinjan tila.
      05        8042-vastaanottopuskuri tyhjä.
      04        8042-vastaanottopuskuri täynnä.
      03-02     Määrittelemätön.
      01        Osoitelinja A20.
      00        Reset.

  Seuraavaksi käymme läpi näppäimistön toimintaan liittyviä käskyjä.Niitä ohjataan portin 60H kautta ja näiden käskyjen yhteydessä portti 64H jää käyttämättömäksi.

  Ensimmäinen näistä käskyistä on ledien sammuttaminen tai sytyttäminen.Muutamissa käskyissä tulee vastaan uusi asia, jota ei ole vielä käsitelty. Se on niin sanottu kättelytoiminto,jolla näppäimistö kertoo,että lähetetty komento on vastaanotettu. Samalla se on merkki, että toinen tavu (käskyn parametri) voidaan lähettää näppäimistölle.

      Komento   Toiminto
      EDH       Sammuttaa ja sytyttää näppäimistön ledejä.Komento on kaksiosainen,
                jossa ensimmäinen tavu on ED ja jälkimmäinen tavu on arvo, joka kertoo
                mitkä valot sytytetään tai sammutetaan. EDH-tavun jälkeen näppäimistö
                lähettää tavun FAH, joka tarkoittaa siis annetun käskyn tunnistamista
                (kättelytapahtuma). Sytytys tai sammutus tapahtuu vuorotellen, eli joka
                toinen kerta sammuttaa ja joka toinen sytyttää ledit.
                      0-Scroll lock
                      1-Num lock
                      2-Capslock
      EEH       Tarkistaa näppäimistön kunnon. Kaiuttaa EE-tavun koko näppäimistö-
                systeemin läpi. Samainen arvo pitäisi löytyä myös portista 60H, mikäli
                näppäimistö toimii.
      F3H       Asettaa toistoviiveen ja -nopeuden. Pelissä voi asettaa arvot haluamikseen,
                jos epäilee, että pelaajalla on liian hidas näppäimistö peliin. Komento on
                kaksiosainen, jossa ensimmäinen tavu on F3 ja jälkimmäinen tavu on arvo,
                joka kertoo viiveen ja toiston nopeuden. Tavujen välissä näppäimistö
                lähettää FAH-tavun.

      Bitit     Toiminto
      07        Aina nolla
      06-05     Viiveen pituus. 0,25, 0,5, 0,75 tai 1 sekunti. Nopein arvolla 00B ja hitain
                arvolla 11 B.
      04-00     Asettaa toiston nopeuden, jossa 32 eri mahdollisuutta. Arvolla nolla
                30 sekuntia ja arvolla 31 kaksi sekuntia.

  ### Hiiri

  Hiirestä on tullut korvaamaton apuväline kaikkiin graafsiin käyttöjärjestelmiin, ja useat muut DOS- pohjaisetkin ohjelmat hyödyntävät hiirtä. Sen avulla voi helposti valita sopivan valikon tai painonapin. Peleissä hiirtä voidaan käyttää esimerkiksi pelihahmon liikuttamiseen. Toinen vaihtoehto on, että näppäimistöllä ohjataan peliä ja hiirellä valitaan näytön alareunasta jokin haluttu toiminto.

  Peliä suunnitellessa kannattaa miettiä, mihin hiiri parhaiten soveltuu. Tärkeintä on saada luotua hyvä ohjattavuus peliin.

  ### Hiiren lukeminen

  Erityyppisiä hiiriä on paljon. Suurin ero on, miten ne liittyvät koneeseen: joko sarjaportin kautta tai oman laajennusväylään liitettävän kortin kautta. Vakiintunutta standardia ei ole olemassa, joten suoraan jostakin portista tai muistipaikasta hiiren liikkeitä ei voida lukea. Tärkeätä on saada hiiri toimimaan pelissä, joten liikkeiden lukemiseen ainoajärkevä tapa on käyttää hiiren ajuria (driver). Sen avulla lukeminen on helppoa, koska voidaan käyttää valmiiksi luotua keskeytystä.

  Keskeytys sisältää muitakin tärkeitä toimintoja. Tutkimme hiiren käyttöä keskeytyksen 33H avulla. Ennen kuin keskeytyksen tuottamia rutiineja on turvallista käyttää, pitää tehdä hiiren tunnistus. Tämä voidaan tehdä rutiinin INT 33H,21 avulla. Se resetoi hiiren ohjelmiston ja siirtää kursorin keskelle ruutua. Samalla se palauttaa tiedon siitä, onko hiiri liitetty systeemiin. Lisää tietoa hiirestä, ajurista ja keskeytyksestä saadaan tämän jälkeen keskeytyksellä 33H,24. Tunnistus on esitetty lyhyesti esimerkin 5.4 avulla.

  ##### Esimerkki 5.4. Hiiren tunnistus.

      ; INT 33H,21 palauttaa tuloksen rekisterissä AX
      ; AX=21, jos hiirtä ei ole asennettu (tai ajuria ei ole ajettu).
      ; AX=FFFFH, jos hiiri on asennettu.

      start:
               mov         ax, 21
               int         33H
               cmp         ax, 21
               je          pois ; hyppy, mikäli hiirtä ei ole
      pois:
               rte

  ### Keskeytyksen käyttö

  Hiirtä liikutettaessa se lähettää pulsseja tietokoneelle. Ajurin tehtävänä on tulkita nämä pulssit, eli selvittää mihin suuntaan hiiri liikkui. Jotta tämä onnistuisi, pitää hiirtä lukea riittävän usein. Yleensä hiiren arvo saadaan tavun mittaisena, jolloin suunta määritetään arvoilla +127 ja -127. Ohjelmoijan ei tarvitse näistävälittää, koska hiiren ajuri tekee ne. Hiiren kursorin sijainti voidaan siis lukea milloin vain, tuloksena saadaan aina oikeat koordinaatit. Samoin pystytään tarkastamaan painikkeiden tilat.

  Keskeytys antaa muutaman mahdollisuuden hiiren lukemiseen: voidaan lukea vain painikkeiden tilat tai painikkeiden lisäksi myös samalla kursorin sijainti. Esimerkissä 5.5 on toteutettu INT 33H,3- rutiinilla jälkimmäinen vaihtoehto, mutta vain vasemman painikkeen tila luetaan.

  ##### Esimerkki 5.5. INT 33H,3-rutiinin käyttö.

      ; Keskeytys palauttaa rekisterissä BX painikkeiden tilat.
      ; Bitti 0 on vasen painike ja bitti 1 on oikea painike
      ; (bitin arvo ykkönen, jos painettu). Rekisteri CX
      ; palauttaa X-koordinaatin (0-640) ja
      ; DX Y-koordinaatin (0-400).

      start:
               mov         ax, 3
               int         33H
               and         bx,1         ; tutki vain vasenta painiketta
               cmp         bx,1
               je          button       ; jos ykkönen, hyppää button-rutiiniin
      ; vasenta painiketta painettu
      button:
               ret

  Muita mahdollisia rutiineja ovat 33H,5 ja 33H,6, jotka antavat tietoa painikkeiden tiloista. Hiiren lukeminen on siis pelkästään keskeytyksen käyttöä, mutta lukemisen lisäksi tarvitaan myös kohde, jota ohjataan. Se voi olla hahmo tai pelkkä kursori.

  Käytössä on ainakin kaksi tapaa kursorin näyttämiseen grafikkaruudulla. Toinen on keskeytysrutiini 33H,10, toinen tapa, joka on parempi peleissä, on luoda itse grafiikkahahmojen liikkumiseen edellytettävät rutiinit.

  ##### Esimerkki 5.6. Kursorin liikuttaminen hiirellä.

      ; Hiiren lukurutiini (hiiri.asm)
      ; Ohjelma tarkastaa, onko hiiri asennettu (driver)
      ; Lukee hiiren koordinaatit, tarkastaa napit
      ; Tulostaa ruudulle (DOSissa) kursorin ja päivittää
      ; sijannin hiiren liikkeiden mukaan.
      ; Käännä: tasm hiiri.asm
      ; Linkkaa: tlink hiiri.obj

      ; Varataan ohjelmalle pino
      pino     segment     stack
               db 200 dup ("pino")
      pino     ends

      ; Ohjelman datat
      dseg     segment     "data"
      MAJOR    DB 0                    ; structure hiiren tiedoille
      MINOR    DB 0
      MTYPE    DB 0
               DB 0
      CURS0R   DW 0
      mode     dw 0                    ; 1=hiiri asennettu

      merkki   dw 20H                  ; näyttömuistin alla oleva merkki ja ansi-koodi

      dseg     ends

      cseg     segment     "code"
      start    proc far
               assume      cs:cseg,ds:dseg,ss:pino
               push        ds
               sub         ax,ax
               push        ax

      ; Oma ohjelmamme alkaa
               mov         ax,dseg
               mov         ds,ax
               mov         ax,47104     ; B800
               mov         es,ax        ; ES osoittaa merkkimuistin alkuun

      ; Tarkista onko hiiri installoitu käyttäen keskeytystä 33H.
               mov         ax,21H
               int         33H
               mov         mode,ax      ; tallenna tieto
               cmp         al,255       ; Jos ei FF,ei installoitu,pois
               jne         pois

      ; Ok. Hiiri installoitu.Ota versio numero.
               mov         ax,24H
               int         33H
               mov         major,bh     ; versio numero
               mov         minor,bl     ; versio numero
               mov         mtype,ch     ; tallenna hiiren tyyppi
      ; Rutiini tulostaa kursorin, jota liikutetaan hiirellä
      lloop:   mov         ax,3         ; Tutki hiiren nappeja ja liikettä.
               int         33H
      ; Jos vasen nappi, poistu ohjelmasta
               and         bx,1
               cmp         bx,1         ; bitti 1 päällä vasen nappi pohjassa
               je          pois
      ; CX ilmaisee X-koordinaatin ja DX ilmaisee Y-koordinaatin
      ; Lasketaan osoite,jonne kursori piirretään
      ; (tulos rekisterissä AX).
               sar         cx,2         ; jaa X-koordinaatti 4:llä
               sar         dx,3         ; jaa Y-koordinaatti 8:lla
               mov         ax,dx
               mov         dx,160
               mul         dx           ; kerro rivin pituudella (merkki,ansi)
               add         ax,cx        ; lisää X-koordinaatti
      ; Vanha kursorin kohta pitää pyyhkiä, mikäli kursorin sijainti on
      ; muuttunut
               mov         bx,cursor    ; ota vanha kohta
               cmp         ax, bx
               je          lloop        ; ei muuttunut, palaa alkuun

      ; pyyhi ja tulosta kursori uuteen paikkaan
               mov         cx,merkki
               mov         word ptr es:[bx],cx ; puhdista merkki
               mov         cursor,ax           ; tallenna kursorin paikka
               mov         bx,ax               ; aseta bx osoittamaan
                                               ; uutta kursorin paikkaa
               mov         word ptr ax,es:[bx] ; ota sisältö talteen
               mov         merkki,ax

               mov         ax, 09DBH           ; kursori
               mov         word ptr es:[bx],ax ; tulosta
               jmp         lloop

      ; poistu rutiinista
      pois :   ret

      start    endp
      cseg     ends
               end

  ### Lataus ja tallennus

  Latausta ja tallennusta pitää tietysti käyttää muutenkin kuin pelaajan tallentaessa tai ladatessa. Ainakaan isompi peli ei mahdu kokonaan muistiin, ellei muistia ole todella paljon. Peli pitäisi ohjelmoida niin, että jopa yhdellä megatavun muistilla varustetulla PC:llä pystyy pelaamaan sitä.

  Lataus kiintolevyltä ei kestä kauan, mutta tärkeintä on ladata silloin, kun se ei muuten hidasta pelin toimintaa, esimerkiksi kuvaruudun päivittämistä.

  Jos levyltä latauksen ja tallennuksen tekee suoraan kovolla, DMA:ta ohjaten, karsitaan valitettavasti samalla pelin toimivuutta eri koneissa. BIOSin käyttö on parempi ratkaisu, koska sitä käyttäen peli toimii koneessa kuin koneessa. BIOS sisältää valmiit rutiinit levynkäsittelyyn ja niitä kutsutaan keskeytyksien avulla.

  Valitettavasti emme tässä kirjassa voi perehtyä syvällisesti levyaseman rakenteeseen. emmekä edes BIOS- rutiineihin. Mielestämme paras tapa tiedostojen lataukseen ja tallennukseen on C- kielen valmiit luku- ja kirjoituskäskyt. Ne toimivat hyvin, ja loppujen lopuksi itse tehdyistä rutiineista ei saa nopeampia. Ohjelmoijalta säästyy paljon aikaa ja vaivaa valmiita käskyjä käytettäessä, joka on pelin tekovaiheessa arvokas asia.

  Luku- ja kirjoituskäskyjä on C-kielen kirjastoissa useita, mutta parhaiten soveltuvat käyttöön FREAD- ja FWRITE-funktiot.

      fread (void *buffer, koko, määrä, tiedosto);
      fwrite (void *buffer, koko, määrä, tiedosto);

  jossa,

- BUFFER on paikka, jonne tieto luetaan tai kirjoitetaan.
- KOKO ilmoittaa luettavan tai kirjoitettavan yksikön koon määrä ilmoittaa kuinka monta yksikköa luetaan. Kokonaispituus on siten määrä kertaa koko.
- TIEDOSTO ilmaisee käyttöön otetun tiedoston.

  ##### Esimerkki 5.7. Fread-funktion käyttö.

      #include <stdio.h>

      void main(void)
      {
        char buffer[200);
        FILE *tiedosto;

        // Avaa tiedosto
        tiedosto=fopen("c:\config.sys","rb");
        // Lue tiedostosta 200 tavua
        fread(buffer,20,10,tiedosto);
        // Sulje tiedosto
        fclose(tiedosto);
      }

  Pelin tekovaiheessa tulee jokaiselle mieleen oman pelin myyminen, tai oikeastaan myyntiluvut. Kopiointia ei tietenkään saisi tapahtua, jotta se ei vähentäisi myyntiä.

  Kopiointisuojaus on avain ongelmaan. Kaupallisissa hyötyohjelmissa on kopiointisuojauksesta luovuttu vuoden -85 vaiheessa. Syynä olivat suuret haittavaikutukset, jotka suojaus aiheutti (myöhemmin tulivat koneen perään liitettävät palikat, jotka poistivat haitat). Suojauksen tekeminen ei ole helppoa. Pelin pitää toimia myös kiintolevyltä, joten disketille tehty suojaus ei ole käyttökelpoinen. Turhien bad sectorien tekeminen kiintolevylle pelin installointivaiheessa on myös kyseenalainen suojauskeino. Kannattaa ehkä käyttää koneen ympäristön tutkimiseen liittyviä suojauksia, tosin nekään eivät ole sataprosenttisia.

  Kopiointisuojauksen tekeminen on kiinnostavaa puuhaa, joten mikään ei estä kehittämästä ennennäkemätöntä suojausta, jota kukaan ei voi murtaa.

  # 6. Grafiikka

  Grafiikan ohjelmointi on yksi mielenkiintoisimmista osa- alueista pelin ohjelmoinnissa. Ohjelmakoodin tulos nähdään heti kuvaruudulla. Grafiikkarutiinit on syytä ohjelmoida huolella, sillä huonolla suunnittelulla niistä tulee helposti hitaita. Monien pelien nopeus on 386-koneella kiinni lähes yksinomaan grafiikkarutiineista. Prosessori suoriutuu monimutkaisistakin laskuista hyvin nopeasti, mutta tiedon kirjoitus näyttökortille vie suurimman osan rutiinin viemästä ajasta.

  Näyttökortteja on lähes yhtä paljon kuin koneita, mutta onneksi lähes kaikki ovat VGA-yhteensopivia. Tämä luku kertoo pääasiassa VGA:n ohjelmoinnista. Myös VESA- yhteensopivia SVGA-kortteja käsitellään. Vanhemmat CGA- ja EGA-kortit unohdetaan kokonaan.

  Yleisin peleissä käytettävä graf`iikkatila on 320x200 256 värillä, johon tässä luvussa keskitytään syvällisimmin. Myös tekstitilaa, SVGA-tiloja ja 640x480/16-väritilaa käsitellään hieman. Grafiikkatilojen rakenteet ovat melko yksinkertaisia, mutta eri VGA-rekistereiden käyttö kussakin grafiikkatilassa on jo vaativampaa. Juuri näiden rekistereiden hyödyntämiseen lähinnä 320x200-tilassa keskitytään tässä luvussa.

  ### VGA-muisti

  Normaalissa VGA-kortissa on 256 kilotavuva muistia, joka onjaettu neljään 64 kilotavun bittitasoon. Kortilla ei ole omaa muistiosoitetta jonne luku/kirjoitusoperaatiot tehtäisiin, vaan osa kortin näyttömuistista näkyy perusmuistissa vakio-osoitteessa. Grafiikkatilat näkyvät osoitteesta a0000h (segmenttinä a000h) eteenpäin ja tekstitilat osoitteesta b8000h (segmenttinä b800h) eteenpäin. Tämä kiinteä osoite a0000h onkin rajoittanut vapaan perusmuistin määrän tuohon 640 kilotavuun.

  Grafikkatiloissa muistia näkyy kerrallaan 64 kilotavua eli muistialue a0000h-affffh. Alue, joka kortin näyttömuistista näkyy tällä alueella, voidaan asettaa tietyillä komennoilla.

  Myös bittitasojen näyttötapa voidaan määrätä. Lisäksi VGA-kortilla on porttiosoitteita, joita käytetään IN- ja OUT-käskyillä.

  Koska VGA-kortti on liitetty koneeseen ISA-väylän kautta ja lisäksi kortin muisti on usein hidasta, kortille kirjoitus ja sieltä luku ovat hidasta puuhaa. VLB-kortit [VESA Local Bus) toimivat tässä suhteessa nopeammin. VGA on 16-bittinen, joten lähesjokaisessa näyttötilassa voidaan kirjoittaa sana samassa ajassa kuin tavu. Useissa VLB- korteissa voidaan kirjoittaa pitkäsana (32 bittiä) samassa ajassa kuin tavu!

  Kun 386-koneessa tehdään kirjoitusoperaatio näyttömuistiin, jää prosessori odottamaan, kunnes tieto on mennyt perille näyttömuistiin. 486-koneissa prosessori vapautuu heti muihin tehtäviin, ja vasta seuraavan kirjoitusoperaation alussa odotetaan tarvittaessa väylän vapautumista. Tämän vuoksi 486-koneilla grafiiikkarutiineista saadaan paljon nopeampia, sillä näyttömuistiin kirjoitusten välissä voidaan esimerkiksi laskea seuraavan pisteen koordinaatit menettämättä aikaa. Jos näyttömuistiin tehtäisiin aivan peräkkäisiä kirjoitusoperaatioita, menisi suurin aika vain väylän vapautumisen odottamiseen.

  Luettaessa näyttömuistista joudutaan tietoa luonnollisesti odottamaan, joten lukuoperaatioiden välissä tehty koodi vie aina aikaa.

  ### Näyttötilat

  VGA-kortissa on valittavana useita eri näyttötiloja. Valittavat tekstitilat ovat kuvassa 6.1 ja grafiikkatilat kuvassa 6.2. Normaalisti käytetty tekstitila on 3. Grafiikka tehdään yleensä tiloihin 18 ( 16 väriä) tai 19 (256 väriä).

  ##### Kuva 6.1.Tekstitilat (VGA).

      Tila   Koko    Värit   Alkusegmentti
      00     40x25   16      b800h
      01     40x25   16      b800h
      02     80x25   16      b800h
      03     80x25   16      b800h
      07     80x25   2       b000h

  ##### Kuva 6.2. Grafiikkatilat (VGA).

      Tila    Koko       Värit Alkusegmentti Bittitasoja
      04      320x200    4     b800h         2
      05      320x200    4     b800h         2
      06      640x200    2     b800h         2
      13      320x200    16    a000h         4
      14      640x200    16    a000h         4
      15      640x350    4     a000h         2
      16      640x350    16    a000h         4
      17      640x480    2     a000h         1
      18      640x480    16    a000h         4
      19      320x200    256   a000h         8

  Näyttötila asetetaan päälle BIOS-kutsulla,sillä tämän toimenpiteen ei tarvitse olla nopea ja samalla säilytetään yhteensopivuus eri korteissa. Tila asetetaan keskeytyksellä 10h esimerkin 6.1 mukaisesti. Muita näyttötiloihin liittyviä BIOS-kutsuja ei kannata käyttää, sillä ne ovat lähes poikkeuksetta liian hitaita.

  ##### Esimerkki 6.1.Näyttötilan asetus.

      mov      ah,0
      mov      al,tilan_numero
      int      10h

  Jos al-rekisterin bitti 7 asetetaan, näyttömuistia ei tyhjennetä tilan asetuksen yhteydessä.

  Näyttötila 3: Tämän tekstitilan hallinta on erittäin yksinkertaista. Jokaiselle ruudun merkille on varattu kaksi tavua näyttömuistia. Ensimmäinen tavu on merkin ascii-koodi. Toinen tavu on merkin attribuutti, joka määrää merkin ja sen taustan värin. Tarkasti ottaen ensimmäinen tavu sijaitsee bittitasolla 0 ja toinen tavu bittitasolla 1, mutta bittitasoja ei tarvitse ajatella, sillä kuvaruutu näkyy käyttäjälle lineaarisena. Täten 80x25 merkin tekstitila vie 2x80x25=4000 tavua muistia. Kuvassa 6.3 on attribuuttitavun rakenne.

  ##### Kuva 6.3. Attribuuttitavun rakenne.

      Bitti   Merkitys
      7       1= merkki on vilkkuva
      6       Taustan värin bitti 2
      5       Taustan värin bitti 1
      4       Taustan värin bitti 0
      3       Merkin värin bitti 3
      2       Merkin värin bitti 2
      1       Merkin värin bitti 1
      0       Merkin värin bitti 0

  Värien numerokoodit. Taustaväri voi saada vain arvot 0-7 (3 bittiä).

      0          Musta
      1          Sininen
      2          Vihreä
      3          Syaani
      4          Punainen
      5          Magenta
      6          Ruskea
      7          Vaalean  harmaa
      8          Tumman harmaa
      9          Vaalean sininen
      10         Vaalean vihreä
      11         Vaalean syaani
      12         Vaalean punainen
      13         Vaalean magenta
      14         Keltainen
      15         Valkoinen

  Tekstitilan käytössä on muistialue b8000h-bffffh, tällöin muistiin mahtuu kahdeksan tekstiruutua. Näitä erillisiä ruutuja sanotaan tekstisivuiksi. Normaalisti ei ole tarpeen käyttää kuin ensimmäistä sivua. Tällöin luku/kirjoitus- operaatiot kohdistuvat muistiosoitteesta b8000h eteenpäin. Kuvassa 6.4 on tilan rakenne.

  ##### Kuva 6.4. Tekstitilan 3 rakenne.

  Esimerkissä 6.2 on assembler-listaus, joka tulostaa ruudun vasempaan yläkulmaan mustan A-kirjaimen siniselle taustalle, sekä toisen rivin alkuun vilkkuvan keltaisen B- kirjaimen mustalle taustalle. Esimerkistä nähdään, että näyttömuistiin voidaan kirjoittaa yhdellä tavulla vairi merkki tai attribuutti. Yhdellä sanalla voidaan kirjoittaa molemmat. Ohjelmasta huomataan, kuinka yksinkertaista tekstin kirjoitus kuvaruutuun on. Suuretkin ikkunat saadaan liikkumaan ruudulla nopeasti. Käytettäessä C-kielen valmiita funktioita ja jopa BIOS-kutsuja, tekstiruudun käsittely saattaa olla hidasta vähänkin hitaammassa koneessa.

  ##### Esimerkki 6.2. Tekstitilaan kirjoitus.

        mov      ax,3                         ;tila 3päälle
        int      010h
        mov      ax,0b800h                    ;alkusegmentti
        mov      es,ax
        mov      di,0
      ;yläkulma:
        mov      [es:di],byte ptr 41h         ;merkki 'A'
        mov      [es:di+1],byte ptr 10h       ;attribuutti
      ;toisen rivin alku:
        mov      [es:di+160],word ptr 8e42h   ;8eh=attribuutti, 42h='B'

  ### Näyttötila 18

  Koska 16 värin esittämiseen tarvitaan 4 bittiä, tässä tilassa ovat käytössä kaikki neljä bittitasoa. Yksi pikseli on esitetty yhdellä bitillä kullakin bittitasolla. Yksi bittitaso vie muistia 640/8 x 480 = 38400 tavua, jolloin yksi kuvaruutu vie muistia 38400 x 4 =153600 tavua. Koska perusmuistissa näkyy kerrallaan vain 64 kilotavua, pitää siellä näkyvä bittitaso valita jollakin keinolla. Nyt kerrotaan vain ruudun rakenne, myöhemmin rekistereistä kerrottaessa tutustutaan bittitasojen valintaan. Kuvassa 6.5 on tilan rakenne.

  Kirjoitettaessa tavu näyttömuistiin muuttuu kerralla kahdeksan pikseliä valituilta bittitasoilta. Haluttaessa muuttaa vain yhtä pikseliä pitää käyttää myöhemmin opittavia rekistereitä. Rutiinit pitää suunnitella huolella, jotta niistä saadaan nopeita. Esimerkki ruutuun kirjoituksesta esitetään myöhemmin tässä luvussa, kun tarvittavat rekisterit on opittu.

  ##### Kuva 6.5. Grafiikkatilan 18 rakenne.

  ### Näyttötila 19

  Perustilassaan tämä grafiikkatila on erittäin helppo rakenteeltaan. Tavussa on 8 bittiä, jolloin suurin esitettävä luku on 255. Tässä tilassa yksi pikseli on esitetty yhdellä tavulla, jonka arvo kertoo suoraan värin numeron. Täten 320x200-kuvaruutu vie muistia 320 x 200 = 64000 tavua. Tässä tilassa käytettävä muistialue on a0000h-affffh, eli 1536 tavuajää ylimääräistä.

  VGA-kortissahan oli käytössä 256 kilotavua muistia, miksi käyttäjälle näkyy vain 64 kilotavua? 256 kilotavua on jaettu neljään 64 kt:n bittitasoon ja tämä tila käyttää bittitasot siten, että bittitason kaikki tavut eivät ole käytössä. Bittitasoista ei tarvitse tässä tilassa välittää, sillä ruutu näkyy lineaarisesti muistialueesta a0000h eteenpäin. Loputkin tavut saadaan kyllä käyttöön tweaked- tilassa, josta kerrotaan myöhemmin.

  Kuvassa 6.6 on esitetty tilan rakenne. Tässä tilassa käyttäjän ei tarvitse vaihtaa bittitasoa itse.

  ##### Kuva 6.6. Grafiikkatilan 19 rakenne.

  Esimerkki 6.3 asettaa ruudun vasempaan yläkulmaan pikselin, jonka värinumero on 100 ja seuraavan rivin alkuun pikselin, jonka värinumero on 255.

  ##### Esimerkki 6.3. Grafiikkatilaan 19 kirjoitus.

               mov         ax,013h      ;tila 19 päälle
               int         010h
               mov         ax,0a000h    ;alkusegmentti
               mov         es, ax
               mov         di,0
      ;yläkulma:
               mov         [es:di],byte ptr 100
      ;toisen rivin alku:
               mov         [es:di+320],byte ptr 255

  ### VGA-rekisterit

  VGA-kortin rekistereillä voidaan muuttaa mitä erilaisempia asioita kortilla. Rekistereitä on niin monta kappaletta, että tutustumme vain tärkeimpiin rekistereihin tarkemmin bittitasolla.

  Rekistereitä käytetään kolmella eri tavalla. Ensimmäisenä esitellään rekisterit, joiden käyttö tapahtuu yksinkertaisesti lukemalla tai kirjoittamalla yhteen porttiosoitteeseen. Tällaiset rekisterit ovat kuvassa 6.7.

  ##### Kuva 6.7.VGA-rekisterit, joissa ei käytetä indeksiä.

      Nimi                     Portti/   Portti/
                               Kirj.     Luku
      GENERAL-rekisterit

      Misc.Output              3c2       3cc
      Feature Control          3da       3ca
      Input Status #0          -         3c2
      Input Status #1          -         3da

      COLOR-rekisterit

      PEL Mask                 3c6       3c6
      PEL Address Read Mode    3c7
      DAC State                          3c7
      PEL Address Write Mode   3c8       3c8
      PEL Data Register        3c9       3c9

  Kuvan 6.8 rekistereitä käytetään siten, että ensin haluttu toiminto (indeksi) kirjoitetaan perusporttiin. Tämän jälkeen haluttu arvo rekisteriin kirjoitetaan perusporttiin+ I.

  ##### Kuva 6.8. VGA-rekisterit, joissa käytetään indeksiä.

      Nimi                                      Perusp.  Portti/   Portti/
                                                /Indeksi Kirj.     Luku
      SEOUENCER-rekisterit
      Porttiosoitteet:                          3c4      3c5       3c5
      Reset                                     0
      Clocking Mode                             1
      Map Mask                                  2
      Character Map Select                      3
      Memory Mode                               4

      CRT CONTROLLER -rekisterit
      Porttiosoitteet:                          3d4      3d5       3d5
      Horizontal Total                          0
      Horizontal Display End                    1
      Start Horizontal Blank                    2
      End Horizontal Blank                      3
      Start Horizontal Retrace                  4
      End Horizontal Retrace                    5
      Vertical Total                            6
      Overflow                                  7
      Preset Row Scan                           8
      Maximum Scan Line                         9
      Cursor Start                              a
      Cursor End                                b
      Start Address High                        c
      Start Address Low                         d
      Cursor Location High                      e
      Cursor Location Low                       f
      Vertical Retrace Start                    10
      Vertical Retrace Low                      11
      Vertical Display End                      12
      Offset                                    13
      Underline Location                        14
      Start Vertical Blank                      15
      End Vertical Blank                        16
      Mode Control                              17
      Line Compare                              18

      GRAPHICS CONTROLLER -rekisterit
      Porttiosoitteet:                          3ce      3cf       3cf
      Set/Reset                                 0
      Enable SeVReset                           1
      Color Compare                             2
      Data Rotate                               3
      Read Map Select                           4
      Mode                                      5
      Miscellaneous                             6
      Color Don't Care                          7
      Bit Mask                                  8

  Esimerkiksi SEQUENCER-rekisterin Map Mask -toiminto tehdään esimerkin 6.4 mukaisesti. Normaalissa kirjoitusoperaatiossa kannattaa käyttää tapaa yksi. Jos taas joudutaan lukemaan jotakin porttia, voidaan käyttää tapaa kaksi siten, että viimeinen out dx,al-käsky muutetaan käskyksi in al,dx.

  ##### Esimerkki 6.4. Indeksien käyttö VGA-rekistereissä.

      ;tapa 1
               mov         dx,03c4h     ;SEQUENCER-perusportti
               mov         ax,0102h     ;indeksi 2 outataan ensin, sitten arvo 1
               out         dx, ax
      ;tapa 2
               mov         dx,03c4h     ;SEQUENCER-perusportti
               mov         al, 02h      ;indeksi 2
               out         dx, al
               inc         dx           ;perusportti+1
               mov         al,1         ;haluttu arvo
               out         dx, al

  Kolmatta tapaa joudutaan käyttämään kuvan 6.9 rekistereissä. Näissä rekistereissä sekä indeksi että arvo kirjoitetaan samaan porttiosoitteeseen.

  ##### Kuva 6.9. VGA-rekisterit, joissa käytetään kiikkua.

      Nimi                    Perusp.   Portti/    Portti/
                              /Indeksi  Kirj.      Luku

      ATTRIBUTE CONTROLLER -rekisterit
      Porttiosoitteet:        3c0       3c0        3c1
      Palette                 0-f
      Mode Control            10
      Overscan Color          11
      Color Plane Enable      12
      Horizontal Pixel Panning 13
      Color Select            14

  VGA-kortilla olevan kiikun tila vaihtuu jokaisen kirjoitusoperaation jälkeen. Kiikun tilan vaihto vie hieman aikaa, joten peräkkäisten kirjoitusten välillä tarvitaan pieni viive, esimerkiksi parikymmentä NOP-käskyä on sopiva nopeissakin koneissa.

  Mistä sitten tiedetään, edustaako porttiosoite indeksiä vai itse arvoa? Kiikku voidaan nollata lukemalla porttia 3da (Input Status #1). Kun kiikku on nollattu, kirjoitetaan perusporttiosoitteeseen haluttu indeksi. Lisäksi portin bitti 5 (PAS) pitää normaaleissa sovelluksissa asettaa. Esimerkissä 6.5. käytetään Horizontal Pixel Panning -toimintoa.

  ##### Esimerkki 6.5. Kiikun käyttö VGA-rekistereissä.

               mov         dx,03dah     ;Input Status #1
               out         al,dx        ;lue->kiikku nollautuu

               ...                      ;viivettä, jotta kiikku
               ...                      ;ehtii nollautua

               mov         dx,03c0h     ;ATTRIBUTE-perusportti
               mov         al,033h      ;indeksi 13h+PAS-bitti!
               out         dx, al

               ...                      ;viivettä, jotta indeksi
               ...                      ;ehtii mennä "perille"

               mov         al, 3        ;haluttu arvo
               out         dx, al

  Seuraavaksi kerrotaan tärkeimpien rekistereiden merkitykset bittitasolla. Rekistereistä pyritään kertomaan myös se, mihin niitä tarvitaan käytännössä. Otsikoissa isoilla kirjaimilla on rekisteri-sarjan nimi, tämänjälkeen alatoiminnon (indeksin) nimi sekä suluissa indeksin numero.

  ### GENERAL - Input Status #1

      Bitit       Nimi
      7-6         Ei käytössä
      5-4         DIA
      3           VR
      2           LSW
      1           LST
      0           DE

      DIA         Vain EGA:ssa! Diagnostiikka, ei käytännön merkitykstä.

  VR ilmoittaa piirretäänkö ruutua (0), vai onko menossa elektronisuihkun siirLyniinen ruudun alareunasta yläreunaan (1). Tätä kutsutaan nimellä Vertical Retrace.

      LSW         Vain EGA:ssa! Valokynä.
      LST         Vain EGA:ssa! Valokynä.
      DE          Ilmoittaa piirretäänkö kuvaruutua, vai siirretäänkö elektronisuihkua.
                  Yhdessä VR-bitin kanssa voidaan selvittää, onko menossa Vertical
                  vai Horizontal Retrace.

  Usein kaikki pelin piirtotoiminnot halutaan tahdistaa kuvaruudun piirtoon, tällöin pitää tietää milloin elektronisuihku (joka piirtää kuvaruutua) on siirtynyt takaisin kuvaruudun yläreunaan. Tähän käytetään VR-bittiä esimerkin 6.6 mukaisesti. Ensin odotetaan kunnes Vertical Retrace alkaa, sitten odotetaan sen loppua.

  ##### Esimerkki 6.6. Kuvaruudun piirron aloituksen odotus.

               mov         dx,03dah
      loop1:
               in          al, dx
               test        al, 8
               jnz         loop1        ;odota, kunnes VR alkaa
      loop2:
               in          al, dx
               test        al, 8
               jz          loop2        ;odota, kunnes VR loppuu

  Jos taas halutaan odottaa, että kuvaruudun piirto alkaa vasemmasta reunasta (kuvaruutuahan piirretään ylhäältä alas vaakarivi kerrallaan), käytetään bittejä VR ja DE yhdessä. Kuva 6.10 kertoo merkitykset bittien eri asennoissa.

  ##### Kuva 6.10. VR- ja DE-bitit yhdessä.

      VR        DE          Tapahtuma ruudulla
      0         0           Ruutua piirretään
      1         1           Elektronisuihkun siirto alhaalta ylös menossa
      0         1           Elektronisuihkun siirto oikealta vasemalle menossa

  ### SEQUENCER - Map Mask (02h)

      Bitit          Nimi
      7-4            Ei käytössä
      3              EM3
      2              EM2
      1              EM1
      0              EM0

      EM3            1 = Prosessorin kirjoitusoperaatiot vaikuttavat bittitasolle 3.
      EM2            1 = Prosessorin kirjoitusoperaatiot vaikuttavat bittitasolle 2.
      EM1            1 = Prosessorin kirjoitusoperaatiot vaikuttavat bittitasolle 1.
      EMO            1 = Prosessorin kirjoitusoperaatiot vaikuttavat bittitasolle 0.

  Kun puhuttiin eri näyttötiloista huomattiin, ettäjoissakin tiloissa sama tavu muistissa edustaa eri pikseleitä eri bittitasoilla. Tällä rekisterillä voidaan valita bittitasot, joille kirjoitusoperaatiot vaikuttavat. Kaikki kombinaatiot ovat mahdollisia, eli voidaan kirjoittaajoko yhdelle tai useammalle bittitasolle yhtä aikaa.

  Esimerkissä 6.7 kirjoitetaan 16-väriseen näyttötilaan kahdeksan ensimmäistä pikseliä (yksi tavu) bittitasolle yksi ja seuraavat kahdeksan pikseliä kaikille bittitasoille.

  Rutiinit kannattaa suunnitella siten, että bittitasoa joudutaan vaihtamaan mahdollisimman harvoin, sillä rekisteriin kirjoitus vie melko paljon aikaa.

  ##### Esimerkki 6.7. Map Mask -rekisterin käyttö.

               mov         ax,012h      ;640x480x16tila
               int         010h
               mov         ax,0a000h
               mov         es,ax
               mov         di,0
               mov         bl,255
               mov         dx,03c4      ;SEQUENCER-perusportti
               mov         ax,0102h     ;indeksi 2,EM0=1
               out         dx,ax
               mov         [es:di],bl
               mov         ax,0f02h     ;indeksi 2,EM0...EM3=1
               out         dx,ax
               mov         [es:di+1],bl

  ### SEQUENCER - Memory Mode (04h)

      Bitit       Nimi
      7-4         Ei käytössä
      3           C4
      2           O/E
      1           EM
      0           A/G

      C4          0 = Bittitasot valitaan "normaalisti" Map Mask -rekisterillä (tweaked).
                  1 = Bittitasot on ketjutettu siten, että osoitteen kaksi alinta bittiä määräävät
                  käytettävän bittitason. Näyttötila 19 käyttää tätä ketjutettua tilaa.

      O/E         Normaaleissa VGA-sovelluksissa bitti pidetään asetettuna (1 ).
                  Bitti nollataan, jos halutaan yhteensopivuus VGA:n ja CGA:n välillä.

      EM          0 = Jos näyttömuistin määrä on alle 64 kt.
                  1= Jos näyttömuistin määrä on yli 64 kt.
                  VGA:ssa bitti pidetään asetettuna.

      A/G         0 = Päällä on grafiikkatila.
                  1 = Päällä on tekstitila.
                  Toiminto vain EGA:ssa.

  Tämän rekisterin C4-bittiä tarvitaan, kun siinytään tweaked-tilaan. Tästä tilasta kerrotaan myöhemmin tarkemmin.

  ### CRTC -Overflow (07h)

      Bitit         Nimi
      7             VRS
      6             VDE
      5             VT1
      4             LC
      3             VBS
      2             VRS
      1             VDE
      0             VT

      VRS           Vertical Retrace Start -rekisterin yhdeksäs bitti.
      VDE           Vertical Display End -rekisterin yhdeksäs bitti.
      VT1           Vertical Total -rekisterin kymmenes bitti.
      LC            Line Compare -rekisterin yhdeksäs bitti.
      VBS           Start Vertical Blank -rekisterin yhdeksäs bitti.
      VRS           Vertical Retrace Start -rekisterin yhdeksäs bitti.
      VDE           Vertical Display End -rekisterin yhdeksäs bitti.
      VT            Vertical Total -rekisterin yhdeksäs bitti.

  VGA-rekisterit ovat kahdeksanbittisiä, mutta jotkut aivot vaativat suurempia lukuja kun kahdeksalla bitillä pystytään esittämään. Tätä varten on Overflow-rekisteri, josta löytyvät monien rekistereiden yhdeksännet bitit.

  Tässä kirjassa ei esitellä moniakaan niistä rekistereistä, joissa alimmat kahdeksan bittiä sijaitsevat. LC-bitti kannattaa pitää mielessä, sillä sitä tullaan käyttämään myöhemmin.

  ### CRTC - Maximum Scan Line (09h)

      Bitit       Nimi
      7           2T4
      6           LC
      5           VBS
      4-0         MSL

      2T4         0 = Normaali tila.
                  1 = Näytetään 200 riviä 400 rivillä.
                      Tällöin jokainen rivi monistuu.

      LC          Line Compare -rekisterin kymmenes bitti.
      VBS         Start Vertical Blank -rekisterin kymmenes bitti.
      MSL         Näyttörivien määrä per merkkirivi -1.

  MSL-kentän nollaamalla saadaan käyttöön enemmän pystyrivejä. Tätä käytetään myöhemmin, kun puhutaan tweaked- tilasta.

  ### CRTC - Start Address High (Och)

      Bitit        Nimi
      7-0          SAH

      SAH          Näytettävän ruudun osoitteen kahdeksan ylintä bittiä.

  ### CRTC - Start Address Low (Odh)

      Bitit        Nimi
      7-0          SAL

      SAL          Näytettävän ruudun osoitteen kahdeksan alinta bittiä.

  Kahdella edellisellä rekisterillä voidaan muuttaa kuvaruudun alkuosoitetta. Näin saadaan aikaan esimerkiksi tasainen vieritys. Normaalisti osoite on nolla. Esimerkissä 6.8 vieritetään kuvaruutua ylöspäin rivi riviltä puolen kuvaruudun verran.

  Tärkeä seikka, joka pitää huomioida tällaisia vierityksiä tehtäessä, on eräs eri VGA-korttien epämiellyttävä eroavaisuus. Normaalissa grafiikkatilassa 13h yksi kuvaruutu vie 64000 tavua perusmuistia (melkein yksi segmentti). Nyt kuvaruudun alkuosoitetta lisätään esimerkiksi 32000 tavulla. Mitä näytöllä näkyy, kun kuva loppuu (kuvaruudun puolen välin jälkeen)? Joissakin näyttökorteissa kuvaruudun loppumisen jälkeen näytöllä näkyy muistialuetta osoitteesta b0000h eteenpäin (a0000h+65535). Joissakin taas segmentti "pyörähtää" ympäri ja kuvaruudun loppuosassa näkyy muistialuetta osoitteesta a0000h eteenpäin, eli sama kuva uudestaan. Tämä tuottaa melkoisia ongelmia jatkuvan vierityksen tekemiseen.

  ##### Esimerkki 6.8. Kuvaruudun alkuosoitteen muutos.

               mov         ax,013h
               int         010h
      ;tässä välissä voidaan laittaa
      ;jokin kuva ruutuun
               mov         bx, 0

      vieritä:

      ;odota kuvaruudun piirtoa
               mov         dx,03dah
      loop1:
               in          al, dx
               test        al, 8
               jnz         loop1        ;odota, kunnes VR alkaa
      loop2:
               in          al,dx
               test        al, 8
               jz          loop2        ;odota, kunnes VR loppuu
      ;muuta osoitetta
               mov         dx,03d4h
               mov         al,0ch
               mov         ah, bl       ;alimmat 8 bittiä
               out         dx, ax
               mov         al,0dh
               mov         ah, bh       ;ylimmät 8 bittiä
               out         dx, ax
               add         bx,320       ;rivi ylöspäin
               cmp         bx,32000
               jne         vieritä

               ret

  ### CRTC - Offset Register (13h)

      Bitit        Nimi
      7-0          OFF

      OFF          Tämä arvo kertoo kahden allekkain olevan pikselin välisen arvon
                   muistissa eli sillä voidaan säätää kuvaruudun virtuaalista leveyttä.

  Norniaaleissa näyttötiloissa rekisteriin kirjoitettava arvo on puolet halutun kuvaruudun leveydestä tavuina. Jos asetetaan päälle tweaked-tila ja halutaan, että muistissa olevan ruudun leveys on kaksi kertaa leveämpi kuin kuvaruudulla näkyvä, asetetaan Offsetrekisteriin arvo 80 (80x2/2).

  ### CRTC - Underline Location (14h)

      Bitit        Nimi
      7            Ei käytössä
      6            DW
      5            CB4
      4-0          UL

      DW           0 = Sana-osoitus (tweaked).
                   1 = Pitkäsana-osoitus (näyttötila 19).

      CB4          0 = Normaali kellotus.
                   1 = Kellotus jaetaan neljällä.

      UL           Merkin alleviivauksen kohta -1.

  Normaalisti vain DW-bitti on merkityksellinen. Tätä käytetään myöhemmin, kun puhutaan tweaked-tilasta.

  ### CRTC - Mode Control (17h)

      Bitit        Nimi
      7            HR
      s            W/B
      5            AW
      4            OC
      3            CBT
      2            HRS
      1            SRS
      0            CMS

      HR           0 = Hardware reset.
                   1 = Normaali toimintatila.

      W/B          0 = Word-osoitus, tällöin näyttömuistiin kohdistuvat osoitteet kerrotaan
                       kahdella ennen käyttöä.
                   1 = Normaali tavu-osoitus (tweaked).

      AW           0 = Kun muistia on alle 64 kt.
                   1 = Kun muistia on yli 64 kt.

      OC           Ei käyttöä VGA:ssa.

      CBT          0 = Normaali kellotus.
                   1 = Kellotus jaetaan kahdella.

      HRS          0 = Scan Line -laskuria lisätään jokaisen vaakarivin piirron jälkeen yhdellä.
                   1 = Scan Line -laskuria lisätään jokaisen vaakarivin piirron jälkeen kahdella.

      SRS          0 = Käytetään Hercules-emulaatiossa.
                   1 = Normaali toimintatila.

      CMS          0 = Käytetään CGA-emulaatiossa.
                   1 = Normaali toimintatila.

  Normaalisti vain W/B-bitti on merkityksellinen. Tätä käytetään myöhemmin, kun puhutaan tweaked-tilasta.

  ### CRTC - Line Compare (18h)

      Bitit        Nimi
      7-0          LC
      LC            Kahdeksan alinta bittiä ruudun jakamiseen kahtia.

  Tällä rekisterillä yhdessä Overflow- ja Maximum Scan Line -rekisterien LC-bittien kanssa määrätään pystyrivi, josta kuvaruudun näyttöosoite nollataan. Normaalista VGA:ssa tämä 10-bittinen arvo on 1023, joka on kuvaruudun alareuna. Kun Scan Line -laskuri saavuttaa tämän 10-bittisen arvon, aletaan kuvaruutua näyttää uudestaan osoitteesta nolla. Näin saadaan kuvaruudun jakaminen kahtia. Start Address Low - ja Start Address High -rekisterien arvot vaikuttavat vain ruudun yläosaan, halutusta pystyrivistä lähtien osoite on aina nolla. Esimerkki 6.9 jakaa kuvaruudun kahtia keskeltä.

  ##### Esimerkki 6.9. Kuvaruudun jako.

               mov         ax,013h
               int         010h

               mov         ax,100*2     ;jako-kohta
               mov         cl,al        ;cl=alimmat 8 bittiä

      ;Siirretään yhdeksäs bitti, bitiksi 4,
      ;jolloin se käy suoraan "0verflow"-rekisteriin.
               mov         bx, ax
               and         bx,256
               shr         bx, 4
               mov         ch, bl       ;ch=bitti 9
      ;Siirretään kymmenes bitti, bitiksi 6, jolloin
      ;se käy suoraan "Maximum Scan Line" -rekisteriin.
               mov         bx, ax
               and         bx,512
               shr         bx,3         ;bl=bittil0

               mov         dx,03d4h     ;CRTC
               mov         al, 018h     ;Line Compare
               mov         ah, cl
               out         dx, ax
               mov         al,07h       ;Overflow
               out         dx, al
               inc         dx
               in          al, dx
               and         al,255-16
               or          al, ch       ; bitti 9
               out         dx, al
               dec         dx
               mov         al,09h       ;Maximum Scan Line
               out         dx,al
               inc         dx
               in          al, dx
               and         al,255-64
               or          al, bl       ; bitti 10
               out         dx, al

               ret

  ### GRAPHICS - Read Map Select (04h)

      Bitit        Nimi
      7-2          Ei käytössä
      1-0          RMS

      RMS          Määrätään bittitaso, jolta lukuoperaatio tapahtuu

  Kun edellä puhuttiin eri näyttötiloista, huomattiin ettäjoissakin tiloissa sama tavu muistissa edustaa eri pikseleitä eri bittitasoilla. Tällä rekisterillä voidaan valita bittitaso, jolta lukuoperaatio tapahtuu. Siis vain yksi bittitaso on mahdollista valita yhtä aikaa.

  ### GRAPHICS - Mode (05h)

      Bitit        Nimi
      7            Ei käytössä
      6-5          SR
      4            O/E
      3            RM
      2            TC
      1-0          WM

      SR           Määritetään tapa, jolla pikselit siirretään sarjamuotoisena eri bittitasoille.
                   00 = Normaali tapa.
                   01 = CGA-tiloissa
                   10 = Näyttötila 19.

      O/E          0 = Normaali toimintatila.
                   1 = CGA-emulaatiossa.
                   Bitin tilan pitää olla käänteinen verrattuna SEQUENCER-sarjan
                   Memory Mode -rekisterin O/E-bittiin.

      RM           Määrittää lukutavan näyttömuistista (0 tai 1).

      TC           Testibitti, pidä nollana.

      WM           Määrittää kirjoitustavan näyttömuistiin (0-3).

  VGA:ssa on kaksi erilaista lukutilaa, joista tila nolla (0) on vakiona 256-värisessä grafiikkatilassa. Tilaa yksi (1) ei käsitellä, sillä sitä käytetään lähinnä 16-värisissä tiloissa.

  Kirjoitustiloista kannattaa esitellä normaalin (0) lisäksi kirjoitustila 1. Tässä tilassa kirjoitettava tavu ei tulekaan esimerkiksi käyttäjän antainasta rekisteristä [mov [es:di],al), vaan kortin sisäisestä 32-bittisestä lukkopiiristä (latch). Kun näyttömuistista luetaan tavu, tallentuu lukkopiiriin arvo kyseisestä osoitteesta kaikilta neljältä bittitasolta.

  Kun kirjoitustilassa yksi tehdään kirjoitusoperaatio, lukkopiirin tavut kirjoittuvat näyttömuistiin. Tämä kirjoitustapa mahdollistaa nopeat näyttömuistin sisäiset tiedonsiirrot.

  ### GRAPHICS - Bit Mask (08h)

      Bitit        Nimi
      7-0          BM

      BM           Määritellään bitit, joille kirjoitusoperaatiot voivat tapahtua.

  Kuten tilasta 18 kerrottaessa huomattiin, kirjoitettaessa näyttömuistiin tavu 16-värisessä tilassa viereiset pikselit muuttuvat operaation myötä. Nollaamalla halutut rekisterin bitit voidaan kirjoitusoperaatiota estää muuttainasta näitä pikseleitä näyttömuistissa.

  ### ATTRIBUTE - Horizontal Pixel Panning (13h)

      Bitit        Nimi
      7-4          Ei käytössä
      3-0          HPP

      HPP          Vasemmalle vieritettävien pikseleiden määrä.

  Tällä rekisterillä voidaan tehdä tasainen pikselin välein vieritys myös 16-värisissä tiloissa, joissa Start Address Lowja High -rekisterin muuttaminen yhdellä siirtäisi kuvaa kahdeksalla pikselillä. Rekisteriin kirjoitus on esitetty edellä esimerkissä 6.5.

  ### Paletin vaihto

  VGA:n paletti koostuu kolmesta 6-bittisestä arvosta: Red (punaisen määrä), Green (vihreän määrä) ja Blue [sinisen määrä). Jokainen komponentti voi siis saada arvot 0-63, näin erilaisia värivaihtoehtoja on 262144 kappaletta.

  256-värisessä tilassa (19) COLOR-sarjan PEL Address Write -rekisteriin kirjoitetaan haluttu värinumero (0-255). Punaisen, vihreän ja sinisen määrät kirjoitetaan PEL Data - rekisteriin. PEL Data -rekisteri on itseasiassa 18-bittinen rekisteri, jonne kuitenkin kirjoitetaan kerrallaan vain yksi tavu (josta vain 6 bittiä huomioidaan). Ensin kirjoitetaan punaisen arvo, sitten vihreän arvo ja lopuksi sinisen arvo. Kun kaikki kolme tavua on kirjoitettu, lisääntyy PEL Address Write -rekisterissä olevan värinumeron arvo yhdellä automaattisesti. Tämä mahdollistaa kaikkien 256 värin (256 x 3 = 768 värikomponentin) muuttamisen peräkkäisillä käskyillä.

  Esimerkissä 6.10 on yksittäisen värin vaihto. Jos viimeisen OUT-käskyn jälkeen tehtäisiin uusi OUT-käsky PEL Data -rekisteriin, muuttuisi seuraavan värin punaisen komponentin arvo.

  ##### Esimerkki 6.10. Näyttötilan 19 paletin vaihto.

               mov         al,[vari]    ;Värin numero
               mov         dx,03c8h     ;PEL Address Write
               out         dx, al
               inc         dx           ;PEL Data
               mov         al, [red]
               out         dx, al
               mov         al, [green]
               out         dx, al
               mov         al, [blue]
               out         dx, al

  16-värisissä tiloissa systeemi on muuten aivan sama, mutta PEL Address Write -rekisteriin ei laiteta suoraan värin numeroa, vaan arvo kuvan 6.11 mukainen arvo.

  ##### Kuva 6.11. Väriä vastaava arvo PEL Address Write - rekisteriin. Käytössä 16-värinen tila.

      Värin numero     0 1 2 3 4 5 6  7 8  9  A  B  C  D  E  F
      Arvo rekisteriin 0 1 2 3 4 5 14 7 38 39 3A 3B 3C 3D 3E 3F

  ### Tweaked-tila

  Tweaked-tila on hieman edellä opituilla rekistereillä muuteltu 320x200x256-tila. Tässä tilassa jokaisen bittitason jokainen tavu saadaan käyttöön. Näin perusmuistiin saadaan kokonaisuudessaan neljä 320x200 kuvaruutua! Map Mask - rekisteriä muuttamalla määrätään mille bittitasoille luku/kirjoitusoperaatiot kohdistuvat. Kuvassa 6.12 on tilan rakenne.

  Tila saadaan päälle asettamalla ensin normaali tila 19. Nyt muutetaan kolmen eri rekisterin arvoa. Ensin SEQUENCER- sarjan re-

  ##### Kuva 6.12. Tweaked-tilan rakenne.

  kisterin Memory Mode (indeksi 4h) bitti 3 nollataan, jolloin bittitasot voidaan valita Map Mask -rekisterillä. Sitten CRTC-sarjan rekisterin Underline Location (indeksi 14h) bitti 6 nollataan. Lopuksi CRTC-sarjan rekisterin Mode Control (indeksi 17h) bitti 6 asetetaan, jolloin näyttömuistin sisäinen osoite pysyy samana kuin käyttäjän antama osoite.

  Koska muistissa näkyy nyt neljä 320x200 kuvaruutua, voimme tuplata pystyresoluution, jolloin muistissa olisi vain kaksi kokonaista kuvaruutua. Nollataan vain CRTC-sarjan Maximum Scan Line -rekisterin viisi alinta bittiä. Nyt meillä on aivan uusi resoluutio; 320x400 256 värillä!

  ##### Esimerkki 6.11. Tweaked-tilan asetus ja käyttö.

      ; Asettaa tweaked-tilan päälle ja sytyttää ruutuun pikseleitä
      ; Tehty TASM-assemblerille.
      ;

      MODEL LARGE
      P386
      . STACK                           ; pino
      IDEAL

      SEQUENCER EQU        003c4h
      CRTC      EQU        003d4h
      DATASEG
      CODESEG

               PUBLIC      tweak, maskx

      PROC     paaohj

               mov         ax,@data
               mov         ds,ax        ;asetetaan oikea datasegmentti

      ;avataan tweaked tila
               call        FAR tweak

      ;es osoittamaan näyttömuistin alkua
               mov         ax,0a000h
               mov         es, ax

      ;tyhjennä näyttömuistin kaikki bittitasot
               mov         dx,0fh
               call        FAR maskx
               mov         ax, 0
               mov         di, 0
               mov         cx,16000/2
               rep         stosw

      ;sytytä 1.pikseli värillä 1
               mov         di,0
               mov         dx,1                   ;bittitaso 0
               call        FAR maskx
               mov         [byte ptr es:di],1     ;osoite a0000
      ;sytytä 4.pikseli värillä 2
               mov         [byte ptr es:di+1],2   ;osoite a0001
      ;sytytä 2.pikseli värillä 3
               mov         dx,2                   ; bittitaso 1
               call        FAR maskx
               mov         [byte ptr es:di],3     ;osoite a0000
      ;sytytä 5.pikseli värillä 4
              mov          [byte ptr es:di+1],4   ;osoite a0001

      ;odota jotain näppäimen painallusta
              mov          ah,1
              int          021h

      ;palaa tekstitilaan
               mov         ax, 3
               int         10h

      ;palaa ohjelmasta DOS:iin
               mov         al, 0
               mov         ah,4ch
               int         021h
      ENDP
      ;
      ; Aseta tweaked-tila päälle
      ;
      PROC tweak

      ;ensin normaali tila 13h
               mov         ax,013h
               int         10h
               cli
               mov         dx,SEQUENCER
               mov         al, 4
      ;aseta bitit 1 ja 2
               mov         ah, 6
               out         dx, ax
               mov         dx,CRTC
               mov         al,014h
               out         dx, al
               inc         dx
               in          al, dx
      ;nollaa bitti 6
               and         al,0bfh
               out         dx, al
               dec         dx
               mov         al,017h
               out         dx, al
               inc         dx
               in          al,dx
      ;aseta bitti 6
               or          al,040h
               out         dx, al
               dec         dx
               sti
      ;jos halutaan 320x400 resoluutio,
      ;tehdään seuraavat käskyt
               mov         dx,CRTC
               mov         al, 9        ; indeksi 9
               out         dx, al
               inc         dx
               in          al, dx
               and         al,0e0h      ;nollaa bitit 4-0
               out         dx, al
               ret

      ENDP

      ;
      ; Aseta aktiiviset bittitasot (annettu rekisterissä DX)
      ;
      PROC maskx

               push        dx
               mov         ah, dl
               mov         al, 2        ; indeksi
               mov         dx,SEQUENCER
               out         dx, ax
               pop         dx
               ret

      ENDP

      END

  ### Rekisterit käytännössä

  Nyt kun on opittu muutamien rekisterien käyttö, tehdään pari ohjelmaa niiden avulla. Esimerkki 6.12 demonstroi grafiikkatilaan 18 kirjoitusta, ohjelmassa tarvitaan lähinnä bittitason vaihtoa.

  ##### Esimerkki 6.12. Grafiikkatilaan 18 kirjoitus.

      #include <stdio.h>
      #include <conio.h>
      #include <alloc.h>

      void MaskX(char plane);
      void SetRGB16(char color,char rr,char gg,char bb);

      void main(void)
      {
      char *p = (char *)0xA0000000;
      unsigned int a,x,y;

      asm{
      mov ax,18
      int 0x10
      }
      SetRGB16(1,63,0,0);               //pun
      SetRGB16(8,0,0,63);               //sin
      SetRGB16(15,63,63,63);            //valk
      //Tyhjennä kaikki planet
      MaskX(0xf);
      for(a=0;a<65535;a++) p[a]=0;
      //Ensimmäiset 8 pikseliä kaikille planeille (valkoinen)
      MaskX(0xf);
      p[0]=0xff;
      //Seuraavat 4 pikseliä planelle 0 (punainen)
      MaskX(0x1);
      p[1]=0xf0;
      //Seuraavat 4 pikseliä planelle 3 (sininen)
      MaskX(0x8);
      p[1]=0x0f;

      getch();

      asm{
      mov ax, 3
      int 0x10
      }
      }

      //Vaihda bittitasoa
      void MaskX(char planes)
      {
               asm{
               mov         ah,[planes]
               mov         al, 2
               mov         dx,0x3c4
               out         dx, ax
               }
      }

      //Aseta haluttu väri
      void SetRGB16(char color,char rr,char gg,char bb)
      {
         char table16[]={0,1,2,3,4,5,20,7,56,57,58,59.60,61,62,63};

         color=table16[color];
         asm{
               xor         ah,ah
               mov         al,[color]
               mov         dx,0x3c8
               out         dx,al
               inc         dx
               mov         al,[rr]
               out         dx,al
               mov         al,[gg]
               out         dx,al
               mov         al,[bb]
               out         dx,al
               }
      }

  Esimerkissä 6.13 esitellään kuvaruudunjakoa LC-bittien avulla. Jotta kuvaruudun jakokohta näkyisi, vieritetään yläosaa SAL- ja SAH-bittien avulla.

  ##### Esimerkki 6.13. Kuvaruudun jako ja vieritys.

      #include <stdio.h>
      #include <conio.h>
      #include <bios.h>

      void RTWait(void);
      void SplitScr(unsigned int ypos);
      void SetRGB(char color,char rr,char gg,char bb);
      void SetAddress(unsigned int pos);

      void main(void)
      {
      unsigned int x,y,a,merkki;
      char *p = (char *)0xa0000000;

      asm{
      mov ax,0x13
      int 0x10
      }
      //Väri 1 valkoiseksi
      SetRGB(1,63,63,63);
      //Jaa ruutu kahtia
      SplitScr(100*2);
      //Tyhjennä ruutu
      for(a=0;a<65535;a++) p[a]=0;
      //Tee valkoisia palkkeja
      for(y=0;y<200;y++)
      {
               for(x=0;x<320;x+=16)
               {
                           p[x+y*320]=1;
               }
      }

      a=0;
      do
      {
               a+=1;
               if(a==80) a=0;
               RTWait();
               //Muuta yläosan osoitetta (scrolli)
               SetAddress(a);
               merkki=0;
               if(bioskey(1)!=0) merkki=bioskey(0);
      }
      while(merkki!=0x011b); //kunnes ESC

      asm{
      mov      ax,0x3
      int      0x10
      }
      }

      //Odota kuvaruudun piirron alkua
      void RTWait(void)
      {
               asm{
               pusha
               mov         dx,0x3da
               }
      loop1:
               asm{
               in          al, dx
               test        al, 8
               jnz         loop1
               }
      loop2:
               asm{
               in          al, dx
               test        al, 8
               jz          loop2
               popa
               }
      }

      //Vaihda haluttu väri
      void SetRGB(char color,char rr,char gg,char bb)
      {
               asm{
               pusha
               xor         ah, ah
               mov         al,[color]
               mov         dx,0x3c8
               out         dx, al
               inc         dx
               mov         al, [rr]
               out         dx, al
               mov         al, [gg]
               out         dx, al
               mov         al, [bb]
               out         dx, al
               popa
               }
      }

      //Muuta kuvaruudun alkuosoitetta
      void SetAddress(unsigned int pos)
      {
               asm{
               pusha
               mov         dx,0x3d4
               mov         al,0xc
               mov         ah,[byte ptr pos+1]
               out         dx, ax
               mov         al,0xd
               mov         ah,[byte ptr pos]
               out         dx, ax
               popa
               }
      }

      //Jaa ruutu kahtia
      void SplitScr(unsigned int ypos)
      {
               char bit7,bit8,bit9;

               asm{
               pusha
               mov         ax,[ypos]
               mov         [bit7],al
               mov         bx, ax
               and         bx,256
      //-Overflow, bit 4
               shr         bx, 4
               mov         [bit8],bl
               mov         bx, ax
               and         bx,512
      //-Maximum scan Line, bit 6
               shr         bx, 3
               mov         [bit9],bl

               mov         dx,0x3d4
      //Line Compare
               mov         al,0x18
               mov         ah,[bit7]
               out         dx, ax
      //Overflow
               mov         al,0x7
               out         dx, al
               inc         dx
               in          al, dx
               and         al,255-16
               or          al, [bit8]
               out         dx, al
               dec         dx
      //Maximum Scan Line
               mov         al,0x9
               out         dx, al
               inc         dx
               in          al, dx
               and         al,255-64
               or          al,[bit9]
               out         dx, al
               popa
               }
      }

  ### VESA-tilat

  Kun ensimmäiset SuperVGA-kortit tulivat markkinoille, niiden ohjelmointi oli melko vaikeaa, koska jokainen kortti oli erilainen. Nykyään lähes kaikki SVGA-kortit ovat VESA- yhteensopivia (Video Electronics Standards Association). Tämä tarkoittaa sitä, että tiettyjä toimintoja voidaan kutsua samoilla BIOS-funktioilla, esimerkiksi ruudun aukaisu on standardisoitu.

  VESA-standardi ei yllä korteilla oleviin kiihdytinpiireihin kuten S3 tai Cirrus. Tätä varten kortin mukana tuleekin erityiset ohjaimet esimerkiksi Windowsia varten.

  VESA-yhteensopivuus ei tarvitse nimestään huolimatta VLB-korttia, vaan myös tavallisessa ISA-väylässä oleva SVGA- kortti voi olla VESA-yhteensopiva. Tällöin yhteensopivuus saadaan yleensä erikseen ajettavalla VESA-ajurilla, joka tulee kortin mukana levykkeellä.

  VESA-standardi määrittelee eri näyttötiloille vakionumeroarvot. Kuvassa 6.13 on lueteltu yleisimpien näyttötilojen arvot. Kaikki näyttötilat eivät ole mahdollisia kaikissa korteissa, tämä pitää erikseen tutkia myöhemmin esitetyllä tavalla.

  ##### Kuva 6.13. VESA-standardin mukaiset näyttötilat.

      Tila        Teksti/      Koko        Värit
                  Grafiikka

      100h        Grafiikka    640x400     256
      101h        Grafiikka    640x480     256
      102h        Grafiikka    800x600     16
      103h        Grafiikka    800x600     256
      104h        Grafiikka    1024x768    16
      105h        Grafiikka    1024x768    256
      106h        Grafiikka    1280x1024   16
      107h        Grafiikka    1280x1024   256
      108h        Teksti       80x60       16
      109h        Teksti       132x25      16
      10Ah        Teksti       132x43      16
      10Bh        Teksti       132x50      16
      10Ch        Teksti       132x60      16
      10Dh        Grafiikka    320x200     32,768
      10Eh        Grafiikka    320x200     65,536
      10Fh        Grafiikka    320x200     16,777,216
      110h        Grafiikka    640x480     32,768
      111h        Grafiikka    640x480     65,536
      112h        Grafiikka    640x480     16,777,216
      113h        Grafiikka    800x600     32,768
      114h        Grafiikka    800x600     65,536
      115h        Grafiikka    800x600     16,777,216
      116h        Grafiikka    1024x768    32,768
      117h        Grafiikka    1024x768    65,536
      118h        Grafiikka    1024x768    16,777,216
      119h        Grafiikka    1280x1024   32,768
      11Ah        Grafiikka    1280x1024   65,536
      11Bh        Grafiikka    1280x1024   16,777,216

  VESA-funktiot toimivat keskeytyksen 10h kautta. Rekisteriin AH laitetaan arvo 4fh ja rekisteriin AL halutun VESA-toiminnon numero. Kuvassa 6.14 on listattu tärkeimmät VESA-toiminnot. Jokainen funktio palauttaa AX-rekisterissä arvon 004fh, jos funktiokutsu onnistuu.

  ##### Kuva 6.14. VESA-toimintoja.

      AL    Toiminto
      0     Palauttaa VESA-informaation. Tällä funktiolla tutkitaan, onko VESA-BIOS
            asennettu koneeseen. Syöttöarvot: ES:DI = 256-tavuisen taulukon
            alkuosoite, minne informaatio halutaan. Palauttaa seuraavanlaisen
            informaation annettuun taulukkoon:

            Lisä     Koko      Tarkoitus

            0        4 tavua   Merkintä VESA
            4        1 sana    VESA-versio
            6        1 p. sana Osoitin OEM-merkkijonoon
            10       4 tavua   Mahdolliset toiminnot (ei määritelty)
            14       1 p. sana Osoitin VESA-tilojen taulukkoon
            18       238 tavua Varattu

      1     Palauttaa informaation halutusta näyttötilasta. Tällä funktiolla tutkitaan,
            onko tietty tila käytössä. Tämä informaatio on tärkeä!

            Syöttöarvot:
            CX = halutun näyttötilan numero.
            ES:DI = 256-tavuisen taulukon alkuosoite, minne informaatio halutaan.

            Palauttaa seuraavanlaisen informaation annettuun taulukkoon:

            Lisä     Koko      Tarkoitus

            0        1 sana    Näyttötilan tietoalkio
                     bitti 0:  Tila on olemassa
                     bitti 1:  Lisätieto saatavissa
                     bitti 2:  Onko normaalit grafiikka-BIOS-funktiot tuettu
                     bitti 3:  1= väritila
                     bitti 4:  1= grafiikkatila, 0 = tekstitila
            2        1 tavu    Ikkunan A tietoalkio
                     bitti 0:  1= ikkuna A on olemassa
                     bitti 1:  1= ikkunaa A voi lukea
                     bitti 2:  1= ikkunaan A voi kirjoittaa
            3        1 tavu    Ikkunan B tietoalkio
                     bitti 0:  1= ikkuna B on olemassa
                     bitti 1:  1= ikkunaa B voi lukea
                     bitti 2:  1= ikkunaan B voi kirjoittaa
            4        1 sana    Pienin arvo kilotavuina, jolla ikkunaa voidaan siirtää
                               näyttömuistissa (GRANUNIT). Tärkeä!
            6        1 sana    Ikkunan koko kilotavuina
            8        1 sana    Ikkunan A alkusegmentti
            10       1 sana    Ikkunan B alkusegmentti
            12       1 p.sana  Osoitin ikkunan siirtofunktioon. Sama kuin VESA-toiminto 5.
            16       1 sana    Tavujen määrä/vaakarivi. Voi olla enemmän kuin
                               vaakaresoluutio!

            Lisätieto:

            18       1 sana    Vaakaresoluutio
            20       1 sana    Pystyresoluutio
            22       1 tavu    Merkin leveys pikseleinä
            23       1 tavu    Merkin korkeus pikseleinä
            24       1 tavu    Bittitasojen määrä
            25       1 tavu    Bittiä/pikseli
            26       1 tavu    Muistipankkien määrä
            27       1 tavu    Muistityyppi
                               0          Tekstitila
                               1          CGA-tila
                               2          Hercules-tila
                               3          4-bittitasoa
                               4          Pakatut pikselit
                               6          256-väritila, ei neljää bittitasoa
                               6-15       Varattu
                               16-255     Kortin valmistajan omat tilat
            28       1 tavu    Muistipankin koko kilotavuina

      2     Aseta näyttötila päälle.
            Syöttöarvot:   BX = halutun näyttötilan numero. Aseta bitti 15 päälle,
                           jos et halua tyhjentää näyttömuistia.

      3     Lue päällä oleva näyttötila.
            Paluuarvot: BX = näyttötila.

      4     Aseta perusmuistissa näkyvän ikkunan kohta näyttömuistissa.
            Syöttöarvot:
                           BH=0
                           BL = 0 = ikkuna A,1= ikkuna B
                           DX = ikkunan osoite näyttömuistissa GRANUNIT-yksiköissä.

      5     Lue perusmuistissa näkyvän ikkunan kohta näyttömuistissa.
            Syöttöarvot:
                           BH=1
                           BL = 0 = ikkuna A,1= ikkuna B
            Paluuarvot:    DX = ikkunan osoite näyttömuistissa GRANUNIT-yksiköissä.

  Esimerkissä 6.14 on ohjelma, joka tutkii, onko VESA-BIOS asennettu. Sen jälkeen tutkitaan onko 640x480x256-tila olemassa ja laitetaan se päälle. hopuksi grafiikkaruutuun tulostetaan kaksi pikseliä, ensimmäinen ruudun vasempaan yläkulmaan, toinen seuraavan ikkunasiirroksen alkuun. Ota huomioon, että seuraavan ikkunan kohdan alku ei välttämättä ole rivin alussa.

  ##### Esimerkki 6.14. VESA-funktioiden testausta.

      #include <stdlib.h>
      #include <conio.h>
      #include <alloc.h>
      #include <string.h>
      #include <dos.h>

      void error(int);
      {
      //-Taulukko VESA-informaatiolle
      typedef struct{
        unsigned long vesa;
        int versio;
        int oemoff,oemseg;
        char kyvyt[4];
        int supoff,supseg;
      }VESAINF0;

      //-Taulukko tila-informaatiolle
      typedef struct{
        unsigned tukee:1;
        unsigned lisainfo:1;
        unsigned biostuki:1;
        unsigned vari:1;
        unsigned graf:1;
        unsigned varattu:11;

        //ikkuna A
        unsigned a_on:1;
        unsigned a_luku:1;
        unsigned a_kirjoitus:1;
        unsigned a_varattu:5;

        //ikkuna B
        unsigned b_on:1;
        unsigned b_luku:1;
        unsigned b_kirjoitus:1;
        unsigned b_varattu:5;

        unsigned int granunit;
        unsigned int win_koko;
        unsigned int win_a_alku;
        unsigned int win_b_alku;
        void (*win_funk)(void);

        unsigned int tavuja_per_viiva;
        unsigned int leveys,korkeus;
        char merkin_leveys,merkin_korkeus;
         char tasot;
        char bit_per_pikseli;
        char pankit;
        char muistityyppi;
        char pankin_koko;
      }VESAMODEINFO;

      VESAINF0 far *vinfo;
      VESAM0DEINF0 far *vminfo;

      union REGS inregs,outregs;
      struct SREGS segregs;

      char *ptr = (char *)0x A0000000;

      //-
      //-Pääohjelma
      //-
      void main(void)
      {
               char tmp[256];

               //-Varaa tilaa taulukoille
               if ((vinfo=(VESAINFO far *)farmalloc(256))==NULL) error(3);
               if ((vminfo=(VESAMODEINF0 far *)farmalloc(256))==NULL) error(3);

               //-Tutki onko VESA asenettu
               inregs.h.ah = 0x4f;
               inregs.h.al = 0x00;
               inregs.x.di = FP_OFF(vinfo);
               segregs.es  = FP_SEG(vinfo);
               int86x(0x10, &inregs, &outregs, &segregs);
               if(outregs.x.ax:=0x004f) error(1);
               //-Tulosta ruutuun 'VESA'
               _fmemcpy(tmp,vinfo,4);
               tmp[4]=0;
               cprintf("%s",tmp);

               //-Tutki onko 640x480x256-tila olemassa
               //-ja ota taulukkoon tiedot
               inregs.h.ah = 0x4f;
               inregs.h.al = 0x01;
               inregs.x.cx = 0x101; //640x480x256
               inregs.x.di = FP_OFF(vminfo);
               segregs.es  = FP_SEG(vminfo);
               int86x(0x10, &inregs, &outregs, &segregs);
               if(outregs.x.ax!=0x004f) error(2);

               //-Aukaise kyseinen tila
               inregs.h.ah = 0x4f;
               inregs.h.al = 0x02;
               inregs.x.bx = 0x101; //640x480x256
               int86(0x10, &inregs, &outregs);
               if(outregs.x.ax!=0x004f) error(2);

               //-Tulosta piste värillä 100
               ptr[0]=100;

               //-Vaihda ikkunan paikkaa. Tällä tavalla koko
               //-näyttömuisti voidaan täyttää, vaikka kerralla
               //-näkyykin vain 64 kt. DX:ssä oleva luku kertoo
               //-kuinka monta GRANUNIT:a muistissa siirrytään!
               inregs.h.ah = 0x4f;
               inregs.h.al = 0x05;
               inregs.x.bx = 0x00;
               inregs.x.dx = 0x01; //2.positio
               int86(0x10, &inregs, &outregs);
               if(outregs.x.ax!=0x004f) error(2);

               //-Tulosta piste värillä 50
               ptr[0]=50;

               getch();

               //-Palaa tekstitilaan
               inregs.h.ah = 0x00;
               inregs.h.al = 0x03; //tila 3
               int86(0x10, &inregs, &outregs);
      }

      //-
      //-Virheiden käsittelijä
      //-
      void error(int nu)
      {
               switch(nu)
               {
                           case 1:
                           cprintf("\n\r VESA ei asennettu:");
                           break;
                           case 2 :
                           cprintf("\n\r Tilaa ei tueta:");
                           break;
                           case 3 :
                           cprintf("\n\r Muisti ei riitä:^);
                           break;
               }
               exit(1);
      }

  ### PCX-formaatti

  PCX on kuvaformaatti, joka tukee myös 256-värisiä kuvia. Windowsin Paintbrush ja DOSin Deluxe Paint tukevat tätä formaattia. Tiedosto alkaa 128 tavua pitkällä headerilla. Kuvassa 6.15 headeri on esitetty structin muodossa, jota voidaan käyttää suoraan C-kielessä.

  ##### Kuva 6.15. PCX-kuvan headeri.

      typedef struct{
        char manufact;            //10=ZSoft.PCX
        char version;             //normaalisti 5
        char coding_type;         //1=PCX run length encoding
        char bits_per_pixel;      //bittejä per plane (1,2,4 or 8)
        short xmin,ymin,xmax,ymax; //kuvan koko
        short Hdpi,Vdpi;          //resoluutio
        char palette[3*16];       //16-värinen paletti
        char reserv;              //aseta nollaksi:
        char planes;              //planet (1 = 256 väriä,
                                  //4 = 16 väriä...)
        short bytes_per_line;     //oltava parillinen!
        short palette_type;       //1 = väri/BW, 2 = harmaasävy
        short Hscreen,Vscreen;    //kuvakoko, vain Paintbrush IV+
        char free[54];            //tyhjää
      }PCXHEAD;

  Tärkeimmät tiedot headerissa ovat kuvan koko (xinin, ymin, xinax ja ymax), bittien määrä per bittitaso sekä bittitasojen määrä. Yleensä PCX-tiedostot on pakattu, jolloin coding-type on 1. Tämän headerin jälkeen alkaa pakattu kuvainformaatio. Purkualgoritmi on erittäin yksinkertainen (kuva 6.16).

  ##### Kuva 6.16. PCX-tiedoston purku.

  1. Aseta laskuri (L) nollaksi.
  2. Lue tavu (A).
  3. Jos (A):n kaksi ylintä bittiä ovat asettuneet (A=cOh), siirrytään kohtaan 7. 4. Kirjoita tavu (A) ruutuun tai omaan taulukkoon.
  5. Lisää laskuria (L) yhdellä.
  6. Jos laskuri (L) on saavuttanut ku,ran tarvitseman tavumäärän, poistu purkurutiinista 7. Nollaa tavun (A) kaksi ylintä bittiä (C = A AND 3fh).
  8. Lue seuraava tavu (B).
  9. Monista tavua (B), (C) kertaa.
  10. Lisää laskuria (L) (C):Ilä.
  11. Siirry kohtaan 6.

  Jos kuva on 256-värinen, on paletti sijoitettu tiedoston loppuun. Kun tiedoston lopusta siirrytään 769 tavua taaksepäin, pitäisi siellä olevan tavun olla 12 (Och) merkkinä 256-värisestä paletista. Seuraavasta tavusta alkaa itse paletti. Paletin formaatti on kuvan 6.17 mukainen.

  ##### Kuva 6.17. PCX-tiedosxon paletin formaatti

      Lisä   Arvo
      0      väri 0:Red      (0-255)
      1      väri 0:Green    (0-255)
      2      väri 0:Blue     (0-255)
      3      väri 1:Red      (0-255)
      4      väri 1:Green    (0-255)
      5      väri 1:Blue     (0-255)
      765    väri 255:Red    (0-255)
      766    väri 255:Green  (0-255)
      767    väri 255:Blue   (0-255)

  Koska VGA:n palettirekisterit voivat saada vain arvot 0- 63, pitää kukin tavu jakaa neljällä. Jos kuva on 16-värinen, sijaitsee vastaavanlainen inforniaatio 16 värin osalta headerin palette-osiossa. Esimerkissä 6. I 5 on 256-värisen PCX-kuvan purkurutiini. Oletuksena on, että kuva on 320 leveä 200 pikseliä korkea (tällöin kuvan vaatima tavumäärä on 320 x 200 = 64000).

  ##### Esimerkki 6.15. 256-värisen PCX:n purku.

      //-
      //-Esimerkki purkaa 256-värisen PCX-tiedoston ruutuun.
      //-Kuvasta oletetaan, että sen koko on 320x200 pikseliä.
      //-
      #include <stdlib.h>
      #include <stdio.h>
      #include <conio.h>
      #include <alloc.h>

      #define PCXHDR 128
      #define PEL_ADDRESS_WRITE 0x3c8

      void setrgb256(char vari, char red, char green, char blue);
      void virhe(char tapaus);

      unsigned char far *kuva;
      unsigned char far paletti[256*3];

      char *ptr = (char *)0xA0000000;
      FILE *fp ;

      void main(void)
      {
        unsigned char a,b;
        unsigned int loop;
        unsigned long l;
        //Varaa kuvalle tilaa
        if ((kuva=(char far *)farmalloc(64000))==NULL) virhe(1));

        //Avaa tiedosto
        if ((fp=fopen("nimi.PCX","rb"))==NULL) virhe(2);
        //-Hyppää headerin yli
        fseek(fp,PCXHDR,SEEK_SET);
        //Pura PCX bufferiin
        l=0;
        while(l<64000)               //64000 tavua (320x200)
        {
               a=fgetc(fp);
               if (a=0xc0)
               {
                    b=fgetc(fp);
                    //Monista tavua
                    for(loop=0;loop<(a&0x3f);loop++)
                    {
                         kuva[l]=b;
                         l++;
                    }
               }
               else
               {
                           kuva[l]=a;
                           l++;
               }
        }

        //Ota paletti
        fseek(fp,-768,SEEK_END);
        for(loop=0<256;loop;loop++)
        {
              paletti[loop*3]=fgetc(fp);
              paletti[loop*3+1]=fgetc(fp);
              paletti[loop*3+2]=fgetc(fp);
        }
        fclose(fp);

        //320x200x256-tila päälle
        asm{
        mov    ax,0x13
        int    0x10
        }
        //Vaihda paletti. Arvo pitää jakaa 4:llä,
        //sillä VGA:n DAC on vain 6-bittinen
        for(loop=0;loop<256;loop++)
        {
            setrgb256(a,paletti[loop*3]>>2,
                        paletti[loop*3+1]>>2,
                        paletti[loop*3+2]>>2);
        }
        //Piirrä kuva ruutuun
        l=0;
        while(l<64000)
        {
               ptr[l]=kuva[l];
               l++;
        }

        getch();

        asm{
        mov ax,0x3
        int 0x10
        }

      //
      //Paletin vaihto-rutiini
      //
      void setrgb256(char vari, char red, char green, char blue)
        asm{
        xor    ah, ah
        mov    al,[vari]
        mov    dx,PEL_ADDRESS_WRITE
        out    dx, al
        inc    dx
        mov    al,[red]
        out    dx, al
        mov    al,[green]
        out    dx, al
        mov    al,[blue]
        out    dx, al
        }
      }

      //
      //Virheiden käsittelijä
      //
      void virhe(char tapaus)
      {
        switch(tapaus)
        {
               case 1:
               cprintf("Muisti ei riitä:\r\n");
               break;
               case 2 :
               cprintf("Tiedostoa ei löydy:\r\n");
               break;
        }
        exit(1);
      }

  # 7. Äänet

  PC:n omat ääniominaisuudet ovat heikot, sillä äänistä huolehtii koneen oma kaiutin (piipperi). Vain yksinkertaiset merkkiäänet ovat sen kautta mahdollisia, tosin viime aikoina on saatu ulos puhetta ja entistä monipuolisempia ääniefektejä.

  Ensimmäisiä äänikortteja PC:lle olivat AdLib ja Sound Blaster, ja näitä äänikortteja tuetaan vielä tänäkin päivänä. Vuonna 1990 esitelty AdLib oli FM-piirin ympärille rakennettu kortti, joka pystyi tuottamaan FM-synteesillä muodostettuja ääniä. Digitoituja ääniä sillä ei pystytty tuottamaan.

  Äänikorttien kirjo

  Ensimmäinen suuren suosion saanut äänikortti oli Suomessa vuonna 1990 esitelty Sound Blaster. Kortti oli AdLib- yhteensopiva ja lisäksi se pystyi äänittämään ja toistamaan 8-bittisiä digitoituja ääniä. Sen seuraajassa, Sound Blaster Prossa, oli kaksi digitoitua äänikanavaa sekä kaksi 11- kanavaista FM-piiriä. Myös digitoidun äänen maksimitaajuus oli nostettu 44 kilohertsiin (mono). Seuraava versio kortista oli Sound Blaster Pro 2, jossa käytetään uutta Yamahan OPL-3-piiriä (YMF262), jolla saadaan 20 kanavaa stereona.

  Nykyään myytävien Sound Blasterien nimitykset ovat erilaiset. Esimerkiksi Sound Blaster Pro -nimellä myytävä kortti on parempi kuin muinainen samanniminen. Nykyinen Sound Blaster Pro on paras 8-bittinen Sound Blaster -sarjan kortti. Siinä on OPL-3 FM-piiri, 44 kilohertsin äänitys ja toisto stereona. Sound Blaster 16 on vastaava kortti kuin SB Pro, mutta digitoidun äänen toisto ja äänitys voidaan tehdä 16-bitillä. Tällöin äänen laatu lähestyy CD-soitinta. Lisäksi uutuutena on Sound Blaster -sarjan lippulaiva Sound Blaster AWE32, jossa on huippuluokan midi-soittimet sisäänrakennettuna.

  Kuten huomataan, jo yhden valmistajan äänikorttien täydellinen tukeminen vaatii paljon tietoa korttien eroista. Kun markkinoilla on lisäksi useiden muiden valmistajien kortteja, voidaan äänikorttien ohjelmointia pitää yhtenä PC-ohjelmoijan vaativimmista tehtävistä. Lisäksi korteista on vaikea saada kunnon teknisiä manuaaleja käsiinsä. Tässä yhteydessä tahdommekin sanoa kiitokset Advanced Gravikselle, joka antoi meidän käyttää kirjassamme tekstiä Gravis Ultrasound -äänikortin teknisistä manuaaleista.

  ### PC-piipperi

  Monen PC:n omistajan ainoa äänen lähde on koneen oma kaiutin, joten myös sen ohjelmoinnin perusteet on hyvä tietää.

  Ääni saadaan aikaan PIT-piirin (Programmable Interval Timer) ajastimella kaksi. Ensin kaiuttimen ulostulo laitetaan päälle asettamalla portin 61h bitit 0 ja 1. Sitten ajastimelle kaksi asetetaan soitettava taajuus. Kun ääni halutaan lopettaa, nollataan portin 61h bitit 0 ja 1. Jotta saataisiin kuuluviin muutakin kuin eri taajuisia piip-ääniä, pitää soittaa nopeasti peräkkäin eri taajuisia ääniä.

  Ajoitus voidaan tehdä PIT-piirin ajastimella nolla. Sopiva taajuus on esimerkiksi 160 kertaa sekunnissa. Kuten luvussa neljä on kerrottu, pitää DOSille palauttaa oikea kellon aika, sillä ajastimen nolla käyttö omiin tarkoituksiin sekoittaa ajan päivityksen DOSille.

  Esimerkissä 7.1 on ohjelma, joka soittaa kahden sekunnin mittaisen nousevan äänen. Ohjelman rakenne on lyhyesti seuraava.
  1. Ohjelmoidaan ajastin 0 taajuudelle 160 Hz.
  2. Jokaisella ajastimen antamalla kesketyksellä tehdään kohdat 3-6.
  3. Otetaan taulukosta (soundi) uusi taajuus.
  4. Jos kaikki taajuudet on otettu hypätään kohtaan 7.
  5. Kaiutin päälle (portti 61h OR 3).
  6. Ohjelmoidaan ajastin 2 taulukosta saadulle taajuudelle.
  7. Ajastin 0 palautetaan entiselleen.
  8. Kaiutin pois päältä (portti 61h AND fch).
  9. Paristovarniennetun kellon aika palautetaan DOSille.
  10. Poistutaan ohjelmasta.

  ##### Esimerkki 6.15. 256-värisen PCX:n purku.

      #include <dos.h>
      #include <alloc.h>
      #include <stdlib.h>

      #define INT_TIMER 0X08
      #define _ _CPPARGS...

      void interrupt ( *oldhandler)(_ _CPPARGS);
      void interrupt speaker_handler(_ _CPPARGS);

      int far *sound_address;           //äänen alkuosoite
      unsigned int sound_length;        //äänen pituus
      unsigned int sound_pos;           //äänen soittokohta
      char loppu=0;

      //-
      //-Pääohjelma
      //-
      void main(void)
      {
      void set speaker(int far *, int, long);
      int restore_time(void);

      int far *soundi;
      int a ;

      oldhandler = getvect(INT_TIMER);

      if((soundi=(int far*)farmalloc(640))==NULL) exit(1);
      //Asetetaan taulukkoon taajuudet 20-340.
      //Taulukkoon voit ladata esimerkiksi levyltä
      //itse laatimiasi taajuusarvoja.
      for(a=0;a<320;a++) soundi[a]=a+20;

      set_speaker(soundi,320.160);

      //Odota äänen loppua
      while(loppu==0);
      restore time();
      }

      //-
      //-Asettaa äänen soimaan
      //-
      void set_speaker(int far *osoite, int pituus, long rate)
      {
        unsigned int timer;

        timer=1193180/rate;
        setvect(INT_TIMER,speaker_handler);
        asm cli;
        sound_pos = 0 ;
        sound_address=osoite;
        sound_length=pituus;
        //Ajastin 0
        asm{
        push               ax
        push               cx
        //bitit  76=00  - counter 0
        //bitit  54=11  - LSB & MSB
        //bitit 321=011 - square wave
        //bitit   0=0   - 16 binary counter
        mov                al,0x36
        out                0x43,al
        pusha
        popa
        mov                cx, [timer]
        mov                al, cl
        out                0x40,al
        pusha
        popa
        mov                al, ch
        out                0x40,al
        pusha
        popa
        pop                cx
        pop                ax
        sti
        }
      }

      //-
      //-Palauttaa oikean ajan DOSille
      //-
      int restore_time(void)
      {
        asm{
        mov                ah, 4
        int                0x1a
        jc                 virhe
        mov                ah, 2
        int                0x1a
        jc                 virhe

        mov                bl,10        //kerroin

        mov                al,ch        //BCD to binary
        and                ch,15
        shr                al, 4
        mul                bl
        add                ch, al

        mov                al,cl         //BCD to binary
        and                cl,15
        shr                al,4
        mul                bl
        add                cl,al

        mov                al,dh         //BCD to binary
        and                dh,15
        shr                al,4
        mul                bl
        add                dh,al

        mov                dl,0
        mov                ah,0x2d
        int                0x21
        cmp                al,0
        jnz                virhe
        }
        return  1;
      virhe:
        return  0;
      }

      //-
      //-Timer-keskeytys
      //-
      void interrupt speaker_handler(_ _CPPARGS)
      {
        asm{
        mov                al,0x20
        out                0x20,al
        dec                word ptr sound_length
        jz                 sound_end
        les                di,sound_address
        add                di,word ptr sound_pos
        add                word ptr sound_pos, 2
        //Otetaan taajuus
        mov                bx,[es:di]
        //Peruskello 1193180(1234dch)
        mov                ax,0x34dc
        mov                dx,0x0012
        cmp                dx,bx
        jnb                end
        div                bx
        mov                bx,ax
        in                 al,0x61
        test               al,3
        jne                aani_paalle
        //Kaiutin päälle
        or                 al, 3
        out                0x61,al
        //Ajastin 2
        //bitit  76=10  - counter 2
        //bitit  54=11  - LSB & MSB
        //bitit 321=011 - square wave
        //bitit   0=0   - 16 binary counter
        mov                al,0xb6
        out                0x43,al
        }
      aani_paalle :
        asm{
        mov                al, bl
        out                0x42,al
        mov                al, bh
        out                0x42,al
        jmp                end
        }
      sound_end:
        //Ajastin 0
        asm{
        mov                al, 54
        out                0x43,al
        pusha
        popa
        //Ajastimen 0 vakioarvo=65536(=0)
        mov                cx, 0
        mov                al, cl
        out                0x40,al
        pusha
        popa
        mov                al, ch
        out                0x40,al
        pusha
        popa
        //Kaiutin pois
        in                 al,0x61
        and                al,0xfc
        out                0x61,al
        }
        setvect(INT_TIMER, oldhandler);
        loppu=1;
      end:
      }

  ### Yamaha OPL3

  Muun muassa uudemmat Sound Blaster -kortit käyttävät Yamahan YMF262-piiriä FM-äänen tuottamiseen. Vanhempi OPL2- piiri jätetään tässä käsittelemättä, sillä sen tuottamat äänet ovat melko yksinkertaisia. Tosin uusi piiri on OPL2- yhteensopviva, joten toiminnot, jotka eivät ole merkitty OPL3-toiminnoiksi, toimivat samalla tavalla vanhalla OPL2- piirillä.

  Piirin porttiosoitteet ovat kuvan 7.1 mukaiset.

  ##### Kuva 7.1. OPL3-porttiosoitteet.

      perus+0         Pankin 0 osoiterekisteri.
      perus+2         Pankin 1 osoiterekisteri.
      perus+1         kirjoitus/lukurekisteri.

  Perusosoite on yleensä 388h (Yamahan määritelmä), joissakin korteissa perusosoitteena toimii myös kortin oma perusosoite (esimerkiksi 220h). Osoiterekistereitä on kaksi, yksi kullekin pankille. OPL3-piiriin on lisätty yksi pankki, jossa on lähes vastaavat toiminnot kuin OPL2-piirin pankissa 0. Jompaan kumpaan osoiterekisteriin kirjoitetaan halutun rekisterin arvo, jonka jälkeen rekisterin arvo kirjoitetaan kirjoitusrekisteriin. Osoiterekisteriin kirjoittamisen jälkeen pitää tehdä pieni viive, ennen kuin kirjoitetaan rekisterin arvo kirjoitusrekisteriin.

  Normaalisti yksi ääni koostuu kahdesta operaattorista. Kullekin operaattorille on kuvan 7.2 mukaisia rekistereitä. Rekisterit käydään läpi tarkemmin myöhemmin.

  ##### Kuva 7.2. Operaattorikohtaiset rekisterit.

      Rekisterit   Käyttö
      20h-35h      AM-mod, vibraatto, EG-tyyppi, skaalaus, kerroin
      40h-55h      Skaalaustaso, voimakkuus.
      60h-75h      Nousu, lasku.
      80h-95h      Pito, vapautus.
      e0h-f5h      Aaltomuoto

  Lisäksi on olemassa rekistereitä, jotka ovat kanavakohtaisia (eli yhteisiä molemmille operaattoreille). Nämä ovat kuvassa 7.3.

  ##### Kuva 7.3. Kanavakohtaiset rekisterit.

      Rekisterit       Käyttö
      a0h-a8h          Taajuus (alimmat kahdeksan bittiä).
      b0h-b8h          Ääni ON, oktaavi, taajuus (2 ylintä bittiä)
      c0h-c8h          Takaisinkytkentä, kytkentätyyppi.

  Kuvan 7.2 rekisterit ovat siis käytössä kaikille operaattoreille. Kuvassa 7.4 on esitetty rekisterien, operaattoreiden ja kanavien vastaavuudet.

  ##### Kuva 7.4. Kanavien operaattorit.

      Kanava        1   2   3   4   5   6   7   8   9
      Rekisteri +
      Operaattori1 00h 01h 02h 08h 09h 0ah 10h 11h 12h
      Operaattori2 03h 04h 05h 0bh 0ch 0dh 13h 14h 15h

  Esimerkiksi jos halutaan muuttaa kanavan 5 operaattorin 1 aaltomuotoa, tehdään seuraavasti:

      mov      dx,388h                  ;pankin 0 os.rek.
      mov      al,0e0h+09h              ;rekisteri
      out      dx, al
      mov      dx,389h                  ;kirjoitus-rek.
      mov      al,2                     ;aaltomuoto2
      out      dx, al

  Saman kanavan taajuuden muutos tapahtuu taas seuraavasti.

      mov      dx,388h
      mov      al,0a0h+4h               ;kanava 5
      out      dx, al
      mov      dx,389h
      mov      al,100                   ;taajuus
      out      dx, al

  Vastaavat rekisterit ovat myös pankissa 1, jolloin osoiterekisteriksi muutetaan 38Ah. Näin kahden operaattorin ääniä saadaan yhteensä 18. Lisäksi on rekistereitä, joilla määrätään kaikille kanaville yhteisiä asioita. Seuraavaksi esitellään tärkeimmät rekisterit bitti bitiltä. Pankin 1 toiminnot ovat toiminnassa vain OPL3-piirillä.

  #### 04h, pankki 1

  Tällä rekisterillä voidaan yhdistää kaksi kahden operaattorin kanavaa yhdeksi neljän operaattorin kanavaksi. Näin tehdyt äänet ovat paljon monipuolisempia, tosin tällöin kanavia voi olla maksimissaan kuusi.

      Bitti   4OP-kanava    Käytetyt 2OP-kanavat
      7-6     Ei käytössä
      5       6             12,15
      4       5             11,14
      3       4             10,13
      2       3             3,6
      1       2             2,5
      0       1             1,4

  #### 05h, pankki 1

  Asettamalla tämän rekisterin bitti 0, saadaan OPL3-piirin uudet ominaisuudet käyttöön. Asetettava aina kun halutaan käyttää OPL3:n ominaisuuksia!

      Bitti     Käyttö
      7-1       Ei käytössä
      0         1= OPL3 käytössä

  #### 08h, pankki 0

  Käytetään oikean taajuuden luomiseen. Aseta aina bitti 6.

      Bitti     Käyttö
      7         Ei käytössä
      6         1
      5-0       Ei käytössä

  #### 20h-35h, pankit 0 ja 1

  Bitillä seitsemän määrätään, sisältääkö operaattori AM- modulaatiota. Bitillä kuusi määrätään, sisältääkö operaattori vibraattoa. Bitillä viisi määrätään äänen soiton tyyppi. Tyyppi nolla soittaa äänen nousu-, lasku-, pito- ja vapautusparametrien mukaan loppuun. Tyyppi yksi pitää äänen pitotasolla, kunnes vastaavan kanavan rekisterin bOh-b8h KEYON-bitti nollataan. Bitillä neljä voidaan lisätä taajuuteen offsettiä, jolla matkitaan aidon soittimen sointia korkeilla taajuuksilla. Biteillä 3-0 määrätään operaattorin taajuus. Arvo nolla jakaa taajuuden kahdella.

      Bitti     Käyttö
      7         AM-modulaatio
      6         Vibraatto
      5         Tyyppi
      4         Nuotin korotus
      3-0       Taajuuden kerroin

  #### 40h-55h, pankit 0 ja 1

  Biteillä 7 ja 6 määrätään voimakkuuden korotus taajuuden mukaan:

      00   0dB
      01-  1,5 dB/oktaavi
      10-  3 dB/oktaavi
      11-  6 dB/oktaavi

  Biteillä 5-0 määrätään äänen ulostulon vaimennus.

      Bitti     Käyttö
      7-6       Voimakkuuden korotus
      5-0       Ulostulon vaimennus

  #### 60h-75h, pankit 0 ja 1

  Biteillä 7-4 määrätään äänen nousuaika. Biteillä 3-0 määrätään äänen laskuaika pitotasolle.

      Bitti     Käyttö
      7-4       Nousuaika
      3-0       Laskuaika

  #### 80h-95h, pankit 0 ja 1

  Biteillä 7-4 määrätään äänen pitotaso. Biteillä 3-0 määrätään äänen vapautusaika nollaan.

      Bitti     Käyttö
      7-4       Pitotaso
      3-0       Vapautusaika

  #### a0h-a8h, pankit 0 ja 1

  Rekisteri sisältää kanavan taajuuden kahdeksan alinta bittiä. Ylimmät bitit ovat rekisterissä bOh-b8h. Nuotteja vastaavat arvot ovat:

      C    345
      C#   365
      D    387
      D#   410
      E    435
      F    460
      F#   488
      G    517
      G#   547
      A    580
      A#   615
      B    651
      C    690

      Bitti Käyttö
      7-0   Kanavan taajuuden kahdeksan alinta bittiä.

  #### b0h-b8h, pankit 0 ja 1

  Asettamalla bitti viisi saadaan ääni soimaan. Biteillä 4- 2 määrätään soitettava oktaavi. Bitit 1-0 sisältävät taajuuden kaksi ylintä bittiä.

      Bitti    Käyttö
      7-6      Ei käytössä
      5        Ääni päälle
      4-2      Oktaavi
      1-0      Kanavan taajuuden kaksi alinta bittiä.

  #### bdh, pankki 0

  Bitillä 7 määrätään AM-modulaation syvyys. Tämä arvo on yhteinen kaikille operaattoreille. Bitillä 6 määrätään vibraaton syvyys. Tämä arvo on yhteinen kaikille operaattoreille. Kun bitti 5 asetetaan, menee piiri rytmitilaan, jolloin kanavia 7, 8 ja 9 käytetään rytmisoittimina. Biteillä 4-0 saadaan kuuluviin kukin soitin. Soittimet käyttävät seuraavia lisiä perusrekistereihin (vertaa kuva 7.4).

      Bassorumpu    Odh,1Oh
      Snare         11h
      Tomit         Ofh
      Symbaali      12h
      Haikka        Oeh

      Bitti     Käyttö
      7         0=1dB AM-modulaatio
                1=4,8dB AM-modulaatio
      6         0= vibraatto 7 centtiä
                1= vibraatto 14 centtiä
      5         1= rytmi-tila
      4         Bassorumpu
      3         Snare
      2         Tomit
      1         Symbaali
      0         Haikka

  #### cOh-c8h, pankit 0 ja 1

  Bitit 7 ja 6 kannattaa nollata, sillä niillä voidaan ohjata vasen tai oikea kanava ulkoiselle muuntimelle. Biteillä 5-4 määrätään tuleeko kanava ulos oikeasta, vasemmasta vaiko molemmista kaiuttimista. Biteillä 3-1 määrätään kanavan takaisinkytkennän määrä. Bitillä 0 valitaan käytettävä operaattoreiden kytkentätapa.

      0 - Operaattorit 1 ja 2 sarjassa
      1 - Operaattorit 1 ja 2 rinnan.

  ##### Kuva 7.5. Operaattoreiden kytkentätavat.

  Jos käytetään OPL3-piirin neljän operaattorin tilaa, kytkennälle on kaksi bittiä. Toinen bitti on rekisteri+3:ssa. Neljän operaattorin kytkennät ovat kuvan 7.5 mukaiset.

      Bitti     Käyttö
      7-6       Ulkoinen muunnin. Nollaa. (OPL3)
      5         1= ääni vasempaan kanavaan (OPL3)
      4         1= ääni oikeaan kanavaan (OPL3)
      3-1       Takaisinkytkentä
      0         Kytkentätapa

  #### e0h-f5h, pankit 0 ja 1

  Biteillä 2-0 määrätään operaattorin käyttämä aaltomuoto. OPL2-piirissä voidaan valita vain neljä ensimmäistä aaltomuotoa. Kuvassa 7.6 on mahdolliset aaltomuodot.

  ##### Kuva 7.6. Aaltomuodot.

      Bitti     Käyttö
      7-3       Ei käytössä
      2-0       Aaltomuodon valinta

  Koska äänen päälle saamiseksi pitää asettaa useita eri rekistereitä, kannattaa tehdä operaattori- ja kanavakohtaiset rekistereiden asetusrutiinit. Esimerkissä 7.2 on mallirutiinit, voit itse muuttaa niitä käyttötarpeesi mukaan.

  ##### Esimerkki 7.2. Rutiinit OPL3-rekistereiden asettamiseksi.

      //-
      //-Rutiini operaattorikohtaisille parametreille
      //-Rutiinia kutsutaan:
      //-outop (pankki, kanava, rekisteri, arvo);
      //-
      void outop(unsigned int pankki,
                 unsigned char kanava,
                 unsigned char rekisteri,
                 unsigned char arvo)
      {
          static const unsigned char kanavaoffs[]=
          {
          0x00,0x01,0x02,0x08,0x09,0x0a,0x1 0,0x11,0x12
          };

          kanava=kanavaoffs[kanava];
          pankki<<=1;
          asm{
      //-Odota noin 12 jaksoa!
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          mov              dx,0x0388    ;perusosoite!
          add              dx,[pankki]
          mov              al,[rekisteri]
          add              al,[kanava]
          out              dx, al
      //-Odota noin 12 jaksoa!
          in               al, dx
          in               al, dx
          in               al,dx
          in               al,dx
          in               al,dx
          in               al,dx
          mov              dx,0x0389
          mov              al, [arvo]
          out              dx, al
          }
      }

      //-
      //-Rutiini kanavakohtaisille parametreille
      //-Rutiinia kutsutaan :
      //-outch(pankki,kanava,rekisteri,arvo);
      //-
      void outch(unsigned int pankki,
                 unsigned char kanava,
                 unsigned char rekisteri,
                 unsigned char arvo)
      {
          pankki<<=1
          asm{
      //-Odota noin 12 jaksoa!
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          mov              dx,0x0388
          add              dx,[pankki]
          mov              al,[rekisteri]
          add              al,[kanava]
          out              dx, al
      //-Odota noin 12 jaksoa!
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          in               al, dx
          mov              dx,0x0389
          mov              al, [arvo]
          out              dx, al
          }
      }

  ### Sound Blaster

  Kuten luvun alussa kerrottiin, Sound Blaster -sarjan kortteja on useita. Koska kaikkien korttien ohjelmoinnin erojen selvittäminen olisi monimutkaista, käymme läpi esimerkkiohjelman, joka toimii uudemmissa Sound Blaster - korteissa.

  Digitoidun äänen ohjelmointi on melko pitkälle kortin signaaliprosessorin (DSP) ohjelmointia. FM-äänet taas muodostetaan OPL2- tai OPL3-piireillä. Kuvassa 7.7 on eri Sound Blaster-kortit ja niissä käytettävien komponenttien versionumerot.

  ##### Kuva 7.7. Sound Blaster -komponentit.

      Kortti       DSP-versio       FM-piiri
      SB1.5        1.xx-2.00        OPL2
      SB2.0        2.01 ja yli      OPL2
      SBPRO        3.xx             OPL3
      SB16         4.xx             OPL3

  Tilannetta sekoittaa se, että vanhemmissa SBPRO-korteissa käytettiin vielä OPL2-piiriä. OPL-piirin versio voidaan määritellä lukemalla FM-piirin perusporttia 388h (tai joissakin korteissa kortin perusosoitetta, esimerkiksi 220h). Jos luettu arvo on 6, piiri on OPL2, jos arvo on 0, piiri on OPL3.

  Kortin perusosoitteen, DMA:n ja keskeytyksen selvittämiseksi ei kannata yrittää tehdä hardwareen perustuvia rutiineja, vaan luetaan ne selkeästi BLASTER- ympäristömuuttujasta. Jos koneessa on kiinni useampia kuin yksi äänikortti, keskeytysvektoreita ja muita arvoja on melko hankala määritellä. Kun käyttäjä on ostanut äänikortin, kirjoittaa asennusohjelma kuitenkin ympäristömuuttujansa autoexec.bat-tiedostoon. Kuvassa 7.8 on kunkin parametrin tarkoitus.

  ##### Kuva 7.8. BLASTER-ympäristömuuttuja.

      Parametri      Tarkoitus
      Axxx           Perusosoite xxx
      Ixx            Keskeytys
      Dx             8-bittinen DMA
      Hx             16-bittinen DMA (*
      Mxxx           Mikserin perusosoite (*
      Pxxx           Midin perusosoite (*

      (* Nämä parametrit eivät ole pakollisia.

  DSP:n toiminnan selvittämiseksi kannattaa tutkia esimerkkejä 7.4 ja 7.5.

  Kuvassa 7.7 esiteltiin eri korttien käyttämät signaaliprosessorit. Kuvassa 7.9 on esitetty eri DSP- versioiden digitoidun äänen toistotaajuudet, jotka pitää ottaa huomioon tehtäessä rutiineja eri korteille.

  ##### Kuva 7.9.Maksimi toistotaajuudet.

      DSP      Moodi           Bittimäärä   Toistotaajuus
      4.xx     mono/normaali   8            5000-44100
               mono/normaali   16           5000-44100
               stereo/normaali 8            5000-44100
               stereo/normaali 16           5000-44100
      3.xx     mono/normaali   8            4000-23000
               mono/nopea      8            23000-44100
               stereo/nopea    8            23000-44100
      2.01     mono/normaali   8            4000-23000
               mono/nopea      8            23000-44100
      2.00     mono/normaali   8            4000-23000
      1.xx     mono/normaali   8            4000-23000

  ##### Kuva 7.10. DSP:n osoiteavaruus.

      Osoite    Toiminta
      2x6       DSP:n resetointi
      2xa       DSP:n luku
      2xc       DSP:n komento/data
      2xc       DSP:n valmis ottamaan komentoja (luku)
      2xe       DSP:n valmius lukuun (luku)

      x on perusosoitteen mukaan (esimerkiksi 220h, x=2)

  DSP:lle kirjoitus tapahtuu yksinkertaisesti esimerkin 7.3 mukaisesti.

  ##### Esimerkki 7.3. DSP:Ile kirjoitus.

        mov    dx,[SBperusosoite]
        add    dx,0ch
      ;Odota, kunnes kirjoitus mahdollista
      viive:
        in     al, dx
        or     al,al
        js     viive
      ;kirjoita haluttu komento tai data
        mov    al,[komento/data]
        out    dx, al

  Periaate digitoidun äänen toistamiseksi on yksinkertaistettuna seuraava:
  1. Resetoidaan DSP
  2. Asetetaan keskeytysvektoriin oma rutiini
  3. Asetetaan kaiutin päälle (ei DSP 4.xx).
  4. Ohjelmoidaan DMA.
  5. Asetetaan DSP:n taajuus.
  6. Asetetaan DSP:lle pituus, jolloin siirto alkaa,
  7. Odotetaan esimerkiksi käyttäjän komentoa.
  8. Asetetaan kaiutin pois (ei DSP 4.xx).
  9. Pysäytetään DMA.
  10. Poistetaan keskeytys.
  11. Resetoidaan DSP.

  Käännä esimerkki 7.4. C++-kääntäjällä large-tilaan ja esimerkki 7.5. assemblerilla. Linkkaa yhteen yhdeksi EXE- tiedostoksi. Ohjelma on WAV-tiedostojen soitto-ohjelma kahdeksanbittisille Sound Blaster -korteille. Ohjelma lukee soittotaajuuden WAV-tiedoston alussa olevasta määriteosasta (header). Jos taajuus on yli 22050 hertsiä, soitto tehdään niin kutsutussa nopea-moodissa.

  Nopea-moodi tarvitaan haluttaessa toistaa yli 23000 hertsin taajuudella. Vain DSP:n versio 4.xx ei tarvitse nopea-moodia, jolloin sen ohjelmointi ei vaikuta ääneen. Jos tiedosto on 16-bittinen, ladataan vain ylimmät kahdeksan bittiä. Stereotiedostoja ohjelma ei soita. Tutki varsinkin WAV-headerin rakennetta (muun muassa Windows käyttää WAV- ääniä). Myös BLASTER-ympäristömuuttujan lukeminen on tärkeä.

  ##### Esimerkki 7.4. WAV-soitto, C-kielinen osuus.

  #####

  ###

  #####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

  ####

      //-
      //- Tiedosto1 : WPLAY.CPP
      //- Käännetään BC v3.1 C++ -kääntäjällä
      //-
      #include <stdio.h>
      #include <string.h>
      #include <dos.h>
      #include <conio.h>
      #include <alloc.h>
      #include <stdlib.h>
      #include <bios.h>
      #include <mem.h>
      #define _ _CPPARGS...

      #define DAC 0
      #define ADC 1

      //Prototyypit SBKIRJA.ASM:lle
      extern"C"{
      //Alustaa SB:n (base,dma,irq)
      int init_sb(int,char,char);
      //Resetoi DSP:n
      void reset_dsp(void);
      //Asettaa suunnan:DAC tai ADC
      void set_direction(char);
      //Asettaa taajuuden
      void set_freq(unsigned int);
      //Asettaa keskeytyksen
      void set_irq(void*);
      //Palauttaa alkuperäisen keskeytyksen
      void disable_irq(void);
      //Kaiutin päälle
      void speaker_on(void);
      //Kaiutin pois
      void speaker_off(void);
      //Aseta DMA (osoite,pituus)
      void set_dma(void *,unsigned int);
      //Käynnistä DSP (ja DMA)
      void set_dsp(void);
      //Pysäytä DMA
      void stop_dma(void);
      }

      void interrupt ( *oldhandler)(_ _CPPARGS);
      void virhe(char);

      //Wav-headeri
      typedef struct wavheader{
      //"RIFF"
               char        wav_id[4];
      //Dataa jäljellä
               long        file_size;
      //"WAVEfmt"
               char        text[8];
               long        data1;
      //Versio
               short       version;
      //Kanavat
               short       channels;
      //Sampleja/sec
               long        sample_rate ;
      //Dataa/sec
               long        data_rate ;
      //Datan tyyppi (1=8bit,2=16bit)
               short       data_type;
      //Bittejä/sample
               short       bits;
      //"data"
               char        data_text[4];
      //Samplen pituus
               long        sample_size;
      }WAVHEAD;

      WAVHEAD wh;

      //Osoitin samplelle
      char huge *sample;
      char buff[80];

      int base=0x220,irq=7,dma=1;
      unsigned long linearstart,startpage;
      //Samplen koko
      unsigned long size;
      //Soittokohta
      unsigned long pos;
      //Soitettavan alueen koko
      unsigned long blocksize;

      //Kun 1, poistutaan ohjelmasta
      int poistu=0;

      //-
      //- SB keskeytys
      //-
      void interrupt sbhandler1(_ _CPPARGS)
      //lue keskeytys pois DSP:stä
        asm{
        mov    dx, [base]
        add    dx,0xe
        in     al, dx
        }

      //lisää soittokohtaa
        pos+=blocksize+1;
      //jos kaikki soitettu, postu=1
        if(pos>=size) poistu=1;
        else
        {
      //jäljellä kokonainen sivu
            if((size-pos)>=65535) blocksize=65535;
      //soita viimeinen osa samplesta
            else blocksize=size-pos;
      //aseta DMA uudestaan
            set_dma(sample+pos,blocksize);
      //aloita soitto
            set_dsp();
        }
      //kuittaa keskeytys
        asm{
        mov al,0x20
        out 0x20,al
        }
      }

      //-
      //- Pääohjelma
      //-
      void main(int parameters,char*para[])
      {
      char *ptr,*blast;
      long a ;
      FILE *fp ;

      if(parameters<2) virhe(100);

      //lue BLASTER-ympäristömuuttujan arvot
      if((blast=getenv("BLASTER"))==NULL) virhe(20);
      _fstrupr(blast);
      if(ptr= fstrstr(blast,"A")) sscanf(ptr+1,"%x",&base);
      if(ptr=_fstrstr(blast,"I")) sscanf(ptr+1,"%d",&irq);
      if(ptr=_fstrstr(blast,"D")) sscanf(ptr+1,"%d",&dma);
      cprintf("Sound Blaster löytyi asetuksilla:\r\n");
      cprintf("Perusosoite %x, Keskeytys %d, DMA %d\r\n",base,irq,dma);

      //avaa tiedosto
      if ((fp=fopen(para[1],"rb"))==NULL) virhe (1);
      fread(&wh,sizeof(WAVHEAD),1,fp);
      for(a=0;a<4;a++) buff[a]=wh.wav_id[a];
      buff[a]=0;
      if(buff[0]!='R'||buff[1]!='I'||buff[2]!='F'||buff[3]!='F')
      virhe (10);
      if(wh.channels>1) virhe(11);
      if(wh.bits==16)
      {
        cprintf("Tiedosto on 16-bittinen, lataan vain ylimmät
      bitit_\r\n");
        size=wh.sample_size/2;
        if((sample=(char huge *)farmalloc(size))==NULL) virhe(2);
      //lataa ylimmät tavut
        for(a=0;a

      Esimerkki 7.5. WAV-soitto, assembler-osuus.

      ;
      ; Tiedosto2 : SBKIRJA.ASM
      ; Käännä TASM kääntäjällä
      ;
      IDEAL
      MODEL LARGE,C
      P386

      DATASEG

      intr_hand            dw ?
                           dw ?

      dma_addr             dw 0002h
      dma_page             dw 83h
      dma_pages            dw 87h, 83h, 81h, 82h

      base                 dw 220h
      dma                  db 1
      irq                  db 5

      dma_direc            db 59h
      dsp_start            db 91h
      dsp_com              db 14h

      dsp_com_da          db 14h
      dsp_com_ad          db 24h

      freq                dw 22050
      timec               db 166
      koko                dw ?
      data_addr           dw ?

      CODESEG
               PUBLIC init_sb,set_direction,set_freq,reset_dsp
               PUBLIC set_dsp,set_irq,disable_irq
               PUBLIC speaker_on,speaker_off,set_dma
               PUBLIC stop_dma,waitport

      ;-
      ;- Alusta SB:n arvot
      ;-

      PROC init_sb ibase:WORD,idma:BYTE,iirq:BYTE

            mov   ax,[ibase]
            mov   [base],ax
            mov   al,[idma]
            mov   [dma],al
            mov   bx,0
            mov   bl,al
            shl   bx,1
            mov   dx,[dma_pages+bx}
            mov   [dma_page],dx
            mov   [dma addr],bx
            add   al,58h
            mov   [dma direc],al
            mov   al,[iirq]
            mov   [irq],al
            mov   al,04h
            or    al,[dma]
            out   0ah,al
            ret

      ENDP

      ;-
      ;- Aseta suunta
      ;-

      PROC set_direction direc:BYTE

            cmp   [direc],0
            je    da
      ad:
            mov   al, [dma]
            add   al,54h
            mov   [dma_direc],al
      ;komento 99h = aseta DSP:n digitoimaan
      ;8-bittistä ääntä (nopea-moodi)
            mov   [dsp_start],99h
            mov   al,[dsp_com_ad]
            mov   [dsp_com],al
            mov   al,[dma_direc]
            out   0bh,al
            ret
      da:
            mov   al,[dma]
            add   al,58h
            mov   [dma_direc],al
      ;komento 91h = aseta DSP:n toistamaan
      ;8-bittistä ääntä (nopea-moodi)
            mov   [dsp_start],91h
            mov   al,[dsp_com_da]
            mov   [dsp_com],al
            mov   al,[dma_direc]
            out   0bh,al
            ret

      ENDP

      ;-
      ;- Aseta taajuus
      ;-
      PROC set_freq ifreq:WORD

            mov   ax,[ifreq]
            mov   [freq],ax
            mov   bx, ax
            mov   dx,15                 ; dx:ax 1000000
            mov   ax,16960
            div   bx
            mov   dx,256
            sub   dx, ax
            mov   [timec],dl
            cmp   [freq],22050
            ja    hi
      ;komento 14h = aseta DSP:n toistamaan
      ;8-bittistä ääntä
            mov   [dsp_com_da],14h
      ;komento 24h = aseta DSP:n digitoimaan
      ;8-bittistä ääntä
            mov   [dsp_com_ad],24h
            jmp   end2
      hi:
      ;nopea-moodi
      ;komento 48h = aseta DSP:n siirtämään
      ;8-bittistä ääntä (nopea-moodi)
            mov   [dsp_com_da],48h
            mov   [dsp_com_ad],48h
      end2:
            cmp   [dsp_start],91h
            je    da2
      ad2:
            mov   al,[dsp_com_ad]
            mov   [dsp_com],al
            jmp   timeconst
      da2:
            mov   al, [dsp_com_da]
            mov   [dsp_com],al
      timeconst:
            call  FAR waitport
            mov   dx, [base]
            add   dx,000ch
      ;komento 40h = aseta DSP:n taajuus
            mov   al,040h
            out   dx, al
            call  FAR waitport
            mov   dx, [base]
            add   dx,000ch
      ;taajuus=timec
            mov   al, [timec]
            out   dx, al
            ret

      ENDP

      ;-
      ;- Resetoi DSP
      ;-
      PROC reset_dsp

            mov   dx,[base]
      ;komento 6h = resetoi DSP
            add   dx,0006h
            mov   al,01h
            out   dx, al
            mov   ax,5000
      @@viive:
            dec   ax              ; n.1ms vi ive
            jnz   @@viive
            mov   dx, [base]
            add   dx,0006h
            mov   al,00h
            out   dx, al
            ret

      ENDP

      ;-
      ;- Aseta keskeytysvektori
      ;-
      PROC set_irq handler:DWORD

            mov   al, [irq]
            test  al,8            ;onko 7
            jz    kymppi1
            add   al,60h
      kymppi1:
            add   al, 8
            push  ax
            mov   ah,35h
            int   21h
            mov   [intr_hand],bx
            mov   bx, es
            mov   [intr_hand+2],bx
            pop   ax
            push  ds
            mov   ah,25h
            mov   dx,[word ptr handler]
            mov   bx,[word ptr handler+2]
            mov   ds, bx
            int   21h
            pop   ds
            mov   cl,[irq]
            mov   ah,254
            test  cl, 8
            jnz   kymppi4
            rol   ah,cl
            in    al, 21h
            and   al, ah
            out   21h,al
            jmp   jatkopois2
      kymppi4:
            and   cl, 7
            rol   ah,cl
            in    al,0A1h
            and   al, ah
            out   0a1h,al
      jatkopois2:
            ret

      ENDP

      ;
      ;- Palauta keskeytysvektori
      ;
      PROC disable_irq

            push  ds
            mov   cl, [irq]
            mov   ah,1
            test  cl, 8
            jnz   kymppi3
            shl   ah, cl
            in    al, 21h
            or    al, ah
            out   21h,al
            jmp   jatkopois
      kymppi3:
            and   cl, 7
            shl   ah, cl
            in    al,0A1h
            or    al, ah
            out   0a1h,al
      jatkopois:
            mov   ah,25h
            mov   al,[irq]
            test  al,8            ;onko 7
            jz    kymppi2
            add   al,60h
      kymppi2:
            add   al, 8
            mov   dx,[intr_hand]
            mov   bx,[intr_hand+2]
            mov   ds, bx
            int   21h
            pop   ds
            ret

      ENDP

      ;-
      ;- Kaiutin päälle
      ;-
      PROC speaker_on

            call  FAR waitport
            mov   dx, [base]
            add   dx,000ch
      ;komento d1h = kaiutin päälle
            mov   al,0d1h
            out   dx, al
            ret

      ENDP

      ;-
      ;- Kaiutin pois
      ;-
      PROC speaker_off
            call  FAR waitport
            mov   dx,[base]
            add   dx,000ch
      ;komento d3h = kaiutin pois
            mov   al,0d3h
            out   dx,al
            ret

      ENDP

      ;-
      ;- Aseta DMA
      ;-
      PROC set_dma data:DWORD,isize:WORD

            pushf
            cli
            mov   al,04h
            or    al, [dma]
            out   0ah,al
            mov   al,00h
            out   0ch,al
            les   si,[data]
            mov   bx, si
            and   si,0fh
            shr   bx, 4
            mov   ax, es
            add   bx, ax
            mov   di, bx
            shl   bx, 4
            add   bx, si
            mov   [data_addr],bx
            mov   dx,[dma_addr]
            mov   al, bl
            out   dx, al          ;off l
            mov   al, bh
            out   dx,al           ;off h
            mov   bx, di
            shr   bx,12
            mov   al, bl          ; page
            mov   dx, [dma_page]
            out   dx, al
            mov   bx,[isize]
            mov   [koko],bx
            mov   dx,[dma_addr]
            inc   dx              ; dx = dma_addr+1
            mov   al,bl           ; size l
            out   dx,al
            mov   al,bh           ; size h
            out   dx,al
            mov   al,[dma]
            out   0ah,al
            popf
            ret

      ENDP

      ;-
      ;- Aseta DSP
      ;-
      PROC set_dsp

            pushf
            cli
            call  FAR waitport
            mov   dx, [base]
            add   dx,000ch
      ;komento (14h,24h tai 48h)
            mov   al,[dsp_com]
            out   dx, al
            call  FAR waitport
      ;aseta koko
            mov   dx, [base]
            add   dx,000ch
            mov   al,[byte ptr koko]
            out   dx, al
            call  FAR waitport
            mov   dx, [base]
            add   dx,000ch
            mov   al,[byte ptr koko+1]
            out   dx, al
            cmp   [freq],22050
            jna   loppu
      ;nopea-moodi (44.1kHz)
            call  FAR waitport
            mov   dx,[base]
            add   dx,000ch
      ;komento (91h tai 99h)
            mov   al,[dsp_start]
            out   dx, al
      loppu:
            popf
            ret

      ENDP

      ;-
      ;- Pysäytä DMA
      ;-
      PROC stop_dma

            mov   al,04h
            or    al,[dma]
            out   0ah,al
            mov   al,00h
            out   0ch,al
            mov   al, [dma]
            out   0ah,al
            ret

      ENDP

      ;-
      ;- Odota DSP:n valmiutta kirjoitukseen
      ;-
      PROC waitport FAR

            mov   cx,30000
      wlo:
            dec   cx
            jcxz  pois
            mov   dx,[base]
            add   dx,000ch
            in    al,dx
            test  al,128
            jnz   wlo
      pois:
             ret

      ENDP

      END

      Gravis Ultrasound

      Advanced Gravis Technologyn suhtautuminen ohjelman
      kehittelijöihin on sikäli ainutlaatuinen, että tekniset
      manuaalit Gravis Ultrasound -äänikortin ohjelmoimiseksi ovat
      vapaasti saatavissa muun muassa Internetin kautta. Mukana on
      myös valmiit ohjelmakirjastot äänien ohjelmoimiseksi omiin
      sovelluksiin. Tämän vuoksi emme käsittele tätä korttia
      kovinkaan tarkasti, sillä valmiiden kirjastojen
      käyttö on todella vaivatonta. Käydään kuitenkin läpi
      kortin perusperiaatteet sekä porttiosoitteet.

      Useista muista äänikorteista poiketen Graviksen
      keskeytys- ja DMA-linjat voidaan vaihtaa ohjelmallisesti
      ilmanjumppereiden siirtelyä. Vain perusosoite valitaan
      kortilla olevalla jumpperilla. Kortin erikoisuutena on myös
      oma muisti. Perusversiossa muistin määrä on 256 kilotavua,
      joka voidaan kasvattaa yhteen megatavuun.

      Digitoidun äänen muodostamisesta huolehtii piiri nimeltä
      GF1. Se pystyy toistamaan 32 digitoitua ääntä
      samanaikaisesti, tosin maksimi toistotaajuus on tällöin vain
      19293 hertsiä. 44100 hertsin taajuudella pystytään
      soittamaan 14 yhtäaikaista ääntä. Kaikki äänet voidaan
      toistaa joko 8 tai 16 bitillä. Perus-Gravis pystyy
      digitoimaan vain kahdeksalla bitillä. Uudempi Gravis MAX ja Gravis Plug-In
      pystyvät myös 16-bittiseen digitointiin. Gravis ACE, jonka etuna on se,
      että se pystyy toimimaan Sound Blasterin rinnalla niin, että kortilta voi
      kytkeä Adlib-emuloinnin täydellisesti pois, ei pystyy
      digitoimaan. Kuitenkin, Gravis ACE on suosittu kortti pelaajille, jos ne käyttävät
      Sound Blasteriakin samassa koneessa, sillä muilta Gravis korteilta Adlib
      emulointia ei voi kytkeä pois - niitä käyttäen monet pelit eivät tunnista koneessa olevaa
      aitoa Adlib/SB korttia.

      GF1 -piirin osoitteet ovat kuvassa 7.11. X on
      riippuvainen perusosoitteesta, esimerkiksijos perusosoite on
      220h, x=2.

      Kuva 7.11. GF1-piirin osoitteet.
      2x6h Keskeytysten status
      2x8h Ajastimien kontrollointi
      2x9h Ajastimien data
      3x2h Aktiivisen äänen valinta
      3x3h Rekisterin valinta
      3x4h Data, alempi tavu
      3x5h Data, ylempi tavu
      3x7h DRAM:n luku/kirjoitus

      2x6h, IRQ Status Register

      Lukemalla tämän rekisterin arvo keskeytyksen tullessa
      tiedetään mikä lähde aiheutti keskeytyksen. Kyseinen bitti
      on aktiivinen.
      Bitti   Lähde
      7       DMA
      6       Voimakkuuden muutos
      5       WaveTable
      4       Ei käytössä
      3       Ajastin 2
      2       Ajastin 1
      1       MIDI-vastaanotto
      0       MIDI-lähetys

      3x2h, GF1 Page

      Rekisterillä valitaan äänen numero (0-31), johon
      äänikohtaisten parametrien halutaan vaikuttavan.

      3x3h, GF1 Global Register Select

      Rekisterillä valitaan rekisterin numero, jota halutaan
      muuttaa.

      3x4h, Global Data Low Byte

      Kun haluttu toiminto on valittu rekisterillä 3x3,
      kirjoitetaan toiminnon 16-bittisen parametrin alempi tavu
      tähän rekisteriin.

      3x5h, Global Data High Byte

      Kun haluttu toiminto on valittu rekisterillä 3x3,
      kirjoitetaan toiminnon 16-bittisen paranietrin ylempi tavu
      tähän rekisteriin. Jos toiminnon parametri on 8-bittinen,
      kirjoitetaan parametri tähän rekisteriin.

      3x7h, DRAM

      Tällä rekisterillä voidaan lukea/kirjoittaa kortin
      muistia suoraan. Osoite määrätään seuraavaksi esiteltävillä
      rekistereillä 43h ja 44h.

      Seuraavaksi esitellään rekisterit, jotka ovat
      riippumattomia äänen numerosta. Tämä arvo kirjoitetaan
      rekisteriin 3x3h.

      41h, DRAM DMA Control
      Bitti     Käyttö
      7         1= Siirrettävän datan ylin bitti käännetään.
      6         0 = Siirrettävä data on 8-bittistä.
                1= Siirrettävä data on 16-bittistä.
      5         1= DMA:n keskeytys sallitaan.
      4-3       DMA:n nopeuden jako
                00 = jako yhdellä
                01 = jako kahdella
                10 = jako kolmella
                11 = jako neljällä
      2         0 = jos DMA-kanava on 8-bittinen (kanavat 0-3)
                1 = jos DMA-kanava on 16-bittinen (kanavat 4-7)
      1         0 = kirjoitus
                1 = luku
      0         1= käynnistä

      42h, DMA Start Address

      DMA:n alkuosoite, mistä/minne dataa siirretään. Jos
      käytetään 8-bittistä DMA:ta, osoitteen pitää ollajaollinen
      16:lla. Käytettäessä 16-bittistä DMA:ta osoitteen pitää
      ollajaollinen 32:lla.

      43h, DRAM I/O Address (Low)

      44h, DRAM I/O Address (High)

      Rekistereillä määrätään osoite kortin muistissa, jonne
      portilla 3x7h kirjoitetaan/luetaan.

      45h, Timer Control
      Bitti     Käyttö
      7-4       Ei käytössä
      3         Salli ajastimen 2 keskeytys
      2         Salli ajastimen 1 keskeytys
      1-0       Ei käytössä

      46h, Timer 1 count

      Tähän laskuriin käyttäjä asettaa alkuarvon, jonka jälkeen
      ajastin lisää sitä 80 mikrosekunnin välein 255 asti, jolloin
      tulee keskeytys.

      47h, Timer 2 count

      Tähän laskuriin käyttäjä asettaa alkuarvon, jonka jälkeen
      ajastin lisää sitä 320 mikrosekunnin välein 255 asti,
      jolloin tulee keskeytys.

      48h, Sampling Frequency

      Arvo = 9878400/(16*(taajuus+2))

      49h,Sampling Control
      Bitti     Käyttö
      7         1= Digitoitavan datan ylin bitti käännetään.(unsigned-signed).
      6         Luku: DMA:n keskeytyksen aikana.
      5         1= DMA:n keskeytys sallitaan.
      4-3       Ei käytössä.
      2         0= jos DMA-kanava on 8-bittinen (kanavat 0-3)
                1= jos DMA-kanava on 16-bittinen (kanavat 4-7)
      1         0= mono
                1= stereo
      0         1= käynnistä

      4bh, Joystick Trim DAC

      Käyttäjän ohjelman ei ole syytä muuttaa tämän rekisterin arvoa.

      4ch, Reset
      Bitti     Käyttö
      7-3       Ei käytössä.
      2         0 = kaikki keskeytyksen kielletään
      1         0 = muunnin pois käytöstä
      0         0 = resetti

      Rekisterin kaikki bitit on syytä pitää asetettuna
      ohjelman aikana.
      Seuraavaksi esitellään rekisterit, jotka
      ovat yksillöllisiä kullekin äänelle. Äänen numero valitaan
      ensin rekisterillä 3x2h, jonka jälkeen tämä arvo
      kirjoitetaan rekisteriin 3x3h. Jos rekisterin arvo halutaan
      lukea, lisätään rekisteriin 80h. Esimerkiksi taajuuden luku
      tapahtuu rekisteristä 1+80h. Jotkin rekistereiden bitit ovat
      itsestään muuttuvia, mikä tarkoittaa, että GFl-piiri saattaa
      muuttaa bitin arvoa itse kesken soiton. Tällainen tilanne on
      esimerkiksi kaksisuuntaisella loopilla soitettu ääni. Nämä
      itsemuuttuvat bitit on merkitty tähdellä [*).

      0h,Voice Control
      Bitti     Käyttö
      *7        1= Wavetable IRQ kuittaus
      *6        0= ääntä soitetaan muistissa eteenpäin
                1= ääntä soitetaan muistissa taaksepäin
      5         1= salli Wavetable IRQ, kun ääni saavuttaa loppuosoitteen
      4         1= kaksisuuntainen looppi äänessä
      3         1= äänessä on looppi
      2         0= 8-bittinen ääni
                1=16-bittinen ääni
      1         1= pysäytä ääni väkisin
      0         1= ääni on pysähtynyt (luku)

      1h, Frequency Control
      Bitti     Käyttö
      15-10     Kokonaisosa
      9-1       Desimaaliosa
      0         Ei käytössä

      Rekisterillä määrätään desimaaliluku, joka lisätään äänen
      datan lukukohtaan taajuuden tahdissa. Näin voidaan
      esimerkiksi soittaa toisia ääniä nopeammin kuin muita.

      2h, Starting Address (High)
      Bitti     Käyttö
      15-13     Ei käytössä
      12-0      Äänen alkuosoitteen 13 ylintä bittiä

      3h, Starting Address (Low)
      Bitti     Käyttö
      15-9      Äänen alkuosoitteen 7 alinta bittiä
      8-5       Äänen alkuosoitteen desimaaliosa
      4-0       Ei käytössä

      4h, End Address (High)
      Bitti     Käyttö
      15-13     Ei käytössä
      12-0      Äänen loppuosoitteen 13 ylintä bittiä

      5h, End Address (Low)

      Bitti     Käyttö
      15-9      Äänen loppuosoitteen 7 alinta bittiä
      8-5       Äänen loppuosoitteen desimaaliosa
      4-0       Ei käytössä

      6h, Volume Ramp Rate
      Bitti     Käyttö
      7-6       Nopeus, jolla voimakkuutta muutetaan
      5-0       Arvo, jolla voimakkuutta muutetaan

      7h, Volume Ramp Start

      Bitti     Käyttö
      7-4       Exponent
      3-0       Mantissa

      8h, Volume Ramp End

      Bitti     Käyttö
      7-4       Exponent
      3-0       Mantissa

      9h, Current Volume

      Bitti     Käyttö
      15-12     Exponent
      *11       Mantissa
      3-0       Ei käytössä

      ah, Current Address (High)

      Bitti     Käyttö
      15-13     Ei käytössä
      12-0      Tämänhetkisen osoitteen 13 ylintä bittiä

      bh, Current Address (Low)

      Bitti     Käyttö
      15-9      Tämänhetkisen osoitteen 7 alinta bittiä
      8-0       Desimaaliosa

  #### ch, Pan Position

      Bitti     Käyttö
      7-4       Ei käytössä
      3-0       Panoroinnin paikka
                (0 = täysin vasen)
                (15 = täysin oikea)

  dh, Volume Control

      Bitti     Käyttö
      *7        Voimakkuuden muutoksen IRQ (luku)
      *6        Muutoksen suunta
                0 = lisääntyy
                1= vähenee
      5         1= keskeytys, kun voimakkuuden muutos saavuttaa lopun
      4         1= kaksisuuntainen looppi
      3         1= salli looppi
      2         1= tee keskeytys, mutta älä lopeta äänen soittoa
      1         1= lopeta voimakkuuden muutos väkisin
      *0        1= voimakkuuden muutos loppu (luku)

  #### eh, Active Voices

      Bitti     Käyttö
      7-6       Aseta ykkösiksi!
      5-0       Aktiivisten äänien määrä -1

  Tämä rekisteri ei ole yksilöllinen kullekin äänelle, vaan tällä määrätään aktiivisten äänien määrä. Mitä vähemmän ääniä, sitä parempaa soittotaajuutta voidaan käyttää. Minimimäärä on kuitenkin 14.

  #### 8fh, IRQ Status (vain luku)

      Bitti     Käyttö
      7         0 = lähde on Wavetable-keskeytys
      6         0 = lähde on voimakkuuden muutoskeskeytys
      5         1
      4-0       Keskeyttävän äänen numero (0-31)

  Tätä rekisteriä lukemalla saadaan selville ääni, joka aiheutti keskeytyksen. Usea ääni voi tehdä keskeytyksen yhtäaikaa. Tällöin luetaan rekisteriä, kunnes molemmat keskeytyslähteen bitit ovat ykkösiä.

  Esimerkissä 7.6 kirjoitetaan äänelle uusi osoite muistissa. Samalla periaatteella toimivat muutkin äänikohtaiset rekisterit.

  ##### Esimerkki 7.6. Graviksen äänikohtaisten rekisterien käyttö.

        pushf
        cli
        mov dx,[gus_base]         //perusosoite
        add dx,0x102              //portti 3x2h
        mov al,[sound]            //äänen numero
        out dx, al

        mov dx,[gus_base]         //perusosoite
        add dx,0x103              //portti 3x3h
        mov al, 0xa               //Rekisteri a
        out dx, al
        mov ax, [hi]              //ylimmät bitit
        inc dx                    //portti 3x4h
        out dx, ax
        dec dx                    //portti 3x3h
        mov al, 0xb               //Rekisteri b
        out dx, al
        mov ax, [low]             //alimmat 16 bittiä
        inc dx                    //portti 3x4h
        out dx, ax
        popf

  # Liite A Scan- ja Make-koodit

  ### Scan-koodit

  Seuraavat 102-näppäimistön scan-koodit saadaan keskeytyksen 16/10 tai 16/11 avulla. Keskeytys 16/0 tai 16/1 antaa eri arvojajoillekin näppäimille!

      Näppäin  Normaali    Shift       Ctrl        Alt
      A        1E61        1E41        1E01        1E00
      B        3062        3042        3002        3000
      C        2E63        2E42        2E03        2E00
      D        2064        2044        2004        2000
      E        1265        1245        1205        1200
      F        2166        2146        2106        2100
      G        2267        2247        2207        2200
      H        2368        2348        2308        2300
      I        1769        1749        1709        1700
      J        246A        244A        240A        2400
      K        256B        254B        250B        2500
      L        266C        264C        260C        2600
      M        326D        324D        320D        3200
      N        316E        314E        310E        3100
      O        186F        184F        180F        1800
      P        1970        1950        1910        1900
      Q        1071        1051        1011        1000
      R        1372        1352        1312        1300
      S        1F73        1F53        1F13        1F00
      T        1474        1454        1414        1400
      U        1675        1655        1615        1600
      V        2F76        2F56        2F16        2F00
      W        1177        1157        1117        1100
      X        2D78        2D58        2D18        2D00
      Y        1579        1559        1519        1500
      Z        2C7A        2CSA        2C1A        2C00

      1        0231        0221        -           7800
      2        0332        0340        0300        7900
      3        0433        0423        -           7A00
      4        0534        0524        -           7B00
      5        0635        0625        -           7C00
      6        0736        075E        071E        7D00
      7        0837        0826        -           7E00
      8        0938        092A        091B        7F00
      9        0A39        0A28        0A1D        8000
      0        0B30        0B29        -           8100

      -        OC2D        OCSF        0C1F        8200
      =        OD3D        OD2B        -           8300
      [        1ASB        1A7B        1A1B        1A00
      ]        1BSD        1B7D        1B1D        1B00
      ;        273B        273A        -           2700
      '        2827        2822        -           2800
      `        2960        297E        -           2900
      \        2BSC        2B7C        2B1C        2B00
      ,        332C        333C        3300        -
      .        342E        343E        3400        -
      /        352F        353F        3500        -

      F1       3B00        5400        5E00        6800
      F2       3C00        5500        5F00        6900
      F3       3D00        5600        6000        6A00
      F4       3E00        5700        6100        6B00
      F5       3F00        5800        6200        6C00
      F6       4000        5900        6300        6D00
      F7       4100        5A00        6400        6E00
      F8       4200        5B00        6500        6F00
      F9       4300        5C00        6600        7000
      F10      4400        5D00        6700        7100
      F11      8500        8700        8900        8B00
      F12      8600        8800        8A00        8C00

      Esc      011B        0118        0118        0100
      1/2      29F5        29AB        -           2900
      BackSpace    0E08    0E08        OE7F        0E00
      Tab      0F09        0F00        9400        A500
      Enter    1COD        1COD        1COA        1C00
      SpaceBar 3920        3920        3920        3920
      UpArrow  48E0        48E0        BDEO        9800
      Down Arrow   50E0    50E0        91EO        A000
      RightArrow   4DEO    4DEO        74E0        9D00
      LeftArrow    4BEO    4BEO        73E0        9B00
      Delete       53E0    53E0        93E0        A300
      Insert       52E0    52E0        92E0        A200
      Pg Dn        51E0    51E0        76E0        A100
      Pg Up        49E0    49E0        84E0        9900
      PrtScrn      -      -            7200        -

  #### Numeronäppäimistö (Num Lock ON)

      1        4F31        4F00        7500        -
      2        5032        5000        9100        -
      3        5133        5100        7600        -
      4        4B34        4B00        7300        -
      5        4C00        4C00        8F00        -
      6        4D36        4D00        7400        -
      7        4737        4700        7700        -
      8        4838        4800        8D00        -
      9        4939        4900        8400        -
      0        5230        5200        9200        -
      ,        532C        5300        9300        -
      Enter    EOOD        EOOD        EOOA        A600
      +        4E2B        4E2B        9000        4E00
      -        4A2D        4A2D        8E00        4A00
      *        372A        372A        9600        3700
      /        EO2F        EO2F        9500        A400

  ### Make-koodit

  Nämä näppäimistön make-koodit voidaan lukea keskeytyksessä 9h portista 60h.

      Näppäin   Make   Näppäin   Make
      A         1E     N         31
      B         30     0         18
      C         2E     P         19
      D         20     Q         10
      E         12     R         13
      F         21     S         1F
      G         22     T         14
      H         23     U         16
      I         17     V         2F
      J         24     W         11
      K         25     X         2D
      L         26     Y         15
      M         32     Z         2C

      1         02     -         OC
      2         03     =         OD
      3         04     [         1A
      4         05     ]         1B
      5         06     ;         27
      6         07     '         28
      7         08     `         29
      8         09     \         2B
      9         OA     ,         33
      0         OB     .         34
      /         35

      Backspace    OE  F1        3B
      Caps Lock    3A  F2        3C
      Enter        1C  F3        3D
      Esc          01  F4        3E
      Ctrl         1D  F5        3F
      Left Shift   2A  F6        40
      Alt          38  F7        41
      NumLock      45  F8        42
      RightShift   36  F9        43
      Scroll Lock  46  F10       44
      Space        39  F11       57
      Sys Req      54  F12       58
      Tab          OF

  #### NumLock OFF

      Up Arrow      48
      Down Arrow    50
      Right Arrow   4D
      Left Arrow    4B
      Delete        53
      Insert        52
      Pg Up         49
      End           4F
      Home          47
      Pg Down       51

  #### Numeronäppäimistö

      0 (Ins)         52
      1 (End)         4F
      2 (Down arrow)  50
      3 (PgDn)        51
      4 (Left arrow)  4B
      5               4C
      6 (Right arrow) 4D
      7 (Home)        47
      8 (Up arrow)    48
      9 (pgUp)        49
      . (Del)         53
      * (PrtSc)       37
      -               4A
      +               4E
      /               35
      Enter           1C

  # Liite B EMS- ja XMS-funktiot

  ### EMS-funktiot

  EMS-funktioita käytetään keskeytyksen 67h kautta, kuten luvussa "PC:n mu Zstiongelma?" on kerrottu. Numero on arvo, joka laitetaazi rekisteriin AH ennen keskeytyksen kutsua. Selitteen lopussa suluissa oleva numero kertoo EMS-version numeron, josta eteenpäin toiminto on olemassa. Virhekoodit palautetaan rekisterissä AH.

  #### Virhekoodit

        00 Ei virhettä
        80 Sisäinen virhe
        81 Hardware virhe
        83 Handle ei varattu
        84 Tuntematon toiminto
        85 Ei vapaita handleja
        86 Virhe mappauksessa
        87 Sivuja varattiin enemmän kuin niitä on olemassa
        88 Sivuja varattiin enemmän kuin niitä on jäljellä
        89 Yritettiin varata nolla sivua
        8A Virheellinen looginen sivunumero
        8B Virheellinen fyysinen sivunumero
        8C Määrittelyalue on täynnä
        8D Handle on jo tallennusalueella
        8E Handlelle ei ole tallennusaluetta
        8F Virheellinen alatoiminto
        90 Tuntematon attribuutin tyyppi
        91 Funktiota ei tueta
        92 OK, osa lähdealueesta on päällekirjoitettu
        93 Lähteen tai kohteen koko liian pitkä
        94 Perus- ja EMS-muistialueet ovat päällekkäin
        95 Lisä loogisen sivun sisällä ylittää sivun pituuden
        96 Alueen koko ylittää yhden megatavun
        97 Lähde- ja kohdealueilla on sama handle
        9B Lähteen tai kohteen tyyppi tuntematon
        9A Määriteltyä rekisterisettiä ei tueta
        9B Kaikki rekisterisetit varattu
        9C Määriteltyä rekisterisettiä ei tueta
        9D Tuntematon rekisterisetti
        9E Haluttuja DMA-kanavia ei tueta
        9F DMA-kanava ei tuettu
        A1 Päällekkäinen handlen nimi
        A2 0soitus yli perusmuistin
        A3 Sivukartan tiedot tuhoutuneet
        A4 Käyttöjärjestelmä kieltää käytön

      40h - Get EMM Status (3.0)
        Palauttaa:
           AH = 00 0K
              = Virhekoodi

      41h - Get Page Frame Base Address (3.0)
        Palauttaa:
           AH = 00 0K
              = Virhekoodi
           BX = EMS-sivujen segmentti

      42h - Get Page Counts (3.2)
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            BX = Vapaiden sivujen määrä
            DX = Sivujen määrä kaiken kaikkiaan

      43h - Get Handle and Allocate Pages (3.2)
        Annetaan:
            BX = Varattavien loogisten sivujen määrä
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            DX = EMM handle

      44h - Map Logical Page Into Physical Page Window (kaikki)
        Annetaan:
            AL = Fyysisen sivun numero (0-3)
            BX = Loogisen sivun numero
            DX = EMM handle
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      45h - Release Handle and Memory Pages (kaikki)
        Annetaan:
            DX = EMM handle
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      46h - Get EMM Version (kaikki)
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            AL = EMM version numero BCD-lukuna

      47h - Save Page Map Context (3.0)
        Annetaan:
            DX = EMM handle
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      48h - Restore Page Map Context (3.0)
        Annetaan:
            DX = EMM handle
        Palauttaa:
            AH = 00 0k
               = Virhekoodi

      4Bh - Get Handle Count (Kaikki)
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            BX = Aktiivisten handlejen määrä (0-256), 0 = EMS ei käytössä

      4Ch - Get Page Count for Handle (Kaikki)
        Annetaan:
            DX = EMM handle
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            BX = Handlelle varattujen sivujen määrä (1-512)

      4Dh - Get Page Count for All Handles (kaikki)
        Annetaan:
            ES:DI = Taulukon osoite, jonne tiedot halutaan
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            BX = Aktiivisten handlejen määrä (0-255)
            ES:DI = Osoitin taulukkoon:

              Lisä   Koko   Tarkoitus
              00     sana   Handlen numero
              02     sana   Sivujen määrä

      4Eh - Get/Set Page Map Context (3.2)
        Annetaan:
            AL = 00 Laita ikkunan tiedot taulukkoon
                    ES:DI = Taulukon osoite
            AL = 01 Ota ikkunan tiedot taulukosta
                    DS:SI = Taulukon osoite
            AL = 02 Laittaa ja ottaa tiedot
                    ES:DI = Kohdetaulukon osoite
                    DS:SI = Lähdetaulukon osoite
            AL = 03 Palauttaa tarvittavan taulukon koon
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            AL = Tavujen määrä (kun AL oli 3)
            ES:DI = Osoitin taulukkoon (kun AL oli 0 tai 2)

      4Fh - Get/Set Partial Page Map (4.0)
        Annetaan:
            AL = 00 Ota tiedot osasta sivukartasta
                    DS:SI = Osoitin taulukkoon, jossa on
                            lista halutuista sivuista
                    ES:DI = Osoitin taulukkoon, jossa tiedot ovat
            AL = 01 Aseta osa sivukartoista
                    DS:SI = Osoitin sivukartta-taulukkoon
            AL = 02 Ota sivukartan koko
                    BX = Mapattavien sivujen määrä
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            AL = Sivukartan koko (jos AL oli 2)

      50h - Map/Unmap Multiple Handle Pages (4.0)
        Annetaan:
            AL = 00 Mappaa sivuina
            AL = 01 Mappaa segmentteinä
            DX = EMM-handle
            CX = Tietojen määrä taulukossa
            DS:SI = Osoitin mappaus-taulukkoon
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      51h - Reallocate Pages (4.0)
        Annetaan:
            DX = EMM-handle
            BX = Varattavien sivujen määrä
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            BX = Varattujen sivujen määrä

      52h - Get/Set Handle Attributes (4.0)
        Annetaan:
            AL = 00 Ota handlen attribuutit
            AL = 01 Aseta handlen attribuutit
            AL = 02 Varmista mahdollisuus attribuutti-toimintoihin
            BL = Uusi attribuutti (jos AL on 1)
            DX = EMM-handle
        Palauttaa:
            AH = 00 0K
               = Virhekoodi
            AL = Attribuutti (jos AL oli 0)
                 00 handle on tuhoutunut
                 01 handle on kestänyt
            AL = Attribuutin ominaisuudet (jos AL oli 2)
                 00 Vain tuhoutuvat handlet on tuettu
                 01 Molemmat handlet on tuettu

      53h - Get/Set Handle Name (4.0)
        Annetaan:
            AL = 00 Ota handlen nimi
                    ES:DI = Osoitin 8-tavuiseen nimi-taulukkoon
            AL = 01 Aseta handlen nimi
                    DS:SI = Osoitin 8-tavuiseen nimi-taulukkoon
            DX = EMM-handle
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      54h - Get Handle Directory (4.0)
        Annetaan:
            AL = 00 Ota handlejen tiedot
                    ES:DI = Osoitin taulukkoon
            AL = 01 Eti handlea nimellä
                    DS:SI = Osoitin 8-tavuiseen nimi-taulukkoon
            AL = 02 Ota handlejen määrä
        Palauttaa:
            AL = Tietojen määrä taulukossa (jos AL oli 0)
            DX = Nimetyn handlen arvo (jos AL oli 1)
            BX = Handlejen määrä (jos AL oli 2)
            AH = 00 0K
               = Virhekoodi

      55h - Alter Page Map and Jump (4.0)
        Annetaan:
            AL = 00 Hyppy tapahtuu sivunumeron perusteella
            AL = 01 Hyppy tapahtuu segmentin perusteella
            DX = EMM-handle
            DS:SI = Osoitin tietotaulukkoon:

             Lisä      Koko       Tarkoitus
             00        4 tavua    Osoitin hypylle
             04        1 tavu     Kartta-taulukon tietojen määrä
             05        4 tavua    Osoitin kartta-taulukkoon:

             Lisä      Koko       Tarkoitus
             00        sana       Loogisen sivun numero
             02        sana       Fyysisen sivun numero (jos AL oli 0)
             02        sana       Fyysisen sivun segmentti (jos AL oli 1)

        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      56h - Alter Page Map and Call (4.0)
        Annetaan:
            AL = 00 Hyppy tapahtuu sivunumeron perusteella
                    DX = EMM-handle
                    DS:SI = Osoitin tietotaulukkoon
            AL = 01 Hyppy tapahtuu sivunumeron perusteella
                    DX = EMM-handle
                    DS:SI = Osoitin tietotaulukkoon
            AL = 02 Ota tarvittavan taulukon koko
        Palauttaa:
            BX = Tarvittava koko (jos AL oli 2)
            AH = 00 0K
               = Virhekoodi

      57h - Move/Exchange Memory Region (4.0)
        Annetaan:
            AL = 00 Kopioi muistialue
            AL = 01 Vaihda muistialueet
            DS:SI = Osoitin taulukkoon:

             Lisä      Koko       Tarkoitus
             00        4 tavua    Siirrettävien tavujen määrä
             04        1 tavu     Lähde: 0 = perusmuisti, 1 = EMS
             05        2 tavua    Lähteen handle
             07        2 tavua    Lähteen offset
             09        2 tavua    Lähteen sivunumero(4 = 1) tai segmentti(4 = 0)
             0B        1 tavu     Kohde: 0 = perusmuisti, 1 = EMS
             0C        2 tavua    Kohteen handle
             0E        2 tavua    Kohteen offset
             10        2 tavua    Kohteen sivunumero(4 = 1) tai segmentti (4 = 0)
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

      58h - Get Mappable Physical Address Array (4.0)
        Annetaan:
            AL = 00 Ota mapattujen sivujen tiedot taulukkoon
                    ES:DI = Osoitin taulukkoon
            AL = 01 Ota mapattavien sivujen määrä
        Palauttaa:
            CX = Taulukossa olevien tietojen määrä
            AH = 00 0K
               = Virhekoodi

      5Ah - Allocate Standard/Raw Pages (4.0)
        Annetaan:
            AL = 00 Varaa standardisivuja (vastaa toimintoa 43h)
            AL = 01 Varaa hardwaresta riippuvia sivuja
            BX = Varattavien sivujen määrä
        Palauttaa:
            DX = EMM-handle
            AH = 00 0K
               = Virhekoodi

      5Ch - Prepare Expanded Memory for Warm Boot (4.0)
        Palauttaa:
            AH = 00 0K
               = Virhekoodi

  ### XMS-funktiot

  Funktioita käytetään hyppäämällä keskeytyksen 2 Fh (AX=431 Oh) antamaan osoitteeseen. Funktion numero annetaan rekisterissä AH. Virhekoodit palautetaan rekisterissä BL.

  #### Virhekoodit

        Virhekoodit
      80   Toiminto ei ole käytössä
      81   VDISK asennettu
      82   A20 virhe
      8E   0hjainvirhe
      8F   Kriittinen ohjainvirhe
      90   HMA-aluetta ei käytössä
      91   HMA-alue varattu
      92   HMA-varaus liian pieni
      93   HMA-aluetta ei ole varattu
      94   A20 vapaana
      A0   XMS-muisti on loppu
      A1   XMS-handlet loppu
      A2   Väärä XMS-handle
      A3   Lähteen XMS-handle on väärä
      A4   Lähteen offset on väärä
      A5   Kohteen XMS-handle on väärä
      A6   Kohteen offset on väärä
      A7   Pituus on väärä
      AB   Lähde ja kohde ovat päällekkäin
      A9   Pariteettivirhe
      AA   XMS ei lukittu
      AB   XMS lukittu
      AC   Laskurin ylivuoto
      AD   XMS-lukitus epäonnistui
      B0   UMB liian suuri
      B1   UMB loppu
      B2   UMB-segmentti väärä

      00h - Get XMS Version Number
        Palauttaa:
            AX = Versionumero BCD-lukuna
            BX = Sisäinen versionumero
            DX = 0= Ei HMA:ta,1= HMA

      01h - Request High Memory Area (HMA)
        Annetaan:
            DX = Haluttavien tavujen määrä (F F F Fh)
        Palauttaa:
            AX = 1 OK

      02h - Release High Memory Area (HMA)
        Palauttaa:
            AX = 1 OK

      03h - Global Enable A20 Line
        Palauttaa:
            AX = 1 OK

      04h - Global Disable A20 Line
        Palauttaa:
            AX = 1 OK

      05h - Local Enable A20 Line
        Palauttaa:
            AX = 1 OK

      06h - Local Disable A20 Line
        Palauttaa:
            AX = 1 OK

      07h - Query A20 Line
        Palauttaa:
            AX = 1 OK

      08h - Query Free Extended Memory
        Palauttaa:
            AX = Suurin vapaana oleva blokki kilotavuina
                 0 Virhe
            DX = XMS-muistin määrä kilotavuina

      09h - Allocate Extended Memory Block
        Annetaan:
            DX = Koko kilotavuina
        Palauttaa:
            DX = Handle
            AX = 1 OK

      0Ah - Release Extended Memory Block
        Annetaan:
            DX = Handle
        Palauttaa:
            AX = 1 OK

      0Bh - Move Extended Memory Block
        Annetaan:
            DS:DI = Siirtotaulukon osoite:

             Lisä      Koko       Tarkoitus
             00        4 tavua    Siirrettävien tavujen määrä
             04        2 tavua    Lähteen handle (0=perusmuisti)
             06        4 tavua    Lähteen osoite
             0A        2 tavua    Kohteen handle (0=perusmuisti)
             0C        4 tavua    Kohteen osoite
        Palauttaa:
            AX = 1 OK

      0Ch - Lock Extended Memory Block
        Annetaan:
            DX = Handle
        Palauttaa:
            DX:BX = Blokin lineaarinen osoite
            AX = 1 OK

      0Dh - Unlock Extended Memory Block
        Annetaan:
            DX = Handle
        Palauttaa:
            AX = 1 OK

      0Eh - Get Handle Information
        Annetaan:
            DX = Handle
        Palauttaa:
            BH = 0=Alue vapaa, >1 = Alue lukittu
            BL = Vapaitten handlejen määrä
            DX = Alueen koko kilotavuina
            AX = 1 OK

      0Fh - Reallocate Extended Memory Block
        Annetaan:
            BX = Uusi koko kilotavuina
            DX = Handle
        Palauttaa:
            AX = 1 OK

      10h - Request Upper Memory Block (UMB)
        Annetaan:
            DX = Alueen koko kilotavuina
        Palauttaa:
            BX = Saadun alueen segmentti
            DX = Palikan saatu koko kilotavuina
            AX = 1 OK

      11h - Release Upper Memory Block IUMB1
        Annetaan:
            DX = Alueen segmentti
        Palauttaa:
            AX = 1 OK

  # Liite C Käskyjen suoritusajat

  Tähän liitteeseen on koottu reaalitilan käskyjen viemät ajat 386- ja 486- prosessoreilla sekä niidet vaikutukset lippuihin.

  #### Liput

        0 - Overflow
        D - Direction
        I - Interrupt
        T - Trap
        S - Sign
        Z - Zero
        A - Auxiliary
        P - Parity
        C - Carry

        * lipun asento muuttuu käskyn mukaan.
        ? lipun asento on epämääräinen käskyn jälkeen.
        0 lippu nollautuu käskyn jälkeen.
        1 lippu asettuu käskyn jälkeen.
        - tarkoittaa, että lipun asento pysyy samana käskyn jälkeen

  "Koko" on käskyn viemän konekoodin tavumäärä. Luvut "386" ja "486"merkintöjen alla ovat käskyn viemät ajat kellojaksoina. Jos luvun tilalla on -, niin käsky ei ole mahdollinen ko. prosessorissa. "Operandi" on käs* kyn saamat operandit, esimerkiksi MOV: reg,reg voi olla MOV AX,BX.

  Ajassa olevat lisämerkinnät:

      n - toistojen määrä
      m - seuraavan käskyn viemä tavumäärä

      AAA - Ascii Adjust for Addition
        Liput : O D I T S Z A P C
                ? - - - ? ? * ? *
        Operandi Koko 386 486
        -        1    4   3

      AAD - Ascii Adjust for Division
        Liput : O D I T S Z A P C
                ? - - - * * ? * ?

  # Liite D I/O-osoitteet

  Tärkeimpien piirien osoitteet.

      Osoite       Piiri

      000-OOF      DMA-ohjain 8237.

                   000 Kanavan 0 osoiterekisteri
                   001 Kanavan 0 laskuri
                   002 Kanavan 1 osoiterekisteri
                   003 Kanavan 1 laskuri
                   004 Kanavan 2 osoiterekisteri
                   005 Kanavan 2 laskuri
                   006 Kanavan 3 osoiterekisteri
                   007 Kanavan 3 laskuri
                   008 Tila/komentorekisteri
                   009 Sallii/estää kanavan keskeytyspyynnön
                   OOA Sallii/estää kanavan käytön
                   OOB Moodi-rekisteri
                   OOC Nollaa MSB/LSB kiikku
                   OOE Sallii kaikkien DMA-kanavien toiminnan
                   OOF Sallii/estää kaikkien kanavien käytön

      020-02F      Ohjelmoitava keskeytyspiiri 8259A (PIC). Isäntäpiiri.

                   020 8259 Komentorekisteri
                   021 8259 Keskeytyksen sallinta/estorekisteri

      040-05F      Ohjelmoitava ajastin piiri 8254 (PIT).

                   040 Ajastin 0, 18,2 kertaa sekunnissa
                   tapahtuva keskeytys
                   041 Ajastin 1, RAM-muistin virkistys
                   042 Ajastin 2, piipperi
                   043 Ajastimien ohjausrekisteri

      060-06F      Näppäimistön ohjauspiiri 8042.

                   060 Näppäimistön datarekisteri (luku/kirjoitus)
                   061 Ohjainportti (ryhteensopivuus piirin 8255 kanssa)
                   064 Näppäimistön komentorekisteri

      070          CMOS RAM/RTC, myös NMI-keskeytyksen poisto/sallinta

      071          CMOS RAM -data-rekisteri

      080-090      DMA sivurekisteri. Sisältää 4 ylintä bittiä 20-
                   bittisestä osoitteesta (AT:ssa 8 ylintä bittiä, koska
                   osoiteväylä on 24 bittiä).

                   081 Ylimmät 4 bittiä DMA kanavan 2 osoitteesta
                   082 Ylimmät 4 bittiä DMA kanavan 3 osoitteesta
                   083 Ylimmät 4 bittiä DMA kanavan 1 osoitteesta
                   087 Ylimmät 4 bittiä DMA kanavan 0 osoitteesta
                   089 Ylimmät 5 bittiä DMA kanavan 6 osoitteesta
                   08a Ylimmät 5 bittiä DMA kanavan 7 osoitteesta
                   08b Ylimmät 5 bittiä DMA kanavan 5 osoitteesta
                   08f Ylimmät 5 bittiä DMA kanavan 4 osoitteesta

      0A0-OBF      Toinen ohjelmoitava keskeytyspiiri 8259 (PIC).

                   OAO Komentoportti
                   OA1 Keskeytyksen sallinta/estorekisteri

      OCO-ODF      Toinen DMA-ohjain 8237.

                   OCO Kanavan 4 osoiterekisteri
                   OC2 Kanavan 4 laskuri
                   OC4 Kanavan 5 osoiterekisteri
                   OC6 Kanavan 5 laskuri
                   OC8 Kanavan 6 osoiterekisteri
                   OCA Kanavan 6 laskuri
                   OCC Kanavan 7 osoiterekisteri
                   OCE Kanavan 7 laskuri
                   ODO Tila/komentorekisteri
                   OD2 Sallii/estää kanavan keskeytyspyynnön
                   OD4 Sallii/estää kanavan käytön
                   OD6 Moodi-rekisteri
                   OD8 Nollaa MSB/I SB-kiikku
                   ODA Sallii kaikkien DMA-kanavien toiminnan
                   ODC Sallii/estää kaikkien kanavien käytön
                   ODE Sallii/estää kanavavien käytön

      0F0-OFF      Matematiikkaprosessori

      110-1EF      Systeemin I/O -kanava

      170-17F      Kiintolevy 1 (AT)

      1F0-1FF      Kiintolevy 0 (AT)

      200-20F      Joystick

      210-217      Laajennuskortti (XT)

      21F          Varattu

      220-26F      Varattu I/O-kanavia varten

      270-27F      Toinen rinnakkaisportti

      280-2AF      Varattu I/O-kanavia varten

      2A2-2A3      M5M58321RS-kello

      2B0-2DF      EGA

      2E2-2E3      Data acquisition adapter (AT)

      2E8-2EF      COM4

      2F0-2F7      Varattu

      2F8-2FF      COM2 UARTilla

      320-32F      Kiintolevyohjain (XT)

      330-33F      Varattu

      340-35F      Varattu I/O-kanavia varten

      360-36F      PC-verkko

      370-377      Levyasemaohjain

      378-37F      Rinnakkaisportti

      380-38F      SDLC-sovitin

      3A0-3AF      Toinen SDLC-sovitin

      3B0-3BF      MDA

      3C0-3CF      EGA/VGA

      3D0-3DF      CGA

      3E8-3EF      COM3

      3F0-3F7      Levyasemaohjain

      3F8-3FF      COM 1 UARTilla

  # Liite E Hiiren keskeytysfunktiot

      INT 33,0 Hiiren nollaus ja testaus.
        Annetaan :
           AX = 00
        Palauttaa:
           AX = 0000, jos ajuria ei ole asennettu.
                FFFF, jos ajuri on asennettu.
           BX = painikkeiden lukumäärä.

      INT 33,1 Näytä hiiren kursori.
        Annetaan:
           AX = 01

      INT 33,2 Piilota hiiren kursori.
        Annetaan :
           AX = 02

      INT 33,3 Lue hiiren sijainti ja nappien tilat.
        Annetaan :
           AX = 03
        Palauttaa:
           CX = X-koordinaatti (0-639)
           DX = Y-koordinaatti (0-199)
           BX = nappien tilat:
        Bitit ovat: 0 = vasen nappi ja 1= oikea nappi.
      INT 33,4 Aseta hiiren kursorin sijanti kuvaruudulla.
        Annetaan:
        AX=4
           CX = X-koordinaatti
           DX = Y-koordinaatti

      INT 33,5 Lue informaatio nappien painalluksista.
        Annetaan :
           AX = 5
           BX = 0 vasen nappi
                1 oikea nappi
        Palauttaa:
           BX = ilmoittaa kuinka monta kertaa nappia on
                painettu (0-32767).
           CX = X-koordinaatti sillä hetkellä, kun viimeksi
                painettiin nappia.
           DX = Y-koordinaatti sillä hetkellä, kun viimeksi
                painettiin nappia.
           AX = tila. Bitit ovat: 0=vasen nappija
                                  1=oikea nappi.
           Bittien sisällön ollessa yksi, nappia painetaan.

      INT 33,6 Lue informaatio nappien vapautuksista.
        Annetaan:
           AX = 6
           BX = 0 vasen nappi
                1 oikea nappi
        Palauttaa:
        Samat tiedot kun keskeytyksessä 33,5.

      INT 33,7 Asettaa hiiren X-suunnan minimin ja maksimin.
        Annetaan:
           AX = 7
           CX = minimiarvo
           DX = maksimiarvo

      INT 33,8 Asettaa hiiren Y-suunnan minimin ja maksimin.
        Annetaan:
           AX = 8
           CX = minimiarvo
           DX = maksimiarvo

      INT 33,9 Aseta hiiren grafiikkakursori.
        Annetaan:
           AX = 9
           BX = vaakasuora piste (-16-16)
           CX = pystysuora piste (-16-16)
           ES:DX = osoitin näyttöön ja kursorin maskiin

      INT 33,A Aseta hiiren tekstikursori.
        Annetaan:
           AX = OA
           BX = 00 ohjelmallinen kursori
                01 hardware-kursori
           CX = maskin aloitusosoite
           DX = maskin lopetusosoite

      INT 33,B Lue hiiren potentiometrin arvo.
        Annetaan :
           AX = OB
        Palauttaa:
           CX = vaakasuora arvo (-32768-32767)
           DX = pystysuora arvo (-32768-32767)

      INT 33,F Aseta hiiren "mickey"-arvo. Mickey on normaalisti
        1 /200 tuumaa.
        Annetaan:
           AX = OF
           CX = vaakasuora arvo (1-32767, vakio 8)
           huom! ylin bitti nollaksi
           DX = pystysuora arvo (1-32767, vakio 16)
           Huom! ylin bitti nollaksi

      INT 33, 1A Aseta hiiren herkkyys.
        Annetaan:
           AX = 1A
           BX = vaakasuoria koordinaatteja pikseliä kohti
           CX = pystysuoria koordinaatteja pikseliä kohti

      INT 33,1B Lue hiiren herkkyys.
        Annetaan:
           AX = 1B
        Palauttaa:
           BX = vaakasuoria koordinaatteja pikseliä kohti
           CX = pystysuoria koordinaatteja pikseliä kohti

      INT 33,1D Aseta hiiren CRT-sivu.
        Annetaan:
           AX = 1D
           BX = CRT-sivunumero

      INT 33,1E Lue hiiren CRT-sivu.
        Annetaan:
           AX = 1E
        Palauttaa:
           BX = CRT-sivun, jossa kursori sijaitsee

      INT 33,1F Poista ajuri käytöstä.
        Annetaan:
           AX =1 F
        Palauttaa:
           AX = 001F, jos onnistui
                FFFF, jos virhe
           ES:BX = edellinen INT 33 -osoite

      INT 33,20 Aseta ajuri käyttöön.
        Annetaan:
           AX = 20h

      INT 33,21 Nollaa hiiren ohjelmisto.
        Annetaan:
           AX = 21 h
        Palauttaa:
           AX = 0021 hiiren ajuria ei ole asennettu
                FFFF hiiren ajuri asennettu
           BX = 2 hiiren ajuri asennettu

      INT 33,24 Lue ajurin versio, hiiren tyyppi ja IRQ:n numero.
        Annetaan:
           AX = 24h
        Palauttaa:
           BH = hiiren versio esim. 6
           BL = hiiren versio esim. 1. Tällöin versio on 6.1
           CH = hiiren tyyppi
                1 väylähiiri
                2 sarjahiiri
                3 InPort-hiiri
                4 PS/2-hiiri
                5 Hewlett Packard -hiiri
           CL = IRQ:n numero.
                0 PS/2
                2 IRQ 2
                5 IRQ 5
                7 IRQ 7

  # Liite F
  Kirjallisuutta

  Programmer's Guide to the EGA and VGA Cards, 3rd edition
  (Richard F.Ferraro, Addison-Wesley) ISBN 0-201-57025-4

  Kirjassa käsitellään kaikki EGA/VGA-rekisterit bitti bitiltä. Lisäksi kirjassa kerrotaan yleisimpiä algoritmeja muun muassa viivojen ja ympyröiden piirtoon. Toinen painos sisältää tiedot SVGA-korteista.

  Advanced Assembly Language
  (Allen L.Wyatt, QUE) ISBN 1-56529-037-2

  Kirjassa kerrotaan monista tärkeistä perusasioista, kuten näppäimistöstä, hiirestä, keskeytyksistä ja lisämuistien käytöstä. Lisäksi kirja käsittelee resident-ohjelmien tekoa, ajureita ja virhetilanteita. Kirjan lopussa on kattavat liitteet muun muassa hiiren fuktioista, EMS-funktioista ja DPMI-funktioista. Esimerkit on tehty MASM-kääntäjällä.

  DOS and Windows Protected Mode
  (Al Williams, Addison Wesley) ISBN 0-201-63218-7

  Kirja kertoo suojatun tilan ohjelmoinnista erilaisten DOS-extenderien kanssa. DPMI on selitetty perinpohjin. Yleistä asiaa suojatusta tilasta sekä DOS- että Windows- ympäristössä on runsaasti.

  Zen of Code Optimization
  (Michael Abrash, Coriolis Group Books) ISBN 1-883577-03-9

  Kirja niille, jotka haluavat tehdä nopeaa koodia koneellaan. Kirjassa käsitellään prosessorit aina 8088:sta Pentiumiin. Kirja esittelee dokumentoimatonta tietoa prosessorien käskyjen viemistä ajoista. Pentiumin liukuhihnatekniikka on selitetty perusteellisesti ohjelmoijan näkökulmasta. Lisäksi kirja sisältää muutamien algoritmien optimointia. Suosittelemme edistyneelle ohjelmoijalle.

  PC Pintaa Syvemmältä
  (Aki Korhonen, Erikoislehdet Oy/Tecnopress) ISBN 951-832- 033-0

  Hyvä suomenkielinen kirja koneen hallinnasta BIOS- tasolla. Sisältää muun muassa tietoa näytönohjaimista, näppäimistöstäja kiintolevystä. Lopussa kattavat liitteet DOS- ja BIOS-keskeytyksistä.

  286/386-ohjelmointi
  (Käännöskirja, Pagina)

  Kirjassa käydään kaikki 286/386 käskyt läpi erittäin tarkasti

  C++ Ohjelmointi
  (Stephen Prata, Pagina)

  Eräs monista C-kieltä opettavista kirjoista. Tarkoitettu lähinnä itseopiskeluun. Perusasiat saattavat jäädä aloittelijalle hieman epäselväksi. C++-kielen ominaisuuksia käsitellään hyvin.

  # Sanasto

  102-näppäimistö

  Nimitys nykyiselle PC:n näppäimistölle. Kyseistä näppäimistöä alettiin käyttää AT-koneista alkaen.

  ADC

  Analog to Digital Conversion eli muutos analogisesta signaalista tietokoneelle sopivaan digitaaliseen muotoon. Tällöin signaalin amplitudiarvo muutetaan numeroksi. Käytetty taajuus ja bittien määrä määrää digitointituloksen laadun.

  ASP

  Advanced Signal Processor. Ohjelmoitavissa oleva signaaliprosessori. Käytetään esimerkiksi AWE32- äänikortissa.

  BC

  Borland C. Borlandin valmistama C-kielikääntäjä.

  Bittitaso

  Näyttömuistin osasta käytettävä nimitys. Näyttömuisti koostuu neljästä bittitasosta. Useissa näyttötiloissa näiden bittitasojen päällekkäisten bittien kombinaatiot määräävät kyseisen pikselin värin.

  DAC

  Digital to Analog Conversion eli muutos digitaalisesta signaalista esimerkiksi korvin kuultavaksi ääneksi.

  DMA

  Direct Memory Access. Koneeseen mikropiireillä rakennettu toiminta, jolla mahdollistetaan tiedonsiirto samaan aikaan kun prosessori suorittaa käskyjä muistissa. DMA:ta käytetään esimerkiksi äänidatan siirtämiseen äänikortin muuntimelle soitettavaksi. Ääniefekti soi taustalla, vaikka prosessori suorittaisi esimerkiksi laskuoperaatioita. PC:ssä käytetään eri DMA-kanavia, jolloin voidaan siirtää useita tietoja yhtä aikaa. Siksi esimerkiksi äänikortissa pitää valita DMA-kanava jota käytetään tiedon siirtoon.

  DSP

  Digital Signal Processor. Esimerkiksi äänikorteissa käytettävä signaaliprosessori, joka kykenee nopeisiin äänenkäsittelyoperaatioihin.

  EMM386

  Ohjain, jolla mahdollistetaan EMS-muistin käyttö DOS-ohjelmissa.

  EMS

  Katso laajennettu muisti.

  FM-synteesi

  Tapa, jolla esimerkiksi AdLib-tuottaa äänet. Tarkempaa tietoa Ääni-luvussa.

  FPU

  Floating Point Unit, eli liukulukuyksikkö, tuttavallisemmin matematiikkaprosessori. Tämä prosessori mahdollistaa nopeat liukulukulaskut kuten trigonometriset funktiot. 486- ja Pentium-prosessoreissa FPU on sisäänrakennettu, muissa se pitää asentaa sille varattuun paikkaan.

  General MIDI

  Soitinvalmistajien välinen standardi, jossa soitinäänille on määritelty tiettyjärjestys. Näin eri GM-yhteensopivat äänikortit tai syntetisaattorit osaavat kommunikoida keskenään oikein. Kun musiikki tehdään GM-soittimilla, kuulostaa musiikki kaikissa GM-soittimissa suurin piirtein samalta. Toki esimerkiksi toisen kortin viuluääni saattaa olla paljon paremman kuuloinen kuin toisen, mutta äänen paikalla soi kuitenkin viulu, joksi ääni oli tarkoitettukin.

  Himem

  Ohjain, jolla mahdollistetaan XMS-muistin käyttö DOS- ohjelmissa.

  Indeksirekisterit

  Nimitys PC:n rekistereille ESI, EDI, EBP ja ESP.

  IRQ

  Katso keskeytys.

  Jatkettu muisti

  Suomennos XMS-muistille. Muistityyppi, jolla kasvatetaan PC:n muistiavaruutta yli yhden megatavun. Tällöin muistia siiretään perusmuistinjajatketun muistin välillä.

  Kaksoisbufferointi

  Menetelmä, jossa näyttömuisti onjaettu kahteen eri osaan. Kun toinen osa näkyy ruudulla, toiseen osaan piirretään grafiikkaa, joka näytetään seuraavalla kuvaruudun piirtokerralla. Tällä menetelmällä estetään kuvaruudussa liikkuvien objektien ikävä välkyntä.

  Keskeytys

  Toiminta, jolla voidaan keskeyttää prosessorin normaali suoritus. Kun prosessori saa keskeytyksen, siirtyy ohjelman suoritus keskeytysvektorin osoittamaan muistipaikkaan. Esimerkiksi äänikortti voi tehdä keskeytyksen kun se on soittanutjonkin äänen loppuun. Prosessori suorittaa tehtäviään tietämättä, että yhtä aikaa soi ääni. Kun äänikortti antaa keskeytyksen, siirtyy prosessori keskeytysohjelmaan, joka voi esimerkiksi lopettaa äänen soiton. Tämän jälkeen ohjelman suoritustajatketaan täsmälleen siitä kohdasta, jossa suoritus oli ennen keskeytyksen tuloa. PC:ssä on useita keskeytyksiä yhtä aikaa, tämän vuoksi esimerkiksi äänikortissa pitää valita sen keskeytyksen numero, jota käytetään.

  Komentorekisteri

  Nimitys PC:n EIP-rekistereille.

  Laajennettu muisti

  Suomennos EMS-muistille. Muistityyppi, jolla kasvatetaan PC:n muistiavaruutta yli yhden megatavun. Tällöin perusmuistissa näkyy kerrallaan maksimissaan 64 kilotavun pala laajennettua muistia. Se, mikä kohta laajennetusta muistista näkyy perusmuistissa voidaan määrittää.

  Linkkeri

  Ohjelma (esimerkiksi TLINK), jolla yhdistetään käännöksistä syntyneet objektitiedostot yhdeksi ajettavaksi tiedostoksi.

  Lippu

  Lipulla ilmoitetaan prosessorille, esimerkiksi että suoritetun vähennyslaskun tulos oli nolla. Liput ovat:

          O - Ylivuoto
          D - Suunta
          I - Keskeytys
          T - Trap
          S - Etumerkki
          Z - Nolla
          A - 4 bitin lainaus
          P - Pariteetti
          C - Lainaus

  Local Bus

  Katso paikallisväylä.

  LSB

  Least Significant Bit eli vähiten merkitsevä bitti. Esimerkiksi tavun bitti nolla.

  MASM

  Microsoft Assembler. Microsoftin valmistama konekielikääntäjä.

  Mode X

  Katso tweaked-tila.

  MSB

  Most Significant Bit eli eniten merkitsevä bitti. Esimerkiksi tavun bitti seitsemän.

  OPL2, OPL3

  Piiri, joka tekee äänet FM-synteesillä. OPL2 on vanhempi ja ominaisuuksiltaan huonompi kuin OPL3.

  Optimointi

  Nimitys toimenpiteestä, jolla ohjelmakoodia muutetaan esimerkiksi nopeammaksi. Ohjelmakoodi voidaan optimoida myös viemään vähemmän muistia.

  Paikallisväylä

  ISA-väylän (normaali PC:n laajennuskorttipaikka) rinnalle kehitetty korttipaikka. Kun ISA-väylä on maksimissaan 16-bittinen on paikallisväylä 32-bittinen, jolloin tiedonsiirto on huomattavasti nopeampaa. Nykyään paikallisväylät ovat VESA-yhteensopivia, jolloin niihin käy mikä tahansa VESA-yhteensopiva näyttökortti. Paikallisväylään on saatavissa myös muun muassa kiintolevyohjaimia. Uusinta tekniikkaa edustavat niin sanotut PCI-väylät.

  PCX

  Kuvaformaatti, jota esimerkiksi Deluxe Paint ja Paint Brush käyttävät.

  Perusmuisti

  PC:n muistiavaruuden ensimmäinen megatavu. Tästä megatavusta on vapaana ohjelmille 640 kilotavua, josta vapaaksi jää erilaisten ajureiden latauksien jälkeen tyypillisesti noin 600 kt (MS-DOS 5.0). Loput megatavusta ovat muun muassa näyttökortin ja BIOSin käytössä.

  Perusosoite

  Perusosoite (Base Address) on osoite I/O-avarizudessa, josta eteenpäin esimerkiksi äänikortin signaaliprosessorin portit sijaitsevat. Näihin osoitteisiin kirjoittamalla voidaan ohjata erilaisia toimintoja. Koska PC:ssä voi olla monta korttia yhtä aikaa, pitää jokaiselle kortille valita perusosoite, jossa se sijaitsee.

  PIC

  Programmable Interrupt Controller. PC:n keskeytyksiä ohjaava piiri.

  PIT

  Programmable Interval Timer. PC:n ajastukset hoitava piiri.

  Protected mode

  Katso suojattu tila.

  Segmenttirekisterit

  Nimitys PC:n rekistereille CS, DS, ES, FS, GS ja SS.

  SIMM

  Nimitys muistipalikalle, jolla kasvatetaan PC:n muistia. SIMM-paloja on esimerkiksi 1/4, 1, 4, 8 ja 16 megatavun kokoisia. Niitä valmistetaan sekä vanhassa 8/9 (joita käytetään varsinkin VLB- ja ISA- koneissa) että uudemmassa 32/36 (uudet PCI-koneet tukevat vain sellaista muistia) bittisessä muodossa. On myös mainittava että oli niin sanottuja SIPP-muistipalikkojakin, niitä on käytetty pääasiassa 286-koneissa eikä niitä enää valmisteta.

  Suojattu tila

  Prosessorin toimintatila, joka on mahdollinen 386- prosessorilla ja paremmilla (varauksin myös 286- prosessorilla). Tässä tilassa koko PC:n muistiavaruus näkyy käyttäjälle lineaarisena (jatkuvana), jolloin 64 kilotavun segmentointia ei tarvita. Tämän tilan ohjelmointiin on kehitetty aivan uusia ohjelmointiympäristöjä, kuten Watcom C.

  SVGA

  Näyttöstandardi, joka tukee suurempia resoluutioitaja värimääriä kuin tavallinen VGA. Esimerkiksi 640x480 256 värillä on tyypillinen SuperVGA-tila. Jokaisen valmistajan SVGA-kortit ovat rekisterirakenteeltaan erilaisia, joten kaikkien korttien tukeminen on hankalaa.

  TASM

  Turbo Assembler. Borlandin valmistama konekielikääntäjä.

  Tweaked-tila

  VGA:n näyttötila, jossa koko 256 kilotavun näyttömuisti saadaan tehokkaasti käyttöön. Katso tarkemmin Grafiikka- luvusta.

  VESA

  Video Electronics Standards Association. Koska eri valmistajien SVGA-kortit ovat täysin erilaisia ohjelmoida, määriteltiin niin sanottu VESA-standardi. VESA-standardi on rajapinta ohjelmoijan ja näyttökortin välissä. Standardi määrittelee tiettyjä funktioita, jotka tekevät saman asian kaikissa eri VESA-yhteensopivissa näyttökorteissa. Yleensä toiminnot suorittaa VESA-BIOS, joten toiminnot ovat melko hitaita.

  VLB

  Katso paikallisväylä.

  WAV

  Ääniformaatti, jota käyttää esimerkiksi Windows.

  XMS

  Katso jatkettu muisti.

  Yleisrekisterit

  Nimitys PC:n rekistereille EAX, EBC, ECX ja EDX.