Preparing for Interruptions

The top line of the logic analyzer shows timer interrupts being received at 100 Hz (one every 10 milliseconds).

The first version of my serial port driver for the Project:65 computer was purely polling based. There was a write subroutine that would send a character out the serial port (and wouldn’t return until it had succeeded) and a read subroutine that would try to read a single character. The read operation didn’t block; it used the CPU’s carry flag to indicate whether or not it actually returned a character.

I wrote the polling version because it was simple and because it matched the model of the software I wanted to run, specifically the EhBASIC interpreter. The problem with polling is that it depends on the application to call the read subroutine on a regular basis. The MAX3100 UART has a built-in 8-character read buffer. At 9600 bps, that means that once the first character has been read into an empty buffer, the CPU has a little over 7 milliseconds in which to retrieve it before the buffer starts to overflow. If bytes keep coming in, the CPU has to retrieve one byte every millisecond on average just to keep up. It’s certainly possible to design a program so that it calls the read routine often enough, but that’s not a very convenient way to structure a piece of software.

(And if you think that’s bad, a lot of older UARTs didn’t have the read buffer – you literally had to retrieve the character before the next one finished coming down the line.)

(And if you think that’s bad, the Commodore 64 didn’t even have a UART – it had to check the state of its input lines in software at the right times to catch each incoming bit. There’s some custom code out there that can handle communications at a blazing 2400 baud.)

Interrupts provide a way to make the CPU respond to an event – to interrupt what it’s doing and perform some other important task, before picking up again where it left off. On the 6502 there’s a pin called IRQ that serves as an interrupt input. When the value on that pin is a logic 0, the processor finishes up the instruction that was currently running, and then it jumps to a new location. On a 6502-based machine, the last two bytes at the top of the address space – $FFFE and $FFFF – contain the location of the code that should be executed when an interrupt happens. This code is referred to as an interrupt service routine (ISR).

When you’re writing a piece of software, you don’t plan for the CPU to jump off into who-knows-where for who-knows-how-long between two adjacent instructions. To keep the main program from becoming very unhappy, the interrupt service routine needs to save the state of the CPU and then restore it when it’s done. Luckily the 6502 doesn’t have a lot of internal state, and some of it – like the CPU status register and the program counter – are automatically preserved. The programmer is responsible for saving the state of any other registers the interrupt routine uses.

This discussion may sound to you a lot like how multitasking and multithreading work, and it is – in fact, this is part of how “preemptive multitasking” is implemented. But that’s a topic for another day.

All that preamble aside, in the first version of the Project:65 computer, I skipped interrupts entirely. I connected the IRQ pin of the 6502 to +5v and never had to worry about it. This approach isn’t unprecedented, either: The Atari 2600 used a cost-reduced cousin of the 6502 that saved money by eliminating a bunch of pins. That design removed the IRQ pin on the outside of the package and tied it to the +5v input on the inside.

When it came time to set up interrupts on the Project:65 CPU, I decided to start with something simple: a clock. The 6522 VIA has a pair of “timers” designed for this purpose. A timer is just a register that decrements itself with every cycle of the system clock. The idea is that you fill it with some value, and when it hits 0 it triggers an interrupt. You can also set up the timer to automatically reset itself so it starts counting down again immediately after it hits 0. In the picture at the top of this post, you can see the timer interrupts being triggered on a regular period.

Now, by “triggers an interrupt”, I mean that the 6522’s IRQ output goes low, and it’s connected to the CPU’s IRQ input. So a few clock cycles after that happens, the CPU will start running the interrupt service routine.

I figured I’d set up an interrupt to go off 60 times a second and use it to keep track of the number of seconds the system had been running. That didn’t quite work out though, because the P:65 runs at 4 MHz, which would require 66,667 clock cycles between interrupts. The register the 6522 uses is 16 bits wide, so its maximum value is only 65,535. So close! Instead, I set up the interrupts for 100 times per second (40k clock cycles!). Every 100th trip through the ISR, I would increment the number of seconds. Once I had it printing out the number of seconds, I timed it against a stopwatch to make sure I was at least in the ballpark. It turned out that everything was working just fine.

It also turns out that this was very much the easy part. Stay tuned; for our next installment, I’m going to try to condense several pages of notes & trial and error into a cohesive narrative about the interactions between the MAX3100 and the 65c22. It should be… interesting.