11 March 2016 - Forum Rules

Main Menu

NES CHR-RAM Question

Started by RetroRain, August 05, 2012, 08:57:36 PM

Previous topic - Next topic


If I wanted to copy an 8x8 tile from PPU address $00 in the left bank ($0000), and paste it into PPU address $20 of the right bank ($1200), would this be how I do it?

LDA $2002
BPL wait_for_vblank

LDA #$00   ; Select tile $00 on the left side, by writing #$00 to $2006 twice.
STA $2006
STA $2006

LDA $2007   ; Read all of tile $00.

LDA #$00   ; Select tile $20 on the right side.
STA $2006
LDA #$12
STA $2006

STA $2007   ; Write all of tile $00 from the left side into $20 on the right side.

If I can master copying a single 8x8 tile, then I will be able to do what I really want.  I'm having a hard time right now with this, so if someone can show me the right way, I would appreciate it.

My YouTube Channel: RetroRainZX85 -


lda $2007 will only read a single byte from VRAM. You will want to read all 16 bytes (probably store them to a buffer somewhere in PRG-RAM) then write a loop to copy that buffer back to VRAM. (you only need to set $2006 for the first read/write, it will automatically be increased each time, so you can just lda/sta $2007 repeatedly to read/write consecutive bytes. Remember that so you don't waste precious VRAM access time.)
Note that when setting the VRAM address, you actually want to write the HIGH byte to $2006 first (12, then 00 to set it to 1200).
Also, I know there's a bug accessing VRAM. If I recall, the first byte read from $2007 is junk data and should be ignored (but I'm not certain as I've never had to read VRAM before).
"My watch says 30 chickens" Google, 2018


All it is doing is giving me a blank tile:

$B758:AD 02 20  LDA $2002     ; Wait for Vblank
$B75B:10 FB     BPL $B758

$B75D:A9 00     LDA #$00         ; Set $2006 to tile $00 on the left side of the PPU
$B75F:8D 06 20  STA $2006
$B762:A9 00     LDA #$00
$B764:8D 06 20  STA $2006

$B767:A2 00     LDX #$00         ; Run a loop 16 times to copy the whole tile from $00
$B769:AD 07 20  LDA $2007     ;  on the left side of the PPU
$B76C:E8        INX
$B76D:E0 10     CPX #$10
$B76F:D0 F8     BNE $B769

$B771:AD 02 20  LDA $2002     ; Wait for Vblank again
$B774:10 FB     BPL $B771

$B776:A9 12     LDA #$12        ; Now we go to tile $20 on the right side of the PPU
$B778:8D 06 20  STA $2006
$B77B:A9 00     LDA #$00
$B77D:8D 06 20  STA $2006

$B780:A2 00     LDX #$00        ; Write the tile we just copied from $00 on
$B782:8D 07 20  STA $2007     ;  the left side of the PPU
$B785:E8        INX
$B786:E0 10     CPX #$10
$B788:D0 F8     BNE $B782

It copies the whole tile, but then it puts a whole blank one in $20 on the right side of the PPU.  I don't get it.  It shouldn't be doing that.
My YouTube Channel: RetroRainZX85 -


You aren't storing the bytes you're reading anywhere, and aren't reading the bytes from anywhere when you output them.
"We are merely sprites that dance at the beck and call of our button-pressing overlord."


I figured it out! :D

I'm posting this here for anyone else who might have trouble with CHR-RAM!  I don't want anyone else to have to go through the hell I did with this thing.

Here it is:

In order to do CHR-RAM

1. Put the wait for Vblank code:

LDA $2002
BPL wait_vblank

2. Set $2006 to where you want to write to.  So if tile $00 on the left side of the PPU, then

LDA #$00
STA $2006
STA $2006

If $00 on the right side of the PPU, then

LDA #$10
STA $2006
LDA #$00
STA $2006

3. Run a loop 16 times, since 16 bytes make up one 8x8 tile.

During that loop, write your tiles from a table close-by, to $2007. (That is where I've been screwing up all along.  You have to have your graphics close by, so that you can read them byte by byte.  I had my graphics in the graphics part of the ROM.  Since graphics are just a bunch of bytes, make sure you put those bytes near-by, so you can look them up as a table).  That's it!  See below.

$B758:AD 02 20  LDA $2002 = #$10
$B75B:10 FB     BPL $B758

$B75D:A9 00     LDA #$00
$B75F:8D 06 20  STA $2006 = #$C1
$B762:A9 00     LDA #$00
$B764:8D 06 20  STA $2006 = #$C1

$B767:A2 00     LDX #$00
$B769:BD 78 B7  LDA $B778,X @ $B779 = #$10
$B76C:8D 07 20  STA $2007 = #$00
$B76F:E8        INX
$B770:E0 10     CPX #$10
$B772:D0 F5     BNE $B769

$B774:AD 13 04  LDA $0413 = #$05
$B777:60        RTS

; This is the graphics table.  There are 16 bytes, which make up one 8x8 tile.
$B778:00        BRK
$B779:10 1B     BPL $B796
$B77B:1F        UNDEFINED
$B77C:1F        UNDEFINED
$B77D:1F        UNDEFINED
$B77E:1F        UNDEFINED
$B77F:0F        UNDEFINED
$B780:00        BRK
$B781:00        BRK
$B782:00        BRK
$B783:00        BRK
$B784:00        BRK
$B785:00        BRK
$B786:00        BRK
$B787:00        BRK

Thanks for your help guys!  I finally got CHR-RAM swapping to work!  Now I know how to do it!! :D

August 06, 2012, 12:57:23 PM - (Auto Merged - Double Posts are not allowed before 7 days.)

CHR-RAM will only let me write $90 bytes before giving me problems and glitching up the PPU and the screen.

Can I write everything I want to write by:

1. Waiting for Vblank
2. Turning off the screen
3. Do all of my writes ($1B0 bytes)
4. Turning the screen back on again?

Or is there a better way?

My YouTube Channel: RetroRainZX85 -


Unless you're doing something very strange, there's really no reason to read from  CHR-RAM. Everything in CHR-RAM got there by being copied there from somewhere else, almost always from PRG-ROM. It's more efficient to copy it from the original source, this should allow you to copy twice as many tiles before running out of vblank. (And use less RAM, less code space, etc.)

To answer your latest question, yes, that should work. However, there is another option that might be less disruptive. Rather than disable video, you can spread the tile updates over two or more vblanks. This does lead to some frames being only partially updated, but it'll probably be less visually distracting than turning video off, especially if it's only for one frame. (Though that depends on when exactly the updates are being made. In some cases, a brief black screen can be appropriate.)

Edit: After posting the above, I reread your post and noticed that you'd need 3 vblanks to move all your data. That's probably a bit long to be spreading it out like I suggested above. If you could elaborate on when and why you're doing all this CHR-RAM movement, we could probably give you more specific advice.
Current ProjectsFinal Fantasy EngineSMB Special for NESStudio Karatorian
@loop: lda (src),y — sta (dst),y — iny — bne @loop — inc src+1 — inc dst+1 — dex — bne @loop


You should also do a dummy $2007 read when reading from VRAM before reading actual data.

But Karatorian is completely right, it would be better not to read from VRAM anyways.