Vývoj her pro herní konzoli NES: zobrazení pozadí, priorita spritů vůči pozadí

1. 9. 2022
Doba čtení: 36 minut

Sdílet

 Autor: Depositphotos
Už jsme se seznámili se zobrazením pohyblivých objektů na obrazovce (spritů). Dnes si řekneme, jak je zobrazeno statické či rolující pozadí (background). Poněkud paradoxně je zobrazení pozadí složitější než zobrazení spritů.

Obsah

1. Zobrazení pozadí herní scény na konzoli NES

2. Datové struktury použité pro definici pozadí (background)

3. Smazání tabulky jmen i tabulky atributů

4. Vyplnění tabulky jmen

5. Vyplnění tabulky atributů

6. Povolení zobrazení pozadí

7. Shrnutí: kód pro inicializaci herní konzole a zahájení vykreslování

8. Úplný zdrojový kód prvního demonstračního příkladu

9. Pozadí zobrazené po překladu a spuštění příkladu

10. Zobrazení prvních 256 dlaždic z ROM

11. Současné zobrazení pozadí i spritů

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

13. Scéna zobrazená po překladu a spuštění příkladu a odstranění „smetí“

14. Priorita zobrazení spritů a pozadí

15. Realizace změny priority zobrazení spritů

16. Ovládání priority tlačítkem ovladače

17. Úplný zdrojový kód třetího demonstračního příkladu

18. Scéna zobrazená po překladu a spuštění příkladu

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

20. Odkazy na Internetu

1. Zobrazení pozadí herní scény na konzoli NES

V předchozí trojici článků o vývoji her a dem pro herní konzoli NES [1] [2] [3] jsme se seznámili se způsobem zobrazení pohyblivých objektů na obrazovce – takzvaných spritů. Ovšem ještě si musíme říct, jakým způsobem je zobrazeno statické či scrollující pozadí (background). Poněkud paradoxně je zobrazení pozadí složitější než zobrazení spritů a proto se tomuto tématu věnujeme až dnes. Pochopitelně si popíšeme i to, jak lze řídit, zda se má vybraný sprite zobrazit před pozadím nebo až za ním (tedy oproti zvolené konstantní barvě podkladu).

Poznámka: v tomto článku budu používat termíny sprite, pozadí a podklad, i když se někdy setkáme s jiným dělením grafických objektů: sprite, herní pole, pozadí.

Obrázek 1: Super Mario Bros ve verzi pro NES. Vidíme zde herní scénu složenou z pozadí i z několika spritů.

2. Datové struktury použité pro definici pozadí (background)

Z předchozích článků již víme, že u herní konzole NES se obraz posílaný na televizor skládá ze dvou částí: pozadí (background) a pohyblivých spritů. Nejprve si stručně popíšeme, jakým způsobem se definuje pozadí. Při použití televizní normy PAL je rozlišení obrazu rovno 256×240 pixelům, zatímco u normy SECAM bylo horních osm řádků a spodních osm řádků zatemněných, tj. rozlišení bylo sníženo na 256×224 pixelů. Teoreticky sice bylo možné vytvořit klasický framebuffer, v němž by bylo celé pozadí uloženo, ale při šestnáctibarevném obrazu, tj. při použití čtyř bitů na pixel, by musela být kapacita takto vytvořeného framebufferu poměrně velká: 28 kilobajtů (navíc je 16 „globálních“ barev relativně malé množství).

Konstruktéři čipu PPU tedy namísto toho využili technologii, s níž jsme se seznámili i u dalších typů herních konzolí: namísto framebufferu byly v obrazové paměti uloženy vzorky o velikosti 8×8 pixelů, které byly skládány do mřížky 32×30 dlaždic, což přesně odpovídá již zmíněnému rozlišení 256×240 pixelů (32×8=256, 30×8=240).

