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.
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:
Update RAM vector ($0314-$0315 - look for
CINVin the Commodore 64 Programmer's Reference Guide) to your IRQ routine.
At the time the ROM does its
JMP ($0314), it has saved the registers on the stack and established that the IRQ is not caused due to the
Keep the old value (which is probably
$FF48) because your IRQ routine should jump to it when yours is done. That will take care of restoring CPU context back to pre-IRQ state.
Disable CIA 1 interrupts by writing 0 to it's interrupt enable bit.
Program the VIC register that tells which scanline to trigger the IRQ on.
Write to the VIC interrupt enable bit.
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).
- JMP to $FF48 for normal IRQ processing (CIA caused IRQ).
If you want to do it NES style that's the general process.
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.
wait_for_next_frame: bit $d011 bpl wait_for_next_frame lda $d012 f: cmp $d012 bmi f rts
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.