I'm not new to 6502 Assembly but I am new to the Commodore 64. I'm used to the NES where you have to write everything yourself, such as the NMI/RESET/IRQ vectors, etc, but from what I understand the BIOS takes care of all that for you. If I'm programming a disk image program on the c64, how can I wait for vBlank? I'm trying to figure out how to slow down an animation. This question isn't about the built-in Commodore BASIC, I'm not using that at the moment.

  • 1
    Have you read any C64 graphics programming tutorials yet?
    – Justme
    Sep 7, 2021 at 6:12
  • 3
    (1) Ask Godot to watch the vBlank. (2) Wait for Godot.
    – Jon Custer
    Sep 7, 2021 at 18:34
  • Have a read of c64-wiki.com/wiki/Raster_interrupt It will tell you how to raise an interrupt based on the screen rastering
    – JeremyP
    Oct 8, 2021 at 7:54
  • 1
    BIOS? KERNAL. As long as you haven't paged it out.
    – Alan B
    Oct 8, 2021 at 9:00

2 Answers 2


I'm used to the NES where you have to write everything yourself, such as the NMI/RESET/IRQ vectors, etc, but from what I understand the BIOS takes care of all that for you

The "BIOS" in the Commodore 64 is the Kernal. The Kernal does handle IRQs and NMIs, but before it does, it indirect jumps through a RAM vector (different than the hardware 6502 vectors in the upper 6 bytes of ROM), so you can hook in your own routine.

Unlike the NES, the VIC video chip does not generate an NMI on each frame. It can generate an IRQ but this is not enabled by default.

There's quite a few things that can generate IRQs and NMIs on the C64. The usual situation: IRQs on the C64 are normally driven by the CIA 1 timer (CIA 2 generates NMIs) which is set by the Kernal on power up to fire every 1/60th of a second. The background tasks done are updating the jiffy timer and scanning the keyboard.

You can turn that off and switch to the VIC though.

Having the VIC generate IRQs is very useful for games to sync for VBlank, so what you need to do is:

Also, in your IRQ routine you need to acknowledge the VIC's IRQ by writing to $D019 - towards the end of your IRQ routine might be best. VIC won't generate another IRQ until you do that. The linked example above explains.

The CIA needs it's IRQ acknowledged too - keep in mind the Kernal will still write to $D0CD to do that, but will be harmless since it's ability to generate IRQs is disabled.

Something to keep in mind - you could hook into the IRQ, not disable the CIA, then implement this logic in your handler:

  • check the IRQ flag on the VIC

  • if VIC caused IRQ

    • do what you want to do during VBlank.

    • check CIA IRQ flag, and if set, JMP to $FF48 [might be needed in case CIA and VIC try to IRQ at or near the same time]

    • either way after checking JMP to $EA81--point in Kernal ROM that simply restores registers and RTI (CIA did not cause IRQ).

  • else

    • JMP to $FF48 for normal IRQ processing (CIA caused IRQ).

If you want to do it NES style that's the general process.

  • 1
    If one doesn't mind having to write different code for different models of C64, using the CIA may in some cases be superior to using the 6502. Among other things, the CIA would allow code to determine how many cycles elapsed between when an interrupt fired and when it was taken, so an IRQ that uses a "cmp/nop" slide could easily align itself with a precise known beam position.
    – supercat
    Oct 19, 2021 at 17:51

Here is one way to do it. Note that this approach is quite wasteful; it just wastes time by going round and round in stupid loops until the next frame is reached. It would probably be nicer to do some kind of useful work in the meantime. You could use interrupts to achieve that, but well, it depends on how you want to structure your code.

On to the subroutine that waits for the next frame.

     bit $d011
     bpl wait_for_next_frame
     lda $d012
f:   cmp $d012
     bmi f

So the first instruction, the bit, checks bit 9 (!) of the raster counter. If this bit is not set, then the raster is still quite high in the screen, so the routine will branch back and check again.

Otherwise, we'll proceed to lda $d012, or load the (lower 8 bits of the) raster counter into the accumulator. By continously comparing with the the current value, the program determines when the raster goes back to the start.

Hope that's clear, just do jsr wait_for_next_frame and you should be good.

  • 1
    There are issue with this code. First, comparing equal values resets the N flag and the second loop doesn’t, well, loop. Second, If the scan line counter wraps between reading $d011 and reading $d012 then this routine returns at a raster index around 128 (barring the other issue). It is easier to detect that the msb of $d011 goes from 1 to 0.
    – WimC
    Sep 7, 2021 at 16:35
  • @WimC What would you suggest? Sep 7, 2021 at 17:23

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .