It is currently Wed Oct 18, 2017 1:30 pm

All times are UTC - 7 hours





Post new topic Reply to topic  [ 14 posts ] 
Author Message
PostPosted: Sat May 13, 2006 8:03 pm 
Offline
User avatar

Joined: Sat May 13, 2006 6:26 pm
Posts: 8
Location: Pullman, WA
Hey Everybody!

Summer vacation from college hit, and now I have some time to tackle some new fun projects. The top on the list is to learn some embedded systems, and I figured I would start with the good old fashioned NES. At the moment I am only interested in learning and writing roms (not emulators themselves). Perhaps in the future my interests will expand.

As I muddled through NES documentation last week, I hit the same problem that everybody has written about in a number of posts - There is very good documentation, but no comprehensive single source, especially on the level of noobs like me. At about day 3 I said to myself "If I ever figure this stuff out, I'd like to write a good guide to NES programming." Of course that's more in the pipe-dream phase right now, as I have yet to learn NES programming, learn it WELL, and then have time (provided I don't get tired of the subject) to write such a tome. It's a nice idea, who knows. Perhaps I'll fizzle out 2 weeks from now and never come back to NES programming. However at the moment I'm very optimistic, and hope I don't lose the passion before I can contribute something.

My ideas for approaching writing a tutorial are a little different. At a first glimpse (and I don't know much mind you) I would suggest:

6502 Programming
A basic (VERY VERY BASIC, barely applicable to programming) overview of NES architecture. More of a fun read than educational.
Basic PPU programming (with pre-supplied iNes headers, register pre-sets, and maybe even chr-roms)
A look into the chr-rom format.
Sprites and animation.
Standard Control Pad programming
Basic game design and theory (supply exercises to make small games)
Intermediate PPU programming (explanation of register presets, iNes headers, scrolling, palettes)
Sound
Advanced PPU programming (the rest)
Advanced Control programming (light gun, power pad, etc.)

I don't know enough to explicitly detail the concepts in order, but the main difference in approaches is to get the user programming quickly in a pre-defined environment without having them get bored or confused with the wealth of information that people have to shuffle through at the moment to learn how to get a simple pattern on the screen.

I looked through most all of the posts in the NESdev subsection of this site, and it seems like a nice community, albiet a tough one to get into. There's no way I can avoid being a noob for now, but I hope you don't hold it against me. The question I'm about to pose seems really silly, yet I've spent 2 full days reading through Yoshi's 1.0 and 2.0 docs, Marat Fayzullin's doc, Patrick Diskin's doc, and a couple of others trying to figure it out. I even read some retard's ultimate NES programming guide that only had 6502 op codes in it, with "NES PROGRAMMING: TO BE WRITTEN" at the end (and a thank you note to some other guy for giving him the op codes). Still no dice for me. I hope you don't take me as one of those "d00d I WANT TO MAD HAXOR THE NES LOL ROFLROXORS BUT WHAT THE CRAP IS 6250 ASSEMBLY YO?" people. I have done a bit of research, and am still stumped.

What I'm using:

NESAsm 2.51 (don't know if there's a better version out)
NES-CHR-ROM Creator 1.1
FCEU 0.98.12
I'm designing with NTSC in mind.

I saw that some people noted NESAsm as the "n00b" assembler, and a number of other opinions on chr-rom editors. I'm very open to suggestions, as I really have no clue what I'm doing.

And if I haven't bored you yet, on with the question!

I keep seeing a purple block in the upper left hand corner of the screen. In PAL mode it's horribly noticeable (I don't care much because I'm looking to do NTSC), but in NTSC mode it's still noticeable as a small line (the very bottom of the square still shows). What is this artifact? Is it something I shouldn't worry about? Is it a result of a poor memory write? Is it my emulator? Grr....

Here is a picture of a cheap demo program that draws a bunch of unhappy faces. This is in PAL mode (Just to make it more obvious in the picture, though it is still somewhat noticeable in NTSC).

Image

And here is a file containing my source:

http://www.geocities.com/thereallucky/ericnes.zip

I suppose my source is short enough to show here if you like...

; One programming bank
; One chr rom
;

.inesprg 1
.ineschr 1

; I have no clue yet as to what inesmir and inesmap do.
; Note: Read Neshdr.doc

.inesmir 1
.inesmap 0

.org $8000
.bank 0

Start:

; Set up the PPU registers!
; PPU Register #1
; D7 - Execute NMI on VBlank is DISABLED
; D6 - Unused.
; D5 - Sprite size is 8x8
; D4 - Background Pattern Table Address is $0000
; D3 - Sprite Pattern Table ADdress is $1000
; D2 - PPU Address Increment is 1
; D1:D0 = Nametable Address to use is $2000

lda #%00001000
sta $2000

; PPU Register #2
; D7:D5 - (Color display determined by D0)
; D7:D5 - No color intensity
; D4 - Sprites VISIBLE
; D3 - Background VISIBLE
; D2 - NO Sprite Clipping
; D1 - NO Background Clipping
; D0 - Color Display (Effects D7:D5 function)

lda #%00011110
sta $2001


waitVBlank:

lda $2002
bpl waitVBlank


; **** THIS SECTION LOADS UP THE PALETTE ****

; Time to write to the palette,
; starting at $3F00

lda #$3F
sta $2006
lda #$00
sta $2006

ldx #$00

loadPalette:

lda palette,x
sta $2007
inx
cpx #$20
bne loadPalette

; **** FINISHED LOADING UP THE PALETTE ****


; **** DRAW A BUNCH OF UNHAPPY FACES TO THE SCREEN ****

; We want to store data into the PPU's
; memory at $2000. We set up the register at $2006.
;
; This sets our starting point on the background 32x30
; grid. $2000 is the very upper left of the screen.

lda #$20
sta $2006
lda #$00
sta $2006

; We want to draw 7 rows of unhappy faces,
; which ends up being 224 unhappy faces.
; X will be our counter, so let's set it to 224
; in preparation for a countdown loop.

ldx #$E0

drawPattern:

lda #$01
sta $2007
dex
bne drawPattern

; **** FINISHED DRAWING UNHAPPY FACES ****


Loop:
jmp Loop


palette:

; Image Palette

.db $32,$01,$06,$2A,$32,$03,$10,$00
.db $32,$38,$33,$3C,$32,$21,$26,$02

; Sprite Palette

.db $32,$16,$12,$14,$32,$07,$17,$27
.db $32,$0B,$07,$2D,$32,$3A,$35,$31


.bank 1
.org $FFFA
.dw 0 ; NMI_Routine
.dw Start ; Reset_Routine
.dw 0 ; IRQ_Routine

.bank 2
.org $0000
.incbin "eric.chr"


**** END OF FILE *****

If you can tell me what I'm doing wrong, me love you long time.

PEACE!

-Eric


Top
 Profile  
 
 Post subject:
PostPosted: Sat May 13, 2006 8:23 pm 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1389
You forgot to initialize/disable the sprites, so they're all appearing at the upper-left corner of the screen.

Also, there are probably a few other problems in that program (which would prevent it from working on an NES), but I'll leave those for somebody else to describe in detail.

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Last edited by Quietust on Sat May 13, 2006 8:27 pm, edited 1 time in total.

Top
 Profile  
 
 Post subject:
PostPosted: Sat May 13, 2006 8:25 pm 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
Your problem is probably rogue sprites. The only way to hide sprites is to move them offscreen (give them a Y value >= 240). If you never do this, most emulators will default Spr-RAM being filled with all 00's, which will draw all sprites at coord 0,1 -- giving you that nice big 8x8 pixel block you see there.


The fast solution to this is to disable sprite rendering (set $2001.4 to 0). You have:

lda #%00011110
sta $2001

Try changing that to:

lda #%00001110 ; disable sprites
sta $2001


Alternatively, you could fill Spr-Ram with $F8 or $FF to hide all the sprites, but that's a bigger job.


EDIT - bah Q is too fast. I should also note I didn't really examine the code in detail, I just looked at the screen and brought up what I thought the problem was. As for your other problems I may reply again tomorrow when I'm not so tired unless someone else does before then. ^^


Top
 Profile  
 
 Post subject:
PostPosted: Sat May 13, 2006 8:38 pm 
Offline
User avatar

Joined: Sat May 13, 2006 6:26 pm
Posts: 8
Location: Pullman, WA
Bingo, sprites it is! Thanks a bunch to both of you. Holy smokes you replied fast too!

Bum deal on my code not working on a real NES. Even more bum deal because I have no clue as to why it wouldn't work. That's some good stuff to know, and I appreciate the feedback.

Thanks again guys!


Top
 Profile  
 
 Post subject:
PostPosted: Sat May 13, 2006 9:40 pm 
Offline
User avatar

Joined: Sat Feb 12, 2005 9:43 pm
Posts: 10052
Location: Rio de Janeiro - Brazil
I see 2 main reason for it not to work on a rea... oops, on a NES. =)

1 - You wait one Vblank and start doing PPU stuff. Why is this wrong? Because the PPU seems to behave erratically during the first couple of frames, meaning you may get a corrupted display. You should wait at least 2 VBlanks before doing any PPU operations (except disabling rendering, wich you can do right in the beginning).

2 - You are writing too much stuff, and the drawing probably spills out of VBlank, meaning you will get a corrupted display. You should do one of the following to avoid that: a) only turn rendering on after you have drawn everything, as the PPU is free for use when rendering is off. b) draw the graphics in smaller portions, that will fit inside VBlank time. c) Partially or completelly unroll the drawing loop, to make it faster so that you can draw more stuff in less time.

Games usually use a combination of those, where the first screen is usually drawn with rendering turned off. Then, during gameplay, since only small parts of the screen change from frame to frame, there is enough time during VBlank to update only those parts with the new data.

There are probably a couple of other things wrong with the code... such as the fact that you never set the scrolling values through $2005, wich means that the part of the name tables that is diplayed will be unstable and unpredictable.

And you should consider using other emulators besides FCEU, as it can be pretty forgiving in certain aspects. Plus, if the program works correctly in more emulators it has a better chance of running on hardware, right? The most accurate emulator around is Quietust's Nintendulator, so you should probably include that in your list.


Top
 Profile  
 
 Post subject:
PostPosted: Sat May 13, 2006 11:49 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19096
Location: NE Indiana, USA (NTSC)
I tend not to release anything until it works in Nintendulator, Nestopia, and FCE Ultra.

But it would be nice if somebody would post some general-purpose init code/project template to the new wiki.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 14, 2006 10:02 am 
Offline
User avatar

Joined: Wed Aug 03, 2005 3:15 pm
Posts: 393
It would be nice if everything wasn't so dated. Everytime I turn around, I'm being told, "Delete that document," "Throw that doc away," etc. I've now read that:

A.) Doing the basic Vblank routine that has been on the web forever pretty much sucks, as you will miss some frames from what I understand.

B.) Using $2007 is slow, and probably wouldn't work on an NES, from what I understand.

C.) Using $2004 for sprites is a bad idea, and I have no clue why, but I know I've read that just recently.


Almost EVERY damn document out there says to use these. Why do these documents still float around if they're wrong? Perhaps if there was something written up that was actually correct, then there wouldn't be so many people posting the EXACT same questions over and over and over and over and over again.

I hope you can achieve what you are trying to, Lucky. Sadly though, you'll probably end up like alot of people... in limbo not knowing what to do because of "bad documentation" which no one seems to care to fix.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 14, 2006 10:27 am 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7230
Location: Chexbres, VD, Switzerland
Using $2007 is slow, but I doubt you would be able to show anything on the screen of the NES without the use of this register. I think you're confusing with $2004, wich isn't a reliable way to write to spries, use $4014 instead.
Docs aren't wrong, they just doesn't specify too much about VBlank time, rendering time and how $2001 affects rendering.

There is no docs that say polling $2002 is more reliable than doing NMI... they just doesn't insist on the unreliability of $2002, wich is only useful at startup to wait the PPU to warm up.

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 14, 2006 11:50 am 
Offline
User avatar

Joined: Wed Nov 10, 2004 6:47 pm
Posts: 1845
- Polling $2002 for VBlank is not a good idea. As has been mentioned, this will lead to frames being ocasionally missed. The only reliable way to catch EVERY frame EVERY time is to use NMIs. Docs really SHOULD mention this, and they really SHOULD show how to use NMIs instead of relying on the "LDA $2002 BPL wait" loop. However, polling $2002 for other information (like sprite overflow or Sprite-0 hit) is not only perfectly reliable and acceptable, but it's also the only way to check for those things. AS LONG AS you don't poll it around when VBlank starts (or the same missed frames could happen).


- Beginner nesasm docs shouldn't even MENTION $2004 since it has practically no real use. Docs should just cover $4014 and mention that you should write $00->$2003 before writing to $4014. If $2004 is mentioned at all (like if the doc wants to be "complete") it should also be mentioned that you should avoid using it.

It's not that $2004 is unreliable. In fact it's very reliable. It's just ultimately slower, much more difficult to use, and all around a bigger pain in the ass.

- Sadly, I don't think any doc mentions that you need to shut the PPU off and wait for two VBlanks to pass to give the PPU "warm up" time. Every NES game should do this. It's not difficult -- in fact you can even use the simple $2002 polling loops since it doesn't matter if frames are missed (you're not doing anything yet anyway, just waiting for bootup).

Throwing a simple:
Code:
lda #$00
sta $2000 ; disable NMIs
sta $2001 ; shut off PPU

; kill two VBlanks to wait for PPU to warm up
:
lda $2002
bpl :-

:
lda $2002
bpl :-


at the start of your program is very simple, and gets the job done.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 14, 2006 12:04 pm 
Offline
User avatar

Joined: Fri Nov 12, 2004 2:49 pm
Posts: 7230
Location: Chexbres, VD, Switzerland
I'm pretty sure that doccuments cover the two frames at startup. At least I found myself doing that in the first few NES programm I ever wrote.
However, I did use $2004 in the very begining. I think $4014 was covered, too, but included demoes used $2004 and anything about timing wasn't coverd. I think the real lack in the docs is that they doesn't say much about frames, timing, VBlank, forced blanking via $2001, etc.... wich are really important points, and almost all newbies (including me before) have serious problems with that.
Then, people would draw their own conclusions about limited writes to $2007, and the fact that $4014 is much better. Of course quickly cover that would be the best, but that doesn't make sense if frametiming related stuff isn't covered first.

_________________
Life is complex: it has both real and imaginary components.


Top
 Profile  
 
 Post subject:
PostPosted: Sun May 14, 2006 4:11 pm 
Offline

Joined: Sun Sep 19, 2004 11:12 pm
Posts: 19096
Location: NE Indiana, USA (NTSC)
Disch wrote:
If $2004 is mentioned at all (like if the doc wants to be "complete") it should also be mentioned that you should avoid using it.

From the PPU page in the new wiki: "Most games access this register through $4014 instead."

Quote:
- Sadly, I don't think any doc mentions that you need to shut the PPU off and wait for two VBlanks to pass to give the PPU "warm up" time.

Got that too. From the PPU page: "The first thing that should be done on startup is to write $00 to registers $2000 and $2001 (to disable rendering and NMIs) and then wait for $2002 bit 7 to be set twice." The sample init code does this too.

If you have anything else to add to the descriptions on the wiki, be bold and make it so.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 15, 2006 12:13 am 
Offline
User avatar

Joined: Sat May 13, 2006 6:26 pm
Posts: 8
Location: Pullman, WA
Thanks for all of the advice. I fired up Nintendulator, and noticed more problems with my code. A little more tweaking, and I got it working.

I took a look at the following code provided in the Wiki, and have some questions...

Code:
reset:
    sei        ; ignore IRQs
    cld        ; disable decimal mode
    ldx #$40
    stx $4017  ; disable APU frame IRQ
    ldx #$ff
    txs        ; Set up stack
    inx
    stx $2000  ; disable NMI
    stx $2001

    ; Optional (omitted):
    ; Set up mapper and jmp to further init code here.

    ; First of two waits for vertical blank to make sure that the
    ; PPU has stabilized
@vblankwait1: 
    bit $2002
    bpl @vblankwait1

    ; We now have about 30,000 cycles to burn before the PPU stabilizes.
    ; Use it to clear RAM.
    txa
@clrmem:
    sta $000,x
    sta $100,x
    sta $200,x
    sta $300,x
    sta $400,x
    sta $500,x
    sta $600,x
    sta $700,x  ; Remove this if you're storing reset-persistent data
    inx
    bne @clrmem
   
@vblankwait2:
    bit $2002
    bpl @vblankwait2


