Code status: illustrative code. Adapt the listing to your program and check it on your target machine. This page links no assembly or execution record for the listing itself.
Overview
The NES has 32 palette entries: 16 for backgrounds (4 palettes of 4 colours) and 16 for sprites. Colour 0 of each palette is shared as the universal background colour. Load palettes during VBlank by setting the PPU address to $3F00 and writing 32 bytes of colour data.
Code
; =============================================================================
; PALETTE LOADING - NES
; Load 32-byte palette into PPU memory
; Taught: Game 1 (Neon Nexus), Unit 1
; Cost depends on the assembled caller and memory placement.
; =============================================================================
PPUSTATUS = $2002
PPUADDR = $2006
PPUDATA = $2007
.segment "CODE"
; Load palette data into PPU
; Call during VBlank or when rendering is disabled
load_palette:
bit PPUSTATUS ; Reset PPU address latch
lda #$3F
sta PPUADDR
lda #$00
sta PPUADDR ; PPU address = $3F00
ldx #0
@loop:
lda palette_data, x
sta PPUDATA
inx
cpx #32
bne @loop
rts
; Palette data: 4 background palettes + 4 sprite palettes
palette_data:
; Background palettes (colours 0-15)
.byte $0F, $00, $10, $20 ; Palette 0: black, dark grey, grey, light grey
.byte $0F, $06, $16, $26 ; Palette 1: black, dark red, red, pink
.byte $0F, $08, $18, $28 ; Palette 2: black, dark yellow, yellow, cream
.byte $0F, $0A, $1A, $2A ; Palette 3: black, dark green, green, lime
; Sprite palettes (colours 16-31)
.byte $0F, $30, $20, $10 ; Palette 0: black, white, light grey, dark grey
.byte $0F, $30, $16, $06 ; Palette 1: black, white, red, dark red
.byte $0F, $30, $12, $02 ; Palette 2: black, white, blue, dark blue
.byte $0F, $30, $14, $04 ; Palette 3: black, white, purple, dark purpleTypical initialisation sequence:
reset:
; ... standard reset code ...
@vblank1:
bit PPUSTATUS
bpl @vblank1
; Clear RAM
; ...
@vblank2:
bit PPUSTATUS
bpl @vblank2
; NOW safe to access PPU
jsr load_palette
; ... rest of setup ...Trade-offs
| Aspect | Cost |
|---|---|
| CPU | Not measured for this illustrative listing; include the caller and hardware scheduling costs |
| Memory | 32 bytes palette data |
| Limitation | Must be done during VBlank |
When to use: During game initialisation and when changing colour schemes.
When to avoid: During active rendering (causes visual glitches).
Palette Layout
$3F00: Universal background colour (shared by all palettes)
$3F01-$3F03: Background palette 0
$3F05-$3F07: Background palette 1
$3F09-$3F0B: Background palette 2
$3F0D-$3F0F: Background palette 3
$3F11-$3F13: Sprite palette 0
$3F15-$3F17: Sprite palette 1
$3F19-$3F1B: Sprite palette 2
$3F1D-$3F1F: Sprite palette 3Note: these seven addresses all alias $3F00 — $3F04, $3F08, $3F0C are the unused first slots of the BG palettes, and $3F10, $3F14, $3F18, $3F1C are the unused first slots of the sprite palettes. Writing to any of them writes to the universal backdrop colour.
NES Colour Reference
Common useful colours:
| Value | Colour |
|---|---|
$0F |
Black |
$00 |
Dark grey |
$10 |
Light grey |
$20 |
White-grey |
$30 |
White |
$06 |
Dark red |
$16 |
Red |
$26 |
Pink |
$02 |
Dark blue |
$12 |
Blue |
$22 |
Light blue |
$0A |
Dark green |
$1A |
Green |
$2A |
Light green |
Mid-frame palette swap
Loading the palette during VBlank gives one palette per frame. To use different palettes on different parts of the screen — for example, a status bar in different colours from the gameplay area — swap palette entries during rendering using sprite-0-hit as the timing trigger:
- Place sprite 0 on a tile that overlaps the playfield at the split point.
- After the NMI’s normal updates, poll
$2002bit 6 (sprite 0 hit flag) until it sets. - Quickly write the replacement palette entries via
$2006/$2007. Only do this for the few colours you’re swapping — there’s not enough time mid-line to rewrite all 32. - Reset for the next frame.
Sprite 0 hit is the standard mid-frame timing hook on the NES; mappers like MMC3 also offer scanline IRQs for the same purpose without burning a sprite slot.
Related
Patterns: NMI Game Loop
Calling contract
Supply32 readable palette bytes, a ready PPU and a valid stack. The VRAM increment must be1; use a shared PPUCTRL shadow to select it without changing unrelated settings. Call with rendering disabled or within a budgeted vblank. This routine changes A/X and flags and the PPU address/latch state. Restore the intended scroll after VRAM writes before rendering resumes. Palette aliases such as $3F10 share storage with their background counterparts, so choose matching values deliberately.