Základní datovou strukturou pro popis pozadí je struktura nazvaná Name Table o velikosti 960 bajtů, která obsahuje indexy všech tvarů tvořících dlaždicovitý obraz 32×30 dlaždic. Mohlo by se tedy zdát, že jedinou další potřebnou strukturou je tabulka všech vzorků (bitmap), z nichž každá má velikost 8×8 pixelů – ostatně naprosto stejně jsme postupovali u spritů. Situace je však v tomto případě poněkud složitější, protože na pozadí lze vykreslit až šestnáct různých barev. V tabulce vzorků (Pattern Table) jsou pro každý pixel vyhrazeny dva bity, tj. lze rozlišit čtyři možnosti/barvy.

Kromě toho existuje ještě tabulka atributů (Attribute Table) o velikosti 64 bajtů, která obsahuje horní dva bity pro oblast o velikosti 4×4 dlaždice, tj. 32×32 pixelů. Díky existenci této druhé tabulky je skutečně možné – i když s mnoha omezeními – použít na pozadí šestnáct různých barev – a to při minimálních paměťových nárocích. Důležité přitom je, že tabulka vzorků může být přemapována do ROM na paměťovém modulu, takže je možné poměrně jednoduchým způsobem například animovat celou scénu pouhou změnou „ukazatele“ na tuto tabulku (což mnohé hry skutečně dělaly).

3. Smazání tabulky jmen i tabulky atributů

Tabulka jmen je z pohledu PPU (nikoli CPU!) uložena od adresy $2000 a má délku přesně 960 bajtů (32×30). Ihned za touto tabulkou se nachází tabulka s atributy. Obě tabulky můžeme snadno smazat kódem popsaným v této kapitole. Nejprve si připravíme symbolická jména pro důležité adresy:

; Jména řídicích registrů použitých v kódu
PPUSTATUS       = $2002
PPUADDR         = $2006
 
; Další důležité adresy
NAME_TABLE_0    = $2000

V dalším kroku nastavíme registry PPU pro zahájení přenosu:

; vynulování tabulek jmen
.proc clear_nametables
        lda PPUSTATUS         ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR

Dále si připravíme pracovní registry. Dvojice registrů X a Y budou tvořit 16bitový čítač, a to z toho důvodu, že budeme zapisovat více než 256 bajtů. Do tabulky jmen uložíme hodnoty $24, což v našem případě představuje dlaždici (tile) s modrou oblohou:

        ldx #$08           ; počitadlo stránek (8)
        ldy #$00           ; X a Y tvoří 16bitový čítač
        lda #$24           ; dlaždice číslo $24 představuje oblohu

Následuje programová smyčka se zápisem hodnoty $24 přes několik paměťových stránek:

:
        sta PPUDATA        ; zápis indexu dlaždice
        dey                ; snížení hodnoty počitadla
        bne :-             ; skok dokud se nezaplní celá stránka
        dex                ; snížení hodnoty počitadla stránek
        bne :-             ; skok dokud se nezaplní 8 stránek

        rts                ; návrat ze subrutiny
.endproc

4. Vyplnění tabulky jmen

Nyní je všech 960 bajtů tabulky jmen nastaveno na hodnotu $24, což odpovídá modré obloze. Následující podprogram vyplní prvních 128 bajtů této tabulky odlišnými hodnotami získanými z dat uložených od adresy nametabledata:

; tabulka jmen
nametabledata:
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; první řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; druhý řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24  ; třetí řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24  ; různé cihly (horní polovina)
 
    .byte $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24  ; čtvrtý řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24  ; různé cihly (spodní polovina)

128 bajtů odpovídá čtyřem řádkům v mřížce 32×30 „superdlaždic“, tedy dlaždic o velikosti 16×16 pixelů. Samotný přenos dat je snadný a vystačíme si zde pouze s využitím jednoho čítače (méně dat než 256 bajtů):

; načtení tabulky jmen
.proc load_nametable
        lda PPUSTATUS         ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
 
        ldx #$00              ; počitadlo
:
        lda nametabledata,X
        sta PPUDATA           ; zápis indexu dlaždice
        inx
        cpx #$80              ; chceme přenést 128 bajtů
        bne :-
 
        rts                   ; návrat ze subrutiny
.endproc
Poznámka: později budeme vyplňovat všech 960 bajtů této tabulky, takže budeme muset použít oba index registry, tedy jak X, tak i Y.

5. Vyplnění tabulky atributů

Tabulka atributů má velikost 64 bajtů a obsahuje horní dva bity (výběr barvové palety) pro oblast o velikosti 4×4 dlaždice, tj. 32×32 pixelů. Vzhledem k tomu, že se v této tabulce pracuje vždy s dvojicemi bitů, je vhodné její obsah reprezentovat v bitové podobě (prefix % značí v assembleru binární hodnoty):

; tabulka atributů
attributedata:
    .byte %00000000, %00010000, %00100000, %00000000, %00000000, %00000000, %00000000, %00110000
    ...
    ...
    ...

Formát:

7654 3210
|||| ||||
|||| ||++- index barvy (bity 2 a 3) pro levý horní kvadrant
|||| ++--- index barvy (bity 2 a 3) pro pravý horní kvadrant
||++------ index barvy (bity 2 a 3) pro levý dolní kvadrant
++-------- index barvy (bity 2 a 3) pro pravý dolní kvadrant

Tato tabulka začíná od adresy $23c0 (pochopitelně opět z pohledu PPU, nikoli CPU):

; Další důležité adresy
ATTRIB_TABLE_0  = $23c0

Samotný přenos atributů je založen na kódu, který již dobře známe – naplnění registru s adresou PPU následovaný zápisem dat do registru PPUDATA. Prozatím nám bude stačit přenos osmi bajtů, protože „kostky“ pozadí jsou vyplněny na prvních čtyřech řádcích:

; načtení atributů
.proc load_attributes
        lda PPUSTATUS           ; reset záchytného registru
        lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0
        sta PPUADDR
        lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0
        sta PPUADDR
        ldx #$00                ; počitadlo smyčky
:
        lda attributedata,X
        sta PPUDATA             ; zápis indexu
        inx                     ; zvýšení hodnoty počitadla
        cpx #$08                ; provádíme kopii osmi bajtů
        bne :-
 
        rts                     ; návrat ze subrutiny
.endproc

6. Povolení zobrazení pozadí

Nyní nám již zbývá pouze nakonfigurovat PPU takovým způsobem, aby pozadí skutečně zobrazil. K tomu je nutné změnit obsah registrů PPUCTRL a PPUMASK.

V registru PPUCTRL povolíme NMI a nastavíme adresu pozadí (poslední a pátý bit):

