NESFab Tutorial - Dynamic Background in NROM

Limitations of the NES Background

As you already know, the NES is very limited about what you can do to the background. It wasn't engineered to have a dynamic background by itself, but there a few ways we can remedy that. You probably know this one: changing the CHR Bank in the middle of a VBlank. Changing the CHR Bank has the effect of changing the background on the fly at the cost of ROM size, but what if we don't want to inflate the ROM size? I'll show you one trick we can do in NROM.

Dynamic Background in NROM

In NROM we don't have CHR banks, so we'll have to settle with changing between the 2 CHR sets of the only CHR bank. Ok, that gives us 2 frames of animation, but, what if we need more? In this example I show you how to get 4 frames of background animation in NROM if your game only uses 1 screen. Indeed, if you don't need scrolling, you can reuse the extra screen in the scrolling space to get 2x2=4 frames of animation.

In the following images you can see that we have 2 sets of CHR. Tile 0 of Set 0 draws ones, and tile 0 of set 1, draws twos. Ok. Tile 1 of set 0 draws a three, and tile 1 of set 1, draws a four.

chr set 1 chr set 2

In the following images made in NEXXT, you can see that we have 2 screens, the first one is made of tile 0, and the second one is made of tile 1.

NEXXT screen 1 NEXXT screen 1

Our example uses horizontal mirroring. We write the first background screen to ADDR $2000 and the second BG screen to ADDR $2800 https://www.nesdev.org/wiki/PPU_nametables. We start displaying image1 CHR set 0.

In the example code, we use the 4 animation frames to display a fake screen scrolling.

Here is the main routine:

 
            while true
                update_pads()
                move_player()
                update_sprites() 
                //
                if (curr_frames>=NUM_FPS_WAIT)
                    curr_frames=0
                    if (dyn_bg_frame==0)
                        dyn_bg_frame=1
                        ppu_ctrl_b=0
                    else
                        if (dyn_bg_frame==1)
                            dyn_bg_frame=2
                            ppu_ctrl_b=PPUCTRL_BG_PT_1000
                        else
                            if (dyn_bg_frame==2)
                                dyn_bg_frame=3
                                ppu_ctrl_b=2
                            else
                                if (dyn_bg_frame==3)
                                    dyn_bg_frame=0
                                    ppu_ctrl_b=PPUCTRL_BG_PT_1000|2
                else
                    curr_frames+=1
                nmi // Wait for the next NMI

It changes the ppu_ctrl_b variable to set or unset the PPUCTRL_BG_PT_1000 (change CHR set) and bit 1 (vertical scroll screen). More information at NESdev . Here is the code of the NMI function that merely writes to PPUCRL:

// entrance point NMI
nmi main_nmi()
    // Update OAM and poll the pads:
    ppu_upload_oam_poll_pads(0)
    // Turn on rendering:
    {PPUMASK}(PPUMASK_ON | PPUMASK_NO_CLIP)
    // Reset the scroll
    ppu_reset_scroll(0, 0)
    //
    fence
    {PPUSTATUS}() // we need to read PPUSTATUS to avoid triggering a double NMI
    fence
    {PPUCTRL}(PPUCTRL_NMI_ON|ppu_ctrl_b)

Here is the final result. It's using dynamic background. There is no scroll: