☰ Menu

Scene.hu

Magyar demoscene portál – grafikusok, zenészek, programozók alkotói közössége

atari vcs (atari 2600)Újra itt! Közben Karácsony lett és Szilveszter is közeleg itt az új év, így mindenkinek lehet ideje egy kis olvasgatásra. Talán már a Harmony Cart is ott figyel a fa alatt egyeseknél, míg másoknál a gyerekek már a vadonatúj Jr-on verik egymás score-jait laposra.

Aki nem olvasta az előző részt annak itt:
Csókolom! Stella lejöhet játszani? part1

Mielőtt belekezdenénk a  hardcore kódolásba, egy kicsit nézzük át hogyan is jön létre egy frame a képernyőn és hogyan kell olyan kernel kódot írni ami ezzel összhangban van. Ez azoknak akik már kódoltak hasonló korú gépekre, ismerős lehet, de adjunk esélyt az újoncoknak is. A cikk végén kicsit kitérek a Stella run-time debug funkciójára is, mivel kihagyhatatlanul hasznos. Igyekszem csak a programozás szempontjából fontos dolgokat tárgyalni – a teljes sztorit mindig megtaláljátok a Stella Programmer’s Guide-ban. Terv szerint nem leszek végletesen szájbarágós. Ez természetesen a cikksorozat többi részére is érvényes – és bármikor megszeghető – kijelentés. :) Na vágjunk bele!

A TIA TV jele

Itt most tulajdonképpen arról van szó, hogy a sugárnyaláb végigpásztázza a képernyőt – balról-jobbra, fentről-lefelé – és ennek a technikai megvalósításához különböző időzítési és vezérlési trükköket kellett kitalálni. Ezek alapján a frame rajzolását több szakaszra bontjuk:

  • Az első teendő, hogy jelezzük a TVnek, hogy kezdjen egy új frame-et. Ezt nevezzük VSync-nek.
  • Ezután megvárjuk, hogy a bal-felső sarokba pozicionáljon a sugár. Ez az idő a VBlank – ilyenkor nincs bekapcsolva a sugár, tehát nem rajzolunk.
  • Majd bekapcsoljuk a sugarat és elkezdjük rajzolni a sorokat amik a képet adják. Minden sor után van egy kis idő amíg a sugár visszatér a következő sor elejére. Ezt az időt HBlank néven ismerjük.
  • Végül amint kirajzoltuk a képet, következik néhány sor ráhagyás amit overscan-nek hívunk. Ez utóbbi azért kellett valamikor, mert nem volt teljesen egyforma az összes TV kalibrációja – mondhatjuk, hogy nem igazán létezett szigorú szabvány. (Plusz tuti használták pár extra dologra, de ehhez nem értek – viszont pl erre is jó a comment rész a cikk alatt. ;) ) Akinek olyan a TVje – vagy tudja a gyári beállító menü kódját – nyugodtan kiírhatja erre a részre, hogy “Szeretlek Bözsi!” és a képet felfelé eltolva látszani fog.
  • Ezzel készen is van a frame, küldhetjük a jelet, hogy jöhet a következő.

Ha emlékeztek, említettem hogy a TIA órajelének harmadán ketyeg a proci. Ez azt is jelenti hogy egyszerre üzemelnek, tehát amíg a TIA rajzol, mi folyamatosan állítgatjuk az állapotát. Itt ugyanis nincs egy bufferünk amibe létrehozzuk a rajzolandó képet, konkrétan úgy viselkedik mint egy state machine – amilyen például az OpenGL is. Minden változás azonnal kifejti hatását. Ezért nagyon fontos a különböző utasítások időzítése. Igen, a frame rajzolásának különböző állapotait is nekünk kell triggerelnünk és követnünk. Ha nincs szinkronban a TIA és a proci, akkor a kép szét fog esni, szaladni fog és hasonló rég elfelejtett jelenségeket produkál. :)
Hogy szinkronba tudjuk tartani őket, két dolgot kell még tudnunk. először is hogy egy sor 228 órajel hosszú, ami 76 processzor ciklusnak felel meg – ugye hárommal osztunk. A 228-ból 68 a HBlank, azaz 160 órajel marad a látható kép rész rajzolására. (Az élelmesebbek már kiszámolhatták, hogy a HBlank órajele nem osztható 3-mal. Igen, ez később még több problémát is fog okozni.) A másik dolog pedig a szabvány amit alkalmazunk. Ebből létezik NTSC, PAL60, PAL50 és SECAM. Ezek a szabványok meghatározzák számunkra hogy egy-egy frame rajzolási szakasz mennyi ideig tart illetve mennyi sornak felel meg. (Elárulom, minket az NTSC fog érdekelni, majd később meglátjátok miért. Azért  a PAL50-et is lehet használni, ha valaki azt szokta meg.) Nézzük a két fontosabb rendszer értékeit:

