z80:Interrupts

From Learn @ Cemetech
Revision as of 06:16, 5 February 2016 by KermMartian (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigationJump to search

Existing Tutorials

Introduction

Interrupts are exactly as their name states: They interrupt the operation of code to operate other code, occurring at fairly steady intervals. Using interrupts is a good way to keep track of time in games because they operate regardless of what other code is currently being executed. There are two kinds of interrupts: hardware and software. Software interrupts are triggered by the RST command, and won't be discussed in this tutorial. Examples of hardware interrupt use:

Using interrupts

Enabling/Disabling

To enable interrupts, use the EI command. To disable interrupts, use the DI command.

Interrupt Modes

There are three different interrupt modes. To change the interrupt mode use the IM command. Each mode is triggered slightly differently or when triggered performs different actions.

Mode 0

We won't worry about mode 0, as TI-Calculators can't make use of this mode. In mode 0, interrupts are triggered by external hardware. However, since we can't connect external hardware to TI-Calculators, an interrupt is never triggered. You can test this with this code:


   ei
   im 0
   halt   ; captures the last interrupt from previous mode
   halt   ; captures the 1st interrupt generated in mode 0
   im 1
   ret


Note: It is very important not to return to the OS in mode 0! It will cause your calculator to crash. It's better in fact to not ever use mode 0, as if you want to turn off interrupts, use DI.

Mode 1

Mode 1 is the 'default' interrupt mode. It is triggered roughly every 0.007 seconds at a frequency of 140 Hz. It is used by the OS, and messing with this interrupt might not be a good idea. The OS uses it to update the run indicator (which is why it always moves at roughly the same speed), detect input (using GetKey/GetCSC), and various other OSy things.

Mode 2

Mode 2 is similar to Mode 1, as it is triggered in roughly the same frequency, except instead of calling a specific location, it jumps to theoretically any location in memory. This means that we can have it jump consistently to code we want executed every 0.007 seconds.

Setting up a Mode 2 Interrupt

How does a Mode 2 interrupt work? The answer is not so carefully. It retrieves a value from the I register, then the lower byte of the address is chosen at random. However, it gets worse. You'd think that'd be where it jumps to, but instead it loads the byte at that address as the MSB and the byte at the address after as the LSB and the interrupt jumps to that address. Confused yet?

Interrupt Jump Table

So how do we safely install a mode 2 interrupt? First, we need to identify a safe area of ram that has at least 257 bytes free. The location must be somewhere that the MSB of the address is constant ($00, $01,...$FE). This will be our interrupt jump table.

Luckily for us, appbackupscreen is located from $9872 to $9B72. So, if we take $9900 to $9A00 inclusive, we'll have the necessary 257 bytes for our jump table.

Interrupt Location

Next, we need to identify an address like $0000, $0101, $0202,..., $FFFF that is also safe to modify. This location will be the location of our interrupt. We'll write this value to every location in the Interrupt Jump Table, resulting in our interrupt being called all the time.

Again, appbackupscreen provides the perfect location for us to install our interrupt. $9A9A satisfies all of the conditions mentioned above, and is well within the confines of appbackupscreen.

The Interrupt Code

What does the interrupt code look like? Well, it is completely identical to regular z80 code. Anything that can be executed normally can be executed from interrupt code. There are a few things you must do to make interrupt code not crash your calculator, though.

Shadow Registers

Since interrupt code is executed normally like regular code, it modifies the regular registers. A way to get around this to either not modify any registers (extremely hard, most instructions modify some flag or another), or to use shadow registers. Shadow registers are copies of the registers. There are not instructions that modify their values except the swap instructions (EX and EXX). It is common practice for interrupts to swap the registers as one of the first instructions.


   interrupt:
   ; start of the interrupt code
   ; swap AF, BC, DE, and HL with their shadow registers
    ex AF,AF'
    exx


Returning

Interrupts return using the RET instruction. There are the RETI and RETN instructions, but they return from certain types of interrupts not available on TI calculators. Note that if you do put RETI or RETN at the end of an interrupt it will still return, but they cost 6 more T-states plus an additional byte, and so their use is discouraged.

Re-enabling interrupts

We wouldn't want another interrupt to be triggered while our interrupt code is being run, so the first thing to do when interrupt code is called is to disable interrupts with DI. To ensure that our interrupt will be executed again, we need to re-enable interrupts with EI at the end of our code.


   interrupt:
   ; interrupt code
   ; ...
    ei
    ret


A Simple Interrupt

Here's a simple program that will install an interrupt that increases a 16-bit timer. It demonstrates most of the topics discussed above.


   main:
    bcall(_ClrLCDFull)
   ; disable interrupts while we're install ours
    di
   ; set up the interrupt jump table to jump to $9A9A
    ld a,$9A
    ld ($9900),a
    ld hl,$9900
    ld de,$9901
    ld bc,256
    ldir
   ; copy our interrupt to $9A9A
    ld hl,interruptStart
    ld de,$9A9A
    ld bc,interruptEnd - interruptStart
    ldir
   ; set $99 into I
    ld a,$99
    ld I,a
   ; set interrupt mode 2
    im 2
   loop:
   ; re-enable interrupts
    ei
   ; rest of our program
    ld hl,0
    ld (curRow),hl
    ld hl,(counter)
    bcall(_DispHl)
    jr loop
   
   interruptStart:
   ; disable interrupts during our interrupt
    di
   ; swap registers
    ex AF,AF'
    exx
   ; our interrupt code
    ld hl,(counter)
    inc hl
    ld (counter),hl
   
   ; swap back registers
    ex AF,AF'
    exx
   ; re-enable interrupts and return
    ei
    ret
   interruptEnd:
   
   counter:
   .dw 0


Interrupt Ports

Review

Conclusion