Hlavní navigace

Vývoj her pro herní konzoli NES: čtení ovladače, pohyb spritů a rutina VBLANK

16. 8. 2022
Doba čtení: 49 minut

Sdílet

 Autor: Depositphotos
V šesté části seriálu o vývoji her a pro slavnou a v mnoha ohledech přelomovou konzoli Nintendo Entertainment System (NES) si ukážeme, jak číst stav tlačítek herního ovladače a jak pomocí něj pohybovat sprity na obrazovce.

Obsah

1. Vývoj her pro herní konzoli NES: čtení ovladače, pohyb spritů a rutina VBLANK

2. Ovládání her na osmibitových počítačích a herních konzolích

3. Posuvný registr v ovladačích NESu

4. Čtení stavu tlačítek na ovladačích NESu

5. Programový test stavu tlačítek

6. Posun spritu s využitím ovladače

7. Rutina VBLANK

8. Praktická realizace posunu spritu

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

10. Assembler se překládal jako JSA – Jazyk Symbolických Adres

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

12. Podpůrná makra increment a decrement

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

14. MOS 6502 není RISCovým procesorem aneb síla instrukcí INCDEC

15. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu

16. Přesun všech osmi spritů tvořících figurku Maria

17. Trik – aritmetické operace s registrem X

18. Úplný zdrojový kód dnešního pátého demonstračního příkladu

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

20. Odkazy na Internetu

1. Vývoj her pro herní konzoli NES: čtení ovladače, pohyb spritů a rutina VBLANK

V (mini)seriálu o vývoji her a multimediálních dem určených pro slavnou a v mnoha ohledech přelomovou osmibitovou herní konzoli Nintendo Entertainment System (NES) jsme si již ukázali, jak má vlastně vypadat kostra hry či dema napsaného v assembleru, konkrétně v assembleru ca65. Taktéž již máme základní informace nutné pro přehrání zvuků a hudby a umíme na obrazovku vykreslit 64 spritů, každý z nich s rozlišením 8×8 pixelů. Dalším krokem bude rozhýbání celé zobrazené scény. Začneme skutečně tím nejtriviálnějším příkladem, konkrétně ukázkou a vysvětlením, jakým způsobem je možné pohybovat spritem po obrazovce s využitím standardního ovladače NESu (ten má namísto joysticku D-pad). Budeme se tedy muset naučit hned několik věcí:

  1. Přečtení stavu tlačítek ovladače (to není zcela triviální)
  2. Změnu pozice spritu (a práce s příznakem přetečení)
  3. Zajištění, že se sprite bude po obrazovce pohybovat konstantní rychlostí
  4. Zajištění pohybu celé postavičky (Maria), která je složena z osmi spritů

Příklady, které budou ukázány v navazujících kapitolách, jsou založeny na příkladu

https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example13.asm, jenž dokáže na obrazovce zobrazit postavičku Maria. Všechny části tohoto příkladu jsme si již vysvětlili v předchozích článcích:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s Mariem
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02                ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0            ; vynulování počitadla
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro osm spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

2. Ovládání her na osmibitových počítačích a herních konzolích

Na většině klasických osmibitových počítačích a herních konzolích se setkáme s možností připojení standardních ovladačů, typicky joysticků (výjimkou je ZX Spectrum, kde původně standard neexistoval, takže „standardů“ vzniklo více – odtud nabídka různých typů ovládání v prakticky každé hře pro Speccy). Typické dobové joysticky byla jednoduchá zařízení – čtyři (mikro)spínače sloužily pro určení směru (náklonu) joysticku; dále byl joystick vybaven jedním nebo dvěma tlačítky, která měla buď stejnou funkci (paralelní zapojení), nebo dvě funkce. Těchto pět resp. šest tlačítek bylo připojeno přímo k počítači „paralelním“ způsobem – každé tlačítko na samostatný pin k tomu určeného konektoru. Programátor tak typicky mohl načtením jednoho osmibitového registru najednou získat informaci o stavu všech tlačítek.

Obrázek 1: Atari joystick a způsob jeho zapojení byl svým způsobem standardem.

Poznámka: některé joysticky podporovaly automatickou střelbu, což ovšem bylo řešeno přímo elektronikou v joysticku – navenek se stále jednalo o rychle mačkané tlačítko.

3. Posuvný registr v ovladačích NESu

Ovladač pro herní konzoli NES vypadá odlišně než klasický joystick. Najdeme zde D-pad pro určení čtyř směrů (nebo jejich kombinací) a taktéž čtveřici tlačítek označených Select, Start, A a B:

Obrázek 2: Ovladač pro herní konzoli NES.

Mohlo by se tedy zdát, že všech osm tlačítek (čtyři na D-padu, čtyři ostatní) budou vyvedeny na konektor ve formě osmi samostatných pinů. Ve skutečnosti tomu tak není, protože v ovladači je posuvný registr (shift register) který na základě signálu z konzole (latch) zaznamená stav všech osmi tlačítek. Poté je nutné tyto informace postupně z posuvného registru vysunout, a to bit po bitu – přenos je tedy sériový. Programátor musí sám zajistit jak signál latch, tak i postupné načtení osmi bitů z posuvného registru (což není těžké).

Poznámka: bližší informace o ovladačích NESu lze najít například na stránkách NES Controller Shift Register a NES (Nintendo Entertainment System) controller pinout
.

4. Čtení stavu tlačítek na ovladačích NESu

