Best viewed in 1024x768

Advanced Interrupts

Welcome to the advanced interrupt section. This section extends on the previous section because the previous interrupt section was just getting too damn long! ;) Anyway you will probably like this section because it includes a lot code snippets and useful data that helps explain the inner workings of the calculator.

Warning: Matt figured out most of this by himself and warned that it might not all be correct.  I haven't verified any of this myself, except the isr (interrupt service routine) templates.  They work nicely.  If you have any comments or corrections, email me and I'll fix it.

There are two types of hardware events that causes interrupts.. the timer interrupts and the ON key interrupts. Basically a 200 hertz timer causes an interrupt, and so does the ON key. There is also a possibility that other events cause an ON key interrupt, although I don't know why or how. Port 3 seems to control all this mayhem. With Port 3, you can check the status of the ON key, the timer interrupt, the ON key interrupt, and the LCD status. This is (as far as I know) the same for the TI-86 as the TI-85. Thus when reading from Port 0:

Read    0000....

Bit 3 = 1: ON not pressed
        0: ON pressed now
Bit 2 = 1: Timer interrupt happened
        0: timer interrupts hasn't happened
Bit 1 = 1: LCD is on
        0: LCD is off
Bit 0 = 1: ON interrupt has happened
        0: ON interrupt hasn't happened
And when we are writing to Port 3 we appear to be writing either 1 byte at a time, or 2 bytes at a time. Some bytes and there functions are listed below: The TI-86 has no NMI's!
This is worth mentioning. If you read the Z80 book you know that there are two types of interrupts on any Z80 platform, Non-Maskable Interrupts or Maskable Interrupts. Basically an NMI is an interrupt which cant be stopped by a halt, a di, anything, it will be executed no matter what. But when an NMI occurs, it jumps to $66. As you see $66 is in the middle of the interrupt routine. This basically proves that there are no NMI's. If you still don't believe me then try this -

DI
HALT

Well the calc wouldn't lock up if the ON interrupt was an NMI because it should jump to the int handler routine no matter what happens. But lo and behold, it simply crashes. That's nice.

The reset - initial routine ran at $00
Right when you put your batteries in your calc, it's alive! Yes, it will start executing assembly code immediately. First off the Z80 will immediately start executing code at the start of memory at $0000. That is:

0000  3e57      ld      a,$57           ;RST 0
0002  d304      out     ($04),a
0004  c32e0e    jp      $0e2e
Port 4 has something to do with the power on routine, it probably signals the TI hardware to do it's things. It is just speculation, however, no one really knows what it does exactly (expect Pat, of course). Anyway, thats not really important. Let's see, after outputting $57 to Port 4 it jumps to $0E2E:
0e2e  0600      ld      b,$00           ;RST 0 continued
0e30  319ffe    ld      sp,$fe9f        ;sp=$FE9F
0e33  10fe      djnz    $0e33
0e35  dd210100  ld      ix,$0001        ;inc sp
0e39  dd39      add     ix,sp
0e3b  ddf9      ld      sp,ix
0e3d  30f4      jr      nc,$0e33        ;if no overflow, delay and do it again
0e3f  31cefb    ld      sp,$fbce        ;sp=$FBCE
0e42  3e57      ld      a,$57
0e44  d304      out     ($04),a
0e46  3e7c      ld      a,$7c           ;set screen memory at 3C00
0e48  d300      out     (scrOffset),a
0e4a  ed56      im      1
0e4c  d307      out     (linkport),a
0e4e  3e0a      ld      a,10            ;set contrast
0e50  d302      out     (contrast),a

0e52  3ec0      ld      a,$c0           ;set default link port state (both lines high)
0e54  d307      out     (linkport),a

0e56  97        sub     a                       ;out (3),0
0e57  d303      out     ($03),a
0e59  3e01      ld      a,$01           ;out (3),1
0e5b  d303      out     ($03),a
0e5d  fb        ei                  ;enable interrupts
0e5e  76        halt                ;wait for ON key interrupt
0e5f  7e        ld      a,(hl)      ;print ASCIIZ string.
0e60  b7        or      a
0e61  2806      jr      z,$0e69
0e63  cd933f    call    _putc
0e66  23        inc     hl
0e67  18f6      jr      $0e5f
0e69  c9        ret
As you see, it initializes the basic Z80 system stuff - the stack, the linkport, the contrast, the screen display area. It then sends $00 $01 to Port 3 and waits for the ON key interrupt. Once the ON keyt interrupt occurs (you press the ON key), it jumps to $38, branches off at $69 since it is in the middle of an ON key interrupt, jumps to $8F, calls $ When the ON interrupt occurs it branches at $8F:
0066  db03      in      a,($03)
0068  1f        rra                 
0069  3824      jr      c,$008f    ;On interrupt has happened - goto 008f(calc on,reset?)
006b  1f        rra
006c  3828      jr      c,$0096   ;if LCD is on and On interrupt hasnt occured,goto 0096
006e  1816      jr      $0086     ;if neither set, end interrupt
Here is $8F:
008f  cdd30b    call    $0bd3                   ; On key pressed - go here
0092  3e0a      ld      a,$0a                   ; On has been pressed
0094  18da      jr      $0070                   ; Finish interrupt
$0BD3 (calc on, reset):
0bd3  0600      ld      b,$00
0bd5  217a09    ld      hl,$097a        ;wait until on key status is the same
loop:
0bd8  db03      in      a,($03)         ;for a certain period of time.
0bda  e608      and     8
0bdc  b8        cp      b
0bdd  47        ld      b,a
0bde  20f5      jr      nz,$0bd5
0be0  2b        dec     hl
0be1  7d        ld      a,l
0be2  b4        or      h                       ; Check if HL = 0
0be3  20f3      jr      nz,$0bd8        ; If not, loop back to loop:

0be5  b8        cp      b                       ;if on was pressed, goto 0bfc
0be6  2814      jr      z,$0bfc

;on NOT pressed
0be8  db06      in      a,(RAMport)            ;if we turned off from 0c5c, goto 0e52
0bea  b7        or      a
0beb  ca520e    jp      z,$0e52
0bee  fdcb095e  bit     onRunning,(iy+onflags) ;if we're on, return
0bf2  c0        ret     nz
0bf3  e1        pop     hl                     ;if on not pressed and calc is off,
0bf4  97        sub     a                      ;end interrupt.
0bf5  d303      out     ($03),a                ; $00 $01  - Wait for ON key interrupt, stay off
0bf7  3e01      ld      a,$01
0bf9  c38800    jp      $0088

;on pressed
0bfc  db06      in      a,(RAMport)     ;if we turned off from 0c5c, goto 0ced
0bfe  b7        or      a
0bff  caed0c    jp      z,$0ced ; Init routine
0c02  fdcb125e  bit     shift2nd,(iy+shiftflags)    ;if not 2nd, turn calc on.
0c06  2861      jr      z,$0c69                     ; Calc on routine
0c08  3e02      ld      a,$02                       ; 2nd pressed??
0c0a  d303      out     ($03),a
0c0c  fdcb0f7e  bit     7,(iy+$0f)
0c10  2806      jr      z,$0c18
0c12  fdcb0856  bit     APDable,(iy+APDflags)
0c16  2832      jr      z,$0c4a         ;turn calc off

; Not from APD but [2nd][on]
0c18  cdb93d    call    $3db9           ;b,662f - _flushallmenus, fill bottom of screen
                                                                        ;from $c29c
0c1b  cdbf3d    call    $3dbf           ;1,44e3 - cursor stuff
0c1e  fdcb01e6  set     4,(iy+$01)
0c22  cd1e0e    call    $0e1e
0c25  fdcb01a6  res     4,(iy+$01)
Now the clever one would be like.. WTF? How can an ON interrupt occur but the ON key is not pressed? That is what I am wondering. The answer - there is a possbility that an ON interrupt is not only caused by the ON key being pressed, but also pressing "Down-Left", remmeber that little Down-Left bug? I mean, that damn loop at $0BD3 probably is what causes the calc to grind to a stop whenever Down-Left is pressed. Weird, ehh? There might be other things that cause an ON interrupt, but I have no idea.

If the calculator is being turned on (ON key pressed) a jump to $0C69 is made, the calc_on routine. Two things can cause a calculator to shut off. [2nd][ON] or APD expiring. If the APD is expiring (see $0C12 and $0C16) it skips to $0C4A, which skips part of the calc_off routine like clearing the screen.. If [2nd][ON] was pressed, the routine falls through at the next instruction at $0C25. Let us take a look at this mysterious calc_off routine:

calc_off:
0c29  fdcb237e  bit     7,(iy+$23)                    ;call user off routine
0c2d  c4c53d    call    nz,user_off
0c30  b7        or      a
0c31  f5        push    af
0c32  21eec3    ld      hl,$c3ee                      ;res 2,(iy+onflags)
0c35  cb56      bit     2,(hl)                        ;if it was set, call _clrScrn
0c37  cb96      res     2,(hl)
0c39  c4cb3d    call    nz,_clrScrn     ;$3dcb
0c3c  f1        pop     af
0c3d  3805      jr      c,$0c44
0c3f  cdfb0d    call    getRAMcksum     ;getRAMcksum
0c42  1803      jr      $0c47
0c44  215aa5    ld      hl,$a55a
0c47  22d8c1    ld      (_onCheckSum),hl             ;save RAM checksum
0c4a  3e01      ld      a,$01
0c4c  d303      out     ($03),a                      ;$01 - Wait for ON key interrupt I guess
0c4e  fdcb129e  res     shift2nd,(iy+shiftflags)
0c52  fdcb099e  res     onRunning,(iy+onflags)
0c56  08        ex      af,af'
0c57  d9        exx
0c58  fb        ei
0c59  76        halt                    ;minumum power loop
0c5a  18fd      jr      $0c59
Well there is the user_off routine! I never mentioned this before but it is the same concept as the user interrupt routine, except it is only called when the calc is shut off and not 200hz. Plus you must set different flags, etc, for its functionality. I might include some sample code for the user_on and the user_off routines in an Advanced TI-OS section.

What's with these flags? Your probably wondering. If the calc is off then it is OFF, right? Well the ON key can be pressed while the calculator is already ON, so they use these flags to determine if the calculator is really off or really on. If the calculator is ON you dont want it executing a Calc_ON routine everytime the ON key would be pressed.

Weirdness more when you see a jr $0C59. I mean, if this was a one time thing lets press the ON key and only one call to the interrupt handler, you wouldn't need this type of loop. But I assume some redudant calls are made to the $38 and it seems to return into this infinite loop until the ON key has actually been pressed. This futhurs me to believe that something else can trigger an ON interrupt other then the ON key being pressed.

Note about HALT: When the HALT instruction executes it puts the processor in a low power mode, consuming only 10 uA (10 millionths) of an AMP. Basically all HALT does is wait for an interrupt, and execut NOP's in the mean time to maintain memory refresh logic.

How to shut off the calc... QUICK!
The following code below does it well. It mimicks what happens in the interrupt handler. The DI disables interrupts which is the state of the calc in the interrupt handler. We send $01 to Port 3, reset those two flags, and wait for the ON interrupt. Simple!

 di
 ld      a,$01
 out     ($03),a                      ;$01 - Wait for ON key int
 res     shift2nd,(iy+shiftflags)     ;Clear 2nd status
 res     onRunning,(iy+onflags)       ;Calc no longer running
 ei                                   ; Enable interrupts
 halt                                 ; Wait for on key interrupt
Now here is a program I wrote that demostrates this:
#include "ti86asm.inc"

.org _asm_exec_ram

InnerLoop:
        
        call _getkey            
        cp kEnter
        jr nz, InnerLoop

; Shut off Calc
        di
        ld      a,$01
        out     ($03),a                      ;$01 - Wait for ON key int
        res     shift2nd,(iy+shiftflags)     ;Clear 2nd status
        res     onRunning,(iy+onflags)       ;Calc no longer running
        ei                                   ; Enable interrupts
        halt                                 ; Wait for on key interrupt

        ld a, '!'
        call _putc
        ret

.end
To use this program simply compile it and run it. The run indicator goes but nothing happens. It is simply waiting for the Enter key. Now press the enter key. Wow! That shut off real quick, didn't it?!? But notice how the ! never gets displayed on the screen. It doesnt return from the interrupt, dude. It branches to another part of the routine that turns on the calc and returns to the homescreen.

Calc-on routine:
Now lets see how the calculator turns ON. If you will look back from earlier at $0C02 and $0C06, it knows the ON key has been pressed, so it simply checks the 2nd key status. If the ON key was pressed but the second key was not, it will jump to $0C69, the calc_on routine, which is:

0c69  cd9b01    call    initTimeoutCnt
0c6c  fdcb095e  bit     onRunning,(iy+onflags)  ;if calc alreaday on, signal that
0c70  2059      jr      nz,$0ccb                ;on key was pressed and ret.
0c72  fdcb09de  set     onRunning,(iy+onflags)  ; The calc is now officially on
0c76  3e03      ld      a,$03
0c78  320bc0    ld      (_APDWarmUp),a
0c7b  21edc3    ld      hl,$c3e5+8
0c7e  cb66      bit     4,(hl)
0c80  202b      jr      nz,$0cad
0c82  fdcb0f7e  bit     7,(iy+$0f)
0c86  2805      jr      z,$0c8d
0c88  cb56      bit     2,(hl)
0c8a  ca230d    jp      z,$0d23
0c8d  ed7bd6c1  ld      sp,(_onSP)
0c91  cd9505    call    $0595
0c94  fdcb0c76  bit     6,(iy+$0c)
0c98  c4d13d    call    nz,$3dd1
0c9b  cdd00c    call    $0cd0
0c9e  fdcb235e  bit     3,(iy+$23)  ;call user on routine
0ca2  c4d73d    call    nz,$3dd7
0ca5  3e0b      ld      a,$0b       ;turn on screen
0ca7  d303      out     ($03),a
0ca9  fb        ei                  ;enable interrupts
0caa  c38502    jp      $0285
First it checks to see if the ON key wasn't pressed while the calculator is already ON. If the calculator is ON and the ON key is pressed, it basically ends the routine by jumping somewhere else that sets the onInterrupt flag and returns from the interrupt handler.

It also seems to mess with the APD and the stack. After that is calls the user ON routine, sends $0B to Port 3 which is basically normal mode (i.e. Screen on, interrupts active, etc). It then enables the interrupts and jumps to $0285, which is basically the Home Screen.

So that is what happened when you ran that example program. It didnt return from the interrupt handler to print the '!' character, it went to the homescreen. If you wanted your program to be able to shut off and when ON is pressed it would return to the program, the calc you would either have to override the default interrupt handler with IM 2 (ahh! - pretty stupid) or simply do not return from the user on routine.

Fixing the Down-Left Bug
Jimmy Mardell posted a fix some time earlier that describes three ways to fix the down-left bug. Since it isn't posted on his web page, I will post it here. I slightly modified it for understandability. I am sure he doesn't mind :-)


Jimmy Mardell's Down-Left Bug Fix


I've made some research on what causes the famous down-left freeze bug (or is it a feature!?) and how to get around it when programming. I hope some programmers find this information useful - the routines below is at least used in the upcoming Sqrxz 1.0.

So - how do you do to avoid the freeze bug? I've found three majorways to solve the problem:

Disabling interrupts

In most games, this method is enough to avoid the problems. There are two disadvantages doing it this way though. Those are:

The first disadvantage is not much of a problem, most of the time. And the second disadvantage is usually not a problem either. But sometimes you want stuff to go on in the background (a timer for example) and then disabling interrupts is not a good way to prevent the down-left bug.

Set indicOnly, indicflags (SET 2,(IY+$12))

When disassembling the ROM I found that if the second bit at (IY+$12) is set, there will be no call to the routine that check port 1 for keypresses. That routine lies at $01A1, and if that routine is not called, the down-left bug is gone. This is also a very simple way to get around the problem, but it still has the disadvantage that you have to read keypresses through ports. And most of the time when method 1 (Disabling interrupts) isn't enough, it usually means you want to make your own interrupt handler, and then method 3 is best suited for it (seebelow). If you use this method, you MUST reset the flag with RES 2,(IY+$12) before the program terminates - else the calc freezes.

Creating your own interrupt handler

This is the most complex method, which I had some problems with first. Creating an interrupt handler is not much of a problem really. This is easily done with the following code:

 ld hl,$8E00
 ld de,$8E01
 ld (hl),$8F
 ld bc,256
 ldir
 ld hl,inthandler
 ld de,$8F8F
 ld bc,intend-inthandler
 ldir
 ld a,$8E
 ld i,a
 im 2
