Basically you shouldn't be writing to the PPU in the main loop. The CPU and PPU run in parallel, so if you have the CPU change colors whenever, without any regard for what the PPU is doing, you're gonna get corrupted results during the entire frame. You may get away with writing to the PPU in the main loop during initialization when the screen is off, but other than that, you shouldn't do it.
The absolute reference of time on the NES is the vertical blank (vblank), which's the little bit of time that comes after the PPU is done drawing a frame and lasts until it starts drawing the next frame. It's during this time that you can make changes to the screen, because the PPU itself isn't using the video memory then. To help programmers know when vblank starts, the engineers of the NES made it so that an NMI fires when vblank starts, causing the CPU to stop whatever it's doing and jump to the NMI handler... And it's via the NMI handler that you keep track of time, because we know that vblanks happen at the rate of the video signal (60fps on NTSC, 50fps on PAL).
What you have to do is limit the rate at which your main loop runs to once per frame, by having it wait for the next frame before looping back. A common way to do this is the following:
Code: Select all
NMI:
;(...)
inc FrameCounter ;change the frame counter
rti ;return from the NMI
Code: Select all
Main:
;(...)
lda FrameCounter ;get the frame counter
Wait:
cmp FrameCounter ;check if it changed
beq Wait ;go check again if it didn't
jmp Main ;now you can process the next frame
This just increments a variable once every vblank, so the main loop can simply wait for this variable to change before processing another frame, and since this variable only changes 60 times per second, the main loop will also run 60 times per second, and your game logic is now synced to the video.
Like I said before, you cannot change the video memory (which includes the palette) while the PPU is drawing a picture, so you need to put the actual write to the palette in the NMI handler, to make sure it happens during vblank:
Code: Select all
NMI:
lda #$3F
sta PPUADDR
lda #$00
sta PPUADDR
lda BackgroundColor
sta PPUDATA
inc FrameCounter
rti
This is a simple NMI handler that just writes the current background color to the palette every frame. It's up to the main loop to change that value periodically:
Code: Select all
lda #1 ;make it so the delay expires immediately
sta Delay
lda #$00
sta BackgroundColor
Main:
dec Delay ;decrement the delay
bne ColorReady ;don't do anything if it didn't reach 0 yet
lda #60 ;prepare to wait 60 frames (1 second)
sta Delay
inc BackgroundColor ;change the color
ColorReady:
lda FrameCounter
Wait:
cmp FrameCounter
beq Wait
jmp Main
Something along these lines.