Vývoj her pro herní konzoli NES: první seznámení s možnostmi PPU

12. 7. 2022
Doba čtení: 34 minut

Sdílet

 Autor: Depositphotos
V další části seriálu o vývoji pro slavnou a v mnoha ohledech přelomovou herní konzoli Nintendo Entertainment System (NES) se již začneme zabývat jejími grafickými schopnostmi. Taktéž si ukážeme další možnosti assembleru ca65.

Obsah

1. První seznámení s možnostmi PPU

2. Řídicí registry PPU

3. Nastavení barvové palety

4. Zvýšení intenzity zvolené barvové složky

5. Úplný zdrojový kód sedmého demonstračního příkladu

6. Vylepšený zápis adresy barvové palety

7. Úplný zdrojový kód osmého demonstračního příkladu

8. Vymazání barvové palety ve formě makra

9. Úplný zdrojový kód devátého demonstračního příkladu

10. Vymazání barvové palety ve formě podprogramu

11. Úplný zdrojový kód desátého demonstračního příkladu

12. Nastavení barev v barvové paletě z tabulkových dat

13. Úplný zdrojový kód jedenáctého demonstračního příkladu

14. Refaktoring – specifikace adresy pro zápis barev s využitím makra

15. Úplný zdrojový kód dvanáctého demonstračního příkladu

16. Příprava pro zobrazení spritů

17. Odkazy na předchozí části seriálu

18. Repositář s demonstračními příklady

19. Odkazy na Internetu

1. První seznámení s možnostmi PPU

Ve čtvrté části 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) se již (konečně) začneme zabývat jejími grafickými schopnostmi. Ukážeme si zejména práci s barvovými paletami, ale taktéž si postupně připravíme kód napsaný v assembleru, jenž bude určený pro vykreslení spritů – protože právě pohyblivé sprity tvoří společně s pozadím (background) celou grafickou herní scénu. Herní konzole NES totiž neobsahuje podporu pro klasickou bitmapovou grafiku ani podporu textových režimů – vše je nutné vykreslit přes sprity a pozadí, což jsou (velmi zjednodušeně řečeno) indexy bitmapových „dlaždic“ (tiles) uložených v k tomu určených blocích paměti. Vše se navíc poněkud komplikuje technologií mapování hodnot uložených v dlaždicích přes barvové palety na výsledné barvy zobrazené na obrazovce.

2. Řídicí registry PPU

Při práci s grafikou na herní konzoli NES je nutné nejprve provést korektní inicializaci čipu PPU. Zejména je nutné nastavit barvovou paletu, což nám umožní korektně navázat dalšími operacemi, například zobrazením pozadí, zobrazením spritů atd. Čip PPU se ovládá přes několik řídicích registrů, které jsou mapovány do adresového prostoru mikroprocesoru 6502. Všechny tyto řídicí registry jsou pro přehlednost vypsány v následující tabulce:

Registr Adresa (CPU) Stručný popis
PPUCTRL $2000 povolení NMI, nastavení výšky spritů, nastavení dlaždic atd.
PPUMASK $2001 zvýraznění vybrané barvy, povolení spritů, povolení pozadí atd.
PPUSTATUS $2002 stav snímku (VBLANK), stav spritů (kolize)
OAMADDR $2003 adresa pro čtení/zápis do OAM (Object Attribute Memory)
OAMDATA $2004 zapisovaná/čtená data do/z OAM (Object Attribute Memory)
PPUSCROLL $2005 použito pro horizontální i vertikální scrolling
PPUADDR $2006 adresa pro čtení/zápis do paměti PPU
PPUDATA $2007 zapisovaná/čtená data do/z paměti PPU
Poznámka: PPU se do určité míry podobá čipu ANTIC použitého v osmibitových domácích mikropočítačích i herních konzolích firmy Atari. Oba čipy totiž dokážou načítat data z paměti, což znamená, že řídicí registry PPU mnohdy pouze obsahují ukazatele na tato data.

3. Nastavení barvové palety

