SRCDIR = src
OBJDIR = obj
BINDIR = bin
-TARGET = bgpalette.nes
+TARGET = background.nes
SOURCES := $(wildcard $(SRCDIR)/*.asm)
OBJECTS := $(SOURCES:$(SRCDIR)/%.asm=$(OBJDIR)/%.o)
.PHONY: clean
clean:
- @$(rm) $(BINDIR)/$(TARGET)
+ @$(rm) $(BINDIR)/*
@$(rm) $(OBJECTS)
Windows.
You will need a copy of [cc65](https://github.com/cc65/cc65) built and
-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`.
+installed. My preferred emulator/debugger is [fceux](http://www.fceux.com) under
+Wine, and **NOT** the one under Linux, as their Windows release has a powerful
+set of debugging tools available. With them, you can inspect pattern and name tables!
+
+Alternatively, I'd recommend [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
+* Lab 1: Hello World!
+* Lab 2: Drawing a background color
+* Lab 3: Pattern, name, and attribute tables
To build and run:
```
--- /dev/null
+; Nametables and attribute tables Demo for NES
+; Following on from the background palette program, this program looks at how
+; to load a pattern table, which contains the actual graphic tiles, a nametable,
+; which contains the layout of graphic tiles for the background, and the
+; attribute table, which specifies what palette to paint everything with.
+;
+; I am not an artist, and even then, converting bitmaps to pattern tables can be
+; a chore. So, I used the "Generitiles" from the NESDev wiki
+; (https://wiki.nesdev.com/w/index.php/Placeholder_graphics). I then ran these
+; through a Python script supplied by Damian Yerrick
+; (https://github.com/pinobatch/nesbgeditor) which converts a 2-bit PNG into
+; pattern tables pretty darn effectively. Thanks, Damian!
+;
+; So, we're now *finally* using the CHARS section, which gets directly mapped
+; into the PPU's memory at power on. This section really should more
+; be named "CHR-ROM", as this is the more common name for it. You'll notice
+; that I can directly include the file produced by Damian's tools, which keeps
+; the code tidy.
+;
+; With the patterns directly mapped in, the next step is to load up some data
+; in the name table. Since we're not doing anything fancy, I've restricted
+; things to the first of the two name tables. Much like we did for loading the
+; palette colors, we load this through the use of PPUADDR and PPUDATA.
+;
+; Finally, we load the attribute table, which says which palette to use for
+; each 32x32 pixel region on the screen. In a more advanced demo, this would
+; raise the number of effective colors I was using on the screen. For now,
+; though, I just want to keep things simple and easy-to-explain.
+;
+; Note that the annyoing "hello world" sound is now gone. The graphics show
+; that everything is working.
+
+.define PPUMASK $2001
+.define PPUSTATUS $2002
+.define PPUADDR $2006
+.define PPUSCROLL $2005
+.define PPUDATA $2007
+
+.define NAMETABLE_0_HI $20
+.define NAMETABLE_0_LO $00
+.define ATTRTABLE_0_HI $23
+.define ATTRTABLE_0_LO $C0
+.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
+
+ ; Interesting little fact I learned along the way. Because it takes two
+ ; stores on PPUADDR to move its pointer, it's good practice to start all of
+ ; your PPUADDR use with a peek at PPUSTATUS since this resets its "latch"
+ ; and ensures you're addressing the address you expect.
+ ; Technically, we don't need this because we did it in the reset code, but
+ ; it's a neat little thing to mention here
+
+ bit PPUSTATUS
+
+ ; load the background palette
+ lda #BGPALETTE_HI
+ sta PPUADDR
+ lda #BGPALETTE_LO
+ sta PPUADDR
+
+ ; prep the loop
+ ldx #0
+
+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)
+
+; move PPUADDR over to nametable 0.
+ lda #NAMETABLE_0_HI
+ sta PPUADDR
+ lda #NAMETABLE_0_LO
+ sta PPUADDR
+
+; This loop iterates over the pattern table, outputting it in lines of 16
+; The other 16 are just padded out with a pattern that's blank. This lets
+; me easily show you some simple graphics that are made up of multiple
+; stacked tiles without getting too fancy. In reality, you'd probably have
+; complete nametables that you'd load in from files and simply run in a loop.
+
+ ldx #$00
+ ldy #16
+nametableloop:
+ stx PPUDATA
+ inx
+ dey
+ bne nametableloop
+ ldy #16
+padright:
+ sta PPUDATA
+ dey
+ bne padright
+ cpx $D0
+ beq done
+ ldy #16
+ jmp nametableloop
+done:
+
+; set up Palette 0 for everything
+ bit PPUSTATUS
+ lda #ATTRTABLE_0_HI
+ sta PPUADDR
+ lda #ATTRTABLE_0_LO
+ sta PPUADDR
+ ldx #64 ; 64 tiles in the attribute table
+ lda #0
+
+attrloop:
+ sta PPUDATA
+ dex
+ bne attrloop
+
+; Enable background and sprite rendering. This is suuuuuper important to
+; remember. I didn't remember to put this in and probably blew a whole day
+; trying to figure out why my emulator hated me.
+ lda #$1e
+ sta PPUMASK
+
+forever:
+ jmp forever
+
+nmi:
+ rti ; Return from the NMI (NTSC refresh interrupt)
+
+
+; The background colors are, in order:
+; $0F: black
+; $15: red
+; $22: blue
+; $20: white
+
+bgpalette:
+ .byte $0F, $15, $22, $20 ; palette 0
+ .byte $0F, $15, $22, $20 ; palette 1
+ .byte $0F, $15, $22, $20 ; palette 2
+ .byte $0F, $15, $22, $20 ; palette 3
+
+
+; vectors declaration
+.segment "VECTORS"
+.word nmi
+.word reset
+.word 0
+
+; As mentioned above, this is the place where you put your pattern table data
+; so that it can automatically be mapped into the PPU's memory at $0000-$1FFF.
+; Note the use of .incbin so I can just import a binary file. Neato!
+
+.segment "CHARS"
+.incbin "generitiles.pat"
+++ /dev/null
-; 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"