So I've been reading Nerdy Nights and they have an example where they teach you how to use nametables. here is the code:
Code:
LoadBackground:
LDA $2002 ; read PPU status to reset the high/low latch
LDA #$20
STA $2006 ; write the high byte of $2000 address
LDA #$00
STA $2006 ; write the low byte of $2000 address
LDX #$00 ; start out at 0
LoadBackgroundLoop:
LDA background, x ; load data from address (background + the value in x)
STA $2007 ; write to PPU
INX ; X = X + 1
CPX #$80 ; Compare X to hex $80, decimal 128 - copying 128 bytes
BNE LoadBackgroundLoop ; Branch to LoadBackgroundLoop if compare was Not Equal to zero
What I don't understand about it is that all it says is LDA background,X. What address is background? Is it a variable because of sprite DMA? That would make sense if each individual X and Y position had its own memory address. And also, what is a High/Low latch?
DementedPurple wrote:
What I don't understand about it is that all it says is LDA background,X. What address is background?
It's a table in ROM where the data is copied from. You have to create this table yourself, it's just a sequence of tile indices:
Code:
background:
.db $00, $02, $01, $10, $12, ...
The
LDA $XXXX, X addressing mode can only access tables up to 256 bytes large though, so if you need to write more than that to a name table (a complete name table is 960 bytes) you will need to use pointers (
LDA ($XX), Y, where the ZP position $XX contains the address of the table, and should be adjusted a few times during the transfer so that more than 256 bytes can be accessed).
Quote:
Is it a variable because of sprite DMA?
What? No sprites have nothing to do with the name tables.
Quote:
That would make sense if each individual X and Y position had its own memory address.
Each tile DOES have its own address, but even though the screen is 2D, memory is 1D. To find the address of a tile at position (X, Y) you need to use a little formula:
address = Y * 32 + X. You can also think of the address in binary as having the following format:
0010NNYY YYYXXXXXX.
Quote:
And also, what is a High/Low latch?
To set the VRAM address, you write both the low and high bytes of the address to register $2006. This latch exists so the PPU knows whether you're writing the low or the high byte, otherwise it wouldn't be able to tell them apart. Reading $2002 resets the latch, letting the PPU know that the next $2006 write will contain the high byte of the VRAM address.
Two separate things going on here:
1) Data table with indexed addressing.
In this sample code, `background` is the name of a table in memory. It's not a `variable`, because it's an address of ROM, which is not modifiable. Instead, this location in memory has a bunch of read-only data, and an amount of bytes equal to $80 is being read from that location and loaded into the PPU.
Let's say, for example, that `background` is pointing at the address $9123. Then the instruction:
LDA background,x
Will get the address at $9123+x and put it into the accumulator. The first time through the loop, X = 0, due to the LDX instead before the loop.
So:
LDA background,x
when X=0, loads A with the value in ROM at $9123
The next time through the loop, when X = 1,
LDA background,x
loads A with the value in ROM at $9124
and so on until X = $80.
This has nothing at all to do with sprite DMA, that's completely unrelated.
2) The high / low latch
The NES is an 8-bit machine, but its uses address spaces that are 16-bits, from $0000 to $ffff. In particular, the PPU has a 16-bit address space. This means that to create a pointer to the PPU's memory, addresses have to be created one byte at a time. When a byte is written to CPU's address $2006, this sets half of the PPU memory pointer. Which half it sets is based upon an internal flop called a "latch". When PPU_STATUS ($2002) is read, it resets the latch to "high", meaning the next write to $2006 will be the high byte of the ppu address, and this write will also flop the latch. The following write to $2006 will then be the low byte of the ppu address.
Stepping through this code a single instruction at a time:
Code:
LDA $2002 - latch=0, ppu_pointer=????
LDA #$20 - latch=0, ppu_pointer=????
STA $2006 - latch=1, ppu_pointer=20??
LDA #$00 - latch=1, ppu_pointer=20??
STA $2006 - latch=0, ppu_pointer=2000
Then how would we know what part of the ROM the name tables are pointing to?
Quote:
Then how would we know what part of the ROM the name tables are pointing to?
The nametables don't point to the ROM, or anything else.
The PPU has it's own RAM chip. The nametables are on that separate RAM chip. You have to transfer the data to it. (accessed with 2007 register).
Do I tell it where the Nametable ram is with the PPU control registers?
Now that is a good question.
The 2000 register selects a nametable (there are 4 nametables).
---- --00 = nametable 0
---- --01 = nametable 1
The scroll register (2005) tells the PPU where in the nametable to start rendering (ie, it shifts the nametable's position on the screen).
The 2006 shares bits with the 2005 register, so writing to 2006 (without then setting 2005 registers) will also shift the position of the screen. This is probably confusing...but it is common to rewrite the scroll registers every frame, even if they haven't changed, to make sure writes to the PPU don't screw up the scroll bits and misalign the screen.
So how would I actually plot sprites?
Sprites have nothing to do with name tables. Name tables are used to draw backgrounds.
To draw sprites you have to fill a page of RAM worth of
sprite attributes and then, during vblank, transfer that to the PPU using a sprite DMA.
To draw backgrounds you have to make sure that rendering is off (either by forcing it off or waiting for vblank) and copy the name table data to VRAM using registers $2006 (address) and $2007 (data). If your game uses a static background, normally you just have to copy 1024 bytes (960 tile indices + 64 attribute bytes) from ROM to VRAM with rendering disabled. But if you have scrolling levels, dynamic backgrounds, etc., then you need a more complex system to buffer the incremental changes to RAM and copy those to VRAM during vblank, respecting the limits of how much data can be transferred during the short vblank time.
So you pretty much would just write tile numbers to the PPU?
To update nametables, you write bits 13-8 of the destination address to $2006, bits 7-0 of the destination address to $2006, and one or more tile numbers to $2007. Every time you write to $2007, the destination address increases by 1, causing tile numbers to be written from left to right. There's a setting in $2000 to make it increase by 32 instead, causing tile numbers to be written from top to bottom.
For backgrounds, yes. You also have attributes to worry about, they select which palette is used for each 16x16-pixel area of the screen, but you don't need to worry about creating this data manually if you use a program like NES Screen Tool. You can design your screens in it and export the data to be included in your ROM.
Could you maybe explain it in simpler terms?
The following is long because "simpler terms" to express the same idea are often longer.
Coordinate pair (12, 10) means x=12 y=10, or 12 cells from the left and 10 cells from the top.
The video memory address of a nametable cell is the nametable's base address plus 32 times the Y coordinate plus the X coordinate.
For example, the video memory address of (12, 10) on the first nametable, whose base address is $2000, is
$2000 + 32 * Y + X
= $2000 + 32 * 10 + 12
= $2000 + 332
= $2000 + $14C
= $214C
To seek to $214C in video memory, write $21 to $2006 and then write $4C to $2006. Then write tile numbers to $2007. The first tile number you write to $2007 will then go to $214C, which corresponds to cell (12, 10).
If you continue to write tile numbers to $2007, where they go depends on the video memory increment, which can be +1 or +32. This depends on bit 2 of the last value written to $2000.
If bit 2 of $2000 is clear (0), the video memory increment is set to +1. This means successive tile numbers written to $2000 will appear to the right. To set the video memory increment to +1, write $80 to $2000. This means vblank NMI enabled (because bit 7 is set to 1) and video memory increment +1 (because bit 2 is clear to 0).
If the video memory increment is set to +1, and you seek to $214C and write three tile numbers, these tile numbers will be written to $214C, $214D, and $214E, which correspond to cell (12, 10) and the two cells to its right.
If bit 2 of $2000 is set (1), the video memory increment is set to +32. This means successive tile numbers written to $2000 will appear downward. To set the video memory increment to +32, write $84 to $2000. This means vblank NMI enabled (because bit 7 is set to 1) and video memory increment +32 (because bit 2 is set to 1).
If the video memory increment is set to +32, and you seek to $214C and write three tile numbers, these tile numbers will be written to $214C, $216C, and $218C, which correspond to cell (12, 10) and the two cells below it.
PPU addresses $2000-23ff represent nametable #0.
The last 64 bytes ($23c0-23ff) is called attribute table, and it's complicated, but it tells which parts of the screen (bg) should use which bg palette.
Just fill it with zero for now, and the whole screen will use the first palette (bg palette #0 = $3f00-3f03).
For some reason, the camera is able to avoid showing the nametable at all costs.
You need to set the scroll after messing with $2006/$2007, otherwise the NES won't know what part of the background you want to show.
After doing everything PPU-related in the NMI handler, while still in vblank, select a name table using register $2000 and set the scroll by writing to $2005 twice (writing zeroes will show the name table from its top left corner).
Do I write to $2000 via $2007?
No, I specifically said "register $2000". If you used $2006/7 you'd be writing to "VRAM address $2000".
It's usually...
;NMI on, BG = 1st tiles, Sprite = 2nd tiles, nametable 0
LDA #$88
STA $2000
LDA #0
STA $2005
STA $2005
or
;NMI on, BG = 2nd tiles, Sprite = 1st tiles, nametable 0
LDA #$90
STA $2000
LDA #0
STA $2005
STA $2005
It sets the scroll when I do 1, but it still doesn't display. As a matter if fact, it never displays.
You can post the .nes file. I'll debug it for you.
Your descriptions of what you're doing and how the result differs from what you expect are terribly vague.
Here is my code:
Code:
;----------------------------------------------------------------
; constants
;----------------------------------------------------------------
PRG_COUNT = 1 ;1 = 16KB, 2 = 32KB
MIRRORING = %0001 ;%0000 = horizontal, %0001 = vertical, %1000 = four-screen
;----------------------------------------------------------------
; variables
;----------------------------------------------------------------
.enum $0000
;NOTE: declare variables using the DSB and DSW directives, like this:
;MyVariable0 .dsb 1
;MyVariable1 .dsb 3
PlayerX=$0000
PlayerY=$0001
PalletteController=$2006
.ende
;NOTE: you can also split the variable declarations into individual pages, like this:
;.enum $0100
;.ende
;.enum $0200
;.ende
;----------------------------------------------------------------
; iNES header
;----------------------------------------------------------------
.db "NES", $1a ;identification of the iNES header
.db $01 ;number of 16KB PRG-ROM pages
.db $01 ;number of 8KB CHR-ROM pages
.db $00|$01 ;mapper 0 and mirroring
.dsb 9, $00 ;clear the remaining bytes
;----------------------------------------------------------------
; program bank(s)
;----------------------------------------------------------------
.base $10000-(PRG_COUNT*$4000)
Reset:
LDA #$3F
STA $2006
LDA #$00
STA $2006
LDA #$2C
STA $2007
LDA #$3F
STA $2007
LDA #$29
STA $2007
LDA #$18
STA $2007
LDA #$3F
STA $2006
LDA #$04
STA $2006
LDX #$2C
STX $2007
LDX #$3F
STX $2007
LDX #$28
STX $2007
LDX #$16
STX $2007
LDY #%11001001
STY $2000
LDX #%00011110
STX $2001
LDX #128
STX $0000
STX $0001
Wait:
JMP Wait
UP:
LDX $0001
INX
STX $0001
JMP Wait
DOWN:
LDX $0001
DEX
STX $0001
JMP Wait
LEFT:
LDX $0000
DEX
STX $0000
JMP Wait
RIGHT:
LDY $0000
INY
STY $0000
JMP Wait
NMI:
LDX #$20
STX $2006
LDX #$00
STX $2006
LDA #$12
STA $2007
LDY #0
STY $2000
LDY #0
STY $2005
STY $2005
RTI
IRQ:
;NOTE: IRQ code goes here
RTI
;----------------------------------------------------------------
; interrupt vectors
;----------------------------------------------------------------
.org $fffa
.dw NMI
.dw Reset
.dw IRQ
;----------------------------------------------------------------
; CHR-ROM bank
;----------------------------------------------------------------
.incbin "Graphics.chr"
Your program would probably push 1 tile to the top left corner of the screen.
Note. Some emulators don't show the upper 8 and bottom 8 pixels of the screen by default. FCEUX, for example. You have to find the display settings, NTSC, and manually adjust the visible scanlines to see them all. (It's trying to accurately emulate 'overscan' of old TVs, which used to cut off the edges of the screen).
This may not be your problem. You are doing STA of #0 to $2000, which means that both BG and Sprites are using the same tile set (the 1st set). And you never set the sprite positions... and they tend to default to the top left of the screen...exactly where your BG tile is...and posibly covering it up.
Also. There could be something wrong with your CHR file. I meant for you to post the actual .nes file. And not the source code.
I haven't run your program, but I can see a few problems with your code:
1)
I can't see your init code. I'd use the one in the
wiki if I where you. I gave you earlier a minimal init code that doesn't have the two required vblank waits, just the required register writes ($4017, $4010 etc), but that was only because that program didn't use the PPU (it was just a minimal noise test program that didn't output any graphics). The PPU requires you to wait twice for a vblank before you send any data to it, or it will probably not work as you expect. You don't need to understand the init routine fully at the moment, just remember that it's necessary for all programs.
2)
Your variable declarations are weird. You use the .dsb directive inside the .enum and .ende like I explained
earlier. In your code it would be:
Code:
.enum $0000
PlayerX .dsb 1
PlayerY .dsb 1
.ende
Then you can use the variables in your code like:
Code:
LDX #128
STX PlayerX
STX PlayerY
Also, the "PalletteController=$2006" is a constant definition and not a variable at all, so place it among the other constants. And BTW, $2006 is not really a palette controller, but is used to select what VRAM address you want to read or write to (using $2007). The palette is a part of the VRAM though, as are the nametables and pattern tables but not the OAM (sprite attribute memory).
VRAM Map3)
Code:
LDY #%11001001
STY $2000
Bit 0 and 1 of $2000 are set to %01, this will select nametable 1 (VRAM $2400) as base nametable address. Is that what you wanted? In your NMI you write to nametable 0 so it sounds like this should be set to %00.
Setting bit 6 of $2000 is very bad as it could potentially damage the chip. It should always be zero.
4)
Code:
UP:
LDX $0001
INX
STX $0001
JMP Wait
DOWN:
LDX $0001
DEX
STX $0001
JMP Wait
LEFT:
LDX $0000
DEX
STX $0000
JMP Wait
RIGHT:
LDY $0000
INY
STY $0000
JMP Wait
This code is outside the flow and will never happen.
And you're still not waiting for the PPU to warm up. In fact, you have no initialization code at all! You absolutely need to initialize some critical things before you can reliably use the console and start displaying anything. Have a look at
what the wiki has to say about initialization code.
Yes that's what I meant with problem number 1).
Oh crap, didn't see you had already mentioned it!