Vlastní realizace čtení stavu tlačítek na ovladačích NESu není ve skutečnosti nijak složitá. Čtení lze provádět pro oba ovladače zvlášť; přitom si vystačíme s pouhými dvěma osmibitovými řídicími registry na adresách $4016 a $4017 (znak $ označuje hexadecimální konstanty):

; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017

Před čtením stavu tlačítek musíme zachytit jejich stav a uložit ho do posuvného registru. Budeme tedy provádět bit banging – pomocí SW budeme generovat řídicí signály, zde konkrétně signál latch:

        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:

Nyní již můžeme postupně přečíst stav jednotlivých tlačítek v pořadí, které naznačuje následující kód. Pořadí nelze změnit (je dáno pořadím bitů v posuvném registru):

        lda JOYPAD1        ; stav tlačítka A jen načteme a ingorujeme
        ...
 
        lda JOYPAD1        ; stav tlačítka B jen načteme a ingorujeme
        ...
 
        lda JOYPAD1        ; stav tlačítka Select jen načteme a ingorujeme
        ...
 
        lda JOYPAD1        ; stav tlačítka Start jen načteme a ingorujeme
        ...
 
        lda JOYPAD1        ; stav tlačítka Up
        ...
 
        lda JOYPAD1        ; stav tlačítka Down
        ...
 
        lda JOYPAD1        ; stav tlačítka Left
        ...
 
        lda JOYPAD1        ; stav tlačítka Right
        ...
Poznámka: stejnou operaci lze použít i pro druhý ovladač.

5. Programový test stavu tlačítek

Hodnota přečtená instrukcí LDA do akumulátoru má pochopitelně osm bitů. Horních sedm bitů obsahuje další (pro nás prozatím nerelevantní) informace, takže jediným bitem, který nás zajímá, je nejnižší bit akumulátoru. Jeho stav lze otestovat mnoha způsoby – vysunutím/rotací do carry, maskováním a zjištěním příznaku zero, odečtením jedničky a zjištěním příznaku zero atd. Podívejme se na nejpřímější (i když ne nejrychlejší) způsob – maskování. Horních sedm bitů akumulátoru zamaskujeme (vynulujeme) instrukcí AND, která automaticky nastaví příznak zero v případě, že je nový obsah akumulátoru nulový (a tedy tlačítko není stisknuto). Tento stav následně využijeme v podmíněném skoku BEQ (branch if equal == branch if zero flag is set):

        lda JOYPAD1        ; stav tlačítka Up
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq up_not_pressed ; není stisknuto? => skok
 
        ; instrukce provedené v případě, že je tlačítko stisknuto
 
up_not_pressed:
 
        ; pokračování běhu programu
 

6. Posun spritu s využitím ovladače

Nyní si ukažme, jak je možné provést posun spritu (prozatím jediného spritu, tedy bloku 8×8 pixelů) s využitím ovladače. Logika je vlastně velmi jednoduchá, protože metainformace o spritech – jejich tvaru (bitmapě), pozici, indexu barvové palety máme zkopírovány z paměti ROM do paměti RAM, konkrétně ve druhé stránce paměti, tedy od adresy $0200. Deklarace tvaru a pozice prvních osmi spritů vypadá následovně:

; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10

Těchto osm spritů vytvoří obrázek Maria:

Obrázek 3: Postavička Maria vykreslená z osmi spritů.

Tato postavička je složena, jak již ostatně velmi dobře víme, z osmi spritů, z nichž každý má rozměry 8×8 pixelů:

Obrázek 4: Postavička Maria je ve skutečnosti složená z osmi spritů.

Posun prvního spritu z této osmice lze zajistit modifikací bajtu na adrese $0200 (y-ová souřadnice) a $0203 (x-ová souřadnice). To je z pohledu programátora vše – modifikaci pozice na obrazovce již zajistí DMA. A kdy změnu provést – v rutině VBLANK!

7. Rutina VBLANK

Jak je však možné zajistit, aby se sprite (či později celá postavička) pohybovala po herním poli určenou konstantní rychlostí? Pro tento účel poměrně dobře poslouží rutina VBLANK, která je zavolána automaticky ve chvíli, kdy PPU dokončil vykreslení jednoho snímku na obrazovce. Na konci (nebo začátku) každého snímku se nachází 21 prázdných obrazových řádků, během nichž není prováděno vykreslování. To mj. znamená, že je to vhodný okamžik pro řízení PPU (tedy „grafického procesoru“). A navíc je tato rutina volána zcela pravidelně, konkrétně 25× za sekundu na NESu v systému PAL a 29.97× (tedy prakticky 30×) na NESu v systému NTSC. Pokud tedy budeme stav tlačítek číst a následně modifikovat pozici spritu v rutině VBLANK, bude se sprite pohybovat přesně rychlostí 25 resp. 30 pixelů za sekundu. Je zde pochopitelně malý háček – jak rozlišit PAL od NTSC. Některé hry běží v systému PAL pomaleji (tj. nijak tento problém neřeší), další existují ve dvou variantách. VBLANK je zavolána automaticky, protože informace o nadcházejícím VBLANK způsobí nemaskovatelné přerušení (NMI).

Poznámka: později uvidíme, že pokud není možné vše „ustíhat“ v době trvání již zmíněných 21 prázdných obrazových řádků, bude v rutině VBLANK jen kód oznamující hlavnímu vláknu aplikace, že může provést přepočet herního světa.

