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

help with NMI

help with NMI
by on (#45661)
Ive made a program that shows words when you select them with a cursor, all the words are in the background and are set in nametable0 so when i select one it knows thanks to some code which word is it and where it is set on screen, i turned off the interrupt(NMI) to refresh the other NT(nametable1) with the word selected, then switch tables and reactivate the NMI. i proved it with the fceuxdsp and it worked quite well, but when i tested it on hardware (i already modified a nrom cartridge to work with eproms and it works fine, the problem is not here) or another sim (nintendulator, nestopia), the words are cut some parts like the time it needs to refresh is too much, i dont know what else to do. here is the complete source code with an assembled version. please help me, im very confused.
the source: http://onexus.110mb.com/proy_v1.zip
hint, maybe: i think the vblank is too short for all the process the program does,smaybe is that but i dont know what to do about it. ideas and modification of the source are always welcome, if anybody needs the .chr just extract it from the .nes

by on (#45662)
seems to work in pal mode (to some extent it always flashes one word for me not the word I selected, not sure if thats supposed to happen) but ya you're probably doing too much in vblank.

by on (#45663)
to see how it should work just run it with fceuxdsp emulator.

by on (#45667)
For one thing, get your controller reading out of NMI.
NMI code should be about rapidly consuming data you build into buffers during rendering time.

Or do it the easy way and turn off the screen before you write your data there, then turn it back on when the next frame begins.

by on (#45670)
i thought i was doing that!, why do you think im not?, maybe i'm doing that wrong, please somebady tell me.
testing in nestopia made me realized that the program is in pal mode, how can i change that?

by on (#45671)
You gotta shorten your vblank time. Thats the only solution.

by on (#45672)
Ok, there's something i'm not getting right, if i turn off $2000 and work that way, does that also count as vblank time?, why?

by on (#45673)
You can force blanking by writing zero to $2001, but only do that when rewriting the entiere screen or if you're doing heavy updates that cannot be done any other way.

Otherwise, you'd just want to update your stuff at VBlank time (at the start of the NMI routine) and make sure it does take shorter than the VBlank time.

by on (#45676)
then, the problem is mainly that i need to do a lot of operations once certain button is pressed, refresh a part of a nametable, and change the values of some memory space. how can i change my code so that it can be able to do that and still being at ntsc vblank time. anyone a clue? hint? idea? anything .. maybe source code(lol)

by on (#45678)
you need to set up a buffer to handle the output to the name table. then when the nmi fires you empty the buffer. everything besides screen display stuff can be handled at other times

i can't give you any code because i haven't written my buffer routines either lol

by on (#45682)
Here's the outline of how an NES game's main loop looks:
Code:
forever:
  jsr readPads
  jsr gameLogic
  jsr updateSound
  jsr prepareVRAMData
  lda retraces
  @nmiloop:
    cmp retraces
    beq @nmiloop
  jsr copyVRAMData
  jmp forever

nmi_handler:
  inc retraces
  rti


The prepareVRAMData subroutine should draw all sprites to an OAM buffer in main RAM and fill another buffer that gets copied to VRAM (nametables, palette, pattern tables if using CHR RAM). Then copyVRAMData copies the data to the PPU. You can usually squeeze up to 160 bytes of VRAM updates (possibly one row, one column, one palette, and four tiles) if you unroll the loop moderately. The tricky part is figuring out how to set up your buffer inside prepareVRAMData so that copyVRAMData doesn't have to think, just copy.

The OAM buffer in main RAM is commonly placed at $0200-$02FF and copied with a write to $4014. Lately, I've been using part of the stack that I don't otherwise use ($0100-$019F) for the VRAM copy buffer.

Some games, such as Super Mario Bros., run as NMI handlers. Their main loop looks different but has mostly the same structure:
Code:
nmi_handler:
  jsr copyVRAMData
  jsr readPads
  jsr gameLogic
  jsr updateSound
  jsr prepareVRAMData
  rti

by on (#45691)
Here's a general outline of what happens in my NMI and main loop routines:

Code:
NMI:
   pha
   txa
   pha
   tya
   pha

   jsr PPUUpdates
   jsr APUUpdates

   inc VBLCount

   pla
   tay
   pla
   tax
   pla
   rti


Main:
   jsr HandlePlayer
   jsr HandleAI

   jsr HandleSoundEffectCompetition

   jsr HandleScrolling
   jsr DrawSprites

   lda VBLCount
-
   cmp VBLCount
   beq -
   jmp Main


My main loop doesn't look exactly like that, but pretty close. The first routine does stuff based off of the players input with the controller (It also reads the controller in this loop). Since my game is a platformer, this routine moves the main character, allows him to attack, etc. depending on the state of the controller. The second routine handles all of the AI (assuming there are enemies or you are playing against the computer). Based on what decisions the player made, the AI for other objects will do what they do. For example, in my game, the player might jump, and the AI for a skeleton will see this and attempt to throw a bone or something at the character. All that is handled in this routine.

In both the player and AI handling code, the player and other objects might do something that would cause a sound effect to happen. The HandleSoundEffectCompetition routine decides which sound effect will take place, as one will dominate over the others.

The next routine will determine if/how much the screen will need to be scrolled over, and what will need to be written to the background once it is. This does NOT write to the background; it prepares data for the NMI to write to the BG, once it gives the NMI permission.

Then the next routine takes all of the coordinates of enemies, the player, and whatnot and draws them in their appropriate location in the OAM page. This does not perform an OAM transfer! That is performed in the PPUUpdates routine in the NMI routine. An OAM transfer is not performed in the NMI unless this sprite drawing process is complete, and the sprite page has been completely updated.

So basically the first two loops take care of all the game logic that happens (characters moving, enemies moving, shooting, killing, etc.), then the next loops prepare communication data (anything that will come out of a TV). The NMI will seal the deal, communicating everything to the player with sound and visuals.

The sound is handled in the NMI every frame, because there is very little game logic involved with sound handling (besides sound effects, which are prepared in the main loop). This is so no matter what, music can play at a constant frame rate. Sound effects will also be played at a constant frame rate, just the rate at which they are requested is dependent on how long the Main loop takes to execute (an integer number of hardware frames. Usually 1, but sometimes 2, hopefully never 3).

I like to think of the NMI routine as the routine that translates what the NES is trying to say to the player, and the Main routine is the routine that translates what the player has to say to the NES. Now, of course it isn't THAT simple, but mainly that's what purpose those routines serve.

by on (#45696)
ok, i got it, but i tried the program without any nmi handling, only with the first background setting, this i did turning off and on $2000, and it doesn't work either, so there is a problem with writing so much data into screen, regardless of the moments you do it, even during setup.
anyone can tell me exactly what exactly happens when $2000.7 is cleared?, i thought you could write anything and modify the background as long as you wanted without a problem, but i was wrong because it appears to appears problems when the setup lasts too long.

by on (#45714)
The other way to do drawing...

You need NMI. You can't get away without it. Otherwise you can't wait for vblank.

Make your NMI handler two instructions long:
inc vblank_variable
rti

Turn off the screen by writing #00 to 2001
Do your drawing

Wait for vblank:
lda #0
sta vblank_variable
waitloop:
lda vblank_variable
beq waitloop

Then turn the screen on by writing #$18 to 2001

Then write to the PPU scroll registers. This is not optional. Otherwise it scrolls based on the last address you set when you did your PPU drawing.

by on (#45730)
euler271 wrote:
anyone can tell me exactly what exactly happens when $2000.7 is cleared?

When you turn off bit 7 of PPUCTRL, the PPU hides its vblank NMI signal from the CPU. That's all. To actually be able to write to VRAM at any time, you need to force blanking. You do this by writing 0 to PPUMASK ($2001).