This is about NES 6502 assembly programming (nesdev) and might be difficult to do in C. If you came here for C programming, then this page is not for you.
What is an IRQ? It is a signal that interrupts the normal CPU operation. For NES development purposes, it is for something that is time sensitive and needs to happen immediately. Like the MMC3 scanline counter. If you need a screen split at exactly scanline 63, you set the MMC3 mapper to count the scanlines, and when it reaches 63, it sends the CPU an IRQ signal. When the CPU gets an interrupt signal, it immediately jumps to the IRQ handler. (the handler would then change the scroll position, or whatever you need to happen).
Even without a special mapper, the NES has 2 built in IRQs, generated by the APU (audio) : The Frame Counter IRQ and The DMC IRQ.
.
Frame Counter IRQs
If you turn this on, an IRQ will fire once per frame. Roughly. The APU and the PPU are not synced and they have no idea what the other is doing, and the APU “frame” is just slightly slower than the PPU. Anyway… what could we do with a once per frame IRQ? At least one game (Dragon Quest ?) uses this to run its music code. If you have your music code tied to your NMI handler, then it will stop running when you turn the screen off (and NMIs off). If it is placed in the IRQ handler, then it will run 60x per second regardless if NMIs are on or off (like when you are loading the next level of the game).
How could we do this?
CLI will allow IRQs to happen. Put that in the INIT code. Then,
will turn on Frame Counter IRQs (this is Mode 0. I’m pretty sure that Mode 1 doesn’t trigger the IRQ).
LDA #$00STA $4017
The you need an IRQ handler. Make sure you save and restore any registers that you change. Make sure you exit with RTI (not RTS).
For the Frame Counter IRQ, you need to acknowledge the interrupt signal, or else it will immediately loop back to the IRQ handler over and over in an endless loop.
IRQ: pha ;save all the registers txa pha tya pha bit $4015 ;this reads 4015, acknowledge the IRQ jsr Music_Code pla ;restore all the registers tay pla tax pla rti
You don’t need to worry about the status flags. The RTI will automatically restore the status flags.
One interesting note about IRQs – when the CPU gets an IRQ signal, it temporarily sets the interrupt disable flag. And when it sees the RTI instruction, it automatically restores the interrupt disable flag (ie. clears it, because we got here because it was clear). And an important point… you SHOULD NOT change the interrupt disable flag inside the IRQ handler. SEI is pointless, because the RTI will just clear it again. CLI could crash your game, because it will just immediately jump back to the IRQ handler in an infinite loop. If you need to turn off IRQs inside the IRQ handler, it should be done by turning off the source of the IRQ, such as LDA #$40 STA $4017 for the Frame Counter.
I don’t personally advocate for using the Frame Counter IRQ. I’m just explaining it for academic reasons.
BTW, it is possible to sync the APU frame to the PPU frame (with or without the IRQ on). If you write $c0 to $4017, it should reset the APU frame. You could do that inside the NMI handler (once per frame) and it should sync them (sorry, I haven’t tried it). I’ve heard that several major Nintendo made games do this. If you do this, I believe you are supposed to put the APU frame in mode 1 (5 step), and with Frame Counter IRQs off. I don’t know why you would want to do this, but the engineers at Nintendo seemed to think it was a good idea.
.
DMC IRQs
This is the useful one. So, DMC is one of the audio channels. It can play sound samples (low quality sound samples). If you turn on DMC IRQs and play a sample, when the sample ends it will send an IRQ signal.
What can you do with this? You can time a mid-screen scroll split. You could time multiple screen splits. Another idea – normally you would use a sprite zero hit to time a screen split, but repeatedly polling for the sprite zero is a waste of CPU processing, so you could have a DMC IRQ happen just before the sprite zero hit, and you wouldn’t waste so much time looking for it.
About the multiple screen splits though… it won’t be exact. It’s not as pixel perfect as the MMC3 IRQ. The APU runs the DMC channel with a slightly variable lag. The best you can do is narrow it down to a range of about 3-4 scanlines. So you would want the screen split to happen where there isn’t much going on and you have the same color all the way across the scanline, for several scanlines in a row. I just grabbed a random title screen that has blank scanlines.