NTSC PAL
VSync (sor) 3
VBlank (sor) 37
kép (sor) 192 242
Overscan (sor) 30
freq (Hz) 60 50/60

Hogy kerülnek szinkronba és mi lesz a sok üresben töltött processzoridővel?

Tehát NTSC-nél maradva – és ezután alapból ez lesz a példa -, 3 + 37 + 30 = 70 sor, azaz 5320 processzor ciklus ahol nem rajzolunk. Mit tehetünk akkor? Sok mindent. Pl.: számolgathatunk, zenét játszhatunk, de ide kerülhet a játék logika egy része is. Ne feledjük el hihetetlen grafikus tárházunkat sem, hiszen ott van nekünk a háttér, a játéktér és az 5 mozgatható objektum is.
A szinkronizálás viszont egy érdekes probléma. Kezdjük talán a könnyebbik felével és haladjunk függőlegesen. Új kép kezdését úgy jelezzük, hogy a VSYNC regiszter D1-be (2. bit – 0 alapú a sorszámozás) 1-et írunk. Majd 3 sor után kikapcsoljuk ugyanerre a bitre 0-át írva. Ezután következik a VBlank amit kísértetiesen hasonlóan kell kezelni, vagyis a VBLANK D1-re 1, majd 37 sor után 0. Az Overscan tulajdonképpen a képhez tartozik így nem kell jelezni, de logikailag érdemes megkülönböztetni – mivel ami ott történik már kívül esik a safe zone-on és nagy valószínűséggel nem látszik a képernyőn.
A nehezebbik része a horizontális szinkron, hogy tudjuk pontosan hány sort rajzoltunk már ki, illetve mikor érünk a sor végére és kerülünk HBlank-ba. A számítások, főleg ha logikai elágazást is tartalmaznak, nem mindig ugyanannyi ciklusig tartanak és ez elcsúszást eredményezhet. Szerencsére van nekünk egy WSYNC nevű regiszterünk, ami egy úgynevezett strobe regiszter – azaz nem tárol értéket, csak azt figyeli írtak-e bele. Ha ide írunk, akkor a proci kikapcsol és úgy marad amíg a TIA fel nem ébreszti az éppen rajzolt sor végére érve. Ennek természetesen nem sok köze van a handheld-eknél szokásos energia felhasználás tudatos kódoláshoz. :)

Most hogy mindent tudunk, itt az ideje egy olyan kernelt írni, ami eleget tesz a fenti feltételeknek, azaz szinkronban tartja a TIA-t és a procit. (Itt jegyzem meg, hogy a példákat amiket itt láttok mindig igyekszem úgy írni, hogy eredeti vason is és emulátorban is működnek. Ha ettől el kell térjek, azt jelezni fogom.)

;
; A basic NTSC kerner for the Atari 2600
; * for PAL draw 242 lines instead of 192
;

	processor 6502

	include "vcs.h"
	include "macro.h"

	SEG
	ORG $F000

Reset

	CLEAN_START

StartOfFrame

; VSync + VBlank
;---------------

	LDA #2		 ; VSync D1 = 1
	STA VSYNC

; 3 scanlines of VSync signal...
	STA WSYNC
	STA WSYNC
	STA WSYNC

	LDA #0		 ; VSync D1 = 0
	STA VSYNC

	LDA #2		 ; VBlank D1 = 1
	STA VBLANK

; 37 scanlines of vertical blank...
	LDX #37
VerticalBlank
	STA WSYNC
	DEX
	BNE VerticalBlank

	LDA #0		 ; VBlank D1 = 0
	STA VBLANK

; 192 scanlines of picture...
;----------------------------

Picture
	LDX #0
	REPEAT 192 ; this will copy instead of creating a loop
	INX
	STX COLUBK
	STA WSYNC
	REPEND

; Overscan
----------

	LDX #30 ; 30 scanlines
Overscan
	STA WSYNC
	DEX
	BNE Overscan

	JMP StartOfFrame ; starting the next frame

; Subroutines & Gfx & Sfx & Other data
;-------------------------------------

	;...

; End of 4k
;----------

	ORG $FFFA

	.word Reset ; NMI
	.word Reset ; RESET
	.word Reset ; IRQ

	END

Megjegyzések

Valószínű ilyen tisztán megírt kódot többé sose láttok, úgyhogy jól csodáljátok meg. :) A limitációk végett nagyon sok trükközésre lesz szükség amik mind rondítják – kevésbé tagolttá, nehezebben érthetővé  teszik – a kódot.
Röviden azért megjegyzek pár fontosabb részletet. Az első sor mindig a processzor típusának megjelölése kell legyen.
A két “header” azért hasznos, mert megkönnyíti a munkánk. Pl.: a “vcs.h” tartalmazza az Atari VCS regisztereinek címét, így furcsa hexa címek helyett olyanokat írhatsz hogy VSYNC, VBLANK, WSYNC, …
A másik header néhány ügyes makrót rejt. Ilyen a használt CLEAR_START is ami kinullázza a memóriát és a regisztereket. Ez bár elfér egy kicsit overkill mivel általában a következő sorokban úgy is azon szorgoskodunk, hogy értelmes értékekkel töltsük fel ezeket a területeket.
Van egy REPEAT makró is ami megismétli a REPEND-ig bezárt kódrészletet ahányszor kértük. Ez nem feltétlen a jó megoldás, mert ilyenkor nem egy loop-ot hozunk létre hanem konkrétan annyiszor fog belekerülni a kódba. Egyébként ez a makró a dasm sajátja és a többi ilyennel együtt megtalálható a dokumentációjában.
A frame vége és a 4K vége előtt találtok egy részt ami kihasználatlan. Én ide szoktam tenni az adatokat és szubrutinokat, de azért azt tartsuk szem előtt, hogy ez nem feltétlenül optimális.

És végül elértük a ROM végét. Itt kapott helyet 3 cím amikre akkor ugrik amikor a kommentekben jelzett megszakítások történnek. Ezekből nekünk most csak a Reset a fontos amit beállítottunk a kódunk elejére, így amikor lenyomjuk a gépen a Reset gombot, akkor onnan fog folytatódni a futtatás.
A másik két megszakítás normálisan nem létezik az Atari 2600-on. a 7800 MARIA grafikus chip-je használja az NMI-t nagyobb felbontás elérése érdekében és az IRQ pedig a cart-hoz van kötve.
Mi esetleg az IRQ-t tudjuk némi debug-ra használni, mivel a BRK egy IRQ megszakítást generál. Ha már itt tartunk, nem árt a kódot egy SEI-vel kezdeni.

Ha már úgyis elmentünk ilyen irányba, akkor a fontosabb címtartományok:

  • $0000 – $007F TIA regiszterek
  • $0080 – $00FF RAM
  • $0200 – $02FF RIOT regiszterek
  • $1000 – $1FFF ROM

Ami közte van azok úgynevezett shadow területek ahol más területek másolatai vannak. Ez a gép működése szempontjából fontos, de mi nem tudunk mit kezdeni velük úgyhogy nem taglalnám…

Egy apró dolog van még amivel majd később fogunk foglalkozni, az a két ORG utasítás. A 4K ROM $1000-tól $1FFF-ig tart. Mi azért pozicionálunk $F000-ra, mert az emulátorok a 6502-re építenek és ott fogják keresni az adott részeket. Ez a trükk azért működik, mert a 6507 csak a alsó 13 bitet nézi, de egy emulátor mind a 16-ot fogja.