7  bit  0
---- ----
VPHB SINN
|||| ||||
|||| ||++- Base nametable address
|||| ||    (0 = $2000; 1 = $2400; 2 = $2800; 3 = $2C00)
|||| |+--- VRAM address increment per CPU read/write of PPUDATA
|||| |     (0: add 1, going across; 1: add 32, going down)
|||| +---- Sprite pattern table address for 8x8 sprites
||||       (0: $0000; 1: $1000; ignored in 8x16 mode)
|||+------ Background pattern table address (0: $0000; 1: $1000)
||+------- Sprite size (0: 8x8 pixels; 1: 8x16 pixels – see PPU OAM#Byte 1)
|+-------- PPU master/slave select
|          (0: read backdrop from EXT pins; 1: output color on EXT pins)
+--------- Generate an NMI at the start of the
           vertical blanking interval (0: off; 1: on)

V registru PPUMASK nastavíme bit povolující zobrazení pozadí:

7  bit  0
---- ----
BGRs bMmG
|||| ||||
|||| |||+- Greyscale (0: normal color, 1: produce a greyscale display)
|||| ||+-- 1: Show background in leftmost 8 pixels of screen, 0: Hide
|||| |+--- 1: Show sprites in leftmost 8 pixels of screen, 0: Hide
|||| +---- 1: Show background
|||+------ 1: Show sprites
||+------- Emphasize red (green on PAL/Dendy)
|+-------- Emphasize green (red on PAL/Dendy)
+--------- Emphasize blue

V programovém kódu to vypadá následovně:

        cli                   ; vynulování bitu I - povolení přerušení
        lda #%10010000
        sta PPUCTRL           ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00011110        ; povolení zobrazení pozadí a současně i spritů
        sta PPUMASK

7. Shrnutí: kód pro inicializaci herní konzole a zahájení vykreslování

Shrňme si nyní, které operace je nutné provést při inicializaci (RESETu) herní konzole tak, aby se zahájilo vykreslování pozadí. Pro zajímavost jsou některé operace realizovány formou makra, jiné formou subrutin (podprogramů):

; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$40
        stx $4017               ; zákaz IRQ z APU
 
        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í tabulek jmen
        jsr clear_nametables  ; zavolání subrutiny 
 
        ; nastavení barvové palety
        jsr load_palette      ; zavolání subrutiny
 
        ; načtení tabulky jmen
        jsr load_nametable    ; zavolání subrutiny
 
        ; načtení atributů
        jsr load_attributes   ; zavolání subrutiny
 
        cli                   ; vynulování bitu I - povolení přerušení
        lda #%10010000
        sta PPUCTRL           ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00001110        ; povolení zobrazení pozadí, nikoli ovšem spritů
        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

8. Úplný zdrojový kód 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ž dvacátého pátého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example25.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example25.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice pozadí a zobrazení pozadí
;
; 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
NAME_TABLE_0    = $2000
ATTRIB_TABLE_0  = $23c0
 
 
 
; ---------------------------------------------------------------------
; 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 #$40
        stx $4017               ; zákaz IRQ z APU
 
        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í tabulek jmen
        jsr clear_nametables  ; zavolání subrutiny 
 
        ; nastavení barvové palety
        jsr load_palette      ; zavolání subrutiny
 
        ; načtení tabulky jmen
        jsr load_nametable    ; zavolání subrutiny
 
        ; načtení atributů
        jsr load_attributes   ; zavolání subrutiny
 
        cli                   ; vynulování bitu I - povolení přerušení
        lda #%10010000
        sta PPUCTRL           ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00001110        ; povolení zobrazení pozadí, nikoli ovšem spritů
        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í tabulek jmen
.proc clear_nametables
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
        ldx #$08           ; počitadlo stránek (8)
        ldy #$00           ; X a Y tvoří 16bitový čítač
        lda #$24           ; dlaždice číslo $24 představuje oblohu
:
        sta PPUDATA        ; zápis indexu dlaždice
        dey                ; snížení hodnoty počitadla
        bne :-             ; skok dokud se nezaplní celá stránka
        dex                ; snížení hodnoty počitadla stránek
        bne :-             ; skok dokud se nezaplní 8 stránek
 
        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í tabulky jmen
.proc load_nametable
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
 
        ldx #$00           ; počitadlo
:
        lda nametabledata,X
        sta PPUDATA        ; zápis indexu dlaždice
        inx
        cpx #$80           ; chceme přenést 128 bajtů
        bne :-
 
        rts                ; návrat ze subrutiny
.endproc
 
 
 
; načtení atributů
.proc load_attributes
        lda PPUSTATUS        ; reset záchytného registru
        lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0
        sta PPUADDR
        lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0
        sta PPUADDR
        ldx #$00             ; počitadlo smyčky
:
        lda attributedata,X
        sta PPUDATA          ; zápis indexu
        inx                  ; zvýšení hodnoty počitadla
        cpx #$08             ; provádíme kopii osmi bajtů
        bne :-
 
        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 jmen
nametabledata:
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; první řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; druhý řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24  ; třetí řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24  ; různé cihly (horní polovina)
 
    .byte $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24  ; čtvrtý řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24  ; různé cihly (spodní polovina)
 
 
; tabulka atributů
attributedata:
    .byte %00000000, %00010000, %00100000, %00000000, %00000000, %00000000, %00000000, %00110000
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

9. Pozadí zobrazené po překladu a spuštění příkladu

Po překladu a spuštění předchozího demonstračního příkladu by se měla objevit tato herní scéna:

Obrázek 2: Pozadí zobrazené po překladu a spuštění příkladu.

10. Zobrazení prvních 256 dlaždic z ROM

Nepatrnou úpravou zdrojového kódu si můžeme nechat zobrazit prvních 256 dlaždic tak, jak jsou definovány v ROM:

Obrázek 3: Prvních 256 dlaždic získaných z ROM.

Úprava vypadá takto:

; načtení tabulky jmen
.proc load_nametable
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
 
        ldx #$00           ; počitadlo
:
        stx                ; samotné počitadlo bude použito jako index dlaždice
        sta PPUDATA        ; zápis indexu dlaždice
        inx                ; automaticky nastaví příznak zero při přetečení
                           ; nemusíme tedy volat instrukci cpx
        bne :-
 
        rts                ; návrat ze subrutiny
.endproc

11. Současné zobrazení pozadí i spritů

V naprosté většině her (snad až na úvodní obrazovku nebo obrazovku „Game over“) se současně zobrazují jak sprity, tak i pozadí. To vlastně není nic těžkého – musíme pouze vyplnit všechny potřebné paměťové regiony PPU a následně zobrazení povolit. Opět se podívejme na řídicí registr PPUMASK, který obsahuje jednotlivé bity, které musíme nastavit. Nastavované bity jsou zvýrazněny:

7  bit  0
---- ----
BGRs bMmG
|||| ||||
|||| |||+- Greyscale (0: normal color, 1: produce a greyscale display)
|||| ||+-- 1: Show background in leftmost 8 pixels of screen, 0: Hide
|||| |+--- 1: Show sprites in leftmost 8 pixels of screen, 0: Hide
|||| +---- 1: Show background
|||+------ 1: Show sprites
||+------- Emphasize red (green on PAL/Dendy)
|+-------- Emphasize green (red on PAL/Dendy)
+--------- Emphasize blue
Poznámka: ve skutečnosti není nastavení prvního a druhého bitu v řídicím registru zcela potřebné (vlastně se zakáže maskování spritů na okrajích obrazovky).

V kódu bude toto nastavení vypadat následovně:

        cli                   ; vynulování bitu I - povolení přerušení
        lda #%10010000
        sta PPUCTRL           ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00011110        ; povolení zobrazení pozadí a současně i spritů
        sta PPUMASK

Dále pochopitelně musíme do programového kódu vrátit všechna makra a subrutiny použité pro zobrazení spritů.

12. Úplný zdrojový kód 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ž dvacátého šestého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example26.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example26.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 Mariem
; Definice pozadí a zobrazení pozadí
;
; 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
NAME_TABLE_0    = $2000
ATTRIB_TABLE_0  = $23c0
 
 
 
; ---------------------------------------------------------------------
; 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 #$40
        stx $4017               ; zákaz IRQ z APU
 
        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í tabulek jmen
        jsr clear_nametables  ; zavolání subrutiny 
 
        ; nastavení barvové palety
        jsr load_palette      ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites      ; zavolání subrutiny
 
        ; načtení tabulky jmen
        jsr load_nametable    ; zavolání subrutiny
 
        ; načtení atributů
        jsr load_attributes   ; zavolání subrutiny
 
        cli                   ; vynulování bitu I - povolení přerušení
        lda #%10010000
        sta PPUCTRL           ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00011110        ; povolení zobrazení pozadí a současně i spritů
        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
        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
 
 
 
; vynulování tabulek jmen
.proc clear_nametables
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
        ldx #$08           ; počitadlo stránek (8)
        ldy #$00           ; X a Y tvoří 16bitový čítač
        lda #$24           ; dlaždice číslo $24 představuje oblohu
:
        sta PPUDATA        ; zápis indexu dlaždice
        dey                ; snížení hodnoty počitadla
        bne :-             ; skok dokud se nezaplní celá stránka
        dex                ; snížení hodnoty počitadla stránek
        bne :-             ; skok dokud se nezaplní 8 stránek
 
        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 :-
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; načtení tabulky jmen
.proc load_nametable
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
 
        ldx #$00           ; počitadlo
:
        lda nametabledata,X
        sta PPUDATA        ; zápis indexu dlaždice
        inx
        cpx #$80           ; chceme přenést 128 bajtů
        bne :-
 
        rts                ; návrat ze subrutiny
.endproc
 
 
 
; načtení atributů
.proc load_attributes
        lda PPUSTATUS        ; reset záchytného registru
        lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0
        sta PPUADDR
        lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0
        sta PPUADDR
        ldx #$00             ; počitadlo smyčky
:
        lda attributedata,X
        sta PPUDATA          ; zápis indexu
        inx                  ; zvýšení hodnoty počitadla
        cpx #$08             ; provádíme kopii osmi bajtů
        bne :-
 
        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 jmen
nametabledata:
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; první řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; druhý řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24  ; třetí řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24  ; různé cihly (horní polovina)
 
    .byte $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24  ; čtvrtý řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24  ; různé cihly (spodní polovina)
 
 
; tabulka atributů
attributedata:
    .byte %00000000, %00010000, %00100000, %00000000, %00000000, %00000000, %00000000, %00110000
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

13. Scéna zobrazená po překladu a spuštění příkladu a odstranění „smetí“

Po překladu a spuštění předchozího příkladu by se měla na obrazovce NESu (tedy většinou v okně emulátoru) zobrazit tato scéna složená z osmi spritů a pozadí:

Obrázek 4: Herní scéna zobrazená po překladu a spuštění příkladu.

Poznámka: povšimněte si, že v levém horním rohu je zobrazeno „smetí“ tvořené sprity s nulovými souřadnicemi. Snadno se ho zbavíme překonfigurováním registru PPUMASK, konkrétně vynulováním druhého a třetího bitu:
        lda #%00011000        ; povolení zobrazení pozadí a současně i spritů
        sta PPUMASK

Obrázek 5: Herní scéna zobrazená po překladu a spuštění upraveného příkladu.

14. Priorita zobrazení spritů a pozadí

Již v předchozích článcích jsme si řekli, že třetí bajt z celé čtyřbajtové struktury s metainformacemi o spritu obsahuje jedno bitové pole a tři samostatné bity, které řídí způsob zobrazení daného spritu (a to zcela nezávisle na ostatních spritech):

7 6 5 4 3 2 1 0
| | | | | | | |
| | | | | | +-+- Index barvové palety
| | | | | |
| | | +-+-+----- Nepoužito
| | |
| | +----------- Priorita (0: před pozadím; 1: za pozadím)
| |
| +------------- Horizontální zrcadlení spritu
|
+--------------- Vertikální zrcadlení spritu

Podívejme se nyní na to, jaký vliv má pátý bit na způsob zobrazení herní scény. V případě, že má daný sprite nastavenou prioritu „před pozadím“, je vždy zobrazen před všemi barvami pozadí. Pokud naopak má nastavenou prioritu „za pozadím“, je sprite viditelný pouze na těch pixelech, kde je barva pozadí nastavena na nulu. Pro všechny tři ostatní barvy pozadí je sprite skrytý. Důvodem, proč existuje výjimka pro barvu pozadí číslo 0, je zřejmá – v opačném případě by sprite nebyl viditelný nikde, neboť pozadí pokrývá celou plochu obrazovky.

Poznámka: samotné sprity mají vůči sobě prioritu nastavenou podle jejich indexu, tedy sprite s vyšším indexem překrývá (část) sprite s nižším indexem.

15. Realizace změny priority zobrazení spritů

Změnu priority zobrazení spritů můžeme realizovat makrem, které již bylo popsáno minule. Konkrétně se jedná o makro, které dokáže změnit (invertovat) zvolený bit či bity v paměťovém bloku, přičemž změna nebude provedena v sousedních bajtech, ale s využitím zadané mezery (gap):

.macro flip_bit_block address, count, gap, mask
        ldx #0             ; inicializace offsetu
:
        lda address, x     ; maskování hodnoty
        eor #mask
        sta address, x
 
        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

Konkrétně budeme měnit osm bajtů v bloku 8×4=32 bajtů. Bitová maska pro inverzi bitů je nastavena takovým způsobem, aby byl invertován pátý bit:

        flip_bit_block ATTRS, 8, 4, %00100000

16. Ovládání priority tlačítkem ovladače

Ještě se podívejme na to, jak budeme měnit prioritu zobrazení spritů vůči pozadí. Tuto změnu bude možné provést tlačítkem B na ovladači, a to díky následujícímu kódu volanému při každém NMI (zobrazení snímku na obrazovce):

        read_button        ; stisk tlačítka B bude sloužit pro přepínání atributů spritů
        beq button_b_not_pressed ; není stisknuto? => skok
 
        dec COUNTER2
        bne button_b_not_pressed
 
        lda #10
        sta COUNTER2
 
        flip_bit_block ATTRS, 8, 4, %00100000

17. Úplný zdrojový kód třetí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 sedmého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example27.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example27.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 Mariem
; Definice pozadí a zobrazení pozadí
; Využití symbolických jmen adres.
; Pomocná makra pro pohyb spritu.
; Změna dalších vlastností spritů s využitím tlačítek A a B
;
; 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
NAME_TABLE_0    = $2000
ATTRIB_TABLE_0  = $23c0
 
; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017
 
; Čítače
COUNTER1        = $00fe
COUNTER2        = $00ff
 
 
 
; ---------------------------------------------------------------------
; 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 increment_block_mask address, count, gap, mask
        ldx #0             ; inicializace offsetu
:
        inc address, x     ; zvýšit pozici spritu o jedničku
 
        lda address, x     ; maskování hodnoty
        and #mask
        sta address, x
 
        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 flip_bit_block address, count, gap, mask
        ldx #0             ; inicializace offsetu
:
        lda address, x     ; maskování hodnoty
        eor #mask
        sta address, x
 
        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
 
        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
        ATTRS = $0202      ; adresa buňky paměti s atributy spritu
 
        read_button        ; stisk tlačítka A bude sloužit pro přepínání barvy spritů
        beq button_a_not_pressed ; není stisknuto? => skok
 
        dec COUNTER1
        bne button_a_not_pressed
 
        lda #10
        sta COUNTER1
 
        increment_block_mask ATTRS, 8, 4, 3
 
button_a_not_pressed:
 
        read_button        ; stisk tlačítka B bude sloužit pro přepínání atributů spritů
        beq button_b_not_pressed ; není stisknuto? => skok
 
        dec COUNTER2
        bne button_b_not_pressed
 
        lda #10
        sta COUNTER2
 
        flip_bit_block ATTRS, 8, 4, %00100000
 
button_b_not_pressed:
 
        read_button        ; stisk tlačítka Select bude sloužit pro přepínání atributů spritů
        beq button_select_not_pressed ; není stisknuto? => skok
 
        dec COUNTER2
        bne button_select_not_pressed
 
        lda #10
        sta COUNTER2
 
        flip_bit_block ATTRS, 8, 4, %10000000
 
button_select_not_pressed:
 
        read_button        ; stav tlačítka Start jen načteme a ingorujeme
 
        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 #$40
        stx $4017               ; zákaz IRQ z APU
 
        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í tabulek jmen
        jsr clear_nametables  ; zavolání subrutiny 
 
        ; nastavení barvové palety
        jsr load_palette      ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites      ; zavolání subrutiny
 
        ; načtení tabulky jmen
        jsr load_nametable    ; zavolání subrutiny
 
        ; načtení atributů
        jsr load_attributes   ; zavolání subrutiny
 
        cli                   ; vynulování bitu I - povolení přerušení
        lda #%10010000
        sta PPUCTRL           ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00011110        ; povolení zobrazení pozadí a současně i spritů
        sta PPUMASK
 
        lda #10           ; inicializace čítačů
        sta COUNTER1
        sta COUNTER2
 
        ; 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
 
 
 
; vynulování tabulek jmen
.proc clear_nametables
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
        ldx #$08           ; počitadlo stránek (8)
        ldy #$00           ; X a Y tvoří 16bitový čítač
        lda #$24           ; dlaždice číslo $24 představuje oblohu
:
        sta PPUDATA        ; zápis indexu dlaždice
        dey                ; snížení hodnoty počitadla
        bne :-             ; skok dokud se nezaplní celá stránka
        dex                ; snížení hodnoty počitadla stránek
        bne :-             ; skok dokud se nezaplní 8 stránek
 
        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 :-
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; načtení tabulky jmen
.proc load_nametable
        lda PPUSTATUS      ; reset záchytného registru
        lda #>NAME_TABLE_0 ; horní bajt adresy
        sta PPUADDR
        lda #<NAME_TABLE_0 ; spodní bajt adresy
        sta PPUADDR
 
        ldx #$00           ; počitadlo
:
        lda nametabledata,X
        sta PPUDATA        ; zápis indexu dlaždice
        inx
        cpx #$80           ; chceme přenést 128 bajtů
        bne :-
 
        rts                ; návrat ze subrutiny
.endproc
 
 
 
; načtení atributů
.proc load_attributes
        lda PPUSTATUS        ; reset záchytného registru
        lda #>ATTRIB_TABLE_0 ; horní bajt adresy $23c0
        sta PPUADDR
        lda #<ATTRIB_TABLE_0 ; spodní bajt adresy $23c0
        sta PPUADDR
        ldx #$00             ; počitadlo smyčky
:
        lda attributedata,X
        sta PPUDATA          ; zápis indexu
        inx                  ; zvýšení hodnoty počitadla
        cpx #$08             ; provádíme kopii osmi bajtů
        bne :-
 
        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 jmen
nametabledata:
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; první řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24  ; druhý řádek: nebe
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24
 
    .byte $24,$24,$24,$24,$45,$45,$24,$24,$45,$45,$45,$45,$45,$45,$24,$24  ; třetí řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$53,$54,$24,$24  ; různé cihly (horní polovina)
 
    .byte $24,$24,$24,$24,$47,$47,$24,$24,$47,$47,$47,$47,$47,$47,$24,$24  ; čtvrtý řádek
    .byte $24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$24,$55,$56,$24,$24  ; různé cihly (spodní polovina)
 
 
; tabulka atributů
attributedata:
    .byte %00000000, %00010000, %00100000, %00000000, %00000000, %00000000, %00000000, %00110000
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

18. Scéna zobrazená po překladu a spuštění příkladu

Pokud příklad přeložíme a spustíme, zobrazí se jak pozadí, tak i osmice spritů tvořících figurku Maria. Tlačítkem B je možné měnit prioritu zobrazení spritu vůči pozadí:

Obrázek 6: Sprity zobrazené před pozadím (zde konkrétně zdí).

Obrázek 7: Sprity zobrazené za pozadím (zde konkrétně zdí).

popř. jiná část stejné obrazovky:

Obrázek 8: Sprity zobrazené před pozadím (zde konkrétně zdí).

ict ve školství 24

Obrázek 9: Sprity zobrazené za pozadím (zde konkrétně zdí).

Poznámka: povšimněte si, že v každém případě je sprite zobrazen před modrým podkladem.

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

# 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 example13.asm zobrazení spritů tvořících Maria https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example13.asm
14 example14.asm posun spritů, aby se zdůraznila jejich nezávislost https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example14.asm
15 example15.asm větší množství spritů na obrazovce rozdělených do řádků https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example15.asm
16 example16.asm větší množství spritů na obrazovce na jediném řádku https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example16.asm
17 example17.asm pohyb jednoho spritu pomocí ovladače https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example17.asm
18 example18.asm odvozeno z předchozího příkladu, symbolická jména adres https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example18.asm
19 example19.asm odvozeno z předchozího příkladu, pomocná makra pro pohyb spritu https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example19.asm
20 example20.asm pohyb spritu je založen na instrukcích INCDEC https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example20.asm
21 example21.asm přesun celého Maria (8 spritů) https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example21.asm
22 example22.asm (rychlá) změna barvové palety spritů tlačítkem A https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example22.asm
23 example23.asm realizace čítače pro snížení frekvence změn barvové palety https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example23.asm
24 example24.asm horizontální a vertikální zrcadlení spritů řízené hráčem stiskem tlačítek https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example24.asm
25 example25.asm definice pozadí a zobrazení pozadí https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example25.asm
26 example26.asm zobrazení pozadí i spritů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example26.asm
27 example27.asm pohyb spritu řízený ovladačem, změna atributů spritů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example27.asm
       
28 link.cfg konfigurace segmentů pro linker ld65 https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/link.cfg
29 Makefile Makefile pro překlad a slinkování všech příkladů https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/Makefile
Poznámka: pro slinkování a spuštění dnešních demonstračních příkladů potřebujete i soubor mario.chr. Ten je stažen automaticky po zadání make example16make example23.

20. Odkazy na Internetu

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