From c05fdf0bde703c457f96617cb5f912ee1f34b6c5 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sat, 15 Sep 2018 11:20:08 -0400 Subject: [PATCH] Draw a background color This demo builds on the previous one and focuses on waiting for the NES PPU to initialize, interacting with VRAM addresses, loading the background palettes, and getting a background color to draw on the screen. --- Makefile | 2 +- README.md | 6 ++ src/bgpalette.asm | 140 +++++++++++++++++++++++++++++++++++++++++++++ src/helloworld.asm | 77 ------------------------- 4 files changed, 147 insertions(+), 78 deletions(-) create mode 100644 src/bgpalette.asm delete mode 100644 src/helloworld.asm diff --git a/Makefile b/Makefile index cba867c..aa81aa7 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ SRCDIR = src OBJDIR = obj BINDIR = bin -TARGET = helloworld.nes +TARGET = bgpalette.nes SOURCES := $(wildcard $(SRCDIR)/*.asm) OBJECTS := $(SOURCES:$(SRCDIR)/%.asm=$(OBJDIR)/%.o) diff --git a/README.md b/README.md index b682bb3..3ac5083 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,12 @@ installed. My preferred emulator/debugger is [mednafen](https://mednafen.github.io/releases/), which on Ubuntu systems can be installed with a simple `sudo apt-get install mednafen`. +My latest example code will always live on the master branch. To see earlier +demos/labs, please look at the labXX branches. Current ones include: + +Lab 1: Hello World! +Lab 2: Drawing a background color + To build and run: ``` make diff --git a/src/bgpalette.asm b/src/bgpalette.asm new file mode 100644 index 0000000..aae3597 --- /dev/null +++ b/src/bgpalette.asm @@ -0,0 +1,140 @@ +; Background Palette Loader Demo For NES +; +; Following on from the hello world program, this program expands out into +; interactions with the NES PPU (Picture Processing Unit). This does the bare +; minimum to draw the screen-- it disables all sprites and background graphics +; and fills the screen with the default color. +; +; Doing this requires some extra work. Firsst off, the PPU is not immediately +; ready at power on. Before interacting with it, you need to wait enough cycles +; for it to become ready. There is a standard wait process employed in the +; reset vector. +; +; Loading information into the PPU involves poking specific memory addresses. +; On the NEW, the PPU and CPU are basically independent of each other and they +; each have their own RAM space (the PPU's is called VRAM). Loading data into +; VRAM is done by setting the address by writing it, one byte at a time, to the +; PPUADDR ($2006) register. After that, VRAM can be read and written to using +; lda or sta instructions from the PPUDATA ($2007) register. +; +; The PPU stores 4 different background palettes; each stores 3 colors. A 4th +; color, the default background color, is stored at VRAM $3F00 and aliased at +; $3F04, $3F08, and $3F0C. (They're also aliased at $3F10, $3F14, $3F18, and +; $3F1C). Because any write to those aliases can change the background color, +; it's easier to treat the palette as containing FOUR colors, with the first +; one always being the same. +; +; In this demo, all the palettes are the same. It's actually one of the +; background palettes from Super Mario Brothers + + +.define PPUMASK $2001 +.define PPUSTATUS $2002 +.define PPUADDR $2006 +.define PPUDATA $2007 + +.define BGPALETTE_HI $3F +.define BGPALETTE_LO $00 + +; Mandatory iNES header. +.segment "HEADER" + +.byte "NES", $1A ; "NES" magic value +.byte 2 ; number of 16KB code pages (we don't need 2, but nes.cfg declares 2) +.byte 1 ; number of 8KB "char" data pages +.byte $00 ; "mapper" and bank-switching type (0 for "none") +.byte $00 ; background mirroring flats + ; + ; Note the header is 16 bytes but the nes.cfg will zero-pad for us. + +; code ROM segment +; all code and on-ROM program data goes here + +.segment "STARTUP" + +; reset vector +reset: + bit PPUSTATUS ; clear the VBL flag if it was set at reset time +vwait1: + bit PPUSTATUS + bpl vwait1 ; at this point, about 27384 cycles have passed +vwait2: + bit PPUSTATUS + bpl vwait2 ; at this point, about 57165 cycles have passed + + lda #$00 ; color image, no sprites, no background + sta PPUMASK + + ; request access to the background palette memory. This starts at VRAM + ; address $3F00. VRAM is in a separate address space from RAM, so we access + ; it via the use of PPUADDR and PPUDATA. PPUADDR sets the address to access + ; while PPUDATA is the data, one byte at a time. When you access VRAM via + ; PPUDATA, it auto-increments the address by default, making loops easier. + + ; Start at VRAM address $3F00 + lda #BGPALETTE_HI + sta PPUADDR + lda #BGPALETTE_LO + sta PPUADDR + + ; prep the loop + ldx #0 + + ; load the background palette +paletteloop: + lda bgpalette, X ; load from the bgpalette array + sta PPUDATA ; store in PPUDATA, PPU will auto-increment + inx ; increment the X (index) register + cpx #16 + bne paletteloop ; run the loop until X=16 (size of the palettes) + + + ; The "hello world" sound from the previous exercise, so we know we made + ; it to the end of the reset vector. + lda #$01 ; square 1 + sta $4015 + lda #$08 ; period low + sta $4002 + lda #$02 ; period high + sta $4003 + lda #$bf ; volume + sta $4000 +forever: + jmp forever + +nmi: + rti ; Return from the NMI (NTSC refresh interrupt) + + +; The background colors are, in order: +; $22: pale blue +; $29: drab green +; $1A: forest green +; $0F: black +; Actual color fidelity varies based on emulator and monitor, but +; this gives a rough sense of the colors +; +; Also note that my practical experience shows me that you *must* +; load up all 4 palettes in order to get the PPU to be happy and +; draw your default color to the screen! + +bgpalette: + .byte $22, $29, $1A, $0F ; palette 0 + .byte $22, $29, $1A, $0F ; palette 1 + .byte $22, $29, $1A, $0F ; palette 2 + .byte $22, $29, $1A, $0F ; palette 3 + + +; vectors declaration +.segment "VECTORS" +.word nmi +.word reset +.word 0 + +; The "hello world" program identified this as a space for ROM data. That is +; technically true, but it misses a verey critical nuance. This segment is for +; CHR-ROM, which is a space in ROM which the PPU directly loads into its memory +; for use in rendering the screen! This is not the rodata section! Your +; read-only data used in your program belongs in the STARTUP segment! + +.segment "CHARS" diff --git a/src/helloworld.asm b/src/helloworld.asm deleted file mode 100644 index b1af0b2..0000000 --- a/src/helloworld.asm +++ /dev/null @@ -1,77 +0,0 @@ -; Hello World for the NES -; -; This initial program is purely to test out the assembling and linking -; features of cc65 and to prove out a build process that will make an -; iNES-format ROM suitable for use on most emulators. There is no console -; output for the NES and no default set of characters for text output, so -; the traditional "Hello World" output has been replaced with generating a -; simple square wave on the audio synthesizer. If your copy of mednafen is -; configured correctly for audio and everything builds correctly, you should -; hear a buzzing noise from the speakers and see a blank window/screen. -; -; The code to generate the square wave came from some code recommended by the -; NESDev Wiki at: -; https://wiki.nesdev.com/w/index.php/Programming_Basics#.22Hello.2C_world.21.22_program -; -; What was seriously lacking from this wiki, however, was information on how -; cc65 links together a complete ROM using the "-t nes" flag. Hopefully, this -; code will help spare a future hobbyist some time in understanding how everything -; goes together. - -; for reference on the linker config, I suggest you pull down the cc65 source and -; locate the "cfg/nes.cfg" file, which provides the layout of the ROM - - -; Mandatory iNES header. Without this, the emulator will likely not load your ROM. -; cc65 declares its location and size in the nes.cfg file -.segment "HEADER" - -.byte "NES", $1A ; "NES" magic value -.byte 2 ; number of 16KB code pages (we don't need 2, but nes.cfg declares 2) -.byte 1 ; number of 8KB "char" data pages -.byte $00 ; "mapper" and bank-switching type (0 for "none") -.byte $00 ; background mirroring flats - ; - ; Note the header is 16 bytes but the nes.cfg will zero-pad for us. - -; Like with "HEADER", "STARTUP" is declared in the nes.cfg file. Your code must -; fit within this 32 KB segment (for now). All your code effectively goes here. -; I will not go into length on 6502 assembly, but I will note this loads a buzzy -; square wave into the audio synthesizer and then loops forever. You must supply -; the label for your startup code in the "VECTORS" segment below - -.segment "STARTUP" - -reset: - lda #$01 ; square 1 - sta $4015 - lda #$08 ; period low - sta $4002 - lda #$02 ; period high - sta $4003 - lda #$bf ; volume - sta $4000 -forever: - jmp forever - -nmi: - rti ; Return from the NMI (NTSC refresh interrupt) - -; "VECTORS" is another mandatory section. It must contain a table for three -; things, in order. The first is for the non-maskable interrupt (NMI), which -; is the NTSC video signal vertical sync (among other things). This interrupt -; triggers on the end of every pass of an NTSC television and is crucial for -; timing game logic. The second is the entry point of your program, triggered -; off the "reset" interrupt which is delivered at power on and at any press of -; the NES reset button. The third handles assorted interrupt requests (IRQs) -; from the system and is not used in this program. - -.segment "VECTORS" -.word nmi -.word reset -.word 0 - -; "CHARS" is a reserved section for data. Our program has no data, so this -; is only here to help the linker finish laying out the ROM. - -.segment "CHARS" -- 2.34.1