8. Praktická realizace posunu spritu

Posun spritu o ± dva pixely (či o jinou hodnotu požadovanou programátorem) v horizontálním i vertikálním směru musíme realizovat pro každý směr zvlášť, a to následujícím způsobem:

  1. Test stavu příslušného tlačítka (Up, Down, Left, Right)
  2. Pokud je tlačítko stisknuto, změní se příslušná hodnota na adrese $0200 (y-ová souřadnice) či $0203 (x-ová souřadnice)

Musíme si jen dát pozor na to, že před operací ADC je nutné vynulovat příznak carry a před operací SBC ho naopak nastavit (tím se mikroprocesor MOS 6502 odlišuje od ostatních osmibitových mikroprocesorů):

Nejprve přečteme stav všech tlačítek do posuvného registru:

        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek

Následně přečteme a zapomeneme stav tlačítek A, B, Select a Start:

        lda JOYPAD1        ; stav tlačítka A jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka B jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka Select jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka Start jen načteme a ingorujeme

Poté následuje čtení stavu směrových tlačítek na D-padu a modifikace x-ové či y-ové souřadnice pixelu. Pokud je příslušné tlačítko zmáčknuto (tedy když je vybrán směr), podmíněný skok se neprovede a vykoná se kód pro zvýšení či snížení pozice spritu o ± 2 pixely:

 
        lda JOYPAD1        ; stav tlačítka Up
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq up_not_pressed ; není stisknuto? => skok
 
        lda $0200          ; změna y-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; y--
        sta $0200          ; uložení nové y-ové pozice spritu
 
up_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Down
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq down_not_pressed ; není stisknuto? => skok
 
        lda $0200          ; změna y-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; y++
        sta $0200          ; uložení nové y-ové pozice spritu
 
down_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Left
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq left_not_pressed ; není stisknuto? => skok
 
        lda $0203          ; změna x-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; x--
        sta $0203          ; uložení nové x-ové pozice spritu
 
left_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Right
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq right_not_pressed ; není stisknuto? => skok
 
        lda $0203          ; změna x-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; x--
        sta $0203          ; uložení nové x-ové pozice spritu
 
right_not_pressed:

Obrázek 5: Posun jediného spritu (8×8 pixelů) ve scéně.

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

Úplný zdrojový kód dnešního prvního demonstračního příkladu (v pořadí již sedmnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example17.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example17.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02           ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
 
        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:
                           ; 
                           ; 1) A                      
                           ; 2) B                      
                           ; 3) Select                 
                           ; 4) Start                  
                           ; 5) Up                     
                           ; 6) Down                   
                           ; 7) Left                   
                           ; 8) Right
 
        lda JOYPAD1        ; stav tlačítka A jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka B jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka Select jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka Start jen načteme a ingorujeme
 
        lda JOYPAD1        ; stav tlačítka Up
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq up_not_pressed ; není stisknuto? => skok
 
        lda $0200          ; změna y-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; y--
        sta $0200          ; uložení nové y-ové pozice spritu
 
up_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Down
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq down_not_pressed ; není stisknuto? => skok
 
        lda $0200          ; změna y-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; y++
        sta $0200          ; uložení nové y-ové pozice spritu
 
down_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Left
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq left_not_pressed ; není stisknuto? => skok
 
        lda $0203          ; změna x-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; x--
        sta $0203          ; uložení nové x-ové pozice spritu
 
left_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Right
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq right_not_pressed ; není stisknuto? => skok
 
        lda $0203          ; změna x-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; x--
        sta $0203          ; uložení nové x-ové pozice spritu
 
right_not_pressed:
 
        rti                ; návrat z přerušení
 
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

10. Assembler se překládal jako JSA – Jazyk Symbolických Adres

V češtině se namísto pojmu assembler používal název „Jazyk Symbolických Adres“ neboli JSA. Ovšem v předchozím kódu jsme přímo používali adresy $0203 a $0200 v jejich numerické podobě, což je zbytečně nízkoúrovňové. V assembleru/JSA máme možnost si tyto adresy pojmenovat, což může vést ke zpřehlednění kódu (v tomto ohledu je assembler na vyšší úrovni abstrakce, než například BASIC):

        XPOS = $0203       ; adresa buňky paměti s x-ovou souřadnicí spritu
        YPOS = $0200       ; adresa buňky paměti y x-ovou souřadnicí spritu

Nyní mohou být tyto magické konstanty v kódu nahrazeny jménem:

        lda JOYPAD1        ; stav tlačítka Up
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq up_not_pressed ; není stisknuto? => skok
 
        lda YPOS           ; změna y-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; y--
        sta YPOS           ; uložení nové y-ové pozice spritu
 
up_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Down
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq down_not_pressed ; není stisknuto? => skok
 
        lda YPOS           ; změna y-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; y++
        sta YPOS           ; uložení nové y-ové pozice spritu
 
down_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Left
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq left_not_pressed ; není stisknuto? => skok
 
        lda XPOS           ; změna x-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; x--
        sta XPOS           ; uložení nové x-ové pozice spritu
 
left_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Right
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq right_not_pressed ; není stisknuto? => skok
 
        lda XPOS           ; změna x-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; x--
        sta XPOS           ; uložení nové x-ové pozice spritu
 
right_not_pressed:
Poznámka: nic nám nebrání jít ještě dále a nahradit i další magické konstanty vhodnými symboly:
        XPOS = $0203       ; adresa buňky paměti s x-ovou souřadnicí spritu
        YPOS = $0200       ; adresa buňky paměti y x-ovou souřadnicí spritu
 
        BUTTON_MASK = %00000001
        STEP = $02

V kódu je stále nutné rozlišovat mezi hodnotou a adresou (u hodnoty je uveden znak #):

        lda JOYPAD1        ; stav tlačítka Up
        and #BUTTON_MASK   ; maskovat všechny bity kromě prvního
        beq up_not_pressed ; není stisknuto? => skok
 
        lda YPOS           ; změna y-ové pozice spritu
        sec                ; nastavit přenos
        sbc #STEP          ; y--
        sta YPOS           ; uložení nové y-ové pozice spritu
 
up_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Down
        and #BUTTON_MASK   ; maskovat všechny bity kromě prvního
        beq down_not_pressed ; není stisknuto? => skok
 
        lda YPOS           ; změna y-ové pozice spritu
        clc                ; vynulovat přenos
        adc #STEP          ; y++
        sta YPOS           ; uložení nové y-ové pozice spritu
 
down_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Left
        and #BUTTON_MASK   ; maskovat všechny bity kromě prvního
        beq left_not_pressed ; není stisknuto? => skok
 
        lda XPOS           ; změna x-ové pozice spritu
        sec                ; nastavit přenos
        sbc #STEP          ; x--
        sta XPOS           ; uložení nové x-ové pozice spritu
 
left_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Right
        and #BUTTON_MASK   ; maskovat všechny bity kromě prvního
        beq right_not_pressed ; není stisknuto? => skok
 
        lda XPOS           ; změna x-ové pozice spritu
        clc                ; vynulovat přenos
        adc #STEP          ; x--
        sta XPOS           ; uložení nové x-ové pozice spritu
 
right_not_pressed:

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

Úplný zdrojový kód dnešního druhého demonstračního příkladu (v pořadí již osmnáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example18.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example18.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02           ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
 
        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:
                           ; 
                           ; 1) A                      
                           ; 2) B                      
                           ; 3) Select                 
                           ; 4) Start                  
                           ; 5) Up                     
                           ; 6) Down                   
                           ; 7) Left                   
                           ; 8) Right
 
        lda JOYPAD1        ; stav tlačítka A jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka B jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka Select jen načteme a ingorujeme
        lda JOYPAD1        ; stav tlačítka Start jen načteme a ingorujeme
 
        XPOS = $0203       ; adresa buňky paměti s x-ovou souřadnicí spritu
        YPOS = $0200       ; adresa buňky paměti y x-ovou souřadnicí spritu
 
        lda JOYPAD1        ; stav tlačítka Up
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq up_not_pressed ; není stisknuto? => skok
 
        lda YPOS           ; změna y-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; y--
        sta YPOS           ; uložení nové y-ové pozice spritu
 
up_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Down
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq down_not_pressed ; není stisknuto? => skok
 
        lda YPOS           ; změna y-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; y++
        sta YPOS           ; uložení nové y-ové pozice spritu
 
down_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Left
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq left_not_pressed ; není stisknuto? => skok
 
        lda XPOS           ; změna x-ové pozice spritu
        sec                ; nastavit přenos
        sbc #$02           ; x--
        sta XPOS           ; uložení nové x-ové pozice spritu
 
left_not_pressed:
 
        lda JOYPAD1        ; stav tlačítka Right
        and #%00000001     ; maskovat všechny bity kromě prvního
        beq right_not_pressed ; není stisknuto? => skok
 
        lda XPOS           ; změna x-ové pozice spritu
        clc                ; vynulovat přenos
        adc #$02           ; x--
        sta XPOS           ; uložení nové x-ové pozice spritu
 
right_not_pressed:
 
        rti                ; návrat z přerušení
 
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

12. Podpůrná makra increment a decrement

Předchozí kód byl zbytečně složitý, protože se v něm 2× vyskytovala sekvence instrukcí určených pro zvýšení či naopak snížení obsahu paměťové buňky o nějakou hodnotu. Tento kód můžeme přenést do subrutiny (tu je nutné volat a zvětšovat tak časové nároky rutiny VBLANK) nebo do makra. V tom nejjednodušším případě mohou příslušná makra vypadat takto:

.macro increment address
        lda address
        clc                ; vynulovat přenos
        adc #$02           ; x++
        sta address
.endmacro
 
.macro decrement address
        lda address
        sec                ; nastavit přenos
        sbc #$02           ; x--
        sta address
.endmacro

Makra akceptují jediný parametr – adresu:

        read_button        ; stav tlačítka Up
        beq up_not_pressed ; není stisknuto? => skok
 
        decrement YPOS
 
up_not_pressed:
 
        read_button        ; stav tlačítka Down
        beq down_not_pressed ; není stisknuto? => skok
 
        increment YPOS
 
Poznámka: bylo by zajímavější, aby makra akceptovala i konstantu, o kterou se má hodnota paměťové buňky zvýšit či snížit. Tuto jednoduchou úpravu ponechám na laskavém čtenáři jako malé cvičení assembleru ca65.

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

