Part 11:
Interrupts

    Interrupts are breaks the processor takes in order execute other code or to repair internal resources.  When an interrupt has been signaled, the current PC is pushed onto the stack to be popped off the stack when the interrupt completes.  Most of the time, the programmer has no control over when the interrupts occurs; in a few instances, however, the programmer has control over what code is run, which can turn out very useful.  There are twelve types of interrupts, four of which can be externally programmed.
    The three maskable interrupt types are interrupt mode 0, interrupt mode 1 and interrupt mode 2.  Only one of these modes can be set at time; the instructions <im 0>, <im 1>, and <im 2> will set the interrupt mode to 0, 1, or 2 respectively.  These three types are called maskable interrupts because they can be masked (disabled) with the instruction <di> (disable maskable interrupts) and enabled with the instruction <ei> (enable maskable interrupts).  With the interrupts enabled, the current interrupt mode will be triggered to execute on even intervals of once every 1/174 seconds, meaning that any code you have designated as an interrupt handler will run 174 times per second.
    Now, you are probably thinking that this will screw up all your suspended code, by destroying the registers and flags in the interrupt and then returning with those modified values.  Well, an interrupt can do that, but you have to program it correctly so that it doesn't use the registers (including F) used in normal operation.  This is done by employing what are called the shadow registers, which exist as shadows of registers A, F, HL, BC and DE.  To access the shadow registers, you must exchange the contents of these registers and their shadows: <exx> (exchange the contents of the shadow registers for HL, BC, DE and the registers HL, BC, DE) and <ex af,af'> (exchange the contents of the shadow registers for A, F and the registers A, F).  At the start of your interrupt handler you should execute <exx / ex af,af'>, then execute it once again at the end of your interrupt handler (allowing you to use the important instructions for the standard registers, without causing your calculator to crash as it returns to suspended code).

    The most common interrupt type is interrupt mode 1.  Every assembly program you have written so far has been running the ROM's im 1 interrupt handler (I know, it's discouraging to find out that you've been running code where you previously thought you had complete control).  When the processor recieves the signal to run an im 1 interrupt, it will push the program counter and jump to the address $0038.  Since this is ROM, you can't install your own handler at $38, but TI was benevolent even to create a user interrupt routine at $d2fe that is called from $38 if the flag iy+$23 is set and the checksum matches.  So, if you copy code to $d2fe, set (iy+$23) and load the right checksum to $d2fd, not only will your code be run every time the interrupt is called while your asm program running, it will be run everytime the interrupt is called from TI-OS as well.  Here is an example im 1 interrupt handler:

#include "asm86.h"
#include "ti86asm.inc"

_alt_int_chksum equ         $d2fd          ;define these equates for checksum
_alt_interrupt_exec equ    $d2fe           ;interrupt RAM addresses
user_counter  equ _alt_interrupt_exec+counter-_asm_exec_ram

.org _asm_exec_ram

 di                                        ;disable interrupts so that they won't get called while finding checksum
 ld hl,int_start                       ;point HL to our interrupt handler
 ld de,_alt_interrupt_exec     ;point DE to where we'll copy it
 ld bc,int_end-int_start         ;BC is length of our handler
 ldir                                     ;copy handler to $d2fe

;now we find the new checksum by adding the values at every $28th byte of the interrupt

 ld de,$28                               ;increment between pointers
 ld a,(_alt_interrupt_exec)       ;start accumulator with first byte of interrupt
 ld hl,_alt_int_chksum+$28     ;ld HL with pointer to first checksum byte
 add a,(hl)                              ;add to accumulator
 add hl,de                               ;checksum + ($28*2)
 add a,(hl)                              ;add to accumulator
 add hl,de                              ;checksum + ($28*3)
 add a,(hl)                              ;add to accumulator
 add hl,de                              ;checksum + ($28*4)
 add a,(hl)                              ;add to accumulator
 add hl,de                              ;checksum + ($28*5)
 add a,(hl)                              ;add to accumulator
 ld (_alt_int_chksum),a          ;put result into checksum byte
 set 2,(iy+$23)                      ;enables our routine to be called from interrupt
 ei                                         ;reenable interrupts
 ret                                        ;end of interrupt installation program

;what follows will be run every im 1 interrupt (174 times a second)
;put whatever you want here, this code just runs a simple interrupt counter

int_start:

 ld hl,user_counter                ;all we're going to do is increment a RAM
 inc (hl)                                ;location called user_counter
 ret

counter:
.db 0                                    ;which is this location

int_end:

.end
.end

    Hmm.  So what does our interrupt handler do?  Let's say that we want to have a cursor in our program that will toggle between off and on every $60 interrupts (~ every .5 seconds).  The way we can tell the amount of time that has passed, is by looking at the user_counter RAM location in the main loop of our program:

;***

 ld a,(user_counter)          ;check the interrupt counter
 sub $60                          ;for $60 (the homescreen cursor flashes this often also)
 call z,toggle_cursor         ;if $60, toggle the cursor

;***

toggle_cursor:
 ld (user_counter),a         ;reset counter to zero (A is zero iff toggle_cursor is called)

;***
 

    Let's take a look at the user_counter RAM location: since we want the address to be defined in the interupt handler, and not in asm space, we must find the offset of counter in asm space by subtracting it's address from _asm_exec_ram, then add that offset to _alt_interrupt_exec.  This must be done for all pointers made to point to asm space from an interrupt.  I should stress that these calculations are made during compilation, not at run-time.  Also note that you don't need to exchange shadow registers because that was done for you at $38.
    $38 does alot; take a look at it disassembled using ti86emu.

    An interrupt mode 2 handler will do closely the same thing, but won't run any of the ROM's code and is a little more difficult to set up.  When an im 2 interrupt is signaled, the processor takes the value from the interrupt vector register (register I) and a random byte to form a 16-bit pointer to the address that it loads to get the interrupt handler address.  Since the random byte is least significant, there are 256 possible RAM locations where the calc could choose to get its interrupt handler address from.  So, to make it point to our handler, we must create an interrupt vector table that tells the calc where to jump to.  This is accomplished using ldir.
    To load a value into the interrupt vector register, you must transfer it from the accumulator using the instruction <ld i,a> (the only other instruction you can use I with is <ld a,i>).  This value will form the most significant 8-bits of the pointer location, while the least signifcant 8-bits are beyond your control.  <ld a,$90 / ld i,a> will cause (when an interrupt occurs) the processor to jump to an unknown address somewhere between $9000 and $90ff that would contain our vector table.
 

 ld hl,$9000                   ;create vector table at $9000
 ld de,$9001
 ld bc,256
 ld a,$91                       ;that will point to $9191
 ld (hl),a
 ldir

 ld hl,int_start                 ;move interrupt handler to $9191
 ld de,$9191
 ld bc,int_end-int_start
 ldir

 ld a,$90                      ;set up interrupt vector register
 ld i,a
 im 2                            ;and set interrupt mode
 ei

    Your interrupt handler will need to exchange shadow registers once at the beginning, then a second time at the end.  Also, instead of using the normal return instruction at the end, you will need to use <reti>, which will signal to the processor that the interrupt has completed, returning to the suspended code.  Make sure that you change the interrupt mode back to im 1 after you're done, or TI-OS will crash.

 exx
 ex af,af'

;interrupt handler

 exx
 ex af,af'
 ei
 reti

    Interrupt mode 0 is the last maskable external interrupt level.  What you do, is load an instruction into the I register, and that instruction will execute every interrupt.  Useless.  The only single byte instructions that could be used are the rst instructions, but these are in ROM and the only rst instruction that is defined as an interrupt handler is <rst $38>.  If you wanted to, you could load <rst $38> into I, but that would be pointless as im 1 already does this.  (If you want to be super cool, load a <halt> instruction into I for im 0 ;-)

    Another kind of external interrupt is a non-maskable interrupt (NMI), meaning it's an interrupt that can not be disabled by <di>.  These, to the best of my knowledge, do not exist on the 86, but if they did, their handler would be located at $0066.

    Each interrupt level has a priority level, which means that if more than one interrupt is waiting to be run, the higher priority interrupt will run first or on top of the lower priority.  The priority levels from top (highest) to bottom (lowest):

1) Trap - run if the processor comes across an undefined opcode like $edfe (internal)
2) NMI - external
3) im 0 - external and internal
4) im 1 - external
5) im 2 - external
6) Timer 0 - internal
7) Timer 1 - internal
8) DMA Channel 0* - internal
9) DMA Channel 1* - internal
10) Clocked Serial I/O Port* - internal
11) Asynchronous SCI Channel 0* - internal
12) Asynchronous SCI Channel 1* - internal

*the speed of occurance of these interrupts is increased in turboed calculators

    <di> will disable all interrupts but the NMI and Trap interrupts.

    There are two bits you should know about as well called the interrupt flip flop bits (IFF1 and IFF2) that contain interrupt status information.  IFF1 will be reset if interrupts are disabled and set if interrupts are enabled, and will be copied into IFF2 during a NMI.  The four instructions <ld a,i>, <ld i,a>, <ld a,r>, <ld r,a> will copy IFF2 into the parity overflow flag.