De működik? És mit csinál?

Persze, hogy működik, csak le kell fordítani. Az utasítás pedig:

dasm kernel.asm -lkernel.txt -f3 -v5 -okernel.bin

Az elkészült .bin-t már tölthetjük is be Stella-ba, rakhatjuk Harmony Cart-ra vagy égethetjük EPROM-ba.

Nekem fut mind NTSC, mind PAL értékekkel Stella-ban is és Atari Jr-on is Harmony Cart-tal. Annyit tesz, hogy stabilan állítja elő a frame-eket és közben a rajzolós részen soronként változtatja a háttérszín értékét.

NTSC kernel

NTSC kernel

PAL kernel

PAL kernel

Színek tára

Már van egy kis keretünk amivel elkezdhetünk alkotni, és láttunk már pár színt is a képernyőn. De még lehet elsiklottunk afelett, hogy a rendelkezésünkre álló színek száma rendszer függő – azaz más NTSC-n, PAL-on és SECAM-on. Alább egy kis összefoglalás:

TIA_colors_NTSC

TIA_colors_PAL

TIA_colors_SECAM

Látható, hogy nem csak kevesebb szín van PAL-on, de máshol, kevésbé használható elrendezésben is helyezkednek el. Ez azon túl, hogy esetleg az NTSC irányába terelhet minket, portolási problémákat is felvet. Ezzel azonban egy másik részben fogunk foglalkozni.

Ha valaki esetleg érez rá affinitást, az ránézhet erre az írásra – english – ahol rendszerek közötti konvertálásról van egy kis olvasmány.

Stella Run-Time Debug

Mivel most más van mit megnézni és a későbbiekben nagyon hasznos lehet, így még röviden összefoglalnám amit tudnunk kell erről. Először is amikor már fut a ROM-unk, akkor nyomjuk meg a ‘0’-át.

Stella Run-Time Debug ablaka

Stella Run-Time Debug ablaka

Egy kicsit zsúfolt és talán elsőre nem is annyira egyszerű, de ez a felület ahol mindent láthatunk ami – remélhetőleg a gépen is ugyanúgy, mint itt az emulátorban – történik.

A bal fölső sarokban a teljes képernyőt láthatjuk kicsiben. Ezen nyomon követhetjük hol tartunk éppen a rajzolásban – konkrétan egy fehér pont jelzi. Ahol ebben a frame-ben még nem jártunk, ott az előző kép látható szürkével.
A mellette lévő információs rész mutatja hányadik frame-et/scanline-t rajzoljuk és azokban hány órajellel vagyunk beljebb. Illetve épp VSync vagy VBlank-ban vagyunk-e. Látható még a pixel pozíció is ami ha negatív, akkor még a HBlank-ban járunk.
Ez alatt pedig egy nagyított képet látunk amit a kis képen jobb kattintással a megjelenő menüből tudunk beállítani a kattintás pozíciójára.
Ugyanebben a menüben akár breakpoint-ot is beállíthatunk az adott pozícióra, vagy akár az adott scanline-ig is futtathatjuk a progit.
Plusz itt kapott helyet egy olyan opció amivel debug színekre tudunk váltani, azaz minden színbeállítás egy fix értéken marad, így könnyebb nyomon követni ki-kivel van.

Alatta szintén egy fontos információkkal teli részt találunk, amiből én ugyan még a Prompt-ot nem használtam, de a másik három fülön a TIA és a PIA regisztereinek tartalmát láthatjuk – amik sokkal többet fognak mondani ahogy haladunk előre felfedező utunkon.

Stella Debugger TIA fül

Stella Debugger TIA fül

Stella Debugger I/O fül

Stella Debugger I/O fül

Stella Debugger Audio fül

Stella Debugger Audio fül