This code stores a vector table at $8E00-$8F00 and the interrupt handler at $8F8F. Since most of RAM page 1 is free to use (????), this seems to work without problems. The interrupt handler could then look something like
inthandler:
 ex af,af'
 exx
 
 ex af,af'
 exx
 jp $38
intend:
This method is used in Sqrxz 0.9. The problem is that each interrupt ends with a jump to the default interrupt at $38 - which has the down-left bug. So, I tried to replace jp $38 with EI \ RETI (which is how the default interrupt handler ends) but that didn't work - The calc crashed :( To find out what was wrong, I had to play around with the default interrupt handler. Anyway, this is how a "homemade" interrupt handler should look like, that doesn't use the buggy handler in the ROM AND still uses ROM routines for reading keys (without having the down-left bug):
inthandler:
 ex af,af'
 exx 
 in a,(3)
 rra
 push af       ; This three lines are not needed
 call nc,$01A1 ; if you only read keys through ports
 pop af
 ld a,9
 adc a,0
 out (3),a
 ld a,$0B
 out (3),a
 ex af,af'
 exx
 ei
 reti
intend:
It's necessary to send $09 $0B to port 3 when ON is _NOT_ pressed and when ON is pressed, you have to send $0A $0B to port 3 - else the calccrashes. This method works very well - it allows the user to create his own interrupt handler and still use the ROM routines to convert port 1 bit code to scancodes without having the annoying down-left bug. 

Ok, it is back to Matt speaking again ;-). How the above routine works is simple. The Down-Left bug appears to somehow trigger an ON interrupt, I am almost sure of this. Since the above code is the only interrupt handler, it does not handle ON interrupts. It simply calls the getkey routine everytime, whether or not the ON interrupt has occured. It does update the hardware with the status of the ON key by sending $09 $0B or $0A $0B

Speeeeeedy IM 2

When we speak of speedy IM 2 we are talking about an IM 2 with an attitude! Yes. An IM 2 with an attitude. See, all that an interrupt routine requires is the basic exx stuff, the ei and reti at the end, and the $09 $0B or $0A $0B at the end of the interrupt routine which updates the status of the ON key and keeps the calc alive. It is fast because there is no jump to $38. We are the interrupt routine, the whole interrupt routine and nothing but the interrupt routine. Because of this, this speeedy IM 2 routine will only work in asm programs. This is because the standard interrupt routine in TI-OS needs to do other things like update cursor, get keys, etc.. If you use this outside of your program and try to make a TSR out of it will crash the calc. A IM 2 TSR routine was shown in the "Basic" interrupt section. Since we are running in an asm program, we know that we Ram Page 1 will be default set at $8000. Most of Ram Page 1 is free memory, so we will use $8E00 for the vector table of IM 2 and $8F8F for the actual routine. Bit 0 of Port 4 is used to check the status of the ON interrupt.

Here is the Speeeeedy IM 2 template:

#include "ti86asm.inc"

int_addr equ $8F8F                   ; Start of interrupt code

.org _asm_exec_ram                   ; Set instruction counter to $D748 (start of all asm programs)

;====================================================================================================
; Install IM 2 Interrupt Handler
;====================================================================================================
 ld hl,$8E00                         ; Source = $8E00 (Start of vector table)
 ld de,$8E01                         ; Destination = $8E01
 ld (hl),$8F                         ; Set first byte so it is copied to all of them ($8F8F)
 ld bc,256                           ; 256 bytes (size of vector table)
 ldir                                ; (DE) <- (HL), BC=BC-1, Loop till B=0
 
 ld hl,int_copy                      ; Source = interrupt routine
 ld de,int_addr                      ; Destination = $8F8F (as specified in vector table)
 ld bc,int_end-int_start             ; Length of custom interrupt routine
 ldir                                ; (DE) <- (HL), BC=BC-1, Loop till B=0

 ld a,$8E                            ; Set up address of vector table
 ld i,a                              ;  MSB = $8E
                                     ;  LSB = (byte supplied by data bus - random)
 im 2                                ; Set interrupt mode 2

; ------[ program code goes here ]------------

 im 1                                ; Back to Interrupt Mode 1
 ret                                 ; Return to shell or TI-OS

;====================================================================================================
; IM 2 Interrupt Handler code
;=================================