Interrupts

What's an interrupt?

Before describing interrupts, I shall present the kind of situation they solve; knowing what benefit they provide will help understanding what they are.

Consider the Game Boy's link cable. It allows exchanging data between consoles byte by byte. However, there is only a single byte of temporary storage, so you need to read the incoming byte and write the next byte quickly. But, you also want to run an entire game (say, Tetris) while this is happening, so you can't just check for incoming bytes in a loop. This could be solved by checking for bytes at specific points in the game code, but that may be impractical and lead to missed bytes, a lot of wasted computing, and hard to track bugs: in short, it's bad.

Interrupts solve this problem by allowing the CPU to "interrupt" execution when it recieves a signal; which signals cause what to happen is defined by the hardware configuration (wiring, etc.). To explain how interrupts work on the Game Boy, we'll need to introduce two registers, the first of them being IF (Interrupt Flag).

IF is a register that can be read from and written to, but this is rarely done—most changes to this register are done by the hardware automatically. When the CPU receives the right external signal (say, one indicating that a byte was just transferred through the link cable), it sets the corresponding bit in IF; the interrupt is now pending. Before executing each instruction, the CPU checks for set bits in IF, and handles the interrupt corresponding to the lowest set bit, giving priority to the interrupts that were considered more important by the hardware designers.

Let's talk about what happens when an interrupt is handled. It makes sense that an interrupt should only temporarily take over execution, and eventually return to the original flow, yes? Why, that's a perfect job for the function calling we just explained! When an interrupt is serviced, the CPU essentially executes a call instruction. Where it jumps to, though, depends on which interrupt is serviced (thankfully). The destination can be computed by taking the number of the "selected" bit (say, bit 2), multiplying it by 8 (now 16 = $10), and adding $40 (now $50). So, for interrupt bit 2 ($04, if need a refresher), the CPU would essentially call $0050.

Further, when an interrupt is serviced, its corresponding bit in IF is reset, a process called acknowledging. This ensures that the CPU will only service an interrupt once each time it fires.

The different types of interrupts

There are five different interrupts, in this order:

Bit in IFInterrupt nameHandler addressNotes
0 ($01)VBlank$0040highest priority
1 ($02)STAT$0048also called LCD or sometimes abusively LCDC
2 ($04)Timer$0050
3 ($08)Serial$0058
4 ($10)Joypad$0060lowest priority; rarely used

The VBlank interrupt is the simplest of them, since it triggers as soon as VBlank starts. (If you don't remember what VBlank is, check out the "Displaying" lesson.) A good way to sum up its main use is that this interrupt triggers at the end of every frame.

Same deal with the STAT interrupt, which triggers based on conditions selected through the STAT register. It'll be further explained in the next lesson. You may also see this interrupt called the "LCD interrupt" or, abusively, "LCDC interrupt".

The timer interrupt triggers at a certain frequency that can be configured using a few registers. It's useful for things that need to occur at a given frequency, such as sound updates.

The serial interrupt triggers every time a byte has been recieved via the serial port. Its job is basically to prepare the following transfer.

Finally, the joypad interrupt... triggers more or less when a button is pressed. It's hardly ever used, though, because it's hard to use reliably and it usually makes more sense to check for input once a frame.

Configuring interrupts

If you think about it, it's not hard to imagine that you won't need to make use of all interrupts all the time. This is where the second interrupt register, IE (Interrupt Enable), comes in. In contrast with IF, this one is only modified by your code.

When the CPU checks for interrupts, it actually computes [IE] & [IF] & $1F (that's to say, both registers are ANDed together, and only the bottom 5 bits are considered). This means that you can enable only the interrupts you need by setting their corresponding IE bits to 1.

There is an additional thing that needs to be explained for this to be complete, though, and it's the IME bit. IME is simply a bit that, when zero, causes the CPU to skip checking for interrupts (the process described in the paragraph above). It being called the Interrupt Master Enable thus makes sense. IME is not exposed through any registers in the I/O area. Instead, it can be reset by the di (Disable Interrupts) and set by theei (Enable Interrupts) instructions.

Finally, when the CPU services an interrupt, IME is automatically reset, which prevents an interrupt being serviced while another is being serviced. Because of this, the interrupts need to be enabled when the interrupt service routine returns. This can be done by placing an ei just before the corresponding ret. However, this combination is executed frequently enough to be available as a single instruction: reti. Using it instead saves both a byte in the ROM and some CPU time.

Recap