A frame és scanline információk mellett jobbra leljük a processzor regisztereinek tartalmát. Gondolom nem kell bemutatni a ProgramCounter-t és StackPointer-t… Az értékeket egyből látjuk hexa, decimális és bináris formában. Értéküket pedig kattintás után a jobbra lévő műveleteket jelző gombokkal változtathatjuk. A PS a flageket jelzi és a nagybetű a beállított érték – tudjátok hi/lo. :) Állapotuk dupla kattintással változtatható.

Ezek alatt a teljes memória területet láthatjuk – gondolom ilyet nem sok platformon láttatok :D – hexadecimális formában kijelzett tartalmával. Kattintással kiválasztva alatta megjelenik a decimális és bináris érték is. Dupla kattintással változtathatóak az értékek és hexában kell megadni – az Enter hajtja végre a módosítást.

Végül jobb lent pedig a programkódot látjuk. A helyét a ROM-ban, az utasítást, a hozzá tartozó adatot, kommentelve az utasítás órajeligényét és mellette a nyers hexa adatot. Itt jobb kattintással beállíthatunk breakpoint-ot, vagy akár futtathatjuk is a progit amíg el nem éri a kijelölt helyet – ha nem éri el leáll egy idő után megelőzve a végtelen ciklus vagy a soha végre nem hajtódó kód problémáját.

Akkor már csak azt kéne tudnunk, hogy hogyan lehet a kódban haladni. Erre a jobb felső sarokban lévő gombok kínálnak lehetőséget. A ‘Step’ és a ‘Trace’ egy utasítással lép előre a kódban – csak a ‘Trace’ talán követi a szubrutinokat is. A ‘Scan +1’ egy scanline-t ugrik előre de figyelembe veszi a WSync-et. A ‘Frame +1′ pedig egy frame-et, amit a VBlank kezdete jelez.

Többszörös szörnyűségre az ezek mellett lévő hosszúra nőtt gomb a kisebb mint jellel egy vissza gomb. Arra való, hogy visszafelé haladjunk vele pontosan úgy ahogy ezt előre tettük – figyelembe veszi tehát a Scan +1 és Frame +1 gombok használatát is.

Végül kilépni is itt tudunk, amivel folytatódik a progi normális futása, de használhatjuk a jól bevált ‘0’-át is.

Minden egyéb funkcionalitáshoz ajánlom a Stella dokumentációját. Ezeket csak azért emeltem ki, mert igen hasznosak.

 Végszó

Most viszont befejezem ezt a részt. Legközelebb elkezdjük használni azokat a csodálatos színeket és sorra vesszük a rajztárunk.

Kellemes ünnepeket!

További cikkek a sorozatból:

5 Responses so far.

  1. avatar robymus says:

    Köszi, nagyon hasznos volt. Várom a következőt.

  2. avatar TLC says:

    Nagyon király, köszi szépen!

  3. avatar Murphy says:

    És mennyire jó az NTSC palettája!

  4. avatar adsr says:

    Mivel elsőre nem működött a stuff, egy kicsit belenéztem a dasm test mappában lévő példákba, így sikerült pótolnom a hiányzó dolgokat.

    include “telepítésí útvonal\dasm-2.20.11\machines\atari2600\vcs.h”
    include “telepítésí útvonal\dasm-2.20.11\machines\atari2600\macro.h”

    VSYNC equ $00
    VBLANK equ $01
    WSYNC equ $02
    COLUBK equ $09

    Persze, ha valakinek ez egyértelmű volt, bocsi ;-).

  5. avatar RawBits says:

    adsr: igen azok az értékek a vcs.h-ban vannak definiálva, azért kell include-olni őket. :) Akinek az include nem volt egyértelmű annak bocsi, de azért írtam az első részben hogy assembly gyorstalpalói kötelező előtte, mert én ezzel már nem akarok – felesleges – foglalkozni.

    “6502 Referencia – aki nem tud Assembly-ben programozni, az vehet egy gyorstalpaló leckét is.”
    http://nesdev.com/6502guid.txt (az igaz, hogy ebben nincs benne az include, de a dasm dokumentációban igen)

Leave a Reply

You must be logged in to post a comment.

Ugrás a lap tetejére Ugrás a lap aljára