Jednou z prvních operací s PPU, kterou je nutné provést, je nastavení barvové palety. Ve skutečnosti grafický subsystém NESu pracuje s dvojicí palet, z nichž každá obsahuje šestnáct barev. První paleta je určena pro pozadí, druhá pro sprity. Vzhledem k tomu, že každá barva je zapsána v jednom bajtu, je celková velikost paměti pro uložení obou palet rovna třiceti dvěma bajtům, které tvoří kontinuální blok, ovšem blok v paměti dostupné PPU a nikoli mikroprocesoru.

Obrázek 1: Zjednodušené schéma architektury herní konzole NES.

Barvové palety jsou v paměti dostupné čipu PPU typicky uloženy na adresách $3f00 až $3f1f. Ukažme si nyní, jakým způsobem se palety vynulují, tj. jak se do nich uloží sekvence 32 kódů šedé barvy (kupodivu nula neodpovídá barvě černé, ale právě šedé). Nejprve je nutné čipu PPU sdělit, na jaké adresy se mají (v následujícím kroku) posílaná data uložit – tato adresa se uloží do interního záchytného registru (latche). Před vlastním posláním adresy je vhodné pro jistotu záchytný registr inicializovat (aby PPU věděl, jak má zpracovat zápisy). Tato operace se provede přečtením bajtu z řídicího registru PPUSTATUS, což lze v assembleru realizovat snadno:

        ; vynulování barvové palety
        lda PPUSTATUS ; reset záchytného registru

Dále do čipu PPU pošleme adresu začátku barvových palet (tedy adresu bloku, kde budou kódy barev uloženy). Adresu je nutné poslat jako sekvenci dvou bajtů do řídicího registru PPUADDR, což vede k zápisu adresy do již zmíněného interního záchytného registru (latche). Budeme požadovat, aby palety začínaly na adrese $3f00, což znamená, že adresu pošleme systémem vyšší bajt, nižší bajt:

        lda #$3f      ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #$00      ; nižší bajt adresy
        sta PPUADDR

V posledním kroku pošleme do řídicího registru PPUDATA sekvenci třiceti dvou bajtů. Ty budou čipem PPU korektně zpracovány, protože se využije interní latch, jehož hodnota se po každém zápisu do PPUDATA zvýší o jedničku. V kódu používáme nám již známé instrukce mikroprocesoru MOS 6502 i anonymních návěští (label) podporovaných assemblerem ca65, takže zde pravděpodobně není zapotřebí dalšího podrobnějšího vysvětlování:

        ldx #$20      ; počitadlo barev v paletě: 16+16
        lda #$00      ; vynulování každé barvy

Vlastní programová smyčka:

:
        sta PPUDATA   ; zápis barvy
        dex           ; snížení hodnoty počitadla
        bne :-
Poznámka: tento způsob zápisu bloku dat nám může připadat poměrně složitý, ovšem umožňuje, aby se veškeré přenosy posléze realizovaly přes DMA, tedy do určité míry „zadarmo“ (z pohledu času stráveného mikroprocesorem). Navíc oddělením paměti pro PPU a CPU není CPU příliš zdržován vykreslovacími operacemi tak, jako je tomu na mnoha jiných platformách.

4. Zvýšení intenzity zvolené barvové složky

Zobrazování grafiky čipem PPU je řízeno přes registr nazvaný PPUMASK. Jednotlivé bity tohoto registru mají následující význam:

Bit Stručný popis
7 zvýraznění modré barvové složky
6 zvýraznění zelené barvové složky
5 zvýraznění červené barvové složky
4 povolení zobrazení spritů
3 povolení zobrazení pozadí
2 povolení/zákaz zobrazení spritů v levých osmi pixelech obrazovky
1 povolení/zákaz zobrazení pozadí v levých osmi pixelech obrazovky
0 barevný nebo monochromatický výstup

Zajímavé jsou pro nás prozatím nejvyšší tři bity tohoto registru, protože ty nám umožňují vykreslit na obrazovku „alespoň něco“ kromě šedé barvy. Tyto bity totiž určují, že jedna z vybraných barvových složek RGB se má v rámci celého obrazu zvýraznit na úkor obou zbývajících barvových složek – to vlastně znamená, že má smysl nastavit pouze jediný z těchto bitů.