1. The first line of code disables IRQs. I have a basic understanding of IRQs, but why are we disabling them? What IRQs exist on startup that we need to disable?

2. The second line disables decimal mode. I've seen a lot of documents state that this isn't necessary, but is good practice and/or better for 6502 simulations. What's your take on it?

3. Storing $40 into $4017 (in Yoshi's doc) says the vertical clock signal is set to not occur. In this code example it states "disable APU frame IRQ". What do both of these comments mean, and why is it important here?

4. Why are we reinitializing the stack pointer? Shouldn't it already be set up? Is this one of those "good programming practices" things too, or are we doubly making sure that the stack pointer isn't borked on reset?

5. The comments state that we have 30,000 cycles to burn before the PPU stabilizes. Does this mean that we have 30,000 cycles or so before the 2nd vBlank occurs (so we have time to clear memory), or does 30,000 cycles imply that we're waiting for an even longer amount of time (perhaps several vblanks) before the ppu stabilizes?

And one last question...

6. Does anything else trigger the NMI besides the VBlank?

Despite the skeptical questions, I really do like the init code. The fancy work with the X-index register is very nice.


Top
 Profile  
 
 Post subject:
PostPosted: Mon May 15, 2006 1:35 am 
Offline
User avatar

Joined: Mon Sep 27, 2004 8:33 am
Posts: 3715
Location: Central Texas, USA
One principle used in example code is to reduce dependency on the particulars of the NES 6502 whenever it's convenient to. Adding a few initialization instructions is trivial. Also you sometimes might want the code to run on other game consoles like NES clones and significantly inaccurate NES emulators, or perhaps even a generic 6502 simulator environment.

1&2: My findings indicate that SEI and CLD at power-up and reset is unnecessary on a NES, as the hardware does that already.

Quote:
3. Storing $40 into $4017 (in Yoshi's doc)


Yoshi's doc is dated; use the wiki's documentation of $4017. For an IRQ to occur, the I flag in the CPU must be clear (CLI) and something must be generating an interrupt request. In this case the APU is an entity that can request one, and by writing $40 to $4017 the APU is told to refrain from requesting an interrupt. This isn't necessary if you never execute CLI, but once you do, it's nice not to have unexpected interrupts occurring; for example in the future you might try to use the MMC3's scanline interrupt and not even think about the APU's interrupts, then waste time tracking down problems to it.

4. Stack pointer initialization isn't necessary if you don't use page 1 ($100-$1FF) for anything else. If you do, for example use $100-$180 for more variables, then you want the stack poiner to be $FF at power-up and after reset. After reset, the stack pointer is not reset to the top, so it needs to be initialized if you want a definite value.

5. You have slightly less than 30000 clocks until the second vblank loop would normally complete. It can be started immediately or after those clocks, so anything you do to fill the time is essentially free. The loop itself doesn't rely on being started at a particular time.

6. The NMI is triggered whenever the NMI signal to the CPU goes from deasserted to asserted (edge-sensitive). That occurs in two situations (that I know of): the last written value to $2000 had bit 7 set and vblank is just beginning, and when the last written value to $2000 had bit 7 clear and the CPU writes a value to $2000 with bit 7 set at a time during vblank.

Quote:
Despite the skeptical questions, I really do like the init code. The fancy work with the X-index register is very nice.


Unfortunately such fancy register work is not that appropriate for tutorial code.

EDIT: Corrected reference to NMI assertion.


Last edited by blargg on Mon May 15, 2006 7:26 am, edited 2 times in total.

Top
 Profile  
 
 Post subject:
PostPosted: Mon May 15, 2006 6:41 am 
Offline
User avatar

Joined: Sun Sep 19, 2004 10:59 pm
Posts: 1389
blargg wrote:
6. The NMI is triggered whenever the NMI signal to the CPU goes from low to high (edge-sensitive).


Other way around - NMI is triggered on a falling edge on the /NMI input line. The rest of your description was okay, though.

_________________
Quietust, QMT Productions
P.S. If you don't get this note, let me know and I'll write you another.


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 14 posts ] 

All times are UTC - 7 hours


Who is online

Users browsing this forum: No registered users and 6 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
Powered by phpBB® Forum Software © phpBB Group