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
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 ||Interrupt name||Handler address||Notes|
|0 ($01)||VBlank||highest priority|
|1 ($02)||also called LCD or sometimes abusively |
|4 ($10)||Joypad||lowest 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.
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 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 the
ei (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.
Interrupts allow the CPU to react to certain external events, such as the end of a frame, by essentially executing a
calljumps to is determined by which event occurred.
Which interrupts are enabled is selected using the
IEregister, and they can also be globally toggled using the