Můžeme se například pokusit o zvýraznění modré barvové složky, a to takto:

lda #%10000000 ; vysoká intenzita modré barvy
sta PPUMASK

S výsledkem:

Obrázek 2: Zvýraznění modré barvové složky.

Dtto pro zelenou barvovou složku:

lda #%01000000 ; vysoká intenzita zelené barvy
sta PPUMASK

S výsledkem:

Obrázek 3: Zvýraznění zelené barvové složky.

A konečně zvýraznění červené barvové složky celého obrazu:

lda #%00100000 ; vysoká intenzita červené barvy
sta PPUMASK

S výsledkem:

Obrázek 4: Zvýraznění červené barvové složky.

5. Úplný zdrojový kód sedmého demonstračního příkladu

Zdrojový kód v pořadí již sedmého demonstračního příkladu popsaného v předchozích kapitolách je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example07.asm (pro překlad a slinkování tohoto demonstračního příkladu použijte tento Makefile):

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; 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
 
 
; ---------------------------------------------------------------------
; 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
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 1
 
; 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
 
 
 
; ---------------------------------------------------------------------
; 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
        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
        stx PPUMASK             ; nastavení PPUMASK = 0
 
        ; č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
 
        ; vynulování barvové palety
        lda PPUSTATUS ; reset záchytného registru
        lda #$3f      ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #$00      ; nižší bajt adresy
        sta PPUADDR
 
        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 :-
 
        lda #%10000000 ; vysoká intenzita modré barvy
        sta PPUMASK
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

6. Vylepšený zápis adresy barvové palety

Podívejme se ještě jednou na tu část programového kódu, v níž do řídicích registrů čipu PPU zapisujeme adresu začátku barvových palet:

        lda #$3f      ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #$00      ; nižší bajt adresy
        sta PPUADDR

Tento kód má několik nectností. Zejména je v něm adresa „zadrátována“, přičemž by bylo vhodnější použít pojmenovanou konstantu (assembler je totiž – poněkud překvapivě – v některých ohledech vyšším programovacím jazykem, než BASIC). A navíc by bylo vhodné, aby se rozdělení 16bitové adresy na vyšší a nižší bajt provedlo automaticky, ideálně s využitím možností assembleru – protože rozdělení 16bitové hodnoty na dvě hodnoty osmibitové je v oblasti, v níž se mikroprocesor MOS 6502 používá, velmi časté.

Nejprve tedy vytvořme symbolickou konstantu obsahující začátek paměťového bloku s paletami. To je triviální operace:

; Další důležité adresy
PALETTE = $3f00

Následně jen musíme zajistit, aby se z této konstanty získal vyšší a nižší bajt. Assembler ca65 pro tyto účely obsahuje dvojici operátorů zapisovaných znaky < a > Tyto operátory se vyhodnocují v čase překladu, takže za jejich použití neplatíme žádným strojovým časem (při běhu programu):

        lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE ; nižší bajt adresy
        sta PPUADDR
Poznámka: kromě operátorů > a < použitých výše existuje i podobně koncipovaný operátor ^, jenž vrací (jednobajtovou) adresu paměťové banky.

7. Úplný zdrojový kód osmého demonstračního příkladu

Zdrojový kód v pořadí osmého demonstračního příkladu popsaného v předchozích kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example08.asm (pro překlad a slinkování tohoto demonstračního příkladu použijte tento Makefile):

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; 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
 
; 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
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 1
 
; 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
 
 
 
; ---------------------------------------------------------------------
; 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
        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
        stx PPUMASK             ; nastavení PPUMASK = 0
 
        ; č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
 
        ; vynulování barvové palety
        lda PPUSTATUS ; reset záchytného registru
        lda #>PALETTE ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE ; nižší bajt adresy
        sta PPUADDR
 
        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 :-
 
        lda #%10000000 ; vysoká intenzita modré barvy
        sta PPUMASK
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

