Skip to content →

interrupts and timers

Our emulator needs to have ways to read user inputs, know when a V-Blank is reached, when the LCD enters a certain mode, or when a timer has expired (to be table to have time-related behavior in the code). This is achieved by interrupts.

V-Blank is the period, right after the screen drew a complete frame, and before starting the next frame. This period often is used to change VRAM, calculate enemy movement etc.

Interrupts allow the developer to actually react to certain events (a button press, a timer expiring etc.). So, we have to be able to handle those interrupts. In general, when a interrupt is fired and handled, it will jump to a predefined location (e.g. 0x40 for a V-Blank interrupt) and run the code at that location, before finally returning to it’s original location, through a RET.

RET returns to the last address that is pushed on the stack, then removes the address from the stack. 
Firing an interrupt, pushes the address of PC + 1 to the stack, then handles the interrupt, finally returns, and proceeds normally.

The interrupts are a bit more complex. There is a general Flag, that enables and disables interrupts in general (usually inside the CPU itself, and directly accessible), it’s called interrupt_enabled in my case. This flag is accessed by the opcodes EI (0xFB) and DI (0xF3).

//	EI / DI
case 0xf3: return op_set_ime(interrupts_enabled, 0, pc); break;
case 0xfb: return op_set_ime(interrupts_enabled, 1, pc); break;

Following that, interrupts have a register, that shows that the condition for a interrupt was met (user pressed a button, for example), this register is the Interrupt Flag registerIF at 0xFF0F.

Only having interrupts_enabled == true and the appropriate bit set in the IF isn’t enough to handle a interrupt, though. We still have another register. The Interrupt Enable registerIE at 0xFFFF. Only interrupts that are enabled in this register will be handled, once they are flagged in IF.

Now that we know the general way how interrupts work, we can implement them:

//	handle interrupts
if (interrupts_enabled) {
	//	some interrupt is enabled and allowed
	if (readFromMem(0xffff) & readFromMem(0xff0f)) {
		//	handle interrupts by priority (starting at bit 0 - vblank)
		//	v-blank interrupt
		if ((readFromMem(0xffff) & 1) & (readFromMem(0xff0f) & 1)) {
			writeToMem(sp, pc >> 8);
			writeToMem(sp, pc & 0xff);
			pc = 0x40;
			writeToMem(0xff0f, readFromMem(0xff0f) & ~1);

As you can see in line 14, we are supposed to turn off the interrupt, after we are done rearranging the PC.

Another important feature of the CPU is the timer. The timer makes it possible to start / stop things after a certain set period. The timer can be run at 4 different frequencies:

4096 Hz
262144 Hz
65536 Hz
16384 Hz

Basically the timer ticks depending on the set frequency. Once the timer overflows its register, a timer interrupt is flagged (that we already implemented the handling of)

void handleTimer(int cycle) {
    //	set divider
	div_clocksum += cycle;
	if (div_clocksum >= 256) {
		div_clocksum -= 256;
		aluToMem(0xff04, 1);

	//	check if timer is on
	if ((readFromMem(0xff07) >> 2) & 0x1) {
		//	increase helper counter
		timer_clocksum += cycle * 4;

		//	set frequency
		int freq = 4096;					//	Hz
		if ((readFromMem(0xff07) & 3) == 1)		//	mask last 2 bits
			freq = 262144;
		else if ((readFromMem(0xff07) & 3) == 2)	//	mask last 2 bits
			freq = 65536;
		else if ((readFromMem(0xff07) & 3) == 3)	//	mask last 2 bits
			freq = 16384;

		//	increment the timer according to the frequency (synched to the processed opcodes)
		while (timer_clocksum >= (4194304 / freq)) {
			//	increase TIMA
			aluToMem(0xff05, 1);
			//	check TIMA for overflow
			if (readFromMem(0xff05) == 0x00) {
				//	set timer interrupt request
				writeToMem(0xff0f, readFromMem(0xff0f) | 4);
				//	reset timer to timer modulo
				writeToMem(0xff05, readFromMem(0xff06));
			timer_clocksum -= (4194304 / freq);

As you can see, to make this timer work, we need to know how many cycles each operation takes.


Leave a Reply
