Earlier we made an interrupt table which assigned addresses for the SNES interrupts. We assigned the address to an empty handler that just returned for every interrupt except the NMI (VBLANK) interrupt. This interrupt occurs whenever the T.V.'s tracer is not tracing to the screen, making it safe to edit data in VRAM and update the screen. Not waiting for VBLANK can create awful results on the screen, such as flicker. To illustrate how this interrupt works, we'll increment the palette bits of a tile to make it flash different colors. This is a cool effect :).

Notes: You should preserve most of your main registers in your NMI routine. Also, you really shouldn't do anything not graphic-related; it's a waste of precious time. VBLANK doesn't last forever, so you only have ample time to do what you need to do. It's still plenty of time though, believe me.

The NMI interrupt is disabled at first, so we need to enable it. Enable it through register $4200:

Register $4200: Counter Enable
n-vh---j        n: NMI interrupt enable         v: vertical counter enable
                h: horizontal counter enable    j: joypad enable

We're only conscerned with the n bit, so just write $80 to this register to enable the NMI.

Register $4210 is also of importance (I think):

Register $4210: NMI Register
x---vvvv:                       x: NMI V-BLANK flag.
                                v: Version # ($5A22 (???))

Bit 7 can be reset to 0 by reading this register. When "1" is written to "NMI enable" of register $4200, bit 7 will show NMI status. Status is 0 for "NMI has not occurred" and 1 for "NMI has occurred".

WAI

This instruction waits for an interrupt to occur. Use this at the end of your main routine to wait for VBLANK. This instruction also reduces the processors power consumption (which is handy on portable systems).

Note: In this code, the Stall macro I made WAI's 7 times. This has the main loop run every 7 VBLANKS. This was my lazy way of slowing the fps down, so normally you would only WAI once, remember that.

Let's Code!

;------------------------------------------------------------------------
;- Written by: Bazz
;-    This code introduces the programmer to the VBLANK interrupt, and
;-    teaches him/her its powers. This should be pretty simple & easy
;-    to go through.
;------------------------------------------------------------------------

;== Include MemoryMap, HeaderInfo, and interrupt Vector table ==
.INCLUDE "header.inc"

;== Include Library Routines ==
.INCLUDE "InitSNES.asm"
.INCLUDE "LoadGraphics.asm"

;== EQUates ==
.EQU PalNum $0000       ; Use some RAM

;==========================================
; Main Code
;==========================================

.MACRO Stall
    .REPT 7
        WAI
    .ENDR
.ENDM

.BANK 0 SLOT 0
.ORG 0
.SECTION "MainCode"

Start:
    InitSNES

    rep #$10
    sep #$20
    
    stz PalNum

    LoadPalette BG_Palette, 0, 14
    LoadBlockToVRAM Tiles, $0000, $0020
    
    lda #$80
    sta $2115
    ldx #$0400
    stx $2116
    lda #$01
    sta $2118

    jsr SetupVideo
    
    lda #$80
    sta $4200       ; Enable NMI

Infinity:
    Stall

    lda PalNum
    clc
    adc #$04
    and #$1C        ; If palette starting color > 28 (00011100), make 0
    sta PalNum

_done:
    JMP Infinity

;============================================================================
VBlank:
    rep #$10        ; X/Y=16 bits
    sep #$20        ; A/mem=8 bit    
    
    stz $2115       ; Setup VRAM
    ldx #$0400
    stx $2116       ; Set VRAM address
    lda PalNum
    sta $2119       ; Write to VRAM

    lda $4210       ; Clear NMI flag
    
    RTI
;============================================================================

;============================================================================
; SetupVideo -- Sets up the video mode and tile-related registers
;----------------------------------------------------------------------------
; In: None
;----------------------------------------------------------------------------
; Out: None
;----------------------------------------------------------------------------
SetupVideo:

    lda #$00
    sta $2105           ; Set Video mode 1, 8x8 tiles, 16 color BG1/2, 4 color BG3

    lda #$04            ; Set BG1's Tile Map offset to $0400 (Word address)
    sta $2107           ; And the Tile Map size to 32x32

    stz $210B           ; Set BG1's Character VRAM offset to $0000 (word address)

    lda #$01            ; Enable BG1
    sta $212C

    lda #$0F
    sta $2100           ; Turn on screen, full Brightness

    rts

.ENDS
;============================================================================

.BANK 1 SLOT 0
.ORG 0
.SECTION "CharacterData"

Tiles:
    .DW $0000, $0000, $0000, $0000, $0000, $0000, $0000, $0000
    .DB $00,$00,$24,$00,$24,$00,$24,$00
    .DB $00,$00,$81,$00,$FF,$00,$00,$00

BG_Palette:
    .DB $00, $00, $FF, $03
    .DW $0000, $0000, $0000
    .DB $1F, $00
    .DW $0000, $0000, $0000
    .DB $E0, $5D
    .DW $0000, $0000, $0000
    .DB $E0, $02

.ENDS

See how you don't even have to call VBlank.. The interrupt take cares of all that, because we told the SNES where to go when the NMI interrupt occurred back in the header file.

I want to explain some things before this section ends

  1. The SetupVideo routine is the same as in the previous tutorial.

  2. I'm 98% sure that modification of the Processor Status Register in the interrupt routine stays in the interrupt routine.

  3. The palette-changing code:

The high byte of an entry in the tile map uses bits 2-4 as its palette bits. Here's how the palette bits work: the # formed by the palette bits is multiplied by the number of colors in the BG to get the starting index in the color palette. We're in 16 color mode, so if the palette bits were 001, then the tile's colors will range from palette entries 16-31. Thanks to Qwertie for that explanation :).

Now, I had to remember that I was writing to bits 2-4, not 0-2,so I just made bits 0 and 1 0. Since we're only using 1 tile, we won't ever use those 2 bits anyways. So, if the first palette bit is set, we have 100, or 4. if were to go up by one, we then have 1000, or 8. I kept doing this, and found out that the number increases by 4 every time until 28. I created an automatic counter that resets by using the AND instruction. A good man Duo from #gameboy showed me how to do that :). "How does it work?" you ask. I'll tell you.

  1. A logical AND takes two values, and compares them in a certain way. If both bits in a certain spot are set, then the result is 1. Any other combination results in an output of 0. Here's an example:

     10011011
    

    AND 11001001

     10001001
    

If you set all the bits you want to compare with to 1, you create a sort of mask, that will only output 1's when the value compared with has bits set in those placeholders. In our case:

    00011100
AND 00001000
-------------
    00001000

So this will output the same value every time, UNTIL it goes over that value and has no bits set in the masked place holders. So when I go over 28 (00011100), I'll be in 32, because I'm incrementing by 4. Watch what happens when I AND the mask to 32.

    00011100
AND 00100000
-------------
    00000000

It resets! So it 'auto-checks' itself. Pretty cool, especially if you want some nice, fast and efficient code (we're coding in ASM anyways, which is fast in its own right). If you ever want to fashion something in this way, just message me and I'll try to help you out.

I could have just CMP #xx, BNE or BEQ'ed to some place, but I thought of doing this by accident actually. Hell, now you you know another way to speed up your code.

Be sure to update header.inc and change the following lines so a NMI interrupt will go to the VBlank label:

.SNESNATIVEVECTOR
    COP EmptyHandler
    BRK EmptyHandler
    ABORT EmptyHandler
    NMI VBlank
    IRQ EmptyHandler
.ENDNATIVEVECTOR

Well, besides seeing that way of using AND, this code should have been easy to follow. Enjoy.

Complete Source Code: vblank-tutorial.7z

On to SNES Sprites!

Tutorial by bazz