8. Vymazání barvové palety ve formě makra

V předchozí dvojici demonstračních příkladů bylo nastavení (resp. přesněji řečeno vynulování) barvové palety řešeno přímo v hlavní proceduře reset. Pro větší přehlednost je však vhodnější přenést tento kód buď do makra nebo do samostatného podprogramu (subrutiny). Nejdříve se podívejme na řešení založené na použití makra. Kód pro vynulování barvové palety překopírujeme do pojmenovaného makra:

; vynulování barvové palety
.macro clear_palette
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        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 :-
.endmacro

Toto makro se velmi jednoduše použije (expanduje) následujícím způsobem:

        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; vynulování barvové palety
        clear_palette  ; expanze makra

Makro je do kódu přímo vloženo, což je jasně patrné i při pohledu na listing, což je assemblerem generovaný soubor, jenž pořizujeme v době překladu:

00003Ar 1
00003Ar 1                       ; vynulování barvové palety
00003Ar 1  AD 02 20 A9          clear_palette  ; expanze makra
00003Er 1  3F 8D 06 20
000042r 1  A9 00 8D 06
000046r 1  20 A2 20 A9
00004Ar 1  00 8D 07 20
00004Er 1  CA D0 FA
000051r 1

9. Úplný zdrojový kód devátého demonstračního příkladu

Zdrojový kód v pořadí devátého demonstračního příkladu popsaného v předchozích kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example09.asm (pro překlad a slinkování tohoto demonstračního příkladu použijte tento Makefile):

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Paleta je nastavena přes makro
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; 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
 
; 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
 
; vynulování barvové palety
.macro clear_palette
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        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 :-
.endmacro
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 1
 
; 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
 
 
 
; ---------------------------------------------------------------------
; 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
        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
        stx PPUMASK             ; nastavení PPUMASK = 0
 
        ; č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
 
        ; vynulování barvové palety
        clear_palette  ; expanze makra
 
        lda #%10000000 ; vysoká intenzita modré barvy
        sta PPUMASK
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

10. Vymazání barvové palety ve formě podprogramu

V některých případech nám však nevyhovuje, že kód makra je vkládán (expandován) při každém volání makra v programovém kódu. Mnohdy budeme chtít, aby byl kód realizován formou podprogramu (subrutiny), která je skutečně volána. Využijeme zde přitom trojici technik:

  1. Instrukci jsr neboli Jump to SubRoutine pro volání podprogramu
  2. Instrukci rts neboli ReTurn from Subroutine pro návrat z podprogrammu
  3. Syntaktickou konstrukci a assembleru ca65, jenž nám umožní subrutiny definovat ve zdrojovém kódu

Volání subrutiny vypadá následovně:

        ; vynulování barvové palety
        jsr clear_palette  ; zavolání subrutiny

Což je rozdílné od expanze makra, které se zapisuje odlišně:

        ; vynulování barvové palety
        clear_palette  ; expanze makra

Kód samotné subrutiny se nepatrně odlišuje od makra, protože nesmíme zapomenout na instrukci zajišťující návrat zpět do volaného kódu:

; vynulování barvové palety
.proc clear_palette
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        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

Volaná subrutina není, na rozdíl od makra, přímo expandována, ale je skutečně zavolána instrukcí jsr:

00003Ar 1
00003Ar 1                       ; vynulování barvové palety
00003Ar 1  20 rr rr             jsr clear_palette  ; zavolání subrutiny
00003Dr 1
Poznámka: konkrétní adresa rr rr je doplněna assemblerem v závěrečné fázi překladu a linkování.

Jedná se prozatím o velmi jednoduchý podprogram, kterému se nepředávají žádné parametry a tudíž se ani nemusíme starat o to, jak vůbec tyto parametry předávat – zda přes registry, zásobník apod.

11. Úplný zdrojový kód desátého demonstračního příkladu

Zdrojový kód v pořadí desátého demonstračního příkladu popsaného v předchozích kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example10.asm (pro překlad a slinkování tohoto demonstračního příkladu použijte tento Makefile):

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; 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
 