Úplný zdrojový kód dnešního třetího demonstračního příkladu (v pořadí již devatenáctého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example19.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example19.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
.macro increment address
        lda address
        clc                ; vynulovat přenos
        adc #$02           ; x++
        sta address
.endmacro
 
.macro decrement address
        lda address
        sec                ; nastavit přenos
        sbc #$02           ; x--
        sta address
.endmacro
 
.macro read_button
        lda JOYPAD1        ; stav tlačítka
        and #%00000001     ; maskovat všechny bity kromě prvního
.endmacro
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02           ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
 
        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:
                           ; 
                           ; 1) A                      
                           ; 2) B                      
                           ; 3) Select                 
                           ; 4) Start                  
                           ; 5) Up                     
                           ; 6) Down                   
                           ; 7) Left                   
                           ; 8) Right
 
        read_button        ; stav tlačítka A jen načteme a ingorujeme
        read_button        ; stav tlačítka B jen načteme a ingorujeme
        read_button        ; stav tlačítka Select jen načteme a ingorujeme
        read_button        ; stav tlačítka Start jen načteme a ingorujeme
 
        XPOS = $0203       ; adresa buňky paměti s x-ovou souřadnicí spritu
        YPOS = $0200       ; adresa buňky paměti y x-ovou souřadnicí spritu
 
        read_button        ; stav tlačítka Up
        beq up_not_pressed ; není stisknuto? => skok
 
        decrement YPOS
 
up_not_pressed:
 
        read_button        ; stav tlačítka Down
        beq down_not_pressed ; není stisknuto? => skok
 
        increment YPOS
 
down_not_pressed:
 
        read_button      ; stav tlačítka Left
        beq left_not_pressed ; není stisknuto? => skok
 
        decrement XPOS
 
left_not_pressed:
 
        read_button      ; stav tlačítka Right
        beq right_not_pressed ; není stisknuto? => skok
 
        increment XPOS
 
right_not_pressed:
 
        rti                ; návrat z přerušení
 
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

14. MOS 6502 není RISCovým procesorem aneb síla instrukcí INCDEC

Původní čip MOS 6502 nabízí programátorům šestici instrukcí, které dokážou zvýšit či snížit obsah paměťové buňky o jedničku, popř. zvýšit či snížit obsah registru X či Y o jedničku:

Instrukce Stručný popis
INC zvýšení obsahu paměťové buňky o jedničku
DEC snížení obsahu paměťové buňky o jedničku
INX zvýšení obsahu index registru X o jedničku
DEX snížení obsahu index registru X o jedničku
INY zvýšení obsahu index registru Y o jedničku
DEY snížení obsahu index registru Y o jedničku

Povšimněte si, že MOS 6502 nemá instrukce INA/DEA pro zvýšení/snížení obsahu akumulátoru (u registrů X a Y se jedná o nutnost, u akumulátoru se používá ADC a SBC se všemi z toho plynoucími důsledky). Tento nedostatek byl napraven v čipu 65C02, který obě zmíněné instrukce obsahuje, což podle některých autorů vedlo ke zmenšení programového kódu až o 5%.

Co je ovšem pro nás důležité – instrukce INC a DEC podporují přímé uvedení absolutní adresy, takže pokud budeme chtít pohybovat sprity o jeden pixel v libovolném směru, bude celý programový kód značně jednodušší:

        read_button        ; stav tlačítka Up
        beq up_not_pressed ; není stisknuto? => skok
 
        dec YPOS
 
up_not_pressed:
 
        read_button        ; stav tlačítka Down
        beq down_not_pressed ; není stisknuto? => skok
 
        inc YPOS
 
down_not_pressed:
 
        read_button      ; stav tlačítka Left
        beq left_not_pressed ; není stisknuto? => skok
 
        dec XPOS
 
left_not_pressed:
 
        read_button      ; stav tlačítka Right
        beq right_not_pressed ; není stisknuto? => skok
 
        inc XPOS
 
right_not_pressed:
Poznámka: společně s instrukcemi posuvu jsou právě instrukce INC a DEC příkladem toho, že MOS 6502 není (a ani neměl být) RISCovým procesorem; ostatně je řízen PLA a sekvencérem a ani neobsahuje klasickou RISCovou pipeline.

15. Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu

Úplný zdrojový kód dnešního čtvrtého demonstračního příkladu (v pořadí již dvacátého příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example20.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example20.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem. Pohyb spritu.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
.macro read_button
        lda JOYPAD1        ; stav tlačítka
        and #%00000001     ; maskovat všechny bity kromě prvního
.endmacro
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02           ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
 
        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:
                           ; 
                           ; 1) A                      
                           ; 2) B                      
                           ; 3) Select                 
                           ; 4) Start                  
                           ; 5) Up                     
                           ; 6) Down                   
                           ; 7) Left                   
                           ; 8) Right
 
        read_button        ; stav tlačítka A jen načteme a ingorujeme
        read_button        ; stav tlačítka B jen načteme a ingorujeme
        read_button        ; stav tlačítka Select jen načteme a ingorujeme
        read_button        ; stav tlačítka Start jen načteme a ingorujeme
 
        XPOS = $0203       ; adresa buňky paměti s x-ovou souřadnicí spritu
        YPOS = $0200       ; adresa buňky paměti y x-ovou souřadnicí spritu
 
        read_button        ; stav tlačítka Up
        beq up_not_pressed ; není stisknuto? => skok
 
        dec YPOS
 
up_not_pressed:
 
        read_button        ; stav tlačítka Down
        beq down_not_pressed ; není stisknuto? => skok
 
        inc YPOS
 
down_not_pressed:
 
        read_button      ; stav tlačítka Left
        beq left_not_pressed ; není stisknuto? => skok
 
        dec XPOS
 
left_not_pressed:
 
        read_button      ; stav tlačítka Right
        beq right_not_pressed ; není stisknuto? => skok
 
        inc XPOS
 
right_not_pressed:
 
        rti                ; návrat z přerušení
 
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

16. Přesun všech osmi spritů tvořících figurku Maria

Náš další úkol je zřejmý – upravit program takovým způsobem, aby bylo možné pohybovat celou figurkou Maria, která se skládá z osmi spritů. Nějakým vhodným způsobem tedy budeme muset vždy zvýšit či snížit souřadnici nikoli jediného spritu, ale hned osmi spritů. To lze provést několika různými způsoby, například využít toho, že nová souřadnice je pro několik spritů vždy stejná atd. Ovšem ukažme si druhý nejjednodušší způsob – realizace formou programové smyčky, která vždy změní hodnotu osmi bajtů v paměti. Tyto bajty však nejsou uloženy ihned za sebou, ale s mezerou 4 adres. To vychází ze způsobu uložení metainformací o spritech v paměti:

spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10

Měnit budeme buď hodnoty v prvním sloupci nebo v sloupci posledním.

17. Trik – aritmetické operace s registrem X

Pro zvýšení nebo naopak snížení hodnot v osmi paměťových buňkách můžeme použít instrukci inc adresa, x či dec adresa, x, což znamená, že k pevně zadané adrese se přičítá offset uložený v registru X. Postačuje nám tedy použít registr X současně i ve funkci počitadla smyčky, která postupně hodnotu tohoto registru nastaví na 4, 8, 12, 16, 20, 24, 28 a 32. To lze provést tímto pseudokódem:

        ldx #0             ; inicializace offsetu
:
        inc address, x     ; zvýšit pozici spritu o jedničku
 
        adc x, #gap        ; zvýšení o hodnotu gap (4, další sprite)
 
        cmp x, #count*gap  ; porovnání, zda jsme již dosáhli posledního spritu
 
        bne :-             ; pokud ne, skok na začátek smyčky

Jediný problém spočívá v tom, že tučně zapsané instrukce mikroprocesor MOS 6502 neobsahuje. Jedná se totiž o aritmetické operace a ty lze provádět jen s akumulátorem. Pomůžeme si tudíž malým trikem – instrukcí TXA přesuneme obsah registru X do akumulátoru, provedeme všechny nutné aritmetické operace a následně přesuneme nový obsah akumulátoru do registru X instrukcí TAX:

        txa                ; přesun offsetu do akumulátoru
        clc
        adc #gap           ; zvýšení o hodnotu gap (4, další sprite)
        tax                ; přesun nového offsetu zpět do registru X

Následně pouze otestujeme, zda jsme dospěli k poslednímu offsetu, který se má použít pro instrukci INC či DEC:

        cmp #count*gap     ; porovnání, zda jsme již dosáhli posledního spritu
Poznámka: zde testujeme hodnotu akumulátoru, protože je shodná s hodnotou uloženou v registru X.

Výsledkem jsou následující dvě makra:

.macro increment_block address, count, gap
        ldx #0             ; inicializace offsetu
:
        inc address, x     ; zvýšit pozici spritu o jedničku
 
        txa                ; přesun offsetu do akumulátoru
        clc
        adc #gap           ; zvýšení o hodnotu gap (4, další sprite)
        tax                ; přesun nového offsetu zpět do registru X
 
        cmp #count*gap     ; porovnání, zda jsme již dosáhli posledního spritu
 
        bne :-             ; pokud ne, skok na začátek smyčky
.endmacro
 
.macro decrement_block address, count, gap
        ldx #0             ; inicializace offsetu
:
        dec address, x     ; zvýšit pozici spritu o jedničku
 
        txa                ; přesun offsetu do akumulátoru
        clc
        adc #gap           ; zvýšení o hodnotu gap (4, další sprite)
        tax                ; přesun nového offsetu zpět do registru X
 
        cmp #count*gap     ; porovnání, zda jsme již dosáhli posledního spritu

        bne :-             ; pokud ne, skok na začátek smyčky
.endmacro

Která se používají takto:

UX DAy - tip 2

        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:
        ...
        ...
        ...
        ...
Poznámka: povšimněte si, jak se díky makrům stává z assembleru relativně mocný programovací jazyk.

18. Úplný zdrojový kód dnešního pátého demonstračního příkladu

Úplný zdrojový kód dnešního posledního demonstračního příkladu (v pořadí již dvacátého prvního příkladu pro NES) je dostupný na adrese https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/example21.asm. Pro překlad a slinkování tohoto příkladu je zapotřebí i Makefile a příkaz make example21.nes:

; ---------------------------------------------------------------------
; Kostra programu pro herní konzoli NES
; Nastavení barvové palety, zvýšení intenzity barvy
; Setup PPU přes makro
; Definice spritu a zobrazení spritů s rozloženým Mariem.
; Pohyb celého Maria.
; Využití symbolických jmen adres.
; Pomocná makra pro pohyb spritu.
;
; Založeno na příkladu https://github.com/depp/ctnes/tree/master/nesdev/01
; Taktéž založeno na https://nerdy-nights.nes.science/#main_tutorial-3
; Viz též článek na https://www.moria.us/blog/2018/03/nes-development
; Audio https://raw.githubusercontent.com/iliak/nes/master/doc/apu_ref.txt
; ---------------------------------------------------------------------
 
