In one of my projects, I needed NTSC/PAL detection so I could clean up screen-split scanlines. I didn't want to use the cycle counting method on the wiki because I preferred not adding one-time-use code to the NMI, so I came up with this alternative method instead, which runs as part of the reset routine, where you need to poll $2002 for vblanks for PPU stabilization anyway.
It works by having a cycle-timed loop that causes a forced blanking period long enough to skip past the first few scanlines on NTSC machines, while short enough to expire while still in vblank on PAL machines. A sprite 0 hit is set up during the portion of the frame that gets blanked on NTSC, so the passing/failing of the sprite 0 hit flag determines whether the machine is NTSC or PAL.
I'm putting it here just in case anyone's interested.
This code assumes CHR-RAM, but it could easily be modified for CHR-ROM; FCEUX indicates that rendering gets enabled on scanline 17 (about double than what's needed), so just removing the tile upload won't disturb the timing. It also runs after a RAM-clearing routine, so all RAM is zeroed when this runs.
A Dendy would be detected as NTSC. Considering the purpose of this code is to determine whether to use NTSC or PAL screen timings, and the Dendy was designed to be compatible with NTSC timings, this is appropriate.
It works by having a cycle-timed loop that causes a forced blanking period long enough to skip past the first few scanlines on NTSC machines, while short enough to expire while still in vblank on PAL machines. A sprite 0 hit is set up during the portion of the frame that gets blanked on NTSC, so the passing/failing of the sprite 0 hit flag determines whether the machine is NTSC or PAL.
I'm putting it here just in case anyone's interested.
This code assumes CHR-RAM, but it could easily be modified for CHR-ROM; FCEUX indicates that rendering gets enabled on scanline 17 (about double than what's needed), so just removing the tile upload won't disturb the timing. It also runs after a RAM-clearing routine, so all RAM is zeroed when this runs.
Code:
ldy #$03 ; Wait for 3 vblanks, for the PPU to ready itself
ppu_stabilize
bit $2002
bpl ppu_stabilize
dey
bne ppu_stabilize
; Detect NTSC/PAL
; At this point, we're right at the beginning of vblank. Upload a known tile.
; Y is 0 right now
sty $2006
sty $2006
dey
sty $2007
sty $2007
sty $2007
sty $2007
iny
; Put tile 0 at the beginning of the first nametable
lda #$20
sta $2006
sty $2006
sty $2007
; Set sprite 0 to 0,0 on screen. All-zeroed ram will already have this.
sty $4014
; Set scrolling such that the first byte of the first nametable is at 0,0 on screen,
; that and we're using page 0 for both bg and sprites
sty $2000
sty $2005
sty $2005
; Wait for a bit
wait_loop1
inc $FF
dec $FF
dey
bne wait_loop1
; On NTSC, this wait will be several scanlines longer than vblank
lda #$1E
sta $2001
; Enable rendering, on NTSC, rendering will start past sprite 0, so the sprite 0
; hit will fail.
wait_loop2
inc $FF
dec $FF
inc $FF
dec $FF
dey
bne wait_loop2
; If this is PAL, we should be in the visible portion of the frame by now, which means
; sprite 0 would've been detected.
lda $2002
asl
asl
rol palFlag
tya
sta $2001
; I defined my reset routine to exit with A = X = Y = 0, so TYA/STA instead of STY.
ppu_stabilize
bit $2002
bpl ppu_stabilize
dey
bne ppu_stabilize
; Detect NTSC/PAL
; At this point, we're right at the beginning of vblank. Upload a known tile.
; Y is 0 right now
sty $2006
sty $2006
dey
sty $2007
sty $2007
sty $2007
sty $2007
iny
; Put tile 0 at the beginning of the first nametable
lda #$20
sta $2006
sty $2006
sty $2007
; Set sprite 0 to 0,0 on screen. All-zeroed ram will already have this.
sty $4014
; Set scrolling such that the first byte of the first nametable is at 0,0 on screen,
; that and we're using page 0 for both bg and sprites
sty $2000
sty $2005
sty $2005
; Wait for a bit
wait_loop1
inc $FF
dec $FF
dey
bne wait_loop1
; On NTSC, this wait will be several scanlines longer than vblank
lda #$1E
sta $2001
; Enable rendering, on NTSC, rendering will start past sprite 0, so the sprite 0
; hit will fail.
wait_loop2
inc $FF
dec $FF
inc $FF
dec $FF
dey
bne wait_loop2
; If this is PAL, we should be in the visible portion of the frame by now, which means
; sprite 0 would've been detected.
lda $2002
asl
asl
rol palFlag
tya
sta $2001
; I defined my reset routine to exit with A = X = Y = 0, so TYA/STA instead of STY.
A Dendy would be detected as NTSC. Considering the purpose of this code is to determine whether to use NTSC or PAL screen timings, and the Dendy was designed to be compatible with NTSC timings, this is appropriate.