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.

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.