NES 6502 Programming Tutorial - Part 2: Palette Setup Plus Colored Background
Hey and welcome back to my NES programming tutorial series. Now that you got a taste of it, it's time to get out of that dull grey background and finally add some color! But before we can setup the color, well need a palette. You can either load a palette from file, or you can manually type out palette data and load it from that. In our case, well just manually code it. The NES has a color palette of 54 colors, with room for 64. However, only 25 colors can be used at a time. Not to mention I heard it's not a good idea to use color $3D for some reason. Probably cause different TV's or chipsets show black instead of grey.
Using 2 palettes, you can load upto a 16 color palette for image data and 16 color palette for sprite data. However, since the first color (the transparency color) mirrors every 4 bytes starting at $3F00 at $3F04, $3F08, $3F0C for one palette, and $3F10, $3F14, $3F18, $3F1C for the other palette, you now only have 13 colors per palette, well 13 and 12 since the first one mirrored all across, giving you a total of 25 colors, which by the way isn't RGB. The NES really doesn't have an RGB system built in, and the colors can vary from TV to TV. On top of that, each 16 color palette section is actually 4 semi palettes which consists of 4 colors; transparency plus 3 other colors. So you can only select 1 palette out of 4 per 8x8 pixel region using the attribute table, no matter if you are drawing the background or a sprite.
Since I'm getting anxious to show you some code, were going to use the same palette as Super Mario Bros. Level 1-1
And just so you don't have to go back and copy and paste, here is the old code from the last tutorial:
Code:
.inesprg 1 ; 1x 16KB PRG code
.ineschr 1 ; 1x 8KB CHR data
.inesmap 0 ; mapper 0 = NROM, no bank swapping
.inesmir 1 ; background mirroring
;;;;;;;;;;;;;;;
.bank 0
.org $C000
RESET:
SEI ; disable IRQs
CLD ; disable decimal mode
LDX #$40
STX $4017 ; disable APU frame IRQ
LDX #$FF
TXS ; Set up stack
INX ; now X = 0
STX $2000 ; disable NMI
STX $2001 ; disable rendering
STX $4010 ; disable DMC IRQs
vblankwait1: ; First wait for vblank to make sure PPU is ready
BIT $2002
BPL vblankwait1
clrmem:
LDA #$00
STA $0000, x
STA $0100, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
LDA #$FE
STA $0300, x
INX
BNE clrmem
vblankwait2: ; Second wait for vblank, PPU is ready after this
BIT $2002
BPL vblankwait2
Foreverloop:
JMP Foreverloop ;jump back to Forever, infinite loop
NMI:
RTI
;;;;;;;;;;;;;;
.bank 1
.org $FFFA ;first of the three vectors starts here
.dw NMI ;when an NMI happens (once per frame if enabled) the
;processor will jump to the label NMI:
.dw RESET ;when the processor first turns on or is reset, it will jump
;to the label RESET:
.dw 0 ;external interrupt IRQ is not used in this tutorial
;;;;;;;;;;;;;;
.bank 2
.org $0000
.incbin "mario.chr" ;includes 8KB graphics file from SMB1
Where it says bank 1 is where we will put our palette database over in the origin of $E000
Code:
.inesprg 1 ; 1x 16KB PRG code
.ineschr 1 ; 1x 8KB CHR data
.inesmap 0 ; mapper 0 = NROM, no bank swapping
.inesmir 1 ; background mirroring
;;;;;;;;;;;;;;;
.bank 0
.org $C000
RESET:
SEI ; disable IRQs
CLD ; disable decimal mode
LDX #$40
STX $4017 ; disable APU frame IRQ
LDX #$FF
TXS ; Set up stack
INX ; now X = 0
STX $2000 ; disable NMI
STX $2001 ; disable rendering
STX $4010 ; disable DMC IRQs
vblankwait1: ; First wait for vblank to make sure PPU is ready
BIT $2002
BPL vblankwait1
clrmem:
LDA #$00
STA $0000, x
STA $0100, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
LDA #$FE
STA $0300, x
INX
BNE clrmem
vblankwait2: ; Second wait for vblank, PPU is ready after this
BIT $2002
BPL vblankwait2
Foreverloop:
JMP Foreverloop ;jump back to Forever, infinite loop
NMI:
RTI
;;;;;;;;;;;;;;
.bank 1
.org $E000
background_palette:
.db $22,$29,$1A,$0F ;background palette 1
.db $22,$36,$17,$0F ;background palette 2
.db $22,$30,$21,$0F ;background palette 3
.db $22,$27,$17,$0F ;background palette 4
sprite_palette:
.db $22,$16,$27,$18 ;sprite palette 1
.db $22,$1A,$30,$27 ;sprite palette 2
.db $22,$16,$30,$27 ;sprite palette 3
.db $22,$0F,$36,$17 ;sprite palette 4
;;;;;;;;;;;;;;
.org $FFFA ;first of the three vectors starts here
.dw NMI ;when an NMI happens (once per frame if enabled) the
;processor will jump to the label NMI:
.dw RESET ;when the processor first turns on or is reset, it will jump
;to the label RESET:
.dw 0 ;external interrupt IRQ is not used in this tutorial
;;;;;;;;;;;;;;
.bank 2
.org $0000
.incbin "mario.chr" ;includes 8KB graphics file from SMB1
After the vblankwait period 2 we need to load the palette into the address $3F00-$3F0F for the background palette. Just for now since we don't have sprites yet. But in order to do this, well need to reset the high low latch over in the address register $2002 (the PPU status register) which is Read-Only to High. Since the 6502 processor (or in our case the 2A03) is little endian, I kinda reversed it to big endian. So we are going to write $3F to the address $2006 (VRAM address Register 2, Write-Only) which should toggle the latch to Low, and then we are going to write $00 to $2006 again. This let's it know that we are going to change the palette. (Fun fact: This can be done during gameplay at anytime, so you are not limited to the palette you have.) Then we are going to load the palette values one by one from the data base using a loop and write them to register $2007, officially loading the palette.
Code:
.inesprg 1 ; 1x 16KB PRG code
.ineschr 1 ; 1x 8KB CHR data
.inesmap 0 ; mapper 0 = NROM, no bank swapping
.inesmir 1 ; background mirroring
;;;;;;;;;;;;;;;
.bank 0
.org $C000
RESET:
SEI ; disable IRQs
CLD ; disable decimal mode
LDX #$40
STX $4017 ; disable APU frame IRQ
LDX #$FF
TXS ; Set up stack
INX ; now X = 0
STX $2000 ; disable NMI
STX $2001 ; disable rendering
STX $4010 ; disable DMC IRQs
vblankwait1: ; First wait for vblank to make sure PPU is ready
BIT $2002
BPL vblankwait1
clrmem:
LDA #$00
STA $0000, x
STA $0100, x
STA $0400, x
STA $0500, x
STA $0600, x
STA $0700, x
LDA #$FE
STA $0300, x
INX
BNE clrmem
vblankwait2: ; Second wait for vblank, PPU is ready after this
BIT $2002
BPL vblankwait2
LoadPalettes:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
LoadPalettesLoop:
LDA background_palette, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$10 ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
BNE LoadPalettesLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
Foreverloop:
JMP Foreverloop ;jump back to Forever, infinite loop
NMI:
RTI
;;;;;;;;;;;;;;
.bank 1
.org $E000
background_palette:
.db $22,$29,$1A,$0F ;background palette 1
.db $22,$36,$17,$0F ;background palette 2
.db $22,$30,$21,$0F ;background palette 3
.db $22,$27,$17,$0F ;background palette 4
sprite_palette:
.db $22,$16,$27,$18 ;sprite palette 1
.db $22,$1A,$30,$27 ;sprite palette 2
.db $22,$16,$30,$27 ;sprite palette 3
.db $22,$0F,$36,$17 ;sprite palette 4
;;;;;;;;;;;;;;
.org $FFFA ;first of the three vectors starts here
.dw NMI ;when an NMI happens (once per frame if enabled) the
;processor will jump to the label NMI:
.dw RESET ;when the processor first turns on or is reset, it will jump
;to the label RESET:
.dw 0 ;external interrupt IRQ is not used in this tutorial
;;;;;;;;;;;;;;
.bank 2
.org $0000
.incbin "mario.chr" ;includes 8KB graphics file from SMB1
Go ahead and compile it using NESASM3 (resort to tutorial one if you didn't get a chance to see it to know what I'm talking about), and run it through the NES emulator FCEUXD SP or NO$NES. You should see a blue background! If you wanna mess around, you can feel free to change the palette values. Well this concludes my tutorial. Next time I am going to show you how to get a sprite to appear on screen. See you next time
Last edited by Jacob Roman; Jan 31st, 2018 at 11:51 PM.
Re: NES 6502 Programming Tutorial - Part 2: Palette Setup Plus Colored Background
Thanks for writing up these tutorials. I'm trying to learn 6502asm from scratch from these (and your Atari 2600 tutorial), but I'm having some difficulty understand what your palette-loading code does.
I think there might be typos or omissions in the codeblock you provided above.
Code:
LoadPalettes:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$3F
STA $2006 ; write the high byte of $3F00 address
LDA #$00
STA $2006 ; write the low byte of $3F00 address
LDX #$00 ; start out at 0
Should one of those STA codes point to $2005 rather than $2006? In Part 1, your memory map says that these are VRAM address register 1 and register 2, but here it looks like you're writing 3F to $2006 and then immediately overwriting it with 00. Which of $2005 or $2006 is the low register?
Code:
LoadPalettesLoop:
LDA background_palette, x ; load data from address (palette + the value in x)
; 1st time through loop it will load palette+0
; 2nd time through loop it will load palette+1
; 3rd time through loop it will load palette+2
; etc
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$10 ; Compare X to hex $10, decimal 16 - copying 16 bytes = 4 sprites
BNE LoadPalettesLoop ; Branch to LoadPalettesLoop if compare was Not Equal to zero
This block of code looks like it will write each of the 16 colors you defined in background_palette to the same VRAM location, $3F00. That is, you're incrementing X so that the next LDA will grab the next color from your palette table, but you never increment the low VRAM address register so that the next color will actually be loaded into the next VRAM location. Am I correct here, or does the 6502 know to automatically advance?
Also, what *is* the "high low latch"? It could be that I'm misunderstanding how this works because putting a value into $2006 (or reading one from $2002?) somehow changes the system state such that the next write to $2006 does something different.
EDIT: I withdraw both questions, there are no mistakes here. The read/write address for VRAM is set by consecutive writes to $2006, and reading from $2002 ensures that the first write will be interpreted as the high byte. On my second question, the PPU *does* know to advance the read/write address after an operation, with the increment determined by bit 2 of $2000.
Last edited by OneWingedAngel; Sep 3rd, 2020 at 09:38 AM.