From 6913995597651172b88301776a9e3a66a7c68117 Mon Sep 17 00:00:00 2001 From: Miquel Sabaté Solà Date: Sat, 30 Mar 2024 22:54:52 +0100 Subject: basics: Add an example on CHR-RAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Moreover, add a simple fix to the CI. Signed-off-by: Miquel Sabaté Solà --- .github/workflows/ci.yml | 4 +- Makefile | 16 +-- README.md | 2 +- basics/README.md | 4 + basics/chr-ram.s | 299 +++++++++++++++++++++++++++++++++++++++++++++++ basics/flicker.s | 1 + fx/README.md | 4 + 7 files changed, 321 insertions(+), 9 deletions(-) create mode 100644 basics/chr-ram.s create mode 100644 basics/flicker.s create mode 100644 fx/README.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 451a907..19e3498 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,9 @@ jobs: uses: actions/checkout@v4 - name: Install dependencies from Ubuntu sources - run: sudo apt-get install cc65 fceux + run: | + sudo apt-get update + sudo apt-get install cc65 fceux - name: Main task run: make diff --git a/Makefile b/Makefile index e2d5006..fd12927 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ -CC65 ?= cl65 -CA65 ?= ca65 -LD65 ?= ld65 -CCOPTS ?= --verbose --target nes +CC65 ?= cl65 +CA65 ?= ca65 +LD65 ?= ld65 +CCOPTS ?= --verbose --target nes .PHONY: all all: clean deps build @@ -21,17 +21,19 @@ deps: build: basics space scroll .PHONY: basics -basics: unrom +basics: $(CC65) $(CCOPTS) basics/sprite.s -o out/basics/sprite.nes $(CC65) $(CCOPTS) basics/input.s -o out/basics/input.nes $(CC65) $(CCOPTS) basics/persist.s -o out/basics/persist.nes -.PHONY: unrom -unrom: $(CA65) $(CCOPTS) basics/unrom.s -o basics/unrom.o $(LD65) basics/unrom.o -C config/unrom.cfg -o out/basics/unrom.nes @rm -f basics/unrom.o + $(CA65) $(CCOPTS) basics/chr-ram.s -o basics/chr-ram.o + $(LD65) basics/chr-ram.o -C config/unrom.cfg -o out/basics/chr-ram.nes + @rm -f basics/chr-ram.o + .PHONY: space space: @cd space && CC65=$(CC65) CCOPTS="$(CCOPTS)" $(MAKE) diff --git a/README.md b/README.md index de5df0d..df49e70 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@

Build Status for main branch - License GPL 3

