I would suggest a few things, starting with a few rules of thumb: never manipulate registers directly in C, and never write timing-sensitive code in C.
So, this recommendation is to do all register interactions in assembly. Don't assign to them directly in C. Yes it technically works sometimes, but not always
, and there's a lot of ways it can fail.
Wrap those interactions up in an interface to an assembly library, something like set_ppu_address(0x2110)
instead of two assignments to the $2006 register, ppu_write(0x50)
or ppu_write_block(buffer, 32)
instead of direct $2007 manipulation, etc.
Finally, have an interface for setting the scroll like set_scroll(258,0)
, but don't have that function immediately write to $2005; keep track of the desired scroll, and use those variables later in your (assembly) code that turns on rendering ($2001) immediately beforehand. There's no need to write the scroll registers at any time earlier than this point, and the further you move it away from that, the more chances there are to accidentally stick something conflicting in between.
Turning on rendering needs to be done with precise timing, specifically within vblank. If you do it anywhere else, you'll get one frame of garbage before it resynchronizes itself. This should be timed using the NMI handler. If you make your library handle the scroll registers at that time, there's no possibility that it well be mistimed or corrupted by accidental intervening code.
From the C side, I'd create a render_on()
interface that waits for the next vblank, at which point it sets up the scroll registers and turns rendering on, before returning control back to your C program. This would also be paired with a render_off()
that waits for vblank to disable rendering (so you don't get a half-blanked frame) before you start using the set_ppu_address/ppu_write interface suggested above. Even when writing directly in assembly, while I would definitely just write STA $2007
for efficiency, I would still put my timed rendering code in common subroutines like render_on/render_off and call those, to keep the number of places in my code that have to do critically timed accesses to a minimum.