; Jména řídicích registrů použitých v kódu
PPUCTRL         = $2000
PPUMASK         = $2001
PPUSTATUS       = $2002
PPUADDR         = $2006
PPUDATA         = $2007
DMC_FREQ        = $4010
OAM_DMA         = $4014
 
; Další důležité adresy
PALETTE         = $3f00
 
; Ovladače
JOYPAD1         = $4016
JOYPAD2         = $4017
 
 
 
; ---------------------------------------------------------------------
; Definice maker
; ---------------------------------------------------------------------
 
.macro setup_cpu
        ; nastavení stavu CPU
        sei                     ; zákaz přerušení
        cld                     ; vypnutí dekadického režimu (není podporován)
 
        ldx #$ff
        txs                     ; vrchol zásobníku nastaven na 0xff (první stránka)
.endmacro
 
.macro wait_for_frame
:       bit PPUSTATUS            ; test obsahu registru PPUSTATUS 
        bpl :-                   ; skok, pokud je příznak N nulový
.endmacro
 
.macro clear_ram
        lda #$00                ; vynulování registru A
:       sta $000, x             ; vynulování X-tého bajtu v nulté stránce
        sta $100, x
        sta $200, x
        sta $300, x
        sta $400, x
        sta $500, x
        sta $600, x
        sta $700, x             ; vynulování X-tého bajtu v sedmé stránce
        inx                     ; přechod na další bajt
        bne :-                  ; po přetečení 0xff -> 0x00 konec smyčky
.endmacro
 
.macro ppu_data_palette_address
        lda PPUSTATUS   ; reset záchytného registru
        lda #>PALETTE   ; nastavení adresy pro barvovou paletu $3f00
        sta PPUADDR
        lda #<PALETTE   ; nižší bajt adresy
        sta PPUADDR
.endmacro
 
.macro increment_block address, count, gap
        ldx #0             ; inicializace offsetu
:
        inc address, x     ; zvýšit pozici spritu o jedničku
 
    txa                ; přesun offsetu do akumulátoru
    clc
    adc #gap           ; zvýšení o hodnotu gap (4, další sprite)
    tax                ; přesun nového offsetu zpět do registru X
 
    cmp #count*gap     ; porovnání, zda jsme již dosáhli posledního spritu
 
        bne :-             ; pokud ne, skok na začátek smyčky
.endmacro
 
.macro decrement_block address, count, gap
        ldx #0             ; inicializace offsetu
:
        dec address, x     ; zvýšit pozici spritu o jedničku
 
    txa                ; přesun offsetu do akumulátoru
    clc
    adc #gap           ; zvýšení o hodnotu gap (4, další sprite)
    tax                ; přesun nového offsetu zpět do registru X
 
    cmp #count*gap     ; porovnání, zda jsme již dosáhli posledního spritu
 
        bne :-             ; pokud ne, skok na začátek smyčky
.endmacro
 
.macro read_button
        lda JOYPAD1        ; stav tlačítka
        and #%00000001     ; maskovat všechny bity kromě prvního
.endmacro
 
 
 
; ---------------------------------------------------------------------
; Definice hlavičky obrazu ROM
; ---------------------------------------------------------------------
 
; Size of PRG in units of 16 KiB.
prg_npage = 2
 
; Size of CHR in units of 8 KiB.
chr_npage = 1
 
; INES mapper number.
mapper = 0
 
; Mirroring (0 = horizontal, 1 = vertical)
mirroring = 1
 
.segment "HEADER"
        .byte $4e, $45, $53, $1a
        .byte prg_npage
        .byte chr_npage
        .byte ((mapper & $0f) << 4) | (mirroring & 1)
        .byte mapper & $f0
 
.segment "ZEROPAGE"
.segment "STARTUP"
.segment "CODE"
 
 
 
; ---------------------------------------------------------------------
; Blok paměti s definicí dlaždic 8x8 pixelů
; ---------------------------------------------------------------------
 
.segment "CHR0a"
.segment "CHR0b"
 
 
.code
 
; ---------------------------------------------------------------------
; Programový kód rutin pro NMI, RESET a IRQ volaných automaticky CPU
;
; viz též https://www.pagetable.com/?p=410
; ---------------------------------------------------------------------
 
; Obslužná rutina pro NMI (nemaskovatelné přerušení, vertical blank)
 
.proc nmi
        lda #$02           ; horní bajt adresy pro přenos + zahájení přenosu
        sta OAM_DMA
 
        lda #$01
        sta JOYPAD1        ; načtení stavu všech osmi tlačítek do záchytného registru
        lda #$00
        sta JOYPAD1        ; začátek načítání jednotlivých bitů se stavy tlačítek v tomto pořadí:
                           ; 
                           ; 1) A                      
                           ; 2) B                      
                           ; 3) Select                 
                           ; 4) Start                  
                           ; 5) Up                     
                           ; 6) Down                   
                           ; 7) Left                   
                           ; 8) Right
 
        read_button        ; stav tlačítka A jen načteme a ingorujeme
        read_button        ; stav tlačítka B jen načteme a ingorujeme
        read_button        ; stav tlačítka Select jen načteme a ingorujeme
        read_button        ; stav tlačítka Start jen načteme a ingorujeme
 
        XPOS = $0203       ; adresa buňky paměti s x-ovou souřadnicí spritu
        YPOS = $0200       ; adresa buňky paměti y x-ovou souřadnicí spritu
 
        read_button        ; stav tlačítka Up
        beq up_not_pressed ; není stisknuto? => skok
 
        decrement_block YPOS, 8, 4
 
