RST xx | Software restart to address $00xx. See table for valid values for xx |
Argument | Result |
---|---|
00h | Simulates taking all the batteries out of the calculator. |
08h | Execute system routine OP1ToOP2. |
10h | Execute system routine FindSym. |
18h | Execute system routine PushRealO1. |
20h | Execute system routine Mov9ToOP1. |
28h | Part of the b_call() macro. |
30h | Execute system routine FPAdd. |
38h | System interrupt routine. |
The RST commands are two bytes smaller than the corresponding b_call() command, and are also a lot faster, so use them whenever possible. And yes, the arguments to RST have to be hex numbers in -h form.
Software or hardware, all interrupts when triggered are like normal CALLs, in that the current PC value is pushed onto the stack and there is a transfer to wherever the ISR is located. The actual address depends on the current interrupt mode.
IM x | Sets interrupt mode x. x = 0, 1, or 2 |
The system interrupt uses SP (naturally) and IY, so if you want to use these registers for other purposes you need to disable interrupts (or just not process the system interrupt).
This is the official ZiLog description of a Mode 2 interrupt, on the TI calculators something is awry and interrupt behavior is contradictory to what was described above. With a lot of experimentation (and a lot of RAM clears), I have discovered that the value of the interrupt vector is made up as follows:
The vector table is initialized with code similar to the following:
; Assume an ISR located at $9DF0 LD HL, $9DF0 LD ($993F), HL LD ($997F), HL LD ($99BF), HL LD ($99FF), HLN.B. All the vectors are stored in AppBackupScreen RAM area.
Now we should create the ISR. When an ISR begins, it has to save the values of all the registers it will modify. The reason for this is simple: if it didn't, then the values of the registers would be changed 140 times a second to something unknown, which would play havoc with the normal program. This can be done either with the stack or very quickly with two instructions.
EX AF, AF' | Exchange register pair AF with alternate register pair AF'. |
EXX | Exchange register pairs BC, DE, and HL with alternate register pairs BC', DE', and HL'. |
DI | Disable interrupts |
EI | Enable interrupts |
HALT | Stop execution and enter low-power mode. On next interrupt, "wake up" and resume execution. |
If a HALT is executed, the LSB of the next interrupt vector will always be %11111111.
You should have interrupts disabled while you are loading the interrupt data. Some cretin might have left the CPU in Mode 2 when you exited his game. If an interrupt triggers while you are overwriting pre-existing interrupt code, you're definitely gonna be feeling below average.
Interrupts should be short and execute quickly (rendering a raytraced 3D scene in an interrupt is definitely not on :-). If an interrupt takes too long to complete it may very well be re-initiated and loop forever. To make sure this doesn't happen, you must know the interrupt enable port (#3).
Bit | If Set | If Reset |
---|---|---|
0 | [ON] key interrupts are serviced | [ON] key interrupts are ignored |
1 | Timer interrupts are serviced | Timer interrupts are ignored |
4 | Linkport interrupts are serviced | Linkport interrupts are ignored |
The meanings of bits 2 and 3 are not known. When the interrupt is entered, output %00000000 to disable all interrupts and prevent an infinite loop. To return, output %00001101, restore the registers, re-enable interrupts, and then finally return.
To check the status of the flip-flops, a LD A, I or LD A, R instruction will store the status of IFF2 in the P/V flag. Reset means interrupts are disabled, set means interrupts are active.
INTRPT_MASK .EQU %00001011 b_call(_ClrLCDFull) DI ; Turn interrupts off until we're ready ; Load interrupt address vectors LD HL, interrupt LD ($993F), HL LD ($997F), HL LD ($99BF), HL LD ($99FF), HL LD A, $99 LD I, A LD A, INTRPT_MASK ; Enable hardware OUT (3), A IM 2 ; Switch to Mode 2 EI ; Activate interrupts ; GetKey and GetCSC only function in Mode 1, ; so gotta use the key port. LD A, %10111111 OUT (1), A KeyLoop: IN A, (1) CP %01111111 ; If [DEL] pressed, exit JR NZ, KeyLoop LD A, %00001011 ; Enable hardware OUT (3), A IM 1 ; Calculator needs Mode 1 RET interrupt: EX AF, AF' EXX XOR A ; Disable hardware OUT (3), A LD HL, 0 LD (CurRow), HL LD HL, (counter) INC HL LD (counter), HL b_call(_DispHL) LD A, INTRPT_MASK ; Enable hardware OUT (3), A EX AF, AF' EXX EI RET counter: .DW $0000
Inputs | ||
---|---|---|
Bit | If Set | If Reset |
0 | [ON] key interrupt has been generated | [ON] key interrupt has not been generated |
1 | Timer interrupt has been generated | Timer interrupt has not been generated |
3 | [ON] key is being depressed | [ON] key is up |
Outputs | ||
0 | Force [ON] interrupt status | |
1-2 | Interrupt speed (0 to 3). %11 is slowest, %00 is fastest. Normal speed is %11 |
The TI-OS has the system flag OnInterrupt that is set if a one is read from bit 0 of port 4. This will result in an ERR: BREAK message when the program returns to the home screen. You can prevent this by
Whoa, whoa, wait a minute. Why are we jumping to $003A? Isn't the system routine at $0038??
Well, yes, the Mode 1 interrupt does jump to $0038. What we are doing is swapping the shadow registers when our interrupt is run, and we want them to stay swapped when the system interrupt is running. A section of the code at $0038 looks like
0038: JR $006A 003A: IN A, (4) . . 006A: EX AF, AF' 006B: EXX 006C: JR $003AYes this is completely redundant, but by jumping to $003A the exchanges get skipped over.
This is useful if you still want GetKey and GetCSC and other Mode 1 features to work while you're in
Mode 2.
Also, if you don't switch back to Mode 1 when the program ends, your interrupt will still be active, even during
graphing and (provided they don't use interrupts themselves) other programs! Unfortunately, drawing and archiving will kill
'em.
DI ; We must store the interrupt somewhere in RAM so it sticks around LD HL, interrupt LD DE, $9A9A LD BC, interrupt_end - interrupt LDIR ; Don't bother with specific memory locations, ; just storing $9A everywhere will work LD HL, $9900 LD DE, $9901 LD BC, 256 LD (HL), $9A LDIR LD A, $99 LD I, A IM 2 EI RET interrupt: EX AF, AF' EXX LD A, $FF OUT (1), A LD A, $DF OUT (1), A IN A, (1) CP $F7 JP NZ, $003A SET TextInverse, (IY + TextFlags) LD HL, interrupt_message - interrupt + $9A9A LD DE, $3300 LD (PenCol), DE b_call(_VPutS) LD D, $39 LD (PenCol), DE b_call(_VPutS) RES TextInverse, (IY + TextFlags) JP $003A interrupt_message: .DB "This TI-83 Plus is property of", 0 .DB "PUT YOUR NAME HERE!!... HANDS OFF!", 0 interrupt_end: