How do I use Vblank?

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
How do I use Vblank?
by on (#189626)
For the game I'm designing I need to use the Vblank bit, but I don't exactly understand how I'm supposed to access it. Is it a memory address? I don't know.
Re: How do I use Vblank?
by on (#189635)
Vertical blanking (vblank) is the period between frames, during which your program can write to video memory through the PPU. It lasts about 2,270 cycles, after which point the rendering logic takes control of video memory again for the next frame. To be notified that vblank has occurred, you need to do three things:

  1. Make an NMI handler, which is a special kind of subroutine that ends with RTI instead of RTS.
  2. Set the vector at $FFFA to point to the NMI handler. Exactly how this is done depends on the assembler you're using.
  3. Set bit 7 of PPUCTRL ($2000) to 1. If bit 7 of PPUCTRL is 1, the CPU will call the NMI handler at the start of every vblank.

The NMI handler usually sets a variable in memory notifying the main program that vblank has occurred. There are two schools of thought on how to organize a game loop:

  1. The main program waits for the variable to change and then proceeds to update video memory.
  2. The main program sets a variable that the updates have been prepared in video memory, the NMI handler does the video memory updates and clears the ready variable, and then the main program waits for the variable to change before beginning the next frame of game logic. This method resembles multithreading, and it's more stable if you're using a raster split such as a status bar.

Which assembler are you using? Is it NESASM, asm6, ca65, or something else?
Re: How do I use Vblank?
by on (#189679)
I don't know, none of the assemblers I've ran into ever seem to work.
Re: How do I use Vblank?
by on (#189683)
What was the problem when you tried to use these assemblers?

In a previous thread, it seemed like you were trying to double-click on the assembler executables. If that's what's happening, you need to learn all about the command-line and how to use command-line applications. Read this - http://www.makeuseof.com/tag/a-beginner ... mand-line/ Once you're done with that, you need to "cd" to the directory that has the assembler and run a command such as:

Code:
nesasm3 code.asm


Where "code.asm" is a source file that contains your assembly code.

These steps cannot be skipped. Otherwise, you'll never get anywhere, let alone close to using vblank.
Re: How do I use Vblank?
by on (#189684)
DementedPurple wrote:
I don't know, none of the assemblers I've ran into ever seem to work.

Well, they do work, because many people are making games with them... But they're not magical, you have to know what you're doing. :wink:

Anyway, don't ever use the vblank bit ($2002.7) to control the frame rate of your games. The PPU has a bug that causes a false negative to be returned in that bit if the register happens to be read at the exact time vblank starts, causing the program to constantly miss vblanks. The reliable way to detect the start of the vertical blank is with NMIs. The NMI is an interrupt that reliably fires every time vblank starts, letting you know it's safe to perform video updates for the next frame.
Re: How do I use Vblank?
by on (#189727)
Okay then, so how would I use the NMI?
Re: How do I use Vblank?
by on (#189728)
tepples explained one way to do it.
Re: How do I use Vblank?
by on (#189739)
Make sure you understand the basics.

Nerdy Nights covers most of it pretty good, although it's not perfect:
http://nintendoage.com/forum/messagevie ... eadid=7155
Re: How do I use Vblank?
by on (#189742)
Doesn't Nerdy Nights use the less-than-ideal "everything in the NMI" model?
Re: How do I use Vblank?
by on (#189774)
Yep, so it's not perfect (and it also has a few other flaws). But I mean it teaches other things, like working the assembler, I think.
Re: How do I use Vblank?
by on (#189783)
Nerdy nights isn't perfect but there isn't a lot of other choices.
Re: How do I use Vblank?
by on (#191016)
I can't find a tutorial anywhere that mentions the address of NMI
Re: How do I use Vblank?
by on (#191018)
It's like the reset address: you set it in the last six bytes of your game.
Re: How do I use Vblank?
by on (#191019)
How do I do that?
Re: How do I use Vblank?
by on (#191020)
What assembler are you using?
Re: How do I use Vblank?
by on (#191021)
The NMI vector is at $FFFA. From "NMI" on NESdev Wiki:
When the CPU checks for interrupts and find that the flip-flop is set, it pushes the processor status register and return address on the stack, reads the NMI handler's address from $FFFA-$FFFB, clears the flip-flop, and jumps to this address."

Thus the behavior of the CPU when servicing NMI is similar to the following pseudocode:
Code:
push pc>>8
push pc & #$FF
php
jmp ($FFFA)


Thank you for prompting me to check "CPU interrupts" on NESdev Wiki and discover it was missing information.
Re: How do I use Vblank?
by on (#191023)
Ok, let's talk about what these are doing

.base $8000
RESET:

(code here)

NMI:
IRQ:
rti

;Vectors
.org $FFFA
.dw NMI
.dw RESET
.dw IRQ

######################

As the assembler goes line by line, converting ASM into machine code, it is also counting. BASE $8000, tells it to start counting at $8000. Why is it counting? So we can use Labels in our code.

The first thing the assembler sees after BASE $8000 is the label RESET. It assigns the value $8000 to the label RESET. Now, any time the assembler sees the word RESET, it replaces it (in the machine code) with the value $8000 (actually $00, $80, little endian style)

The advantage here, is, you should be able to move code around in your file, but the assembler should still be able to reassign a new value to RESET, if needed. Let's say that you wrote some code between the BASE $8000 and the label RESET. Now when it counts, it might be at $823c by the time it sees the RESET label... and would then replace all uses of that label with $823c.

The lines past .ORG FFFA, are addresses. DW = 2 bytes. The assembler will see the label for NMI, and replace that with the address of the NMI label. But, the important part, is the NMI label can be anywhere. You don't have to calculate its position and insert it at FFFA. The assembler will do that for you, if you put .DW NMI here.
Re: How do I use Vblank?
by on (#191090)
Would it be like in C# where you use the below code
Code:
int main()
{
NMI();
return 0;
}
static void NMI()
{
// put NMI code here
}

But instead of the NMI function you use BASE($8000)? And also, would I just use the command
Code:
bit $FFFA
to test the state of the NMI?
Re: How do I use Vblank?
by on (#191102)
$FFFA is a ROM location. Doing bit $FFFA will not read the state of the PPU.

The state of the NMI generator is at $2002, but there's a race condition associated with reading it. Normally you want to make $FFFA point at a routine that modifies a variable in RAM, then make your main program wait for that variable to change.

If you want an analogy to high-level languages, then $FFFA, $FFFC, and $FFFE contain hardcoded function pointers to listeners for NMI, reset, and IRQ events respectively, and the NMI and IRQ listeners run in a separate thread with priority over the main thread.
Code:
volatile unsigned char nmis = 0;
volatile unsigned char display_list_ready = 0;

int main() {
  RegisterNMIListener(NMI);
 
  // Omitted: init code
  while (!gameover) {
    PollControllers();
    GameLogic();
    PrepareDisplayList();
    display_list_ready = 1;
    vsync();
  }
}

static void vsync() {
  unsigned char oldnmis;
  while (oldnmis == nmis) { }
}

static void NMI() {
  nmis += 1;
  if (display_list_ready) {
    BlitOAM();
    BlitVRAM();
    display_list_ready = 0;
  }
}

In 6502 assembly language, you RegisterNMIListener() by putting a function pointer at $FFFA.
Re: How do I use Vblank?
by on (#191106)
What is a function pointer, and second, what does BASE $8000 and labels have to do with testing a bit? can't you just use a bit command? I don't understand why having the reset label be in $8000 would help test the state of the NMI.
Re: How do I use Vblank?
by on (#191109)
Base $8000 and Labels have nothing to do with testing a bit. They are instructions for the assembler to generate code with correct addresses.

The memory map of the NES has the program ROM located between $8000 and $ffff. That's why we (have the assembler) start counting bytes from $8000.

The last 6 bytes of the program ROM are special addresses, the starting address of NMI, the starting address of RESET, and the starting address of an IRQ (and BRK).

Writing the code like I did, will ensure that when the ROM is assembled, the last 6 bytes will contain the correct location of these functions.

These addresses POINT to a function. They are function POINTERS.
Re: How do I use Vblank?
by on (#191110)
DementedPurple wrote:
What is a function pointer

"Function pointer" is the term used in C and C++ for something akin to what C# calls a "delegate".
Re: How do I use Vblank?
by on (#191119)
Just to be clear, I was talking about RESET vectors as an example of how to set up a functional ROM, with a functioning NMI.

From tepples example earlier...

    1. Make an NMI handler, which is a special kind of subroutine that ends with RTI instead of RTS.

    2. Set the vector at $FFFA to point to the NMI handler. Exactly how this is done depends on the assembler you're using.

    3. Set bit 7 of PPUCTRL ($2000) to 1. If bit 7 of PPUCTRL is 1, the CPU will call the NMI handler at the start of every vblank.

I was explaining part 2. Set the NMI vector to point to the NMI handler. I guess I was also explaining part 1...the NMI handler itself is everything between the label NMI: and the RTI.

To USE the NMI, you do part 3, and the PPU will generate an NMI Interrupt at the start of every v-blank, and your code will AUTOMATICALLY jump to the NMI code at this time (which happens 60 times a second).

EDIT, the NMI code should terminate in RTI. At which point it will return to your regular code.
Re: How do I use Vblank?
by on (#191234)
Okay, so would do the BASE $5000 but instead of $5000 use $FFFA?
Re: How do I use Vblank?
by on (#191236)
BASE changes the PC instantaneously, while ORG fills the ROM with the fill value up to the specified address. For this reason, BASE has to be used carefully, or you might end up with an invalid ROM. Normally you'll use BASE to switch to a different addressing space (from bank to bank, from PRG to CHR, etc.), but when advancing within the same space you probably want to use ORG.
Re: How do I use Vblank?
by on (#191237)
Don't mix up assembler commands and the actual program. Things like BASE and ORG are used only by the assembler, to know where to put the code in the ROM and what addresses to use for labels, variables and such. The program running in the CPU doesn't care or know about any of that, it just needs everything to be in the correct places.
Re: How do I use Vblank?
by on (#191279)
So what would I do if I wanted to put my game on an actual NES cartridge?
Re: How do I use Vblank?
by on (#191280)
If you mean put it on an NES cartridge for testing, the PowerPak and EverDrive N8 are the most common choices for this. These adapters take digital camera memory (CompactFlash or Secure Digital respectively) and plug into an NES Control Deck.

If you mean put it on an NES cartridge for distribution to the public, you have a few options: DIY kits from RetroStage, preassembled user-flashable carts from Infinite NES Lives, or having RetroUSB or Infinite NES Lives manufacture and sell the things for you.
Re: How do I use Vblank?
by on (#191281)
Would I be able to use the BASE command on an EPROM cartridge? What equivalent would I have?
Re: How do I use Vblank?
by on (#191284)
What is this thing about BASE? BASE is something you use to structure the ROM correctly. If you already have a working ROM, you don't need anymore BASE statements. If you put anymore of those you'll probably break the ROM's structure. Why are you insisting on that? What exactly are you trying to accomplish with it?
Re: How do I use Vblank?
by on (#191288)
Sorry, I thought the BASE statement was something like a .db statement that wouldn't work on the actual hardware. If I were to write the binary file to an actual EPROM, would the NMI code appear in that section?
Re: How do I use Vblank?
by on (#191290)
.db reserves space in memory (RAM or ROM, depending on usage and the assembler), and it works just fine on hardware.

Generally speaking, anything in a compiled .nes file that runs in emulator will work the same on hardware when burned on an EPROM. There are tons of exceptions (buggy emulators, startup state, improper mapper usage, lua scripting), but generally speaking the features are the same, especially when talking about how assemblers work.

NMI code will appear in the NES hardware's address space the same way it will in an emulator, regardless of whether you use .BANK for linker relocation, or .SEGMENT directives, or whatever.
Re: How do I use Vblank?
by on (#191292)
The .nes file you run on emulators contains the exact same binary data you burn on EPROMs to run on real hardware, you don't need 2 separate builds when coding the program. To burn EPROMs you just need to take the .nes file and use a tool or an hex editor to split it into header, PRG-ROM and CHR-ROM, and then burn the PRG and CHR to different chips.
Re: How do I use Vblank?
by on (#191293)
Then how would the actual NES know where to go when the NMI is true?
Re: How do I use Vblank?
by on (#191294)
Tepples mentioned this earlier. The address at $FFFA contains the NMI "vector", which is just an address that points to the NMI function. So for example, if your NMI function gets compiled such that it is in the ROM at $8123, then the value at $FFFA is $23, and the value at $FFFB is $81 (little-endian order swaps the bytes of the address). You maybe were confused and thought that the vectors were "metadata", like the 16 byte header at the start of the ROM, but they're not. They exist in the binary even when on hardware.
Re: How do I use Vblank?
by on (#191340)
If the NMI vector is at $FFFA, would that mean I'm limited to only 4 bytes of NMI code?
Re: How do I use Vblank?
by on (#191341)
The vector $FFFA can point anywhere in ROM or RAM.

The vast majority of Super Mario Bros., for instance, is its NMI handler. That's because it's structured roughly like the following pseudocode:
Code:
main() {
  setup_hardware();
  init_globals();
  enable_nmi();
  for (;;) { }
}

__interrupt__ void nmi() {
  PUSH_ALL;
  if (vram_data_ready) {
    copy_data_to_vram();
    vram_data_ready = 0;
  }
  read_controllers();
  game_logic();
  prepare_data_for_vram();
  vram_data_ready = 1;
  POP_ALL;
}
Re: How do I use Vblank?
by on (#191343)
Nothing at fffa but an address.

When the processor gets an Interrupt signal, it will jump somewhere. Fffa tells it where to jump.
Re: How do I use Vblank?
by on (#191346)
DementedPurple wrote:
If the NMI vector is at $FFFA, would that mean I'm limited to only 4 bytes of NMI code?

Think of it as a function pointer.
Re: How do I use Vblank?
by on (#191348)
DementedPurple wrote:
What is a function pointer
Re: How do I use Vblank?
by on (#191349)
DementedPurple wrote:
If the NMI vector is at $FFFA, would that mean I'm limited to only 4 bytes of NMI code?

No the interrupt vectors just points to your interrupt handlers. They don't contain any code, just addresses (addresses are 2 byte each). The interrupt handlers contains the code.

The templates you are using looks something like this:
Code:
;iNES header here

.org $8000

RESET:
;init code here
main:
  ;main loop code here
  jmp main

NMI:
;nmi code here
  rti

IRQ:
;irq code here
  rti

;Interrupt vectors:
  .org $FFFA
  .dw NMI
  .dw RESET
  .dw IRQ

You use labels like "RESET" so you don't have to hard-code the addresses for your interrupt handlers. Of course when the ROM is assembled the vectors will contain constant addresses, but that is the assemblers job to solve. Instructions like .org and .base is for the assembler to know where in the output file everything should be placed. The reason we start at $8000 (or $C000 if ROM is small) is because $8000 is where the ROM area starts in the address space. If we would use .org $0000 as start address, the interrupt vectors (if we don't change their start addresses) would be in the wrong place and the assembled ROM would become too big. It makes more sense to start counting from a number that matches the start address in the ROM area in the address space.

The order of the three interrupt handlers (RESET, NMI and IRQ) doesn't matter, they can be anywhere in the ROM (or RAM as Tepples said). Just make sure that NMI and IRQ handlers ends with an RTI instruction, and the RESET handler ends with an endless loop.

The order of the interrupt vectors DO matter however.
Re: How do I use Vblank?
by on (#191350)
Think of it as the NES doing something similar to
Code:
JSR $FFFA
every time there is an NMI. That's not literally what's going on, but the point is that $FFFA stores the address of your NMI routine, and the 6502 reads that memory to find where to jump. We say that $FFFA points to a location in memory.
Re: How do I use Vblank?
by on (#191352)
So it goes to the NMI code automatically, no matter what the CPU is doing. So if I where to have a loop like the one down below:
Code:
example:
jmp example

would it still go to the NMI? And also, does the NROM template have that built in? Also, could I just do BASE $FFFA before the header?
Re: How do I use Vblank?
by on (#191355)
DementedPurple wrote:
would it still go to the NMI?

Yes, as long as NMI generation is enabled in the PPU (bit 7 of register $2000), which you normally do at the end of the initialization code.

Quote:
And also, does the NROM template have that built in?

All mappers behave the same way in regard to NMIs.

Quote:
Also, could I just do BASE $FFFA before the header?

Again, what's with this BASE stuff, man? No more BASE, forget about BASE. Specially before the header, that makes no sense whatsoever. Just out of curiosity, what would you be trying to accomplish with a BASE before the header anyway?
Re: How do I use Vblank?
by on (#191358)
I don't know, I think that a BASE command is like a header, I honestly have no idea. How would the NES know where $FFFA is?
Re: How do I use Vblank?
by on (#191359)
DementedPurple wrote:
I don't know, I think that a BASE command is like a header, I honestly have no idea. How would the NES know where $FFFA is?


The 6502 processor, as well as the derivative 2A03 processor in the NES, is hard-coded to look to $FFFA, $FFFC, and $FFFE for the three vectors. When it boots up (reset state), it immediately does a JMP ($FFFC) effectively, and that's how your program begins. Whatever you put in $FFFC tells it where to start execution.

I don't know what BASE is - it may be related to the assembler you're using, or whatever - but there's nothing linking it to 6502 programming intrinsically.
Re: How do I use Vblank?
by on (#191361)
OK, let's look at what happens in my simple NROM example:

First we have the variables. To set their address we use ENUM, which is an ASM6 command that temporarily sets the program counter without generating any output for anything inside the ENUM block. This is great for variables because they live in RAM, and an .NES file only contains ROM, so the goal is to define the addresses of all variables, without storing anything anywhere.

Then comes the header. The header must be written to the output file, but since it's never mapped to any address space (it's only used to give the emulator or Flash cart information about what to do with the rest of the file), we don't even need to set an address, just output the header bytes.

Then the PRG-ROM starts. To let the assembler know where in the CPU space the code that follows will be mapped to (so it can calculate addresses for labels and stuff), we use BASE, another ASM6 command. BASE simply sets the program counter to whatever value we want. Since we know that PRG-ROM on the NES starts at $8000 (or $C000 if you only have 16KB of it), we use BASE $8000.

Then comes all of the code, and normally we don't need to do any address manipulation in this part. We just write the code and let the assembler update the program counter accordingly.

Now we need to write the interrupt vectors to the output file. Those must be at $FFFA, always, because that's where the CPU will look for them. So now we use ORG $FFFA (ORG is more universal, and works mostly the same in any assembler). Why ORG and not BASE? Because ORG pads the ROM with zeroes (or another value of our choice) up until the specified address. We need that because we don't know how big our code is, so we need this padding to make the ROM size correct. BASE doesn't pad anything, so if you had only 6KB of code and used BASE, your PRG-ROM would end up being 6KB large, instead of the expected 16 or 32KB.

For NROM we don't really need to use BASE... that command is only necessary when we have to roll back the program counter, which normally only happens when we use bankswitching. I only used BASE in the NROM example for consistency with the other templates, that have multiple banks. For NROM ORG would have worked fine, because ORG behaves exactly like BASE if the program counter hasn't been set yet.
Re: How do I use Vblank?
by on (#191365)
DementedPurple wrote:
So it goes to the NMI code automatically, no matter what the CPU is doing. So if I where to have a loop like the one down below:
Code:
example:
jmp example

would it still go to the NMI?

Sounds like maybe you are not fully familiar with interrupts yet? When an interrupt happens, the program counter is interrupted in the code, no matter where it currently is, and then it jumps to the interrupt handler (the NMI handler in this case). It will then run the code from the start of the interrupt handler until it runs into an RTI instruction. When it does, it will jump back to wherever it was before the interrupt happened.

Quote:
does the NROM template have that built in?

As long as the interrupt vectors are in the correct place the CPU will be able to find the address for the three interrupt handlers, so yes it does have this built in. The three vectors are a minimal requirement for a 6502 program to work at all I think, it's not NES-specific.
Re: How do I use Vblank?
by on (#191366)
DementedPurple wrote:
So it goes to the NMI code automatically, no matter what the CPU is doing. So if I where to have a loop like the one down below:
Code:
example:
jmp example

would it still go to the NMI?

Yes. Super Mario Bros. does exactly this. It sets up the hardware and game state and falls into an infinite JMP loop, and then the entire game runs inside of the NMI handler.

DementedPurple wrote:
And also, does the NROM template have that built in? Also, could I just do BASE $FFFA before the header?

As far as I can tell, BASE isn't very useful unless you're trying to copy code to RAM, such as for self-modification or to keep code available no matter which 32K bank is switched in.

mikejmoffitt wrote:
When it boots up (reset state), it immediately does a JMP $FFFC effectively, and that's how your program begins. Whatever you put in $FFFC tells it where to start execution.

I thought it was JMP ($FFFC), which reads the program counter from $FFFC instead of actually looking for an opcode at $FFFC.
Re: How do I use Vblank?
by on (#191367)
tepples wrote:
mikejmoffitt wrote:
When it boots up (reset state), it immediately does a JMP $FFFC effectively, and that's how your program begins. Whatever you put in $FFFC tells it where to start execution.

I thought it was JMP ($FFFC), which reads the program counter from $FFFC instead of actually looking for an opcode at $FFFC.

As far as I can tell, BASE isn't very useful unless you're trying to copy code to RAM, such as for self-modification or to keep code available no matter which 32K bank is switched in.


You are right. That is what I meant, I'll fix it.
Re: How do I use Vblank?
by on (#191368)
tepples wrote:
As far as I can tell, BASE isn't very useful unless you're trying to copy code to RAM

Or if you have more than one PRG-ROM bank, in which case you have to move the PC from the end of the bank back to the beginning ($C000 back to $8000 in the case of UNROM, for example). Another option for multiple banks in ASM6 I'm aware of is to assemble each bank separately, but that can get really hard if the banks reference each other a lot.
Re: How do I use Vblank?
by on (#191369)
Good point, if you're just using it like .bank in NESASM or .segment in ca65.
Re: How do I use Vblank?
by on (#191379)
tokumaru wrote:
Another option for multiple banks in ASM6 I'm aware of is to assemble each bank separately, but that can get really hard if the banks reference each other a lot.

That is what I do for the FDS mapper, I couldn't figure out another way that would work.
Re: How do I use Vblank?
by on (#191392)
I'm sure BASE would've worked.
Re: How do I use Vblank?
by on (#191473)
I tried with base, but I think I probably didn't understand how it worked back then. I might try it again.
Re: How do I use Vblank?
by on (#191634)
Also, is there a way to know when the NMI is not true?
Re: How do I use Vblank?
by on (#191635)
It depends on what exactly you mean by "know[ing] when the NMI is not true". What would "know[ing] when the NMI is not true" tell you? Are you trying to wait for the end of vertical blanking?
Re: How do I use Vblank?
by on (#191637)
tepples wrote:
It depends on what exactly you mean by "know[ing] when the NMI is not true". What would "know[ing] when the NMI is not true" tell you? Are you trying to wait for the end of vertical blanking?

Yes.
Re: How do I use Vblank?
by on (#191640)
The only reliable way to know when vblank ends is to wait for the sprite overflow or sprite 0 hit flags to be cleared (which happens near the end of vblank), but you have to make sure they were set in the previous frame to begin with. May I ask why you'd need this information? Most people just optimize their vblank handling code so it never extrapolates the allowed time.
Re: How do I use Vblank?
by on (#191642)
I'm afraid that my Vblank my be too short, and it'll finish early and go to the part where it will read the controllers and then you will be still pressing the right arrow and the x-position will be increasing too fast.
Re: How do I use Vblank?
by on (#191644)
DementedPurple wrote:
I'm afraid that my Vblank my be too short

That's not a problem, don't worry.

Quote:
and it'll finish early and go to the part where it will read the controllers and then you will be still pressing the right arrow and the x-position will be increasing too fast.

People will normally hold buttons for several frames (specially if it's a direction button, you have to hold them if you want to walk around!), so this is expected. What you have to do is structure your program so that only one cycle of game logic + PPU updates happens per frame. This will guarantee that your game runs at 60Hz (or 50 for PAL) and all the physics constants can be adjusted for that speed. What you absolutely can't do is read the controllers and react to input several times per frame, that'll sure make things go faster than they should, but if you're doing it only once per frame, it doesn't really matter *when* in the frame you're doing it, because 1/60th of a second is too little of a window for humans to be able to analyze what happens inside it. The vertical blank lasts about 20 scanlines, which is about 1.3 milliseconds, so it really doesn't matter.
Re: How do I use Vblank?
by on (#191645)
I think, what tokumaru and tepples mean, is...for a new programmer, you aren't going to do anything that requires timing something mid-frame.

I think a sprite zero hit at the top left would be the easiest way to time when the frame is on scanline 1.

You could also do an MMC3 scanline IRQ count of 1 scanlines.

Neither one do you need, until much later, when you know much more about screen rendering.
Re: How do I use Vblank?
by on (#191646)
I've looked at your code from the other thread and yes, you're running the game logic over and over in the same frame, causing things to move too fast. This has nothing to do with a short vblank handler. You jump to a label called "wait" but... there's no waiting at all, the program simply reads the controller again and performs the movement again, over and over, without ever waiting for the next frame to start.

What you need to do is set up a way to make your program wait for the vblank handler to run, so it knows that one frame is complete and it can proceed to processing the next one. The simplest way to do this is to increment a counter anywhere in your NMI handler:
Code:
INC FrameCounter

And wait for this counter to change in your main loop:
Code:
  LDA FrameCounter
Wait:
  CMP FrameCounter
  BEQ Wait


Another thing you're not doing is backing up registers before you use them in the NMI handler. Remember, this code is going to interrupt the main loop, and if you change the registers without restoring them to their original values, when you return from the NMI the program will not be able to resume whatever it was doing, resulting in bugs and possibly even crashes. Most people have their NMI handlers save and restore all 3 registers, like this:

Code:
NMI:
  PHA ;save A
  TXA
  PHA ;save X
  TYA
  PHA ;save Y
  ;(NMI logic here)
  PLA ;restore Y
  TAY
  PLA ;restore X
  TAX
  PLA ;restore A
  RTI


EDIT: Corrected mistake pointed out by tepples.
Re: How do I use Vblank?
by on (#191649)
tokumaru wrote:
And wait for this counter to change in your main loop:
Code:
Wait:
  LDA FrameCounter
  CMP FrameCounter
  BEQ Wait

This will catch the change only if NMI happens between LDA and CMP, a roughly 1 in 3 chance if FrameCounter is in zero page.

A more reliable loop loads the previous frame counter value only once and then waits for it to change:
Code:
  LDA FrameCounter
Wait:
  CMP FrameCounter
  BEQ Wait
Re: How do I use Vblank?
by on (#191652)
Oh yeah, I misplaced the label. Sorry about that.