up_not_pressed:
 
        read_button        ; stav tlačítka Down
        beq down_not_pressed ; není stisknuto? => skok
 
        increment_block YPOS, 8, 4
 
down_not_pressed:
 
        read_button      ; stav tlačítka Left
        beq left_not_pressed ; není stisknuto? => skok
 
        decrement_block XPOS, 8, 4
 
left_not_pressed:
 
        read_button      ; stav tlačítka Right
        beq right_not_pressed ; není stisknuto? => skok
 
        increment_block XPOS, 8, 4
 
right_not_pressed:
 
        rti                ; návrat z přerušení
 
.endproc
 
 
 
; Obslužná rutina pro IRQ (maskovatelné přerušení)
 
.proc irq
        rti                     ; návrat z přerušení
.endproc
 
 
 
; Obslužná rutina pro RESET
 
.proc reset
        ; nastavení stavu CPU
        setup_cpu
 
        ; nastavení řídicích registrů
        ldx #$00
        stx PPUCTRL             ; nastavení PPUCTRL = 0 (NMI)
        stx PPUMASK             ; nastavení PPUMASK = 0
        stx DMC_FREQ            ; zákaz DMC IRQ
 
        ldx #$40
        stx $4017               ; interrupt inhibit bit
 
        ; čekání na vnitřní inicializaci PPU (dva snímky)
        wait_for_frame
        wait_for_frame
 
        ; vymazání obsahu RAM
        clear_ram
 
        ; čekání na další snímek
        wait_for_frame
 
        ; nastavení barvové palety
        jsr load_palette  ; zavolání subrutiny
 
        ; nastavení spritů
        jsr load_sprites  ; zavolání subrutiny
 
        ; vlastní herní smyčka je prozatím prázdná
game_loop:
        jmp game_loop           ; nekonečná smyčka (později rozšíříme)
.endproc
 
 
 
; vynulování barvové palety
.proc clear_palette
        ppu_data_palette_address
 
        ldx #$20        ; počitadlo barev v paletě: 16+16
        lda #$00        ; vynulování každé barvy
 
:
        sta PPUDATA     ; zápis barvy
        dex             ; snížení hodnoty počitadla
        bne :-
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; nastavení barvové palety
.proc load_palette
        ppu_data_palette_address
 
        ; $3f00-$3f0f - paleta pozadí
        ; $3f10-$3f1f - paleta spritů
 
        ldx #$00        ; vynulovat počitadlo a offset
 
:
        lda palette, x  ; načíst bajt s offsetem
        sta PPUDATA     ; zápis barvy do PPU
        inx             ; zvýšit počitadlo/offset
        cpx #32         ; limit počtu barev
        bne :-          ; opakovat smyčku 32x
 
        rts             ; návrat ze subrutiny
.endproc
 
 
 
; načtení spritů
.proc load_sprites
        ldx #0
:
        lda spritedata,X  ; budeme přesouvat data z této oblasti
        sta $0200,X       ; uložení do paměti spritů
        inx               ; zvýšení hodnoty počitadla
        cpx #32           ; každý sprite má 4 bajty: y-coord, tile, attributy, y-coord * 8 spritů = 32
        bne :-
 
        cli               ; vynulování bitu I - povolení přerušení
        lda #%10000000
        sta PPUCTRL       ; při každém VBLANK se vyvolá NMI (důležité!)
 
        lda #%00010000    ; povolení zobrazení spritů
        sta PPUMASK
 
        rts               ; návrat ze subrutiny
.endproc
 
 
 
; samotná barvová paleta
palette:
    .byte $22, $29, $1a, $0F, $22, $36, $17, $0F, $22, $30, $21, $0F, $22, $27, $17, $0F  ; barvy pozadí
    .byte $22, $16, $27, $18, $22, $1A, $30, $27, $22, $16, $30, $27, $22, $0F, $36, $17  ; barvy spritů
 
; data pro větší množství spritů
spritedata:
    .byte $10, $00, $00, $08   ; y-coord, tile number, attributes, x-coord
    .byte $10, $01, $00, $10
    .byte $18, $02, $00, $08
    .byte $18, $03, $00, $10
    .byte $20, $04, $00, $08
    .byte $20, $05, $00, $10
    .byte $28, $06, $00, $08
    .byte $28, $07, $00, $10
 
 
 
; ---------------------------------------------------------------------
; Tabulka vektorů CPU
; ---------------------------------------------------------------------
 
.segment "VECTORS"
.addr nmi
.addr reset
.addr irq
 
 
 
.segment "CHARS"
    .incbin "mario.chr"
 
 
 
; ---------------------------------------------------------------------
; Finito
; ---------------------------------------------------------------------

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

Demonstrační příklady napsané v assembleru, které jsou určené pro překlad pomocí assembleru ca65 (jenž je součástí cc65), byly uložen do Git repositáře, který je dostupný na adrese https://github.com/tisnik/8bit-fame. Jednotlivé demonstrační příklady si můžete v případě potřeby stáhnout i jednotlivě bez nutnosti klonovat celý (dnes již poměrně rozsáhlý) repositář:

# 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 link.cfg konfigurace segmentů pro linker ld65 https://github.com/tisnik/8bit-fame/blob/master/NES-ca65/link.cfg
23 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 example21.

20. Odkazy na Internetu

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

Byl pro vás článek přínosný?