--- @@ -42,6 +41,7 @@ The examples are distributed like this: perform an action like shooting bullets. - `scroll`: different scrolling tactics. Read the [scroll/README.md](./scroll/README.md) file for more info. +- `fx`: miscellanous effects that can be achieved with this humble machine. Other than that, I have also written complete games. Take a look at [jetpac.nes](https://github.com/mssola/jetpac.nes), which is simple enough so a diff --git a/basics/README.md b/basics/README.md index 2a03f0e..84d43a6 100644 --- a/basics/README.md +++ b/basics/README.md @@ -19,5 +19,9 @@ and shoot bullets depending on the given input. Whenever you are done with that, you can then move into other topics like: +- `flicker.s`: flickers sprites which are aligned so they don't disappear due to + NES horizontal sprites limit. - `persist.s`: using the MMC1 chip in order to persist data. - `unrom.s`: bank switching using the UNROM chip. +- `chr-ram.s`: how to show background and sprites via CHR-RAM instead of + CHR-ROM. This is a combination of `sprite.s` and `unrom.s`. diff --git a/basics/chr-ram.s b/basics/chr-ram.s new file mode 100644 index 0000000..c2fdfe9 --- /dev/null +++ b/basics/chr-ram.s @@ -0,0 +1,299 @@ +;;; +;; This is the same example as `basics/sprite.s` but ported to the UNROM chip. +;; This example could have been done with other chips, but the main idea is to +;; render the same thing as `basics/sprite.s` but with CHR-RAM instead of +;; CHR-ROM. You can take a look at the difference between these two approaches +;; here: https://www.nesdev.org/wiki/CHR_ROM_vs._CHR_RAM. + +;; Same as the `basics/unrom.s` example. Just annotating what's relevant for +;; this example. +.segment "HEADER" + .byte 'N', 'E', 'S', $1A + .byte $08 ; 128KB of PRG-ROM (8 x 16KB) + .byte $00 ; No CHR-ROM. + + .byte $20, $08 ; Mapper 2, horizontal mirroring, NES 2.0 + + .byte $00 + .byte $00 + .byte $00 + .byte $07 ; 8192 (64 * 2^7) bytes CHR RAM, no battery + +.segment "VECTORS" + .addr nmi, reset, irq + +;;; +;; Bank names as defined on the `basics/unrom.s` example. For this example we +;; use BANK0 to store the graphics that will be moved into `CHR-RAM` by calling +;; `transfer_to_chr_ram`. This is the main point to be taken from this example. + +.segment "BANK0" + +chr: .incbin "assets/basic.chr" + +.segment "BANK1" +.segment "BANK2" +.segment "BANK3" +.segment "BANK4" +.segment "BANK5" +.segment "BANK6" + +;;; +;; Just like in the `basics/unrom.s` example, we use the fixed bank for the core +;; functionality. + +.segment "FIXED" + +;;; +;; Bank switching code as with `basics/unrom.s`. Not used here. + +banktable: + .byte $00, $01, $02, $03, $04, $05, $06 + +m_current_bank = $00 + +bankswitch: + sty m_current_bank +bankswitch_nosave: + tya + sta banktable, y + rts + +;;; +;; From here on the code is basically the same as `basics/sprite.s`, but with a +;; special twist that will be commented in. For comments on the rest of the code +;; just check `basics/sprite.s`. + +reset: + sei + cld + + ldx #$40 + stx $4017 + + ldx #$ff + txs + inx + stx $2000 + stx $2001 + stx $4010 + +@vblankwait1: + bit $2002 + bpl @vblankwait1 + + ldx #0 + lda #0 +@ram_reset_loop: + sta $000, x + sta $100, x + sta $300, x + sta $400, x + sta $500, x + sta $600, x + sta $700, x + inx + bne @ram_reset_loop + + lda #$ef +@sprite_reset_loop: + sta $200, x + inx + bne @sprite_reset_loop + + ;; NOTE: after sprites have been reset we can transfer data from PRG-ROM + ;; into RAM so the rest of the code can assume that the data is there. This + ;; is the main difference with the `basics/sprite.s` example. + jsr transfer_to_chr_ram + + lda #$00 + sta $2003 + lda #$02 + sta $4014 + +@vblankwait2: + bit $2002 + bpl @vblankwait2 + + lda #$3F + sta $2006 + lda #$00 + sta $2006 + + lda #$0F + ldx #$20 +@palettes_reset_loop: + sta $2007 + dex + bne @palettes_reset_loop + + jmp main + +;;; +;; Transfer the CHR data from PRG-ROM into RAM. +.proc transfer_to_chr_ram + ;;; + ;; The code is pretty much taken from the NESDev wiki (see: + ;; https://www.nesdev.org/wiki/CHR_ROM_vs._CHR_RAM). + + ;; First we set a 16-bit pointer that points `chr`, which contains the + ;; actual data. This pointer will be stored at $00-$01, which is probably + ;; not ideal, but this is just an example. + lda #chr + sta $01 + + ;; Load the destination address into the PPU. We have to initialize both + ;; pattern tables, one starting at $0000, and the other at $1000. Hence we + ;; start by simply pointing at PPU address $0000, and the loop will simply + ;; fill both tables from here. + ldy #0 + sty $2006 + sty $2006 + + ;; - x contains the number of 256-byte pages to copy. + ;; - y will index within the page ($00-$FF). + ldx #32 +loop: + ;; First part of the loop: copy each byte of the current page. + lda ($00), y + sta $2007 + iny + bne loop + + ;; Go to the next page and repeat the first part of the loop. + inc $01 + dex + bne loop + + rts +.endproc + +;;; +;; And from here on it's pretty much copied from `basics/sprite.s`. + +.proc main + jsr init_palettes + jsr init_nametable + jsr init_sprites + + cli + lda #%10110000 + sta $2000 + lda #%00011110 + sta $2001 + +@main_game_loop: + lda #%10000000 + ora $20 + sta $20 +@wait_for_render: + bit $20 + bmi @wait_for_render + + jmp @main_game_loop +.endproc + +.proc init_palettes + lda #$3F + sta $2006 + lda #$00 + sta $2006 + + ldx #0 +@load_palettes_loop: + lda palettes, x + sta $2007 + inx + cpx #$20 + bne @load_palettes_loop + rts +palettes: + .byte $0F, $12, $22, $32 + .byte $0F, $00, $28, $30 + .byte $0F, $28, $16, $2D + .byte $0F, $28, $16, $2D + + .byte $0F, $00, $05, $30 + .byte $0F, $00, $00, $00 + .byte $0F, $00, $00, $00 + .byte $0F, $00, $00, $00 +.endproc + +.macro WRITE_PPU_DATA address, value + bit $2002 + lda #.HIBYTE(address) + sta $2006 + lda #.LOBYTE(address) + sta $2006 + lda #value + sta $2007 +.endmacro + +.proc init_nametable + bit $2002 + + WRITE_PPU_DATA $20C8, $02 + WRITE_PPU_DATA $20B9, $04 + WRITE_PPU_DATA $21CE, $04 + WRITE_PPU_DATA $21BA, $04 + WRITE_PPU_DATA $22B8, $04 + WRITE_PPU_DATA $22E7, $04 + WRITE_PPU_DATA $227A, $02 + + WRITE_PPU_DATA $23CE, %00000001 + + rts +.endproc + +.proc init_sprites + NUM_SPRITES = 2 + + ldx #$00 +@load_sprites_loop: + lda initial_sprite_data, x + sta $0200, x + inx + cpx #(4 * NUM_SPRITES) + bne @load_sprites_loop + rts +initial_sprite_data: + .byte $B0, $00, %00000000, $7A + .byte $B0, $00, %01000000, $82 +.endproc + +nmi: + bit $20 + bpl @next + + pha + txa + pha + tya + pha + + lda #$00 + sta $2003 + lda #$02 + sta $4014 + + bit $2002 + lda #$00 + sta $2005 + sta $2005 + + lda #%01111111 + and $20 + sta $20 + + pla + tay + pla + tax + pla +@next: + rti + +irq: + rti diff --git a/basics/flicker.s b/basics/flicker.s new file mode 100644 index 0000000..d6c08a7 --- /dev/null +++ b/basics/flicker.s @@ -0,0 +1 @@ +;; TODO diff --git a/fx/README.md b/fx/README.md new file mode 100644 index 0000000..746e922 --- /dev/null +++ b/fx/README.md @@ -0,0 +1,4 @@ +TBD + +- Animating through CHR bank switching (a la Megaman 5 with MMC3) +- Shrinking a sprite rendered through CHR-RAM -- cgit v1.2.3