Init code: Difference between revisions
(what happens next) |
(skip $200,x and explain why) |
||
Line 50: | Line 50: | ||
sta $000,x | sta $000,x | ||
sta $100,x | sta $100,x | ||
sta $300,x | sta $300,x | ||
sta $400,x | sta $400,x | ||
Line 56: | Line 55: | ||
sta $600,x | sta $600,x | ||
sta $700,x ; Remove this if you're storing reset-persistent data | sta $700,x ; Remove this if you're storing reset-persistent data | ||
; We skipped $200,x on purpose. Usually, RAM page 2 is used for the | |||
; display list to be copied to OAM. OAM needs to be initialized to | |||
; $EF-$FF, not 0, or you'll get a bunch of garbage sprites at (0, 0). | |||
inx | inx | ||
bne @clrmem | bne @clrmem | ||
; Other things you can do between vblank waits are set up audio | |||
; or set up other mapper registers. | |||
@vblankwait2: | @vblankwait2: |
Revision as of 13:31, 24 June 2010
When the NES is powered on or reset, the program should do the following within a fixed bank:
- Set IRQ ignore bit (not strictly necessary as the 6502 sets this flag on all interrupts, including RESET, but it allows program code to simulate a reset by
JMP ($FFFC)
) - Disable PPU NMIs and rendering
- Initialize stack pointer
- Initialize the mapper (if any)
The init code after this point may be placed either in the fixed bank or in a separate bank using a bankswitch followed by a JMP
:
- Disable decimal mode (not strictly necessary as the 2A03 has no decimal mode, but it maintains compatibility with generic 6502 debuggers)
- If using a mapper that generates IRQs, disable APU timer IRQs
- Disable DMC IRQs [1]
- Set all RAM that your program uses to a known state. This often involves clearing internal RAM (@ $0000-$07FF) (and PRG RAM if needed (@ $6000-$7FFF)), except that which is intended to survive a reset (such as high scores).
- Wait at least 30,000 cycles (see PPU power up state) before reading or writing registers $2003 through $2007. This is commonly done by waiting for the PPU to signal the start of vertical blank twice through $2002.
Some mappers have no fixed bank because they switch all 32 KB of PRG at a time. These include AxROM, BxROM, GxROM, and some configurations of MMC1. You'll have to put the interrupt vectors and the code up to the end of the JMP
in a separate section that is duplicated in each bank. Often, the 256-byte page $FF00-$FFFF contains the vectors, the start of the init code, and a "trampoline" for jumps from code in one bank to code in another.
Sample implementation:
reset: sei ; ignore 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 ; Optional (omitted): ; Set up mapper and jmp to further init code here. ; Clear the vblank flag, so we know that we are waiting for the ; start of a vertical blank and not powering on with the ; vblank flag spuriously set bit $2002 ; First of two waits for vertical blank to make sure that the ; PPU has stabilized @vblankwait1: bit $2002 bpl @vblankwait1 ; We now have about 30,000 cycles to burn before the PPU stabilizes. ; One thing we can do with this time is put RAM in a known state. ; Here we fill it with $00, which matches what (say) a C compiler ; expects for BSS. Conveniently, X is still 0. txa @clrmem: sta $000,x sta $100,x sta $300,x sta $400,x sta $500,x sta $600,x sta $700,x ; Remove this if you're storing reset-persistent data ; We skipped $200,x on purpose. Usually, RAM page 2 is used for the ; display list to be copied to OAM. OAM needs to be initialized to ; $EF-$FF, not 0, or you'll get a bunch of garbage sprites at (0, 0). inx bne @clrmem ; Other things you can do between vblank waits are set up audio ; or set up other mapper registers. @vblankwait2: bit $2002 bpl @vblankwait2
At this point, the program can fill the nametables, fill the pattern tables (if the board uses CHR RAM), fill the palette, and start displaying things.