Entries RSS

Comments RSS

Refreshing the Palette in NMI

The palette! This is one of my favorite parts of the NES, as it can be used to make your game look like more is going on than there really is.

Since I’ve gotten into NES development, I’ve found that one of the better ways to use NMI is for PPU updates, while saving game logic for the main loop. Considering that the palette is written to via $2006 (writes to the PPU), sticking it in the NMI makes sense. Not only because of that, but this allows you to update the palette in your main loop via RAM, which will update on the next firing of NMI. No switching the screen off and back on during the main loop, which is bad practice from my experiences!

Note: The syntax I use is for ca65. You may need to modify it according to what assembler you use.

The first thing that we have to do is figure out where we want to stick our virtual palette register in RAM. I like using the last 32 bytes in a page, so let’s go with $4e0:

pal_address	= $4e0

Let’s stick some initial palette somewhere in our file. You can make a file and use include binary…

palette:
	.incbin "our_palette.pal"

… or you can code it like this (except with the proper colors that you need):

palette:
	.byte $0f,$00,$10,$30, $0f,$00,$10,$30, $0f,$00,$10,$30, $0f,$00,$10,$30
	.byte $0f,$00,$10,$30, $0f,$00,$10,$30, $0f,$00,$10,$30, $0f,$00,$10,$30

Just for the halibut, here is a chart showing the colors available on an NES. I’m sure most devvers know this, but be sure to use the left number first, then the top number. So if we wanted to use the white in the bottom left corner, it would be $30, and NOT $03:

NES Colors

After establishing which colors you want to start out with, the next thing to do before the main loop of the game begins (and sometime after clearing RAM before switching NMIs back on), is fill pal_address with the initial data. The following code just copies everything from our label palette in ROM to the label pal_address in RAM. The X register is set to zero, and we load the first byte from palette (ROM) and save it as the first byte in pal_address (RAM). After that, we increase the X register by one, test to see if we have done this loop 32 times, and if not, go back to load the next byte from ROM to stick in the next byte of RAM:

	ldx #$00
:	lda palette,x
	sta pal_address,x
	inx
	cpx #$20
	bne :-

Next, taking that address and putting it to use, we stick this in our NMI*:

	lda #$3f
	sta $2006
	ldx #$00
	stx $2006
:	lda pal_address,x
	sta $2007
	inx
	cpx #$20
	bne :-

What that does is set the PPU to $3f00, where the NES’ palette is stored. Notice that before we stored the low end of the address ($00) that we also set X to $00. Now we can use that as the offset like we did before, but this time we are storing the first byte of RAM (pal_address) into the first address where the NES’ palette is stored ($3f00) via the PPU address $2007. Once again, increase the X register by 1, then check to see if the loop has gone 32 times. If not, repeat the loop. Now, this gives us access to our palette in the main loop by simply writing to our virtual palette register in RAM (pal_address). Changing parts of the palette during our main loop is as easy as:

	lda #$2c
	sta pal_address+11

When the next NMI fires, this is what the palette from above would look like in the PPU:

	 0f 00 10 30  0f 00 10 30  0f 00 10 2c  0f 00 10 30
	 0f 00 10 30  0f 00 10 30  0f 00 10 30  0f 00 10 30

Notice where the 2c is? If you count over, it is 11 bytes from the starting point of the palette, and this is why we made it pal_address+11… to change that specific color!

You might not notice it right now, but this gives you a lot of power over the palette. Next time, we’ll go over animating the palette to add some life to the backgrounds and/or sprites in your games! Stay tuned!

*Side note: If you have bit 2 of $2000 set, this means you are incrementing the PPU by 32 for every write to it (for instance, laying down tiles vertically). If this is the case, keep in mind that ANY writes to the PPU will increase by 32, this includes the palette. This can be worked around by sticking these code snippets before and after the palette refresh in NMI (h/t to Quietust):

	lda reg2000_save	; before palette refresh code in NMI
	and #%11111011		;
	sta reg2000_save	;
	sta $2000		;

	; ...

	lda reg2000_save	; after palette refresh code in NMI
	ora #%00000100		;
	sta reg2000_save	;
	sta $2000		;

Leave a Reply