This page is a mirror of Tepples' nesdev forum mirror (URL TBD).
Last updated on Oct-18-2019 Download

Simple CHR RAM Example

Simple CHR RAM Example
by on (#159622)
Hello, I am an NES and 6502 novice, but have so far managed to learn from this forum and other online sources. Now I am trying to implement CHR RAM to draw tiles dynamically and having a hard time getting a hello world sample working. The examples I have seen either only show bits of code and assume a deeper understanding of how to set up banks than I have, or else they are complex examples with lots of other functionality, so I am having a hard time finding the basic steps. Can anybody point me to a simple CHR RAM sample that writes a dynamically generated tile? If not, can anybody outline the minimum steps to implement one?
Re: Simple CHR RAM Example
by on (#159627)
Have you already made a functioning example of updating the name table and/or attribute table in a software with CHR-ROM?

If you have that, updating a tile that's stored in CHR-RAM is very similar, except that it takes 16 bytes to store the contents of a 8x8 tile (instead of 4¼ bytes to specify which tiles are used in a 16x16 pixel area)
Re: Simple CHR RAM Example
by on (#159628)
I've written code to do this before, looked at it and it was quite ugly, and figured what the hell, I'll code it up and play with it later. I wouldn't say it's a simple example, but I don't think a pixel-level addressing example of any type will be.

Note that this is utterly, completely, entirely untested. Didn't even try to assemble it. If it doesn't help I hope it makes sense, at least. Hopefully it's not screwed up, or with typos.

Code:
; licensed under WTFPL

.segment "ZEROPAGE"
addr_lo: .res 1
addr_hi: .res 1
temp_a: .res 1
temp_x: .res 1
temp_y: .res 1
bit_position: .res 1

.segment "CODE"
; plot pixels into buffer in WRAM at $6000-$6FFF.  When finished, write this buffer to pattern tables at PPU $0000-$0FFF
; note: this routine only SETS pixels, does not clear pixels!  The bits in the buffer must be all 0s to start with!
;
; a = color 0~3
; x = x position 0~127
; y = y position 0~127
plot_pixel:
        sta temp_a
        stx temp_x
        sty temp_y

        lda #0
        sta addr_hi
        sta temp
                                ; tile address based on X position
        txa
        and #%01111000
        asl
        sta addr_lo
                                ; address within tile based on Y position
        lda temp_y
        and #%00000111
        clc
        adc addr_lo
        sta addr_lo
                                ; tile address based on Y position
        lda temp_y
        and #%01111000
        asl
        asl
        rol addr_hi
        asl
        rol addr_hi
        asl
        rol addr_hi
        asl
        rol addr_hi

        lda addr_hi   ; change address to point to buffer in WRAM at $6000-$6FFF
        clc
        adc #$60
        sta addr_hi

                                ; bit position within tile based on X position
        lda temp_x
        and #%00000111
        sta bit_position
        lda temp_a              ; grab color bit 0
        and #%00000001
        lsr
        ror                     ; move D0 to D7 as starting position
        ldx bit_position
        beq @skip
@shift:
        lsr
        dex
        bne @shift
@skip:
        ldy #0
        ora (addr_lo),y
        sta (addr_lo),y
        lda temp_a              ; grab color bit 1
        and #%00000010
        lsr
        lsr
        ror
        ldx bit_position
        beq @skip2
@shift2:
        lsr
        dex
        bne @shift2
@skip2:
        ldy #8
        ora (addr_lo),y
        sta (addr_lo),y

        rts
Re: Simple CHR RAM Example
by on (#159635)
Basic use of CHR-RAM is quite simple. The only difference from CHR-ROM is that it's empty on power on, and your program has to copy tiles from PRG-ROM to CHR-RAM before it can display any tiles. Here's a simple example of how to populate the CHR-RAM so you can start using the name table, palettes, etc. to show something on the screen:

Code:
   ;set up a pointer to read the tiles
   lda #<MyTilesStart
   sta Pointer+0
   lda #>MyTilesStart
   sta Pointer+1

   ;set the destination address to the beginning of the pattern tables
   lda #$00
   sta $2006
   sta $2006

   ;prepare to loop as many times as necessary to copy all the tiles
   ldx #((MyTilesEnd - MyTilesStart) / 16)

CopyTile:

   ;prepare to copy the first byte of the tile
   ldy #$00

CopyByte:

   ;copy one byte an move on to the next
   lda (Pointer), y
   sta $2007
   iny

   ;go copy another byte if we haven't copied all 16 yet
   cpy #$10
   bne CopyByte

   ;update the tile counter and skip to the end if done
   dex
   beq Done

   ;move the pointer over to the next tile and go copy that tile
   clc
   lda Pointer+0
   adc #$10
   sta Pointer+0
   bcc CopyTile
   inc Pointer+1
   bcs CopyTile

Done:

(...)

MyTilesStart:

.incbin "tiles.chr" ;<- this is still in the PRG-ROM area, not after it as is the case with CHR-ROM

MyTilesEnd:

This will copy all the tiles in the CHR file (up to 256, or 4KB) to the beginning of the first pattern table. Note that this can be optimized in many ways, this is intended simply as a straightforward example. After this code runs, you can use those tiles just as if you were using CHR-ROM.

You can do the same thing later on to update a few tiles each frame as your game runs, to create animations and the like. Keep in mind that you can only do this during vblank, which is a very short time, so you won't be able to update a lot of tiles each time. Normally it's possible to update 8 to 12 tiles per frame, depending on what other updates you have to do. That estimate also assumes a much more optimized code than the slow ass example I wrote above. One way to do it is to buffer the data on the stack before vblank starts, and when it does you just run a series of PLA + STA $2007 commands, which takes 8 CPU cycles per byte (i.e. 128 cycles per tile).
Re: Simple CHR RAM Example
by on (#159639)
If you're doing character updates while a game is running (with the screen enabled), they need to be done very quickly, so unrolled code helps here.
With this example code, you'd be able to draw up to 13 tiles per frame if you're aren't doing any other screen updates, so if you're also updating the background, figure on up to 4 or 8 tiles updated per frame.
The inner loop ends up looking something like this:

;first set the destination vram address yourself
;X = source tile number * 16
;Y = number of tiles to update
update_tiles_loop:
lda some_graphics+0,X
sta $2007
lda some_graphics+1,X
sta $2007
;repeat the code with +2, +3, etc...
...
lda some_graphics+15,X
sta $2007
;add 16 to X
txa
clc
adc #16
tax

dey
bne update_tiles_loop

;142 cycles (462 dots) per tile written