Between Palcom Software and the TMNT logo, you could do a split. Between Push Start and 1990 you could do a split. Etc. Anywhere you have several blank scanlines you could do a screen split with the DMC IRQ. Or maybe if you like crazy graphics, you could do it where there isn’t blank scanline area and you’d get some crazy glitchy mid screen splits. If you’re into that sort of thing.
Another problem is that you won’t be able to use the DMC channel for any musical purposes. And you will have to reserve some space for a blank DMC sample (all zeros, for example). Well, it wouldn’t need to be very big, so that’s not much of a concern. But, if I had a screen where not much happened and I wanted multiple screen splits, I would probably used TIMED CODE. That’s just a block of code that doesn’t really do anything, except that it uses a very specific amount of time to execute. With TIMED CODE, you could get exact scanline splits. But, let’s show how the DMC IRQ works, just for fun.
CLI will allow IRQs to happen. Put that in the INIT code. Then somewhere in our NMI code we would put the rest of the IRQ setup. First, we shut off the DMC channel, because you can’t trigger a new sample if it’s currently playing a sample. Just to be sure, we turn off that channel.
LDA #$0fSTA $4015
Then turn on, DMC IRQs
LDA #$8f ;IRQ on, not looped, rate of FSTA $4010
Set the rest of the DMC settings…
LDA #$C0 ; ROM address $f000STA $4012 ; tells where the "sample" isLDA #$02STA $4013 ;sample length, 2 = $21 bytes longLDA #$1fSTA $4010 ;turning the DMC channel back on will trigger the "sample" to play
I put sample in quotes, because it should just be a block of zeros. 21 hex is 33 decimal. So… 33 zeros.
Rate of F and length of 2 will take 126 scanlines, so we could time for a screen split in the middle of the screen with this. See the chart at the bottom for other possible timings.
https://www.nesdev.org/wiki/APU_DMC
This page will also explain the DMC registers, and the formulas to calculate address and length of the samples.
Then we need an IRQ handler.
IRQ: pha ;save all the registers txa pha tya pha lda #$0f sta $4010 ;turn off DMC IRQs, to acknowledge the IRQ ;screen split code here pla ;restore all the registers tay pla tax pla rti
Alternatively, we could leave DMC IRQs turned ON and set up another DMC IRQ inside the IRQ handler.
I don’t think we need the LDA #$0f STA $4015 beforehand, because the sample should have ended already, so the APU is ready to play another sample. The LDA #$1f STA $4015 will reset the timer and start playing the blank sample, so we won’t get another IRQ until that sample ends.
;set up whatever DMC settings you need for the next splitLDA #$1fSTA $4015 ;DMC on, will trigger another IRQ
If you have multiple scanline splits, you might need multiple versions of the IRQ code, and use some variable to direct your code to various options. I would assume that if you are advanced enough in 6502 asm to write an IRQ handler that you would also be able to write a case/switch in assembly.
LDA #$0f STA $4015 would also work to acknowledge the DMC IRQ (stop another IRQ from firing), and that would work in place of the LDA #$0f STA 4010 above. I think any write to $4015 will acknowledge the IRQ.
.
BRK (give me a break)
It should be noted that if the CPU ever sees opcode zero (00 = BRK) that it will jump to the IRQ handler. They share the same vector, so both IRQ and BRK will jump to the same location.
Computer systems that use the 6502, such as Apple ii and Commodore 64, sometimes use BRK as part of the code, and you would then put the BRK handler in place of the IRQ handler.
Interestingly, some NES games actually use BRK as part of their programming. If you do this, it should be noted that BRK doesn’t put the return address on the stack, but return address +1, so you need to put a byte after the BRK and then the next line of code after that. Also, there is a bug in the NES, where an NMI signal at the exact same moment as a BRK signal, the BRK will be ignored and it will go to the NMI handler instead. Probably advisable to not use BRK except as an error handler… because if your code runs into a zero, then something has gone terribly wrong.
…anyway…
How does our IRQ code know if we handling a BRK, or a Frame Count IRQ, or a DMC IRQ? You can actually tell the difference. Inside the IRQ handler, read from $4015, and bit 7 (1000 0000) will be 1 if it is a DMC IRQ. Bit 6 (0100 0000) will be 1 if it is a Frame Count IRQ. Both will be zero if it is neither (and probably a BRK).
.
Other IRQs
Some mappers generate IRQs. MMC3, MMC5, VRC4, VRC6, VRC7, FME-7, and Namco 163. I believe the Rainbow mapper has an IRQ. The Famicom Disc System has an IRQ. And the Expansion Port has an IRQ line (although I don’t think anyone has ever used it).