; 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
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 1
 
; 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
 
 
 
; ---------------------------------------------------------------------
; 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
        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
        stx PPUMASK             ; nastavení PPUMASK = 0
 
        ; č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
 
        ; vynulování barvové palety
        jsr clear_palette  ; zavolání subrutiny
 
        lda #%10000000 ; vysoká intenzita modré barvy
        sta PPUMASK
 
        ; 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
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        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
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

12. Nastavení barev v barvové paletě z tabulkových dat

Podívejme se nyní, jakým způsobem lze nastavit barvovou paletu na barvy zvolené programátorem, nikoli tedy na nudnou šedou barvu, jak tomu bylo v předchozích demonstračních příkladech. Připomeňme si, že se vlastně nastavuje dvojice palet – šestnáct barev pozadí a šestnáct barev spritů. Každá barva v paletě je reprezentována jediným bajtem, tj. obě palety tvoří v paměti PPU blok o délce třiceti dvou bajtů. Barvy jsou vybírány z této palety:

Obrázek 5: Barvová paleta používaná herní konzolí NES.

Poznámka: povšimněte si, že nula (levý horní roh) skutečně odpovídá šedé barvě.

Budeme chtít nastavit paletu, která odpovídá slavné hře Super Mario Bros. Tuto paletu lze reprezentovat sekvencí 32 bajtů uvozených návěštím (label) palette. Jednotlivé bajty jsou v assembleru ca65 definovány přes .byte (zatímco NESASM používá .db – ovšem význam je totožný):

; 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ů
Poznámka: povšimněte si, že první barva pozadí i spritů je nastavena na hodnotu $22, což je (při pohledu na obrázek 5 s paletou) typická „mario brosovská“ modrá barva:

Obrázek 6: Super Mario Bros ve verzi pro NES.

Podprogram (subrutina) pro nastavení barvové palety se do značné míry podobá subrutině pro nastavení všech barev na totožnou (nulovou) hodnotu – provede se přesun 32 bajtů z RAM CPU do RAM PPU přes registr PPUDATA. Pouze se namísto zápisu nulové hodnoty uložené v akumulátoru hodnota načte z palety instrukcí lda adresa,x, kde registr x obsahuje offset, který se postupně zvyšuje od nuly do 31. Současně tento registr slouží i jako počitadlo, jehož hodnota je testována instrukcí cpx následované podmíněným skokem bne:

; nastavení barvové palety
.proc load_palette
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        ; $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
Poznámka: povšimněte si, že si stále vystačíme s minimem instrukcí a stále používáme i stejný podmíněný skok.

Podprogram pro nastavení barvové palety zavoláme nám již známou instrukcí jsr:

        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny

Výsledkem bude tato „herní scéna“:

Obrázek 7: První barva palety pozadí.

13. Úplný zdrojový kód jedenáctého demonstračního příkladu

Zdrojový kód v pořadí jedenáctého demonstračního příkladu popsaného v předchozích kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example11.asm (pro překlad a slinkování tohoto demonstračního příkladu použijte tento Makefile):

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; 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
 
; 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
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 1
 
; 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
 
 
 
; ---------------------------------------------------------------------
; 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
        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
        stx PPUMASK             ; nastavení PPUMASK = 0
 
        ; č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
 
        ; 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
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        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
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        ; $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
 
 
 
; 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ů
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

14. Refaktoring – specifikace adresy pro zápis barev s využitím makra

Pokud se podíváme na instrukce v podprogramech (subrutinách) clear_palette a load_palette, zjistíme, že prvních pět instrukcí je totožných, protože provádí inicializaci PPU a nastavení interního záchytného registru (latche) pro přenos dat do RAM PPU (která jinak není pro CPU dostupná). Těchto pět instrukcí je v následujících dvou výpisech zvýrazněno:

.proc clear_palette
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        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

Totožných pět instrukcí v subrutině load_palette:

.proc load_palette
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
 
        ; $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

Program by bylo vhodné nějakým způsobem refaktorovat, aby se totožný kód ve zdrojovém textu neopakoval. Máme dvě možnosti – použít makro nebo ze subrutiny volat další subrutinu. Oněch pět shodných instrukcí má délku 13 bajtů, což je (alespoň podle mého názoru) na hranici mezi použitím makra (více volání – větší kód) a subrutiny (více volání – déletrvající kód). Zkusme využít makro, do něhož instrukce prostě zkopírujeme:

.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

Úprava zdrojového kódu obou subrutin je nyní triviální:

.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

a:

.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
Poznámka: v rámci tréninku můžete makro nahradit za subrutinu ukončenou instrukcí rts, která se volá instrukcí jsr.

15. Úplný zdrojový kód dvanáctého demonstračního příkladu

Zdrojový kód v pořadí již dvanáctého demonstračního příkladu popsaného v předchozích kapitole je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example12.asm (pro překlad a slinkování tohoto demonstračního příkladu opět použijte tento Makefile):

bitcoin školení listopad 24

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; 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
 
; 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 = 1
 
; 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
 
 
 
; ---------------------------------------------------------------------
; 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
        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
        stx PPUMASK             ; nastavení PPUMASK = 0
 
        ; č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
 
        ; 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
 
 
 
; 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ů
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

16. Příprava pro zobrazení spritů

V rámci dalšího kroku zobrazíme na ploše obrazovky sprite, tedy pohyblivý rastrový obrázek. Zatímco například na C64 je práce se sprity triviální a na osmibitových počítačích Atari jen nepatrně složitější, vyžaduje práce se sprity na herní konzoli NES poněkud komplexnější přípravy (o to jednodušší je ovšem následně zobrazení či pohyb spritů):

  1. V obrazu cartridge (který se nahrává do emulátoru nebo reálné herní konzole NES) musí být i bitmapa s definicí tvarů, z nichž se sprity skládají.
  2. Data spritů je nutné přenést do paměti PPU, a to ideálně s využitím DMA (přímého přístupu do paměti). Tato operace se provede jen jedenkrát, pokud se spokojíme s jednou sadou spritů (někdy se pro různé levely používají odlišné sady).
  3. Je nutné správně nastavit konfiguraci spritů – jejich případné otočení okolo souřadných os, prioritu vůči pozadí a výběr části barvové palety (lze měnit jak v rámci snímku, tak i mezi snímky).
  4. Přes PPU se musí povolit zobrazení spritů.
  5. Taktéž se musí povolit NMI, aby se sprity automaticky zobrazily v každém snímku.
Poznámka: konkrétní demonstrační příklad, v němž sprity zobrazíme, bude uveden příště.

17. Odkazy na předchozí části seriálu

  1. Kouzlo minimalismu: vývoj her a demo programů pro herní konzoli NES
    https://www.root.cz/clanky/kouzlo-minimalismu-vyvoj-her-a-demo-programu-pro-herni-konzoli-nes/
  2. Základy tvorby her pro herní konzoli NES: mikroprocesor 6502 a assembler ca65
    https://www.root.cz/clanky/zaklady-tvorby-her-pro-herni-konzoli-nes-mikroprocesor-6502-a-assembler-ca65/
  3. Základy tvorby her pro herní konzoli NES: triky nabízené assemblerem, tvorba zvuků a grafiky
    https://www.root.cz/clanky/zaklady-tvorby-her-pro-herni-konzoli-nes-triky-nabizene-assemblerem-tvorba-zvuku-a-grafiky/

18. 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. 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ář:

# Příklad Stručný popis Adresa
1 example01.asm zdrojový kód příkladu tvořeného kostrou aplikace pro NES https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example01.asm
2 example02.asm použití standardní konfigurace linkeru pro konzoli NES https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example02.asm
3 example03.asm symbolická jména řídicích registrů PPU https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example03.asm
4 example04.asm zjednodušený zápis lokálních smyček v assembleru https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example04.asm
5 example05.asm zvukový výstup s využitím prvního „square“ kanálu https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example05.asm
6 example06.asm použití maker bez parametrů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example06.asm
       
