Obsah
1. Vývoj her pro herní konzoli NES: čtení ovladače, pohyb spritů a rutina VBLANK
2. Ovládání her na osmibitových počítačích a herních konzolích
3. Posuvný registr v ovladačích NESu
4. Čtení stavu tlačítek na ovladačích NESu
5. Programový test stavu tlačítek
6. Posun spritu s využitím ovladače
8. Praktická realizace posunu spritu
9. Úplný zdrojový kód dnešního prvního demonstračního příkladu
10. Assembler se překládal jako JSA – Jazyk Symbolických Adres
11. Úplný zdrojový kód dnešního druhého demonstračního příkladu
12. Podpůrná makra increment a decrement
13. Úplný zdrojový kód dnešního třetího demonstračního příkladu
14. MOS 6502 není RISCovým procesorem aneb síla instrukcí INC a DEC
15. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu
16. Přesun všech osmi spritů tvořících figurku Maria
17. Trik – aritmetické operace s registrem X
18. Úplný zdrojový kód dnešního pátého demonstračního příkladu
19. Repositář s demonstračními příklady
1. Vývoj her pro herní konzoli NES: čtení ovladače, pohyb spritů a rutina VBLANK
V (mini)seriálu o vývoji her a multimediálních dem určených pro slavnou a v mnoha ohledech přelomovou osmibitovou herní konzoli Nintendo Entertainment System (NES) jsme si již ukázali, jak má vlastně vypadat kostra hry či dema napsaného v assembleru, konkrétně v assembleru ca65. Taktéž již máme základní informace nutné pro přehrání zvuků a hudby a umíme na obrazovku vykreslit 64 spritů, každý z nich s rozlišením 8×8 pixelů. Dalším krokem bude rozhýbání celé zobrazené scény. Začneme skutečně tím nejtriviálnějším příkladem, konkrétně ukázkou a vysvětlením, jakým způsobem je možné pohybovat spritem po obrazovce s využitím standardního ovladače NESu (ten má namísto joysticku D-pad). Budeme se tedy muset naučit hned několik věcí:
- Přečtení stavu tlačítek ovladače (to není zcela triviální)
- Změnu pozice spritu (a práce s příznakem přetečení)
- Zajištění, že se sprite bude po obrazovce pohybovat konstantní rychlostí
- Zajištění pohybu celé postavičky (Maria), která je složena z osmi spritů
Příklady, které budou ukázány v navazujících kapitolách, jsou založeny na příkladu
https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example13.asm, jenž dokáže na obrazovce zobrazit postavičku Maria. Všechny části tohoto příkladu jsme si již vysvětlili v předchozích článcích:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s Mariem ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 ; vynulování počitadla : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro osm spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
2. Ovládání her na osmibitových počítačích a herních konzolích
Na většině klasických osmibitových počítačích a herních konzolích se setkáme s možností připojení standardních ovladačů, typicky joysticků (výjimkou je ZX Spectrum, kde původně standard neexistoval, takže „standardů“ vzniklo více – odtud nabídka různých typů ovládání v prakticky každé hře pro Speccy). Typické dobové joysticky byla jednoduchá zařízení – čtyři (mikro)spínače sloužily pro určení směru (náklonu) joysticku; dále byl joystick vybaven jedním nebo dvěma tlačítky, která měla buď stejnou funkci (paralelní zapojení), nebo dvě funkce. Těchto pět resp. šest tlačítek bylo připojeno přímo k počítači „paralelním“ způsobem – každé tlačítko na samostatný pin k tomu určeného konektoru. Programátor tak typicky mohl načtením jednoho osmibitového registru najednou získat informaci o stavu všech tlačítek.
Obrázek 1: Atari joystick a způsob jeho zapojení byl svým způsobem standardem.
3. Posuvný registr v ovladačích NESu
Ovladač pro herní konzoli NES vypadá odlišně než klasický joystick. Najdeme zde D-pad pro určení čtyř směrů (nebo jejich kombinací) a taktéž čtveřici tlačítek označených Select, Start, A a B:
Obrázek 2: Ovladač pro herní konzoli NES.
Mohlo by se tedy zdát, že všech osm tlačítek (čtyři na D-padu, čtyři ostatní) budou vyvedeny na konektor ve formě osmi samostatných pinů. Ve skutečnosti tomu tak není, protože v ovladači je posuvný registr (shift register) který na základě signálu z konzole (latch) zaznamená stav všech osmi tlačítek. Poté je nutné tyto informace postupně z posuvného registru vysunout, a to bit po bitu – přenos je tedy sériový. Programátor musí sám zajistit jak signál latch, tak i postupné načtení osmi bitů z posuvného registru (což není těžké).
4. Čtení stavu tlačítek na ovladačích NESu
Vlastní realizace čtení stavu tlačítek na ovladačích NESu není ve skutečnosti nijak složitá. Čtení lze provádět pro oba ovladače zvlášť; přitom si vystačíme s pouhými dvěma osmibitovými řídicími registry na adresách $4016 a $4017 (znak $ označuje hexadecimální konstanty):
; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017
Před čtením stavu tlačítek musíme zachytit jejich stav a uložit ho do posuvného registru. Budeme tedy provádět bit banging – pomocí SW budeme generovat řídicí signály, zde konkrétně signál latch:
lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:
Nyní již můžeme postupně přečíst stav jednotlivých tlačítek v pořadí, které naznačuje následující kód. Pořadí nelze změnit (je dáno pořadím bitů v posuvném registru):
lda JOYPAD1 ; stav tlačítka A jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka B jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka Select jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka Start jen načteme a ingorujeme ... lda JOYPAD1 ; stav tlačítka Up ... lda JOYPAD1 ; stav tlačítka Down ... lda JOYPAD1 ; stav tlačítka Left ... lda JOYPAD1 ; stav tlačítka Right ...
5. Programový test stavu tlačítek
Hodnota přečtená instrukcí LDA do akumulátoru má pochopitelně osm bitů. Horních sedm bitů obsahuje další (pro nás prozatím nerelevantní) informace, takže jediným bitem, který nás zajímá, je nejnižší bit akumulátoru. Jeho stav lze otestovat mnoha způsoby – vysunutím/rotací do carry, maskováním a zjištěním příznaku zero, odečtením jedničky a zjištěním příznaku zero atd. Podívejme se na nejpřímější (i když ne nejrychlejší) způsob – maskování. Horních sedm bitů akumulátoru zamaskujeme (vynulujeme) instrukcí AND, která automaticky nastaví příznak zero v případě, že je nový obsah akumulátoru nulový (a tedy tlačítko není stisknuto). Tento stav následně využijeme v podmíněném skoku BEQ (branch if equal == branch if zero flag is set):
lda JOYPAD1 ; stav tlačítka Up and #%00000001 ; maskovat všechny bity kromě prvního beq up_not_pressed ; není stisknuto? => skok ; instrukce provedené v případě, že je tlačítko stisknuto up_not_pressed: ; pokračování běhu programu
6. Posun spritu s využitím ovladače
Nyní si ukažme, jak je možné provést posun spritu (prozatím jediného spritu, tedy bloku 8×8 pixelů) s využitím ovladače. Logika je vlastně velmi jednoduchá, protože metainformace o spritech – jejich tvaru (bitmapě), pozici, indexu barvové palety máme zkopírovány z paměti ROM do paměti RAM, konkrétně ve druhé stránce paměti, tedy od adresy $0200. Deklarace tvaru a pozice prvních osmi spritů vypadá následovně:
; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10
Těchto osm spritů vytvoří obrázek Maria:
Obrázek 3: Postavička Maria vykreslená z osmi spritů.
Tato postavička je složena, jak již ostatně velmi dobře víme, z osmi spritů, z nichž každý má rozměry 8×8 pixelů:
Obrázek 4: Postavička Maria je ve skutečnosti složená z osmi spritů.
Posun prvního spritu z této osmice lze zajistit modifikací bajtu na adrese $0200 (y-ová souřadnice) a $0203 (x-ová souřadnice). To je z pohledu programátora vše – modifikaci pozice na obrazovce již zajistí DMA. A kdy změnu provést – v rutině VBLANK!
7. Rutina VBLANK
Jak je však možné zajistit, aby se sprite (či později celá postavička) pohybovala po herním poli určenou konstantní rychlostí? Pro tento účel poměrně dobře poslouží rutina VBLANK, která je zavolána automaticky ve chvíli, kdy PPU dokončil vykreslení jednoho snímku na obrazovce. Na konci (nebo začátku) každého snímku se nachází 21 prázdných obrazových řádků, během nichž není prováděno vykreslování. To mj. znamená, že je to vhodný okamžik pro řízení PPU (tedy „grafického procesoru“). A navíc je tato rutina volána zcela pravidelně, konkrétně 25× za sekundu na NESu v systému PAL a 29.97× (tedy prakticky 30×) na NESu v systému NTSC. Pokud tedy budeme stav tlačítek číst a následně modifikovat pozici spritu v rutině VBLANK, bude se sprite pohybovat přesně rychlostí 25 resp. 30 pixelů za sekundu. Je zde pochopitelně malý háček – jak rozlišit PAL od NTSC. Některé hry běží v systému PAL pomaleji (tj. nijak tento problém neřeší), další existují ve dvou variantách. VBLANK je zavolána automaticky, protože informace o nadcházejícím VBLANK způsobí nemaskovatelné přerušení (NMI).
8. Praktická realizace posunu spritu
Posun spritu o ± dva pixely (či o jinou hodnotu požadovanou programátorem) v horizontálním i vertikálním směru musíme realizovat pro každý směr zvlášť, a to následujícím způsobem:
- Test stavu příslušného tlačítka (Up, Down, Left, Right)
- Pokud je tlačítko stisknuto, změní se příslušná hodnota na adrese $0200 (y-ová souřadnice) či $0203 (x-ová souřadnice)
Musíme si jen dát pozor na to, že před operací ADC je nutné vynulovat příznak carry a před operací SBC ho naopak nastavit (tím se mikroprocesor MOS 6502 odlišuje od ostatních osmibitových mikroprocesorů):
Nejprve přečteme stav všech tlačítek do posuvného registru:
lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek
Následně přečteme a zapomeneme stav tlačítek A, B, Select a Start:
lda JOYPAD1 ; stav tlačítka A jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka B jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Select jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Start jen načteme a ingorujeme
Poté následuje čtení stavu směrových tlačítek na D-padu a modifikace x-ové či y-ové souřadnice pixelu. Pokud je příslušné tlačítko zmáčknuto (tedy když je vybrán směr), podmíněný skok se neprovede a vykoná se kód pro zvýšení či snížení pozice spritu o ± 2 pixely:
lda JOYPAD1 ; stav tlačítka Up and #%00000001 ; maskovat všechny bity kromě prvního beq up_not_pressed ; není stisknuto? => skok lda $0200 ; změna y-ové pozice spritu sec ; nastavit přenos sbc #$02 ; y-- sta $0200 ; uložení nové y-ové pozice spritu up_not_pressed: lda JOYPAD1 ; stav tlačítka Down and #%00000001 ; maskovat všechny bity kromě prvního beq down_not_pressed ; není stisknuto? => skok lda $0200 ; změna y-ové pozice spritu clc ; vynulovat přenos adc #$02 ; y++ sta $0200 ; uložení nové y-ové pozice spritu down_not_pressed: lda JOYPAD1 ; stav tlačítka Left and #%00000001 ; maskovat všechny bity kromě prvního beq left_not_pressed ; není stisknuto? => skok lda $0203 ; změna x-ové pozice spritu sec ; nastavit přenos sbc #$02 ; x-- sta $0203 ; uložení nové x-ové pozice spritu left_not_pressed: lda JOYPAD1 ; stav tlačítka Right and #%00000001 ; maskovat všechny bity kromě prvního beq right_not_pressed ; není stisknuto? => skok lda $0203 ; změna x-ové pozice spritu clc ; vynulovat přenos adc #$02 ; x-- sta $0203 ; uložení nové x-ové pozice spritu right_not_pressed:
Obrázek 5: Posun jediného spritu (8×8 pixelů) ve scéně.
9. Úplný zdrojový kód dnešního prvního demonstračního příkladu
Úplný zdrojový kód dnešního prvního demonstračního příkladu (v pořadí již sedmnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example17.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example17.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu. ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right lda JOYPAD1 ; stav tlačítka A jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka B jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Select jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Start jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Up and #%00000001 ; maskovat všechny bity kromě prvního beq up_not_pressed ; není stisknuto? => skok lda $0200 ; změna y-ové pozice spritu sec ; nastavit přenos sbc #$02 ; y-- sta $0200 ; uložení nové y-ové pozice spritu up_not_pressed: lda JOYPAD1 ; stav tlačítka Down and #%00000001 ; maskovat všechny bity kromě prvního beq down_not_pressed ; není stisknuto? => skok lda $0200 ; změna y-ové pozice spritu clc ; vynulovat přenos adc #$02 ; y++ sta $0200 ; uložení nové y-ové pozice spritu down_not_pressed: lda JOYPAD1 ; stav tlačítka Left and #%00000001 ; maskovat všechny bity kromě prvního beq left_not_pressed ; není stisknuto? => skok lda $0203 ; změna x-ové pozice spritu sec ; nastavit přenos sbc #$02 ; x-- sta $0203 ; uložení nové x-ové pozice spritu left_not_pressed: lda JOYPAD1 ; stav tlačítka Right and #%00000001 ; maskovat všechny bity kromě prvního beq right_not_pressed ; není stisknuto? => skok lda $0203 ; změna x-ové pozice spritu clc ; vynulovat přenos adc #$02 ; x-- sta $0203 ; uložení nové x-ové pozice spritu right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
10. Assembler se překládal jako JSA – Jazyk Symbolických Adres
V češtině se namísto pojmu assembler používal název „Jazyk Symbolických Adres“ neboli JSA. Ovšem v předchozím kódu jsme přímo používali adresy $0203 a $0200 v jejich numerické podobě, což je zbytečně nízkoúrovňové. V assembleru/JSA máme možnost si tyto adresy pojmenovat, což může vést ke zpřehlednění kódu (v tomto ohledu je assembler na vyšší úrovni abstrakce, než například BASIC):
XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu
Nyní mohou být tyto magické konstanty v kódu nahrazeny jménem:
lda JOYPAD1 ; stav tlačítka Up and #%00000001 ; maskovat všechny bity kromě prvního beq up_not_pressed ; není stisknuto? => skok lda YPOS ; změna y-ové pozice spritu sec ; nastavit přenos sbc #$02 ; y-- sta YPOS ; uložení nové y-ové pozice spritu up_not_pressed: lda JOYPAD1 ; stav tlačítka Down and #%00000001 ; maskovat všechny bity kromě prvního beq down_not_pressed ; není stisknuto? => skok lda YPOS ; změna y-ové pozice spritu clc ; vynulovat přenos adc #$02 ; y++ sta YPOS ; uložení nové y-ové pozice spritu down_not_pressed: lda JOYPAD1 ; stav tlačítka Left and #%00000001 ; maskovat všechny bity kromě prvního beq left_not_pressed ; není stisknuto? => skok lda XPOS ; změna x-ové pozice spritu sec ; nastavit přenos sbc #$02 ; x-- sta XPOS ; uložení nové x-ové pozice spritu left_not_pressed: lda JOYPAD1 ; stav tlačítka Right and #%00000001 ; maskovat všechny bity kromě prvního beq right_not_pressed ; není stisknuto? => skok lda XPOS ; změna x-ové pozice spritu clc ; vynulovat přenos adc #$02 ; x-- sta XPOS ; uložení nové x-ové pozice spritu right_not_pressed:
XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu BUTTON_MASK = %00000001 STEP = $02
V kódu je stále nutné rozlišovat mezi hodnotou a adresou (u hodnoty je uveden znak #):
lda JOYPAD1 ; stav tlačítka Up and #BUTTON_MASK ; maskovat všechny bity kromě prvního beq up_not_pressed ; není stisknuto? => skok lda YPOS ; změna y-ové pozice spritu sec ; nastavit přenos sbc #STEP ; y-- sta YPOS ; uložení nové y-ové pozice spritu up_not_pressed: lda JOYPAD1 ; stav tlačítka Down and #BUTTON_MASK ; maskovat všechny bity kromě prvního beq down_not_pressed ; není stisknuto? => skok lda YPOS ; změna y-ové pozice spritu clc ; vynulovat přenos adc #STEP ; y++ sta YPOS ; uložení nové y-ové pozice spritu down_not_pressed: lda JOYPAD1 ; stav tlačítka Left and #BUTTON_MASK ; maskovat všechny bity kromě prvního beq left_not_pressed ; není stisknuto? => skok lda XPOS ; změna x-ové pozice spritu sec ; nastavit přenos sbc #STEP ; x-- sta XPOS ; uložení nové x-ové pozice spritu left_not_pressed: lda JOYPAD1 ; stav tlačítka Right and #BUTTON_MASK ; maskovat všechny bity kromě prvního beq right_not_pressed ; není stisknuto? => skok lda XPOS ; změna x-ové pozice spritu clc ; vynulovat přenos adc #STEP ; x-- sta XPOS ; uložení nové x-ové pozice spritu right_not_pressed:
11. Úplný zdrojový kód dnešního druhého demonstračního příkladu
Úplný zdrojový kód dnešního druhého demonstračního příkladu (v pořadí již osmnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example18.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example18.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu. ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right lda JOYPAD1 ; stav tlačítka A jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka B jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Select jen načteme a ingorujeme lda JOYPAD1 ; stav tlačítka Start jen načteme a ingorujeme XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu lda JOYPAD1 ; stav tlačítka Up and #%00000001 ; maskovat všechny bity kromě prvního beq up_not_pressed ; není stisknuto? => skok lda YPOS ; změna y-ové pozice spritu sec ; nastavit přenos sbc #$02 ; y-- sta YPOS ; uložení nové y-ové pozice spritu up_not_pressed: lda JOYPAD1 ; stav tlačítka Down and #%00000001 ; maskovat všechny bity kromě prvního beq down_not_pressed ; není stisknuto? => skok lda YPOS ; změna y-ové pozice spritu clc ; vynulovat přenos adc #$02 ; y++ sta YPOS ; uložení nové y-ové pozice spritu down_not_pressed: lda JOYPAD1 ; stav tlačítka Left and #%00000001 ; maskovat všechny bity kromě prvního beq left_not_pressed ; není stisknuto? => skok lda XPOS ; změna x-ové pozice spritu sec ; nastavit přenos sbc #$02 ; x-- sta XPOS ; uložení nové x-ové pozice spritu left_not_pressed: lda JOYPAD1 ; stav tlačítka Right and #%00000001 ; maskovat všechny bity kromě prvního beq right_not_pressed ; není stisknuto? => skok lda XPOS ; změna x-ové pozice spritu clc ; vynulovat přenos adc #$02 ; x-- sta XPOS ; uložení nové x-ové pozice spritu right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
12. Podpůrná makra increment a decrement
Předchozí kód byl zbytečně složitý, protože se v něm 2× vyskytovala sekvence instrukcí určených pro zvýšení či naopak snížení obsahu paměťové buňky o nějakou hodnotu. Tento kód můžeme přenést do subrutiny (tu je nutné volat a zvětšovat tak časové nároky rutiny VBLANK) nebo do makra. V tom nejjednodušším případě mohou příslušná makra vypadat takto:
.macro increment address lda address clc ; vynulovat přenos adc #$02 ; x++ sta address .endmacro .macro decrement address lda address sec ; nastavit přenos sbc #$02 ; x-- sta address .endmacro
Makra akceptují jediný parametr – adresu:
read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement YPOS up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment YPOS
13. Úplný zdrojový kód dnešního třetího demonstračního příkladu
Úplný zdrojový kód dnešního třetího demonstračního příkladu (v pořadí již devatenáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example19.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example19.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu. ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro increment address lda address clc ; vynulovat přenos adc #$02 ; x++ sta address .endmacro .macro decrement address lda address sec ; nastavit přenos sbc #$02 ; x-- sta address .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right read_button ; stav tlačítka A jen načteme a ingorujeme read_button ; stav tlačítka B jen načteme a ingorujeme read_button ; stav tlačítka Select jen načteme a ingorujeme read_button ; stav tlačítka Start jen načteme a ingorujeme XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement YPOS up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment YPOS down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok decrement XPOS left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok increment XPOS right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
14. MOS 6502 není RISCovým procesorem aneb síla instrukcí INC a DEC
Původní čip MOS 6502 nabízí programátorům šestici instrukcí, které dokážou zvýšit či snížit obsah paměťové buňky o jedničku, popř. zvýšit či snížit obsah registru X či Y o jedničku:
Instrukce | Stručný popis |
---|---|
INC | zvýšení obsahu paměťové buňky o jedničku |
DEC | snížení obsahu paměťové buňky o jedničku |
INX | zvýšení obsahu index registru X o jedničku |
DEX | snížení obsahu index registru X o jedničku |
INY | zvýšení obsahu index registru Y o jedničku |
DEY | snížení obsahu index registru Y o jedničku |
Povšimněte si, že MOS 6502 nemá instrukce INA/DEA pro zvýšení/snížení obsahu akumulátoru (u registrů X a Y se jedná o nutnost, u akumulátoru se používá ADC a SBC se všemi z toho plynoucími důsledky). Tento nedostatek byl napraven v čipu 65C02, který obě zmíněné instrukce obsahuje, což podle některých autorů vedlo ke zmenšení programového kódu až o 5%.
Co je ovšem pro nás důležité – instrukce INC a DEC podporují přímé uvedení absolutní adresy, takže pokud budeme chtít pohybovat sprity o jeden pixel v libovolném směru, bude celý programový kód značně jednodušší:
read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok dec YPOS up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok inc YPOS down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok dec XPOS left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok inc XPOS right_not_pressed:
15. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu
Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu (v pořadí již dvacátého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example20.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example20.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu. ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right read_button ; stav tlačítka A jen načteme a ingorujeme read_button ; stav tlačítka B jen načteme a ingorujeme read_button ; stav tlačítka Select jen načteme a ingorujeme read_button ; stav tlačítka Start jen načteme a ingorujeme XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok dec YPOS up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok inc YPOS down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok dec XPOS left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok inc XPOS right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
16. Přesun všech osmi spritů tvořících figurku Maria
Náš další úkol je zřejmý – upravit program takovým způsobem, aby bylo možné pohybovat celou figurkou Maria, která se skládá z osmi spritů. Nějakým vhodným způsobem tedy budeme muset vždy zvýšit či snížit souřadnici nikoli jediného spritu, ale hned osmi spritů. To lze provést několika různými způsoby, například využít toho, že nová souřadnice je pro několik spritů vždy stejná atd. Ovšem ukažme si druhý nejjednodušší způsob – realizace formou programové smyčky, která vždy změní hodnotu osmi bajtů v paměti. Tyto bajty však nejsou uloženy ihned za sebou, ale s mezerou 4 adres. To vychází ze způsobu uložení metainformací o spritech v paměti:
spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10
Měnit budeme buď hodnoty v prvním sloupci nebo v sloupci posledním.
17. Trik – aritmetické operace s registrem X
Pro zvýšení nebo naopak snížení hodnot v osmi paměťových buňkách můžeme použít instrukci inc adresa, x či dec adresa, x, což znamená, že k pevně zadané adrese se přičítá offset uložený v registru X. Postačuje nám tedy použít registr X současně i ve funkci počitadla smyčky, která postupně hodnotu tohoto registru nastaví na 4, 8, 12, 16, 20, 24, 28 a 32. To lze provést tímto pseudokódem:
ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku adc x, #gap ; zvýšení o hodnotu gap (4, další sprite) cmp x, #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky
Jediný problém spočívá v tom, že tučně zapsané instrukce mikroprocesor MOS 6502 neobsahuje. Jedná se totiž o aritmetické operace a ty lze provádět jen s akumulátorem. Pomůžeme si tudíž malým trikem – instrukcí TXA přesuneme obsah registru X do akumulátoru, provedeme všechny nutné aritmetické operace a následně přesuneme nový obsah akumulátoru do registru X instrukcí TAX:
txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X
Následně pouze otestujeme, zda jsme dospěli k poslednímu offsetu, který se má použít pro instrukci INC či DEC:
cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu
Výsledkem jsou následující dvě makra:
.macro increment_block address, count, gap ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro decrement_block address, count, gap ldx #0 ; inicializace offsetu : dec address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro
Která se používají takto:
read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement_block YPOS, 8, 4 up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment_block YPOS, 8, 4 down_not_pressed: ... ... ... ...
18. Úplný zdrojový kód dnešního pátého demonstračního příkladu
Úplný zdrojový kód dnešního posledního demonstračního příkladu (v pořadí již dvacátého prvního příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example21.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example21.nes:
; --------------------------------------------------------------------- ; Kostra programu pro herní konzoli NES ; Nastavení barvové palety, zvýšení intenzity barvy ; Setup PPU přes makro ; Definice spritu a zobrazení spritů s rozloženým Mariem. ; Pohyb celého Maria. ; Využití symbolických jmen adres. ; Pomocná makra pro pohyb spritu. ; ; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01 ; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3 ; Viz též článek na https://www.moria.us/blog/2018/03/nes-development ; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt ; --------------------------------------------------------------------- ; Jména řídicích registrů použitých v kódu PPUCTRL = $2000 PPUMASK = $2001 PPUSTATUS = $2002 PPUADDR = $2006 PPUDATA = $2007 DMC_FREQ = $4010 OAM_DMA = $4014 ; Další důležité adresy PALETTE = $3f00 ; Ovladače JOYPAD1 = $4016 JOYPAD2 = $4017 ; --------------------------------------------------------------------- ; Definice maker ; --------------------------------------------------------------------- .macro setup_cpu ; nastavení stavu CPU sei ; zákaz přerušení cld ; vypnutí dekadického režimu (není podporován) ldx #$ff txs ; vrchol zásobníku nastaven na 0xff (první stránka) .endmacro .macro wait_for_frame : bit PPUSTATUS ; test obsahu registru PPUSTATUS bpl :- ; skok, pokud je příznak N nulový .endmacro .macro clear_ram lda #$00 ; vynulování registru A : sta $000, x ; vynulování X-tého bajtu v nulté stránce sta $100, x sta $200, x sta $300, x sta $400, x sta $500, x sta $600, x sta $700, x ; vynulování X-tého bajtu v sedmé stránce inx ; přechod na další bajt bne :- ; po přetečení 0xff -> 0x00 konec smyčky .endmacro .macro ppu_data_palette_address lda PPUSTATUS ; reset záchytného registru lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00 sta PPUADDR lda #<PALETTE ; nižší bajt adresy sta PPUADDR .endmacro .macro increment_block address, count, gap ldx #0 ; inicializace offsetu : inc address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro decrement_block address, count, gap ldx #0 ; inicializace offsetu : dec address, x ; zvýšit pozici spritu o jedničku txa ; přesun offsetu do akumulátoru clc adc #gap ; zvýšení o hodnotu gap (4, další sprite) tax ; přesun nového offsetu zpět do registru X cmp #count*gap ; porovnání, zda jsme již dosáhli posledního spritu bne :- ; pokud ne, skok na začátek smyčky .endmacro .macro read_button lda JOYPAD1 ; stav tlačítka and #%00000001 ; maskovat všechny bity kromě prvního .endmacro ; --------------------------------------------------------------------- ; Definice hlavičky obrazu ROM ; --------------------------------------------------------------------- ; Size of PRG in units of 16 KiB. prg_npage = 2 ; Size of CHR in units of 8 KiB. chr_npage = 1 ; INES mapper number. mapper = 0 ; Mirroring (0 = horizontal, 1 = vertical) mirroring = 1 .segment "HEADER" .byte $4e, $45, $53, $1a .byte prg_npage .byte chr_npage .byte ((mapper & $0f) << 4) | (mirroring & 1) .byte mapper & $f0 .segment "ZEROPAGE" .segment "STARTUP" .segment "CODE" ; --------------------------------------------------------------------- ; Blok paměti s definicí dlaždic 8x8 pixelů ; --------------------------------------------------------------------- .segment "CHR0a" .segment "CHR0b" .code ; --------------------------------------------------------------------- ; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU ; ; viz též https://www.pagetable.com/?p=410 ; --------------------------------------------------------------------- ; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank) .proc nmi lda #$02 ; horní bajt adresy pro přenos + zahájení přenosu sta OAM_DMA lda #$01 sta JOYPAD1 ; načtení stavu všech osmi tlačítek do záchytného registru lda #$00 sta JOYPAD1 ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí: ; ; 1) A ; 2) B ; 3) Select ; 4) Start ; 5) Up ; 6) Down ; 7) Left ; 8) Right read_button ; stav tlačítka A jen načteme a ingorujeme read_button ; stav tlačítka B jen načteme a ingorujeme read_button ; stav tlačítka Select jen načteme a ingorujeme read_button ; stav tlačítka Start jen načteme a ingorujeme XPOS = $0203 ; adresa buňky paměti s x-ovou souřadnicí spritu YPOS = $0200 ; adresa buňky paměti y x-ovou souřadnicí spritu read_button ; stav tlačítka Up beq up_not_pressed ; není stisknuto? => skok decrement_block YPOS, 8, 4 up_not_pressed: read_button ; stav tlačítka Down beq down_not_pressed ; není stisknuto? => skok increment_block YPOS, 8, 4 down_not_pressed: read_button ; stav tlačítka Left beq left_not_pressed ; není stisknuto? => skok decrement_block XPOS, 8, 4 left_not_pressed: read_button ; stav tlačítka Right beq right_not_pressed ; není stisknuto? => skok increment_block XPOS, 8, 4 right_not_pressed: rti ; návrat z přerušení .endproc ; Obslužná rutina pro IRQ (maskovatelné přerušení) .proc irq rti ; návrat z přerušení .endproc ; Obslužná rutina pro RESET .proc reset ; nastavení stavu CPU setup_cpu ; nastavení řídicích registrů ldx #$00 stx PPUCTRL ; nastavení PPUCTRL = 0 (NMI) stx PPUMASK ; nastavení PPUMASK = 0 stx DMC_FREQ ; zákaz DMC IRQ ldx #$40 stx $4017 ; interrupt inhibit bit ; čekání na vnitřní inicializaci PPU (dva snímky) wait_for_frame wait_for_frame ; vymazání obsahu RAM clear_ram ; čekání na další snímek wait_for_frame ; nastavení barvové palety jsr load_palette ; zavolání subrutiny ; nastavení spritů jsr load_sprites ; zavolání subrutiny ; vlastní herní smyčka je prozatím prázdná game_loop: jmp game_loop ; nekonečná smyčka (později rozšíříme) .endproc ; vynulování barvové palety .proc clear_palette ppu_data_palette_address ldx #$20 ; počitadlo barev v paletě: 16+16 lda #$00 ; vynulování každé barvy : sta PPUDATA ; zápis barvy dex ; snížení hodnoty počitadla bne :- rts ; návrat ze subrutiny .endproc ; nastavení barvové palety .proc load_palette ppu_data_palette_address ; $3f00-$3f0f - paleta pozadí ; $3f10-$3f1f - paleta spritů ldx #$00 ; vynulovat počitadlo a offset : lda palette, x ; načíst bajt s offsetem sta PPUDATA ; zápis barvy do PPU inx ; zvýšit počitadlo/offset cpx #32 ; limit počtu barev bne :- ; opakovat smyčku 32x rts ; návrat ze subrutiny .endproc ; načtení spritů .proc load_sprites ldx #0 : lda spritedata,X ; budeme přesouvat data z této oblasti sta $0200,X ; uložení do paměti spritů inx ; zvýšení hodnoty počitadla cpx #32 ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32 bne :- cli ; vynulování bitu I - povolení přerušení lda #%10000000 sta PPUCTRL ; při každém VBLANK se vyvolá NMI (důležité!) lda #%00010000 ; povolení zobrazení spritů sta PPUMASK rts ; návrat ze subrutiny .endproc ; samotná barvová paleta palette: .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F ; barvy pozadí .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17 ; barvy spritů ; data pro větší množství spritů spritedata: .byte $10, $00, $00, $08 ; y-coord, tile number, attributes, x-coord .byte $10, $01, $00, $10 .byte $18, $02, $00, $08 .byte $18, $03, $00, $10 .byte $20, $04, $00, $08 .byte $20, $05, $00, $10 .byte $28, $06, $00, $08 .byte $28, $07, $00, $10 ; --------------------------------------------------------------------- ; Tabulka vektorů CPU ; --------------------------------------------------------------------- .segment "VECTORS" .addr nmi .addr reset .addr irq .segment "CHARS" .incbin "mario.chr" ; --------------------------------------------------------------------- ; Finito ; ---------------------------------------------------------------------
19. Repositář s demonstračními příklady
Demonstrační příklady napsané v assembleru, které jsou určené pro překlad pomocí assembleru ca65 (jenž je součástí cc65), byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:
20. Odkazy na Internetu
- NesDev.org
https://www.nesdev.org/ - The Sprite Attribute Byte
https://www.patater.com/nes-asm-tutorials/day-17/ - How to Program an NES game in C
https://nesdoug.com/ - Cycle reference chart
https://www.nesdev.org/wiki/Cycle_reference_chart - Getting Started Programming in C: Coding a Retro Game with C Part 2
https://retrogamecoders.com/getting-started-with-c-cc65/ - NES game development in 6502 assembly – Part 1
https://kibrit.tech/en/blog/nes-game-development-part-1 - NES (Nintendo Entertainment System) controller pinout
https://pinoutguide.com/Game/NES_controller_pinout.shtml - NES Controller Shift Register
https://www.allaboutcircuits.com/uploads/articles/nes-controller-arduino.png?v=1469416980041 - „Game Development in Eight Bits“ by Kevin Zurawel
https://www.youtube.com/watch?v=TPbroUDHG0s&list=PLcGKfGEEONaBjSfQaSiU9yQsjPxxDQyV8&index=4 - Game Development for the 8-bit NES: A class by Bob Rost
http://bobrost.com/nes/ - Game Development for the 8-bit NES: Lecture Notes
http://bobrost.com/nes/lectures.php - NES Graphics Explained
https://www.youtube.com/watch?v=7Co_8dC2zb8 - NES GAME PROGRAMMING PART 1
https://rpgmaker.net/tutorials/227/?post=240020 - NES 6502 Programming Tutorial – Part 1: Getting Started
https://dev.xenforo.relay.cool/index.php?threads/nes-6502-programming-tutorial-part-1-getting-started.858389/ - Minimal NES example using ca65
https://github.com/bbbradsmith/NES-ca65-example - List of 6502-based Computers and Consoles
https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/ - History of video game consoles (second generation): Wikipedia
http://en.wikipedia.org/wiki/History_of_video_game_consoles_(second_generation) - 6502 – the first RISC µP
http://ericclever.com/6500/ - 3 Generations of Game Machine Architecture
http://www.atariarchives.org/dev/CGEXPO99.html - bee – The Multi-Console Emulator
http://www.thebeehive.ws/ - Nerdy Nights Mirror
https://nerdy-nights.nes.science/ - The Nerdy Nights ca65 Remix
https://github.com/ddribin/nerdy-nights - NES Development Day 1: Creating a ROM
https://www.moria.us/blog/2018/03/nes-development - How to Start Making NES Games
https://www.matthughson.com/2021/11/17/how-to-start-making-nes-games/ - ca65 Users Guide
https://cc65.github.io/doc/ca65.html - cc65 Users Guide
https://cc65.github.io/doc/cc65.html - ld65 Users Guide
https://cc65.github.io/doc/ld65.html - da65 Users Guide
https://cc65.github.io/doc/da65.html - Nocash NES Specs
http://nocash.emubase.de/everynes.htm - Nintendo Entertainment System
http://cs.wikipedia.org/wiki/NES - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - NesDev
http://nesdev.parodius.com/ - 2A03 technical reference
http://nesdev.parodius.com/2A03%20technical%20reference.txt - NES Dev wiki: 2A03
http://wiki.nesdev.com/w/index.php/2A03 - Ricoh 2A03
http://en.wikipedia.org/wiki/Ricoh_2A03 - 2A03 pinouts
http://nesdev.parodius.com/2A03_pinout.txt - 27c3: Reverse Engineering the MOS 6502 CPU (en)
https://www.youtube.com/watch?v=fWqBmmPQP40 - “Hello, world” from scratch on a 6502 — Part 1
https://www.youtube.com/watch?v=LnzuMJLZRdU - A Tour of 6502 Cross-Assemblers
https://bumbershootsoft.wordpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/ - Nintendo Entertainment System (NES)
https://8bitworkshop.com/docs/platforms/nes/ - Question about NES vectors and PPU
https://archive.nes.science/nesdev-forums/f10/t4154.xhtml - How do mapper chips actually work?
https://archive.nes.science/nesdev-forums/f9/t13125.xhtml - INES
https://www.nesdev.org/wiki/INES - NES Basics and Our First Game
http://thevirtualmountain.com/nes/2017/03/08/nes-basics-and-our-first-game.html - Where is the reset vector in a .nes file?
https://archive.nes.science/nesdev-forums/f10/t17413.xhtml - CPU memory map
https://www.nesdev.org/wiki/CPU_memory_map - How to make NES music
http://blog.snugsound.com/2008/08/how-to-make-nes-music.html - Nintendo Entertainment System Architecture
http://nesdev.icequake.net/nes.txt - MIDINES
http://www.wayfar.net/0×f00000_overview.php - FamiTracker
http://famitracker.com/ - nerdTracker II
http://nesdev.parodius.com/nt2/ - How NES Graphics work
http://nesdev.parodius.com/nesgfx.txt - NES Technical/Emulation/Development FAQ
http://nesdev.parodius.com/NESTechFAQ.htm - Adventures with ca65
https://atariage.com/forums/topic/312451-adventures-with-ca65/ - example ca65 startup code
https://atariage.com/forums/topic/209776-example-ca65-startup-code/ - 6502 PRIMER: Building your own 6502 computer
http://wilsonminesco.com/6502primer/ - 6502 Instruction Set
https://www.masswerk.at/6502/6502_instruction_set.html - Chip Hall of Fame: MOS Technology 6502 Microprocessor
https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor - Single-board computer
https://en.wikipedia.org/wiki/Single-board_computer - www.6502.org
http://www.6502.org/ - 6502 PRIMER: Building your own 6502 computer – clock generator
http://wilsonminesco.com/6502primer/ClkGen.html - Great Microprocessors of the Past and Present (V 13.4.0)
http://www.cpushack.com/CPU/cpu.html - Jak se zrodil procesor?
https://www.root.cz/clanky/jak-se-zrodil-procesor/ - Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/ - Mikrořadiče a jejich použití v jednoduchých mikropočítačích
https://www.root.cz/clanky/mikroradice-a-jejich-pouziti-v-jednoduchych-mikropocitacich/ - Mikrořadiče a jejich aplikace v jednoduchých mikropočítačích (2)
https://www.root.cz/clanky/mikroradice-a-jejich-aplikace-v-jednoduchych-mikropocitacich-2/ - 25 Microchips That Shook the World
https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world - Comparison of instruction set architectures
https://en.wikipedia.org/wiki/Comparison_of_instruction_set_architectures - Day 1 – Beginning NES Assembly
https://www.patater.com/nes-asm-tutorials/day-1/ - Day 2 – A Source Code File's Structure
https://www.patater.com/nes-asm-tutorials/day-2/ - Assembly Language Misconceptions
https://www.youtube.com/watch?v=8_0tbkbSGRE - How Machine Language Works
https://www.youtube.com/watch?v=HWpi9n2H3kE