I really didn't get how to scroll for PART of a name table, it didn't really make sense to me. I know it's possible, as I can see from many games/tech demos: CMC 80's, Cosmic Epsilon, 3D battles of world runner. But I'm not trying 3D stuff yet, that's at the, well, bottom of my list of things I need to learn how to do. Way to complicated for me now. Anyways, I am going to put together a technical demo of partiall scrolling, and I need to well, know how to do it! What I want is the half of the middle row to scroll slowly, the other half will be still, and I want the rest to move faster. How would I do such a thing? I don't even know where to start. Please help. Thanks.
You can accomplish this by changing the PPU scroll values mid-frame (ideally, in HBlank between scanlines). Say you break the screen into 3 sections:
Code:
----------
Top section, no scroll
(height: 60 scanlines)
----------
Mid section, half scroll
(height: 60 scanlines)
----------
Bottom section, full scroll
(height: remaining (240-60-60 = 120) scanlines)
----------
As you probably, know, each scanline is 113.66667 CPU cycles on an NTSC system, so you can count the number of cycles that need to pass from NMI to know when the PPU is rendering the scanline you want.
To create simple
HORIZONTAL scroll effects, a la Excitebike, SMB, all you need to do is perform the first write to $2005 (second $2005 couldn't hurt, but it doesn't really have any effect) -- and also $2000.0 (for the nametable). You perform these writes at the proper time in the frame to split the screen.
Note that vertical scroll effects are far more difficult and usually require crafty alternating $2006/$2005 writes.
For the above example... say if the screen is supposed to be scrolled 80 pixels... you'd set the scroll to 0 in VBlank... then after 60 scanlines, change the scroll to 40... then after 60 more scanlines, set the scroll to 80.
Splitting the screen in this fashion is most easily done with the support of mapper IRQs (like MMC3's scanline counter). Without any mapper IRQ, the next easiest thing to do is to set up Sprite 0 so it'll hit at a certain time in the frame... then watch $2002 and wait for the Sprite 0 flag to raise. Or... if you have a LOT of free CPU time, you could have a big timed loop which counts the cycles (actually... Balloon fight does this for the Balloon Trip mode -- it doesn't use Sprite 0 hit at all I don't think... it just times it).
Wanting to split the screen TWICE per my previous example would be difficult (or CPU time consuming) without the aid of mapper IRQs... since you can only use Sprite 0 once in the frame.
Parallax scrolling
First use sprite 0 to determine where you are on the raster, by waiting for a bit in $2002 that indicates whether an opaque part of sprite 0 has been drawn overlapping the an opaque part of background (or vice versa). Then use timed writes to $2005 and $2006 (you can get away with a much simpler $2005-only loop if you only want to scroll horizontally).
Okay, this may seem dumb, but when you say 113.666667 CPU cycles per scanline, do you mean per vblank? Okay, I don't think you do, I may sound stupid, because I know what I'm talking about more than it seems, but! I did a short test with the hello world program I made, and! I stored 0 in vbl_count before my endless loop. In my code I said this:
Code:
lda #$00
sta vbl_count
loop:
lda vbl_count
cmp #0
beq keepinc
jmp loop
keepinc:
inc stuff
jmp loop
and stuff ended up being #$72, which is 114! Which is most likely the number you're refering to: 113.666667, just the NES rounds it up because it doesn't do precise decimal things. But am I right about what I said? I think I am, I may be stupid, I don't know.
No, he meant 113+2/3 CPU cycles PER SCANLINE.
If you want the number of cycles per VBLANK, that'd be 2273+1/3.
Okay, this is really dumb, but how are there 2273.33333 scanlines per vblank? Oh man, I sound dumb, but I don't know that much about CPU speeds. It never said in like nestech or anything, so I wouldn't know. Please don't be hard on me, just nicely direct me to the document where this information is present. Thanks....
Celius wrote:
Okay, this is really dumb, but how are there 2273.33333 scanlines per vblank?
There aren't - I made a typo; that should be
cycles per VBLANK.
1 scanline = 1 row of rendered pixels.
also:
1 scanline = 341 PPU cycles
3 PPU cycles = 1 NTSC CPU cycle
therefore:
1 scanline = (341/3)=113.666667 CPU cycles
One NTSC frame consists of 262 scanlines. Constructed in the following manner:
Code:
------------
20 scanlines of VBlank
------------
1 "pre-render" scanline
------------
240 rendered scanlines
------------
1 idle scanline
------------
NMI starts right at the start of VBlank. If you start counting from NMI, and if you want to split the screen in half (after 120 scanlines have rendered), you would have to change the scroll 16027 cycles after NMI:
20 (VBlank) + 1 (pre-render) + 120 (half the rendered screen) = 141 scanlines
141 * 113.666667 = ~16027 CPU cycles
Of course if you can't count that many cycles (if your CPU is busy doing stuff), you can put sprite 0 so it'll hit on scanline 120 and wait for the Sprite-0 flag to raise. When it does, you'll know the PPU is rendering scanline 120 and you can change the scroll so the next scanline will have a different scroll value.
Oh, so you're saying in 341 PPU cycles, 1 scanline is made? I guess the PPU travles at like a million miles an hour. So (this may be stupid) in 170.5 PPU cycles, a half scanline is made? I sound really dumb, don't I. Okay, I need to study sprite 0 hits and hblank and vblank and stuff a little more. Am I really dumb sounding about this whole thing?
To put it roughly:
the first 256 cycles are the PPU rendering pixels
the last (341-256) 85 cycles are HBlank
It's not quite that cut-and-dry, but that's basically the setup. One pixel is rendered on screen for each of the first 256 PPU cycles that pass. After that the PPU loads sprite stuff for the next scanline (your HBlank time), and then loads some tiles for the next scanline.
And no, you don't sound dumb ;P we all had to learn this stuff at some point.
Okay, this is really bad, but I JUST want to make sure. Okay, now this really is a little bit dumb. So when you say CPU cycle, you mean like say, you increment a variable in your endless loop. Everytime it increases, is that a CPU cycle? If not, I am not sure what you mean! And it is very important that I learn! Help!
http://www.obelisk.demon.co.uk/6502/reference.html
NOP takes 2 cycles
LDA immediate takes 2 cycles
LDA zero page takes 3 cycles
etc
Okay, I just made a demo, it doesn't have to do with my partiall scrolling, unfortunately, but I made an interesting demo here, it's on my site, which I have a link to in my signature. Go to tech demos, and download "hello1.zip", because it has a very interesting demo that I was kind of proud of, even though I didn't try to do it, I made something cool happen. I should submit it to pdroms tech demos. Maybe not. I don't know. See for yourself. Tell me if it's cool. Okay so I am going to create my complete full fledged partial scrolling demo later. Right now, I want the top and the bottom of the screen to scroll different ways. So my screen says "Hello, World!" twice. It says it once on the bottom row of the top half of the screen, then it says it once on the top row of the bottom half of the screen. Yeah, I want to say put sprite 0 on the scanline where the bottom "Hello, World!" text is, and then I'd say, dec scrollx, and :
stuff:
lda $2002
and #%01000000
bne lada
inc scrollx
lda scrollx
sta $2005
rts
lada:
rts
But that's what I used in my NMI routine, to get my sprite collide with color demo. Wow, I'm even more proud of that demo than I thought. I could really manipulate that effect to my own good. Okay, but I think I'm getting close, I just need a little more help here. Thank you, this is very helpful!
Just checking sprite 0 flag once won't do -- you'll have to constantly check it and wait for the flag to raise. Additionally... once the Sprite 0 flag is set in the frame... it will remain set until the next vblank is over! So what you'll probably want to do is wait for the sprite 0 flag to go down... then wait for it to go back up. When it goes back up -- that's when sprite 0 hit, and then you can split your screen.
Code:
nmi:
; sprite DMA, set initial scroll, turn on the screen
; all the typical NMI stuff -- do it here
wait_spr0_clear:
bit $2002 ; sprite 0 flag gets put in the V flag
bvs wait_spr0_clear ; if the flag is set, loop until it's clear
wait_spr0_hit:
bit $2002
bvc wait_spr0_hit ; if flag is clear, loop until it's set
; here, sprite 0 has been hit. set your new scroll
I use BIT+BVC instead of LDA+AND+BNE since it's a little faster and does more or less the same job.
Also note that if sprite-0 never hits (like if you place it wrong) -- you may end up in an infinite loop, waiting for sprite-0 to hit when it never will. Remember that to make sprite-0 hit, a non-transparent sprite-0 pixel must be drawn on a non-transparent BG pixel.
that was me -- I got logged out for some reason
Okay! it sort of worked! It's kind of cool, here, see for yourself at my site. It's "hello2.zip". By the way, what do you think of my site? Anyways, I noticed some really bad bugs where it will all of the sudden just stop when the Sprite 0 hit flag is set. This is good! I'm actually learning many things that I have just been having so much trouble with on NES! Like, before, I never used $2002 ever. I was just like "Screw this, just say LDA+CMP+BEQ for everything!" Here's my response to myself saying that "Your a dumbass!" no, I didn't know, so it's okay. But see what you think could be fixed, and see if you can spot that bug! You probably will, when the screen stops moving, and you can't move your player, haha. But I was wondering, do you have to use sprite 0 hits for everything like this? Now I'm really wondering how the hell Cosmic Epsilon and the 3D battles of the World Runner pulled this off... Thanks again!
Celius wrote:
and see if you can spot that bug!
They both seemed to work fine for me.
Quote:
But I was wondering, do you have to use sprite 0 hits for everything like this? Now I'm really wondering how the hell Cosmic Epsilon and the 3D battles of the World Runner pulled this off... Thanks again! 8)
Well like I said it's a lot easier with mapper IRQs. Although games like Rad Racer just count the cycles and change the scroll every scanline (every 113.6667 CPU cycles).
If you wanted to split the screen twice with just sprite 0 hit -- you could watch for sprite-0 once.. change the scroll, then have the CPU loop for 3410 cycles (30 scanlines), then change the scroll again to split the screen again.
Okay, how could I count CPU cycles? Sorry for all the questions, I'm trying to figure things out on my own, but I need to know this...
To give the link again:
http://www.obelisk.demon.co.uk/6502/reference.html
Each instruction takes a certain number of cycles to complete. You can make a bunch of instructions which don't really do anything other than "waste time".. but you have them waste just the right amount of time (however many cycles you want to wait).
For example, the following code will wait for exactly 3 scanlines (counts 341 cycles):
Code:
ldx #$43 ;2 cycles
; each time this loop repeats, it takes 5 cycles.
; it will repeat $42 times ($42*5 = 330 cycles)
; plus it will dex one more time and not take the branch (+4 more cycles)
: dex
bne :-
;2 + 330 + 4 = 336 cycles so far
ldx $00 ; +3 = 339
nop ; +2 = 341
; here is exactly 341 cycles after the start of this code
; (exactly 3 scanlines)
Note that if that bne crosses a 256-byte page boundary (like say it goes from $8101 to $80FE), this timing will be off, since the branch will use up an extra cycle every time that loop continues. So use caution when using branches in timed code.
Oh! Sorry, I forgot about that! Oops! I made yet another cool demo! It's kind of an inacurrate demo. It's just the NMI routine fighting with itself. I say restore scroll at the beggining of the NMI, then I tell it to load <30 into horizontal scroll, then I increment <30. What happens is, the hello world text splits into two "Hello World" text things, and one scrolls horizontal, when the other scrolls verticly! Weird! But yeah, check it out on my site, if you want. Okay, my other demo, with the sprite 0 hit checks, in Nintendulator, if you dink around near the upper W in the hello world strings, it will eventually say "Bad opcode, CPU blocked." But FCEUXD ultra just freezes, because it doesn't really have error messages. I was also wondering how I should set my scroll after waiting for the CPU cycles. And I'm doing this in my NMI, correct? Yeah, I'm pretty sure, because doing it not in your NMI, or in vblank is a bad idea, and is not good at all! It scrolls 114 pixles per second, if you do it that way. But I need to figure this out. I don't think I need to bit test $2002 in any way if I'm doing it in my NMI, and I'm not using sprite hits. I know how to wait CPU cycles, so should I just wait, then set scroll, then wait, then set scroll if I want to have multiple scrolls on the Name Table? and should I be doing this in my NMI? I'm sure I am, but just making sure. Thanks.
P.S. Do you like my site? Just wondering.
Quote:
Okay, my other demo, with the sprite 0 hit checks, in Nintendulator, if you dink around near the upper W in the hello world strings, it will eventually say "Bad opcode, CPU blocked."
Hrm, yeah that's bad. You must be jumping to garbage code or something somewhere.
Quote:
And I'm doing this in my NMI, correct?
You definatly won't be splitting the screen when in VBlank if that's what you mean --- since if you're in VBlank you're not really splitting the screen, you're just setting the scroll =P.
But anyway .. where in your code you do it doesn't matter. It's WHEN you do it that matters. Whatever routine you decide to put this in will work.. as long as you change the scroll at the right time in the frame.
The PPU and CPU run side by side. While the PPU is drawing, the CPU can still do things which change how the screen gets displayed. From the time that an NMI is raised... you have about 2387 CPU cycles until actual pixel rendering starts (20 scanlines of VBlank + 1 prerender scanline -- see my previous diagram of the frame layout). After those 2387 cycles have passed, every 113.6667 cycles that pass is another scanline that has rendered to the screen. To split the screen.. all you need to do is time your scroll writes so that they lie between the scanlines you want to split.
I think you're best bet is to tinker around with more demos until you get a better understanding. I've done about all the explaining I can =).
Quote:
P.S. Do you like my site? Just wondering.
*shrugs* it's a site. Don't know what else to say about it.
Okay, sorry, I'm just having a little bit of trouble, but I've created yet another demo. I realized my other one sucked, it was just increasing the scroll, storing the value in $2005. And I was wondering, how is it going up and over? Oh, wait. I was never storing #$00 in the vertical scroll! So it would just put the value in horizontal scroll, then in verticle, because it was reading it as a second write to $2005, which made it scroll verticly. But I made a firewave type demo! It's really good this time, I promise. It waits 114 cycles (due to know fractions in 6502), and after it waits 114 cycles, it increases the scroll value, then waits 114 more, and decreases the scroll value, creating a firewave! See at my site. I realized that other demo was REALLY DUMB. So I took it off. But I was wondering like how I could do parrallax with such a thing. I would have to do this in my endless loop, but it would scroll very fast! I'm sort of figuring things out as I go in this post. So check out my firewave demo on my site, it's in place of my crappy demo. But yes, how would I increase the scroll slowly, instead of at 114 pixels per vblank? I don't know how! Do you know? It's hard to explain. Any suggestions?
Celius wrote:
It waits 114 cycles (due to know fractions in 6502), and after it waits 114 cycles, it increases the scroll value, then waits 114 more, and decreases the scroll value,
Just to clarify... if it was 114, I would've said 114 -- but it's actually 113.66667 ;P
You can simulate this by waiting 114, 114, 113, 114, 114, 113, etc instead of 114 every time. You could probably get away with 114... but you'll get further and further away from your desired time the further in the frame you get (you could even end up a whole scanline or two off from where you meant to be)
Quote:
But yes, how would I increase the scroll slowly, instead of at 114 pixels per vblank? I don't know how! Do you know? It's hard to explain. Any suggestions?
Erm... I don't know what you mean. If you don't want it to scroll so fast... then don't make it scroll so fast. =P And 114 pixels per vblank? I don't know what you're talking about there.
Sorry, I was kind of doing something when writing that post, and I wasn't very clear at all. Okay, if you increase something in an endless loop, it will increase from #$00 to #$72 by the time NMI is excecuted. Okay, nevermind about that. If you scroll in your endless loop, and not in vblank, it will scroll at unnecisarrily fast rates! and I'm not sure how to slow it down. Any ideas? I know you can't do it durning vblank, because it won't have the same effect, it will just shake the screen, it won't be scrolling at different rates. Yeah, I have to go now. I may make more sense, I may not, I'm not sure. Thanks.
Quote:
Okay, if you increase something in an endless loop, it will increase from #$00 to #$72 by the time NMI is excecuted
Erm... not necessarily. It depends how much CPU time is left until the next VBlank.
You should regulate the speed of your program flow by using the framerate. No matter how much work your program is doing... frames will always happen at a constant rate (one frame every 1/60th of a second) -- so you can use them to manage how much work your game does.
The wait-for-next-vblank loop should probably never do ANYTHING but wait for the next vblank. All motion/logic is usually driven by the framerate.
If you want to scroll 60 pixels per second... just change the scroll every frame. If you want 30 pixels per second, do it every other frame. For 120 pixels per second... scroll 2 pixels every frame. Etc.
Okay, I'm sorry, I'm really trying to understand, I'm just stuck, and I appreciate your help. I don't understand how you can wait for say 16 scanlines, and then scroll those scanlines 1 pixel per vblank! Because waiting for 16 scanlines is definitely not a long wait compared to waiting for vblank. and by the time 16 scanlines have gone by, you need to increment the scroll! and if vblank is not ready by the time 16 scanlines have gone by (which is the case), it won't work. It will increment the scroll of the whole name table, as apossed to seperate scanlines. Yesterday, my mother came home from las vegas, I was helping my brother with something, I was kind of preoccupied, and I needed to finish posting, and I ended up not making sense, and I'm sorry. But do you have any suggestions about doing that? thanks.
Let me introduce you to a wonderful programming concept called "variables"
Variables are these little containers which will hold any number you want, for a long as you want. You can use these variables to store any kind of information you want.. so that you can use the information at a later time.
How can variables help you?
Rather than trying to figure out the scroll as it's time to scroll... figure out what all scroll values you want in VBlank, and write those scroll values to a variable. When it comes time to actually change the scroll, simply load your variable.
For an example:
Code:
; define a variable to hold the scroll for the bottom of the screen
botscroll = $80 ;to be placed at $0080 in RAM
; infinite wait loop -- wait for next NMI
loopforever:
JMP loopforever
; routine called on NMI
nmi_routine:
JSR do_sprite_dma
BIT $2002 ; clear ppu write toggle
LDA #0
STA $2006
STA $2006 ; clear PPU address (clear scroll)
JSR turn_on_screen_and_enable_nmis
; increase our desired scroll by 1 pixel (one pixel per frame)
INC botscroll
; wait for sprite 0 hit
: BIT $2002
BVS :-
: BIT $2002
BVC :-
; sprite 0 is hit -- change the horizontal scroll
LDA botscroll
STA $2005
RTI ; exit NMI routine (return to infinite loop)
If you're still working on basic programming fundamentals such as this -- then I'd suggest staying away from more complicated things like splitting the screen until you're more familiar with the system. Make some simple games... like a tetris clone or something. That'll get you familiar with most of the things you'll need to know for programming.
...-_-.... I can't believe I never thought of that. WHY!? THAT is dumb! Thank you! But I can't believe i didn't think of that! -_-... No, that is really really dumb that I didn't think of that! But trust me, I know what variables are, and I know how to use them. My game on the 2005 minigame compo: The game, and on my site, is pretty much made out of variables. Not a very good thing, because I used pretty much nothing but variables, and that's not very good. I know where I stand in my knowledge of the 6502, thank you very much. I made a good split scroll that works, thank you for your help! I remember in June when I was like following the Gba guy's tutorials, and I was just like "Oh, I understand NES programming". Yeah, um, you learn about .001% of all NES programming aspects there, and that is don't follow his tutorials, haha. And you were explaining scrolling to me, just regular scrolling, and I would copy and paste it into my code, and be like "it doesn't work". And I knew like 2 instructions: LDA, STA. But yes, I actually understand now. I do understand. I'm not lying. I don't have the greatest level of common sense, and I need things to be explained very well to me, as you've noticed. But I'm slowly getting the hang of things. If someone asked me to make a demo for them about something, I would probably be able to. Well, thanks for your help. I really appreciate it! I'll create something really cool and put it on my site! Thanks!
Celius, sometimes I get the impression that you'll do what looks cool on the screen, without actually knowing what you did. When I read your posts, I do understand: "I was trying to do xxxx, but then effect yyyy showd up, and it looked so weird and cool, so I'll leave it like this!"
You have to understand what every piece of code in your games/demos does, our you'll be in trouble sooner or later. NEVER copy and paste code to see what happens. Read the code until you do understand what it is supposed to do, see if it fits to your needs, and code your own version of the stuff. Code posted here is for example, not for direct copy and use.
If you leave stuff in your code just because it "seems" to work, you'll surelly have a lot of trouble when trying to run your stuff on real hardware.
I know you refuse to listen to us and work on simpler projects, but you are not ready for a Final Fantasy remake or something like this, yet. Someday you probably will be, since you are very interested and you practice a lot, ask a lot of questions and such. These are great qualities for a person who is learning about game programming, but please, consider less complicated things for now.
You probably won't listen, again, but at least try to understand EVERYTHING you're doing when programming. Don't do stuff just because it seems to work, ok?
This is the way things were a while ago. I was making a reference to when I first started NESdev, I don't copy/paste code anymore. I try to understand most of what is posted. Dang it, we talk about this in every dang forum I make! I know alot more than I did awhile ago. And I post "I did xxxx, and yyyy happend! Weird!" then I go and try and find out what happened, I don't just leave it like that. And I know what I'm doing alot more than you think, but I probably know what I'm doing a lot less than I think. Can we not mention this anymore? I really hate talking about this in every forum I post in/make! I understand what you're trying to tell me: Slow down, don't try to make complex games yet. I wasn't planning on starting the real game of FFVII yet, I was going to make a tech demo of the first sequence. I'd really appreciate a little more faith in me than I get. I'm sorry, it just puts me down, and makes me feel like I'm never going to be able to make a good game, I'll be making pacman for the rest of my life. No more talking about this. We're done. I got the message. Sorry, don't mean to be an ass.
I don't mean to be an ass, sorry if it may seem so. And I do have faith in you. You learn very fast, you put a lot of dedication in your work and you will probably achieve great things in NES development. But my advice is only that you don't take steps larger then your legs, or you'll miss some important stuff in the way. But, in the end, it is your decision.
Sorry if it seemed like I was saying only bad things about you, it started directed to you but then it got more general, and I even thought about me a while ago, as I was writing my last post.
I know you learned a lot lately, and I believe when you say you don't copy/paste anymore. Your improvements are very clear.
It is just that sometimes the questions you ask tell us you are not ready for some things yet. You say you need detailed explanations and all, but why is that? It's not because you're dumb, 'cause you're not. It's because you don't yet know ANYTHING about what you're asking.
Unfortunatelly, not always we can turn a thread in a classroom and teach you everything from the ground. Sometimes we try, but usually it is just so much new stuff, that it seems impossible to explain it to you in enough detail.
Now you asked about screen splitting, but you didn't know anything about CPU and PPU cycles. You simply shouldn't be asking about screen splitting without knowing that. Then, we try to go through all the steps to get where you want, but sometimes there is so much stuff in between that there is no way to cover everything in a few posts and expect you to understand.
That's why there is an order for learning things. When you understand the basics of timing, then you experiment with screen splitting, that would be a reasonable order to do things.
I'm not trying to teach you a lesson or anything. I get nothing out of it. What you do in here does not bother me at all. When I say these things to you, It may seem I'm playing the role of the annoying dad, who says all that boring stuff but only wants good things for his child. And it may be exactly like this in the end.
We're all here for NES development. Because we love to see new, exciting stuff hapenning in the limited system that is the NES. All I want here is to see the great stuff you'll do for the NES someday, so I try to talk you into doing things in a more ordered way, so you'll actually release quality software in the future.
I too talk too much sometimes, and usually forget where I was going after a few paragraphs.
Well, I'm not trying to bug you. And will try not to say a thing about this subject anymore. Just wanted to explain to you why I do it, and why I insist on doing it. If it is such a bother I'll just shut up.
I didn't mean it like you're the bad guy here, I am very hot headed sometimes. I understand what you're saying, and I'll take it into account. And you don't really bother me, it's just that we talk about this in every forum. If we could just talk about it in one, it would be fine. But I understand your intentions are good, and you mean well. Thank you. And I was a bit of an ass back there. Sorry. Well, I need to learn a little more before I can say I know exactly what I'm doing. If I know I don't know something, I'll either go learn it from a document(probably best), or ask about it here. Like I should've asked about CPU cycles before partiall scrolling.
And I sadly... Need to know more about IRQs, all I know is it's excecuted on BRK. That's really all I know about IRQs. 100% of my knowledge about IRQs was put into that sentence. And I feel bad making these long ass forums, because I think that people don't really like answering 1000 times. That Level Designing forum and the Object Collision forum are unnecisarilly long! I felt bad. I didn't think people were too in to posting 20 times everyday there. I should ask about IRQs in the Newbie Help Center, because that is something you should understand before posting here. Maybe if you could lend me a little of your knowledge here, I wouldn't have to start one. Could you? If you want to, only. Thanks for everything!
IRQs are almost the exact same thing as NMIs. They're both kinds of interrupts.
An interrupt is anything that will "interrupt" your code and force it to jump to another label (without you specifically telling it to in code.. like with a JMP or JSR command). In your programs so far... you have your 'NMI routine' which gets called when an NMI is tripped (every VBlank when enabled). The program jumps here because you give the address to your nmi routine in the vector table (at $FFFA)
IRQs are the same idea. But instead of tripping at the start of VBlank like NMIs do... they trip under different circumstances. The most common type of IRQ is some sort of scanline or cycle timer which a game uses to be interrupted when the rendering has gotten so far in the frame. So then in their IRQ routine, they could split the screen. Without having to constantly check for Sprite 0 hit or count CPU cycles.
For example... MMC3's IRQ counter counts every scanline that passes. So you could tell it to fire after 120 scanlines (if you want to split the screen in the middle) and when those 120 scanlines are up, it'll jump to your irq routine automatically.
The key differences between NMI and IRQ are:
1) They have different vectors. NMI vector is at $FFFA... IRQ vector is at $FFFE
2) IRQs can be completely disabled by setting the I CPU flag (SEI command), but can be re-enabled by clearing I (CLI command). The I flag has no effect at all on NMIs.
3) Only thing on the NES that will trip an NMI is the PPU on VBlank. But There are several things which can trip IRQs (APU frame sequencer, DMC, mapper, etc). If you only want to use one kind of IRQ you must be sure to disable all other kinds of IRQs.
Oh, I get it! Thanks! But I'm a little confused about MMC3's scanline counter, like how do you set something like this up? Like, do you still have to count CPU cycles? Or what? I'm a little confused, but what you just said cleared a whole lot up for me, thank you! So just to get this right, an IRQ is just like the NMI, but it can be enabled/disabled via CLI and SEI, and you can use it for many things such as well, scanline counting, and DMC and stuff like that?
Celius wrote:
Oh, I get it! Thanks! But I'm a little confused about MMC3's scanline counter, like how do you set something like this up?
Well for starters, you have to be using MMC3 (mapper 4). Rather than get into the nitty-gritty details here, I'll link you to this doc:
http://www.tripoint.org/kevtris/mappers/mmc3/index.html
The basic idea is you set the number of scanlines to wait by writing to $C000, the you clear the IRQ counter so it can reload (by writing to $C001). Then you enable mapper IRQs by writing to $E001 (and of course CLI). You also have to be sure to disable APU frame IRQs (by writing $40 or $C0 to $4017), and you should also disable DMC IRQs (write $0x to $4010).
The counter will count down every scanline and when it reaches 0, it will trigger an IRQ. Like... basically you say "hey... let me know when 60 scanlines have passed" -- then the mapper butts in after 60 scanlines with an IRQ. You don't have to rely on sprite 0 or count cycles or anything (though there are some other weird rules you have to follow).
I played with this MMC3 stuff a while back and this is how I did (figured out by watching how kirby did):
In your NMI, you store the number of the scanline you want the IRQ to fire, minus 1, in $C000. Then you write anything to $C001 (it will clear the counter or something), and then write anything to $E001 (will enable the counting I think). You can enable this before the NMI ends, 'cause it will only count when lines are actually rendered. You must now enable IRQ's (do a CLI) so the IRQ will actually fire when it is the time.
Now, in the IRQ section of your code (the code that will run when the counter reaches the scanline you wanted and the IRQ fires), you have to write anything to $E000 (will acknowledge the interrupt, I don't know why this is so important) and then do whatever you want. Change the scroll, turn the screen off, whatever. When you're done, return from the interrupt. If you want the IRQ to fire again in the same frame, you can set it inside the IRQ code as many times as you want, but you'll have to think of a way to diferentiate between the many IRQ's, probably by setting a variable and checking it at the beginning of the IRQ routine.
Oh, and remember to turn sound IRQ's off (you have to write something to $4017, I don't remember what) or it will fire all the f*cking time and make you crazy trying to figure out what's wrong, as happened to me.
I may not be 100% correct here, as I havent played with this stuff in a while, but the process is pretty much it.
You may not need to count cycles when using scanline counters, but I'm not sure at exactly what time the IRQ fires. If it fires at the beginning of the scanline, it is desireble for you to wait 'till hblank to write the value that will change the rendering, so you don't get any graphical glitches.
Do any of you more experienced people know where exactly does the IRQ fire? I see many games with small glitches where the game screen splits into the status bar. I find it a little annoying.
EDIT: to disable sound IRQ's:
Code:
LDA #$40
STA $4017
tokumaru wrote:
Do any of you more experienced people know where exactly does the IRQ fire?
Under normal circumstances... scanline cycle 260 (4 PPU cycles into HBlank) -- of course if you tack on the 7 eaten CPU cycles it takes for the interrupt to complete, you'll be at cycle 281 at the earliest -- so it might be a good idea to have the IRQ trip at least 1 scanline early, then wait for almost one full scanline in your IRQ routine.
And I agree... most MMC3 games were done sloppily/lazily and have that ugly visual distortion *cough*Megaman3*cough*
*shakes fingers with a tsk-tsk at those game programmers*
Okay... so it is a good thing to have the IRQ fire earlier and wait with timed code for a better time (next hblank) to do the writes that will affect rendering.
Why do you say "under normal circumstances" when saying it happens at cycle 260? What are the exceptions?
A bit off-topic but... why exactly does it take 7 cycles to get into the IRQ routine? What happens in the process?
tokumaru wrote:
Why do you say "under normal circumstances" when saying it happens at cycle 260? What are the exceptions?
The MMC3 IRQ counter doesn't really count scanlines, it watches the rising edge of the ?PPU's? A12 line. "Normal circumstances" is when the BG uses the left pattern table (ppu $0xxx) and sprites use the right pattern table ($1xxx) -- in which case the rising edge will happen on the first sprite tile fetch (cycle 260). However if you put sprites on the left and BG on the right, it'll happen at cycle 6 (I think?). Or if you put both BG and sprites on the right pattern table, it may never happen at all. Additionally you can manually toggle the A12 line by alternating $0000 -> $1000 writes to $2006 to make the counter count anywhere.
So yeah... as long as you keep all BG tiles on the left pattern table and all sprites on the right pattern table.. you've got your normal circumstances, and the IRQ will count on 260.
Quote:
A bit off-topic but... why exactly does it take 7 cycles to get into the IRQ routine? What happens in the process?
1 cycle for each (they may not happen in this order):
1) PC high pushed to stack
2) PC low pushed to stack
3) Status pushed to stack
4) Read PC low from $FFFE
5) Read PC high from $FFFF
I don't know what the other two cycles are for... possibly dummy reads?
Disch wrote:
"Normal circumstances" is when the BG uses the left pattern table (ppu $0xxx) and sprites use the right pattern table ($1xxx)
Yeah... I heard of these limitaions before... not such a big deal though, if you still can use 8x16 sprites. Can you?
Quote:
Additionally you can manually toggle the A12 line by alternating $0000 -> $1000 writes to $2006 to make the counter count anywhere.
But that is useless, right? What's the point of firing an IRQ manually? If you know when to do a certain thing just do it instead of using IRQ's... right?
Quote:
So yeah... as long as you keep all BG tiles on the left pattern table and all sprites on the right pattern table.. you've got your normal circumstances, and the IRQ will count on 260.
You mentioned that if you use the pattern tables the other way around, it would happen at cycle 6 (although you're not sure), now *that* may be usefull, as you'll have to wait less for the hblank, so you waste less time just "waiting".
Quote:
1 cycle for each (they may not happen in this order):
1) PC high pushed to stack
2) PC low pushed to stack
3) Status pushed to stack
4) Read PC low from $FFFE
5) Read PC high from $FFFF
I don't know what the other two cycles are for... possibly dummy reads?
I thought it seemed too much... but if these are the rules...!
So you don't really have to do anything different for $8000 and $8001, because it's just specifying the bank # and data section(org, right?). And I was confused about what you have to do with $A000 and $A001, something to do with WRAM(...Okay, this is bad, but what does WRAM stand for?), and you write the counter to $C000, and just a value to $C001, and another value to $E001, am I missing anything? And you say you write to $C000, $C001, and $E001 in NMI? and do a CLI in the NMI? Well, I do a CLI, then store #$5D(120-1) in $C000, and I store random values in $E001, and $C001, and #$00 in $4010, and #$40 in $4017 every NMI. I increment a variable in my NMI that I store in $2005 in my IRQ routine. Am I missing something? because the screen just sits there. Anyone know what's wrong? Thanks!
$8000 and $8001 are related to bankswitching and have nothing to do with the scanline counter. About the CLI, I did what kirby did: IRQ's stay off (SEI) until you set up the counter. Then, inside the IRQ routine it turns IRQ's off again. I guess this is not to risk getting an unwanted IRQ.
I think writing #$00 in $4010, and #$40 in $4017 can be done only once in the beginning of your program, if you're not using these registers. I don't think anything external changes their contents.
About writing random values to $E001 and $C001, just write the same thing you wrote to $C000, it's faster, since you don't have to load any new values. Also, try to keep the order: first write to $C001 and then to $E001.
Then it should work. The IRQ routine should do the following:
-acknowledge the interrupt by writing to $E000;
-write your variable to $2005 (as you're already doing);
-disable IRQ's (SEI), kirby did;
-return from the interrupt (RTI);
And remember to enable NMI's by setting bit 7 of $2000, or your NMI routine will never run.
I guess that's all there is to it...
here is my code, I don't know what's wrong, I read kevin horton's MMC3 page several times, and I read your post several times as well, and I don't know what's wrong with my code. The screen won't scroll in Nintendulator, I open up the dissassembly thing in it, and click IRQ, and it scrolls halfway, and says "Bad opcode, CPU blocked." Then I open it in FCE UXD ultra, and it scrolls to that point and then stops. And I'm not even trying to scroll like that. I'm counting scanlines, and I want it to you know, do what MMC3 does, and it doesn't work. Would you please just look over my code, and see if you see anything wrong? I didn't want to have to do this, but I've been trying this for a while, and it's giving me a headache. Any ideas? thanks.
1. IRQ is an interrupt, so you need to perform an RTI instruction at the end, not RTS.
2. You do not need the "lda #$FF" before "sta $E000", since the value does not matter.
3. If you modify registers in your NMI/IRQ routines, you should save copies of them (and restore them at the end) so as to not disturb any other code that may have been interrupted. The standard way to do this is to start all interrupt routines with "PHA | TXA | PHA | TYA | PHA" and then end them with "PLA | TAY | PLA | TAX | PLA | RTI".
Yeah, the RTS definitly caused many problems. And I set my counter you know? Yeah, it does a regular scroll, but not in Nintendulator. My IRQ routine isn't even running in Nintendulator. And in FCEUXD ultra, it scrolls, and in NESticle(no one cares) it scrolls. But not in Nintendulator. Why? I think maybe my IRQs aren't initiated correctly. I'll look at it, but if you see why, please tell me! Thanks! You guys are so cool!
Celius wrote:
FCEUXD ultra
I know I'm being picky here, but I've been noticing this for a while, and I must speak up. The name of the emu is "FCEUXD". The 'U' in that acronym is what stands for Ultra... so you don't need to put another 'ultra' at the end ;P
It's not working in Nintendulator because you have both the sprite and BG using the left pattern table ($2000 is always 00 or 80). For MMC3 IRQs to work normally, sprites must use the right pattern table and the BG must use the left ($2000.4 must be clear... and $2000.3 must be set)
I'm sorry, you should have spoke up earlier, I didn't realize that, oops... But yeah, I flipped bit 3 on, and yes! it scrolls in Nintendulator! But! Still one more problem. Why isn't it waiting for as many scanlines as I'm telling it to? It's just doing a regular scroll, why isn't it waiting? I know it's really dumb to debug someone elses code, but do know that I really do appreciate it, and I really don't want to plop on my problems with the NES on you guys, so thank you guys so much! Really helful!
Are you writing the scroll only once per frame? Do the scroll write for the upper part of the screen in the NMI, and the lower part of the screen in the IRQ.
Okay, I really didn't want to come back here, and post, but I must. How can you use scanline counters to do like a scroll for the upper 3rd of the screen, have it stopped in the middle 3rd, and scroll another way for the bottom half. I was trying to do this by resetting the counter in my IRQ, but that has no effect. Any ideas? Don't have to go too in to detail, I just want the general idea. Okay? Thanks! I really appreciate it!
You'll have to set up the counter, exactly as you did in the NMI, in the first IRQ too.
But remember there is only one IRQ routine, that will be called once for the first split and once more for the second split. So you'll have to make up a way to tell them apart. I suggest using indirect jumping.
You can set up 2 routines in addition to the IRQ one:
Code:
IRQ:
STA $E000 ;acknowledge the interrupt
JMP (SPLIT) ;jump to the desired split routine
SPLIT1:
;here you do the FIRST change to the scroll registers
;AND set up the second IRQ, as you did with the first in the NMI
;AND update SPLIT to point to SPLIT2, like this:
LDA #<SPLIT2
STA SPLIT+0
LDA #>SPLIT2
STA SPLIT+1
;don't forget to push and pull A if you use it in an IRQ
RTI ;return from the interrupt
SPLIT2:
;here you do the SECOND change to the scroll registers
;if you want, you can set the jump address back to SPLIT1 or you can do it in the NMI, when you first set the counter, wich I think is best
RTI ;return from the interrupt
Oh, in the example, SPLIT is an address in RAM, working as a pointer.
I guess this is it...
EDIT:
Here I wrote something stupid about beeing able to use the same IRQ routine for both splits... but then I realized I was wrong and wrote the following:
Actually... you're not doing the exact same thing... you don't want to set up the counter the second time. Well, you may need to split your code after all....
Oh man! Thank you so much once more! Okay, this question does not have to be answered right now, this isn't why I posted. I was wondering how you could wait half a scanline, and keep half of a row still, and have the other side scroll over it. Do you know? Well, that's not the main reason I posted, I want you to see my parralax demo on my site! I was so proud when I made it. I know you guys will think "Gee, he could have just used the "adc" instruction to do this" when you see that I do things like: inc <30 inc <30 inc <30 or something, but I really wanted to have nothing to do with the carry flag. I really hate the carry flag. But I just wanted to show you guys I understand, and you've been very helpful, and thanks for everything! I hope you like it! Perhaps you could give it a rating from 0 to 10. I really won't be offended if you think it sucks. Just tell me what you think! I really like it, it's cool! to me at least. Okay, well, thanks again! Bye!
Celius wrote:
I was wondering how you could wait half a scanline, and keep half of a row still, and have the other side scroll over it. Do you know?
If you mean split the screen vertically.. the short answer is: you can't.
The only practical way to do it is to use MMC5's split screen feature... and even that is extremely limited in what it can do.
Now... I'm going to get a little mean...but don't take it the wrong way:
Quote:
I know you guys will think "Gee, he could have just used the "adc" instruction to do this" when you see that I do things like: inc <30 inc <30 inc <30 or something, but I really wanted to have nothing to do with the carry flag. I really hate the carry flag.
I really think you should focus more on these basic areas before diving into the more advanced areas you're going in. You're trying to cover too much ground in one swoop. You really need to slow down.
You're asking questions about how to do these things... and when they're answered, you don't understand so you need us to go more in depth. But I don't think the reason you don't understand is because we didn't go in enough depth the first time... it's because you don't have the fundamentals down.
For example... this thread started simple enough -- "how do you do split-screen effects". A reasonable question... and not such a horribly complex topic to go over -- but the thread has expanded to 4 pages and over 50 replies because in order to tell you how to split the screen we've had to go over everything from interrupts, frame layout, CPU cycles, CPU/PPU interaction, RTI vs. RTS, and a dozen other topics... most of which you should probably already have been familiar with.
I know I'm being harsh... and I know you don't like to hear this... but it's true. You really need to slow it down. Touching a little bit of everything and making a demo that looks nifty doesn't mean a thing unless you understand what it is you're doing... and I get the distinct impression that you don't completely understand it.
Seeing you here talking about how your simple "hello world" demos are randomly locking on bad opcodes.. and how you still have trouble with performing simple addition... and then seeing you asking all these questions about such advanced topics... it's no wonder you're having such a hard time with it. You're trying to run before you've learned to crawl.
So yeah... hold off on these tricks. Make some simple games. I know I've said it before.. but I really feel it's what will help you the most.
I really don't mind helping... but if answering a question is going to involve a complete lecture/tutorial on NES workings... even I'm going to get worn out.
Okay, I know I'm going too fast. And just for the record, RTS was a typo, I did not intend for RTS, I know RTI vs. RTS. And in real life I need things to be explained really well. I don't pick up on things REALLY easy. Like if something is slightly vague when explained to me, I will gather the completely wrong information about what was explained to me . Or I'm afraid I will. And what I meant for half the ROW scrolling over the other half of the row, is like those moving wooden platforms on mario 3, you know? But I don't need to learn how to do that yet. I need to do simpler things. I do know how to do simple addition, it's just like the whole CLC thing and SEC thing really get on my nerves. And it just turns out that the C flag is set even if I do a CLC, and I don't know what's happening. So I'm really not as dumb as I seem. Or maybe I am. I don't know. Okay, I'm going to go now.
Celius wrote:
And what I meant for half the ROW scrolling over the other half of the row, is like those moving wooden platforms on mario 3, you know?
Moving platforms in SMB1, flying carpets in SMB2, and moving wooden platforms in SMB3 are sprites.
Celius wrote:
And it just turns out that the C flag is set even if I do a CLC
Now that is just impossible. There is no way that will ever happen. You must be doing something that affects the carry after the CLC. A shift maybe... there is something *seriously* wrong here! Maybe you're changing it in your NMI, something IS happening.
Take some time to study the carry flag. Use a simulator, there is no need to build a whole ROM for that. Play with the simulator and with the instructions that affect the carry for about 15 minutes and you'll understand it all. Trust me.
You absolutelly need to learn these things. You keep dodging from simple stuff just to get to the goooooood part soon. Just learn about the carry flag already. We'll gladly answer any questions you have.
There is no dumb question. Anyone here will be more than glad to answer the most basic of the questions. In fact, I believe it is much more pleasant than going forever with these huge threads.
ps: I'l check your demo soon, I'm in the middle of a major reconfiguration of my system!
Well, thank you tokumaru. I will go play with the simulator! I have a simulator you reccomended, but I'm not to familiar with it. Don't you have to type in a whole bunch of code anyways just to get it to run? I don't know. But yeah, I can't beleive I didn't think about them being sprites on mario 1, 2, and 3! Okay, I'm really not that dumb, I just don't know why I didn't think of that! That is so dumb! Yeah, well, I'm going to go now. I think I'll start on my simplest "big" project now. It's a really simple game I've been putting off. It's like pacman, but a little different. Okay, bye.
Celius wrote:
Don't you have to type in a whole bunch of code anyways just to get it to run?
Not at all. The only thing needed before any code is a ".ORG" followed by an address, so the simulator can have any idea of where to start running the code from.
It is in fact really easy to use. You don't have to set the vectors, or any of these things... just ".ORG" some address and start coding. Of course it supports more advanced and detailed stuff, but they are not required. I use it as an assembler, I do all my dev'ing there, since it can output binary data.
Anyway, it has a nice status window that shows all registers, flags, etc, has a memory window, a step by step option when running the code... I learned a LOT whith it. When I first started I also didn't get the carry and other flags very well, but after playing with the simulator a bit it was all very clear to me.
After you type the code, look for 2 buttons in the toolbar that are next to each other: one assembles the code, and the other debugs it. If there were no assembling errors, the debug button will become active, and you then can press it and see all the debugging windows I told you about, and there is a button in the toolbar that will run the code instruction by instruction so you can really see what's happening.
Hi Celius
I'm writing to tell you I saw your demo. It looks pretty cool, with the many scroll layers it looks pretty 3D. Is the thing by the bottom (I don't know if it's a sidewalk, or maybe rails?) supposed to split in half? It does add some 3D, but also looks a bit weird.
I just got a little scared when I looked at the source code. I remember some time ago, when you used to ask about the "reliable" way of doing things, you seemed to be pretty worried about writing "reliable" (you used that word a LOT! hehe) code. But your demos seem to have a lot of unreliable details in them.
For example, someone in this thread told you to push/pull the registers you modify in the NMI and IRQ routines (as both fire off at unexpected times during code execution, and simply changing these registers may screw up the logic when the routine ends). You did this in the NMI right, you saved all 3 registers and restored them at the end. But in the IRQ you saved only register A and right after that you destroyed X. Couldn't you just have done the compares using only the A register?
In your demo this will not create big problems, since your NMI code is small and the IRQ will hardly fire off while other code is running besides your infinite loop. However, when projects get bigger, code will be interrupted in favour of IRQ's, you'll destroy registers you were using, the thing will screw up and you'll not know why. And you'll get very frustrated, kicking your PC 'cause you don't know what's going on.
You understand the concept of NMI's and IRQ's, right? You understand that other code is interrupted for them to run? And that when you exit these routines you must leave everything as it was when the routine started, so that the code that was interrupted can continue to run normally?
Well, I got a bit offtrack here, but, back to the subject, the demo looks cool. I hope to see more nice effects using split screens comming from you. Keep it going.