7 example07.asm nastavení barvové palety, zvýšení intenzity zvolené barvové složky https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example07.asm
8 example08.asm využití operátorů < a > https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example08.asm
9 example09.asm vymazání barvové palety realizované makrem https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example09.asm
10 example10.asm vymazání barvové palety realizované podprogramem https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example10.asm
11 example11.asm nastavení barvové palety pozadí i spritů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example11.asm
12 example12.asm refaktoring předchozího příkladu makrem https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example12.asm
       
13 link.cfg konfigurace segmentů pro linker ld65 https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/link.cfg
14 Makefile Makefile pro překlad a slinkování všech příkladů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/Makefile

19. Odkazy na Internetu

  1. NesDev.org
    https://www.nesdev.org/
  2. How to Program an NES game in C
    https://nesdoug.com/
  3. Getting Started Programming in C: Coding a Retro Game with C Part 2
    https://retrogamecoders.com/getting-started-with-c-cc65/
  4. NES game development in 6502 assembly – Part 1
    https://kibrit.tech/en/blog/nes-game-development-part-1
  5. „Game Development in Eight Bits“ by Kevin Zurawel
    https://www.youtube.com/wat­ch?v=TPbroUDHG0s&list=PLcGKfGE­EONaBjSfQaSiU9yQsjPxxDQyV8&in­dex=4
  6. Game Development for the 8-bit NES: A class by Bob Rost
    http://bobrost.com/nes/
  7. Game Development for the 8-bit NES: Lecture Notes
    http://bobrost.com/nes/lectures.php
  8. NES Graphics Explained
    https://www.youtube.com/wat­ch?v=7Co_8dC2zb8
  9. NES GAME PROGRAMMING PART 1
    https://rpgmaker.net/tuto­rials/227/?post=240020
  10. 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/
  11. Minimal NES example using ca65
    https://github.com/bbbradsmith/NES-ca65-example
  12. List of 6502-based Computers and Consoles
    https://www.retrocompute.co.uk/list-of-6502-based-computers-and-consoles/
  13. History of video game consoles (second generation): Wikipedia
    http://en.wikipedia.org/wi­ki/History_of_video_game_con­soles_(second_generation)
  14. 6502 – the first RISC µP
    http://ericclever.com/6500/
  15. 3 Generations of Game Machine Architecture
    http://www.atariarchives.or­g/dev/CGEXPO99.html
  16. bee – The Multi-Console Emulator
    http://www.thebeehive.ws/
  17. Nerdy Nights Mirror
    https://nerdy-nights.nes.science/
  18. The Nerdy Nights ca65 Remix
    https://github.com/ddribin/nerdy-nights
  19. NES Development Day 1: Creating a ROM
    https://www.moria.us/blog/2018/03/nes-development
  20. How to Start Making NES Games
    https://www.matthughson.com/2021/11/17/how-to-start-making-nes-games/
  21. ca65 Users Guide
    https://cc65.github.io/doc/ca65.html
  22. cc65 Users Guide
    https://cc65.github.io/doc/cc65.html
  23. ld65 Users Guide
    https://cc65.github.io/doc/ld65.html
  24. da65 Users Guide
    https://cc65.github.io/doc/da65.html
  25. Nocash NES Specs
    http://nocash.emubase.de/everynes.htm
  26. NES – Init Code
    https://nesdev-wiki.nes.science/wikipages/I­nit_code.xhtml
  27. Nintendo Entertainment System
    http://cs.wikipedia.org/wiki/NES
  28. Nintendo Entertainment System Architecture
    http://nesdev.icequake.net/nes.txt
  29. NesDev
    http://nesdev.parodius.com/
  30. 2A03 technical reference
    http://nesdev.parodius.com/2A03%20techni­cal%20reference.txt
  31. NES Dev wiki: 2A03
    http://wiki.nesdev.com/w/in­dex.php/2A03
  32. Ricoh 2A03
    http://en.wikipedia.org/wi­ki/Ricoh_2A03
  33. 2A03 pinouts
    http://nesdev.parodius.com/2A03_pi­nout.txt
  34. 27c3: Reverse Engineering the MOS 6502 CPU (en)
    https://www.youtube.com/wat­ch?v=fWqBmmPQP40
  35. “Hello, world” from scratch on a 6502 — Part 1
    https://www.youtube.com/wat­ch?v=LnzuMJLZRdU
  36. A Tour of 6502 Cross-Assemblers
    https://bumbershootsoft.wor­dpress.com/2016/01/31/a-tour-of-6502-cross-assemblers/
  37. Nintendo Entertainment System (NES)
    https://8bitworkshop.com/doc­s/platforms/nes/
  38. Question about NES vectors and PPU
    https://archive.nes.science/nesdev-forums/f10/t4154.xhtml
  39. How do mapper chips actually work?
    https://archive.nes.science/nesdev-forums/f9/t13125.xhtml
  40. INES
    https://www.nesdev.org/wiki/INES
  41. NES Basics and Our First Game
    http://thevirtualmountain­.com/nes/2017/03/08/nes-basics-and-our-first-game.html
  42. Where is the reset vector in a .nes file?
    https://archive.nes.science/nesdev-forums/f10/t17413.xhtml
  43. CPU memory map
    https://www.nesdev.org/wi­ki/CPU_memory_map
  44. How to make NES music
    http://blog.snugsound.com/2008/08/how-to-make-nes-music.html
  45. Nintendo Entertainment System Architecture
    http://nesdev.icequake.net/nes.txt
  46. MIDINES
    http://www.wayfar.net/0×f00000_o­verview.php
  47. FamiTracker
    http://famitracker.com/
  48. nerdTracker II
    http://nesdev.parodius.com/nt2/
  49. How NES Graphics work
    http://nesdev.parodius.com/nesgfx.txt
  50. NES Technical/Emulation/Development FAQ
    http://nesdev.parodius.com/NES­TechFAQ.htm
  51. Adventures with ca65
    https://atariage.com/forum­s/topic/312451-adventures-with-ca65/
  52. example ca65 startup code
    https://atariage.com/forum­s/topic/209776-example-ca65-startup-code/
  53. 6502 PRIMER: Building your own 6502 computer
    http://wilsonminesco.com/6502primer/
  54. 6502 Instruction Set
    https://www.masswerk.at/6502/6502_in­struction_set.html
  55. Chip Hall of Fame: MOS Technology 6502 Microprocessor
    https://spectrum.ieee.org/tech-history/silicon-revolution/chip-hall-of-fame-mos-technology-6502-microprocessor
  56. Single-board computer
    https://en.wikipedia.org/wiki/Single-board_computer
  57. www.6502.org
    http://www.6502.org/
  58. 6502 PRIMER: Building your own 6502 computer – clock generator
    http://wilsonminesco.com/6502pri­mer/ClkGen.html
  59. Great Microprocessors of the Past and Present (V 13.4.0)
    http://www.cpushack.com/CPU/cpu.html
  60. Jak se zrodil procesor?
    https://www.root.cz/clanky/jak-se-zrodil-procesor/
  61. Osmibitové mikroprocesory a mikrořadiče firmy Motorola (1)
    https://www.root.cz/clanky/osmibitove-mikroprocesory-a-mikroradice-firmy-motorola-1/
  62. 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/
  63. 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/
  64. 25 Microchips That Shook the World
    https://spectrum.ieee.org/tech-history/silicon-revolution/25-microchips-that-shook-the-world
  65. Comparison of instruction set architectures
    https://en.wikipedia.org/wi­ki/Comparison_of_instructi­on_set_architectures
  66. Day 1 – Beginning NES Assembly
    https://www.patater.com/nes-asm-tutorials/day-1/
  67. Day 2 – A Source Code File's Structure
    https://www.patater.com/nes-asm-tutorials/day-2/
  68. Assembly Language Misconceptions
    https://www.youtube.com/wat­ch?v=8_0tbkbSGRE
  69. How Machine Language Works
    https://www.youtube.com/wat­ch?v=HWpi9n2H3kE

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.