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.
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.
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.
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.
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
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.
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
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.
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ö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.
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.
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.
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ä.
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.
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.
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.
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.
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.
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
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.
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.
; 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ää.
mov cx,1000 ; Silmukka suoritetaan 1000 kertaa silmu: mov [es:di],0 inc di dec cx jnz silmu ret
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.
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
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
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.
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.
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
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.
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ä.
_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.
#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.
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.
#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; }
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.
#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.
#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.
// // 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(); }
; ; 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.
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.
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.
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.
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.
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ää.
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 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ä.
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ää.
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
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ä.
; ; 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 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.
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.
; ; 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.
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ä.
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.
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.
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
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:
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).
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
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.
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.
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-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.
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ä.
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.
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.
Portti Toiminta 40H Ajastin 0 (laskuri) 41H Ajastin 1 (laskuri) 42H Ajastin 2 (laskuri) 43H Asetusrekisteri (ajastimet 0,1,2)
Ajastimien käyttöä hankaloittaa niiden vähyys. Toiseksi jokaiselle on jo ennestään keksitty käyttöä. Kuvassa 4.4 on eritelty ajastimet ja toiminnot.
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.
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ä
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ö.
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:
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.
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
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ö.
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.
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.
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.
#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 }
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.
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.
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ä.
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
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.
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
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
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 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
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.)
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.
; 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 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.
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.
; 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.
; 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
; 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
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.
; 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.
; 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
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.
jossa,fread (void *buffer, koko, määrä, tiedosto); fwrite (void *buffer, koko, määrä, tiedosto);
#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.
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.
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.
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ä).
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
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.
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.
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.
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.
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'
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.
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.
Esimerkki 6.3 asettaa ruudun vasempaan yläkulmaan pikselin, jonka värinumero on 100 ja seuraavan rivin alkuun pikselin, jonka värinumero on 255.
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-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.
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.
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.
;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.
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.
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.
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.
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.
VR DE Tapahtuma ruudulla
0 0 Ruutua piirretään
1 1 Elektronisuihkun siirto alhaalta ylös menossa
0 1 Elektronisuihkun siirto oikealta vasemalle menossa
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.
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
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.
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.
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.
Bitit Nimi 7-0 SAH SAH Näytettävän ruudun osoitteen kahdeksan ylintä bittiä.
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.
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
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).
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.
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.
Bitit Nimi 7-0 LCLC 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ä.
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
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.
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.
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.
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.
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.
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.
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 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-
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ä!
; 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
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.
#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.
#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 } }
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.
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.
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.
#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 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ä.
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).
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.
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 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); }
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.
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.
#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: }
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.
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.
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.
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.
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ä.
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
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ä
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ä
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
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
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
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
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ä.
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ä.
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
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.
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
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.
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.
//- //-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 } }
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.
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.
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.
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
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.
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ä.
//- //- 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;aEsimerkki 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 ENDGravis 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 RegisterLukemalla 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ähetys3x2h, 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= mono1= 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 muutetaan7h, Volume Ramp Start
Bitti Käyttö 7-4 Exponent 3-0 Mantissa8h, Volume Ramp End
Bitti Käyttö 7-4 Exponent 3-0 Mantissa9h, 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
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)
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.
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.
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
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 -
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
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
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
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
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.
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
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 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
Tähän liitteeseen on koottu reaalitilan käskyjen viemät ajat 386- ja 486- prosessoreilla sekä niidet vaikutukset lippuihin.
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 ? - - - * * ? * ?
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
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
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.
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.