83Plus:Interrupts

From WikiTI
Revision as of 14:34, 27 March 2013 by Dr. D'nar (Talk | contribs)

Jump to: navigation, search


Z80 Interrupts

The Z80 CPU supports three different modes of handling interrupts. They are called IM 0, IM 1, and IM 2. In all modes, after accepting an interrupt, the Z80 disables interrupts and you must manually enable them again at the end of your interrupt service routine. The IM 0 mode is identical to the Intel 8080's interrupt handling and is provided for compatibility. In IM 0, the interrupting hardware takes over the bus and sends an instruction to the CPU to execute. This instruction would usually be an RST instruction, which is what the RST instructions were designed for. In IM 1, the Z80 always executes RST 38h.

In IM 2, the Z80 uses an interrupt vector table. The interrupting hardware gives the Z80 an index into the table, such that up to 128 devices can automatically specify which interrupt service routine (ISR) needs to be run. The I register specifies where the IVT starts. Since I is 8 bits, it only specifies the high byte; the table always starts at an address in which the low byte is zero. The interrupting hardware is supposed to send an 8-bit number that's an index into the table. So the Z80 combines the index and the value of the I register to form a 16-bit address. The Z80 uses the 16-bit number at that address as the location of the ISR.

The TI-83+ family operating system always operates in IM 1, in which any interrupt signal causes the CPU to execute the RST 38h instruction. The OS provides no hooks for allowing software to catch and process interrupts. Fortunately, the Z80 also has the IM 2 mode, which allows us to move the interrupt vector table (IVT) to any of 256 different locations. Unfortunately, in the TI-83+ IM 2 is semibroken because it would actually be useful to us if it functioned properly. The ASIC doesn't bother to specify an index into the table, so we have to poll different ports to figure out what generated the interrupt. Furthermore, you will note that the number the Z80 expects to be passed is an 8-bit number. Because the addresses in the table are 16-bits, each address takes 2 bytes. But the number the Z80 receives from the ASIC is effectively random. And the Z80 doesn't mask out the bottom bit. So you have to fill the table with 257 repeating bytes, and your ISR can only be at an address in which the high and lows bytes are the same.

If you want to know whether interrupts are currently enabled or not (so you can disable them, and then enable interrupts again if and only if they were enabled before), the ld a, i and ld a, r instructions will copy the value of IFF2 into the P/V flag. In other words, if interrupts are enabled, the P/V flag will be set. The original NMOS-based Z80 had a bug that would cause ld a, i and ld a, r to reset the P/V flag if an interrupt occurred near the instruction. This bug was corrected in the CMOS version of the Z80, which is the version that is in the ASICs; therefore, you do not need to worry about the bug if you don't need your software to run on the older TI-83+s that were manufactured with a discrete Z80. The workaround for the TI-83+ is either to prohibit using older units, or to perform the ld a, i/r operation twice, and compare the results.


TI-8x Interrupts

Basic Interrupt Sources

The TI-83+ has four basic interrupt sources:

  • ON key
  • Linkport
  • HW timer 1 (TI calls this APD)
  • HW timer 2 (TI calls this PAPD)

These are controlled via ports 2 (pIntAck), 3 (pIntMask), and 4 (pIntId). Each bit in those ports relates to a specific interrupt source:

Bit    Interrupt Source   
0 ON key
1 Timer 1
2 Timer 2
3 Not related to interrupts
4 Linkport
5 Crystal timer 1
6 Crystal timer 2
7 Crystal timer 3

Port 3 (pIntMask) controls which interrupts are passed on to the Z80. Set a bit to enable the interrupt. When an interrupt occurs, check port 4 to see which interrupt triggered. A set bit means that the interrupt is actively firing. Note that I used the present progressive tense. If you don't acknowledge the interrupt, the Z80 will be reinterrupted the moment interrupts are reenabled. Currently, the popular method of ACKing interrupts is to zero port 3, and then rewrite the old value of port 3 again. However, the proper method is to write to port 2: write a 0 to a particular bit to reset that interrupt source, and write a 1 to leave it alone. Thus, if multiple interrupt sources trigger at once, you can ACK only the one you see first, and upon EI, the remaining ones will trigger, which you can then check and ACK in due time, instead of blithely ACKing everything.

Ports 2 and 4 are asymmetric: reading and writing control completely different parts of the hardware. Bit 3 does nothing in port 2. In port 3, bit 3 enables low power mode on HALT if zero. In port 4, bit 3 is 0 if the ON key is currently being held. On everything after the TI-83+, reading port 4 can tell you which crystal timer fired an interrupt, but you can't ACK or mask using ports 2 and 3.

Extra Interrupts on the TI-83+SE and Above

On everything after the TI-83+, there are even more interrupt sources: Link assist, crystal timers, and USB.

For link assist interrupts: See port 8.

KillLinkAssist:
	xor	a
	out	(pLnkAstSeEnable), a
	ret

For the crystal timers, the page on port 30 says everything.

KillCrystalTimers:
	xor	a
	out	(pCrstlTmr1Freq), a
	out	(pCrstlTmr2Freq), a
	out	(pCrstlTmr3Freq), a
	ret

For USB: Unless you're actually planning to handle USB activity, just kill USB entirely by doing this:

KillUsb:
	xor	a
	out	(57h), a
	out	(5Bh), a
	out	(4Ch), a
	ld	a, 2 ; This is important. Resetting this bit will ENABLE USB stuffs!
	out	(54h), a
	ret