# Appendix 2: Interrupts and CPU Sleep

This document describes the Handy interrupts and CPU Sleep capability. It contains the following sections:

- Handy Interrupts
- CPU Sleep
- Using Interrupts with Handebug

## 1. Handy Interrupts

When a hardware interrupt occurs or the CPU executes a BRK instruction, the software routine pointed to by the BREAK vector is called after the processor status byte and the return address are pushed on the stack. The Interrupt Disable status flag is set before interrupt code begins executing, which means that unless we clear the flag within our interrupt code we won't be interrupted by a hardware interrupt until we execute an RTI instruction (except of course for NMI, which overrides everything).

The break-handler routine must first check whether the B bit is set in the status byte and if so then a BRK was executed so the software break routine should be called. In the Handy debugging environment, BRK causes us to break to the monitor, which notifies the host program on the Amiga that we've stopped executing and are awaiting its command.

On the other hand, if the break-handler routine finds that the B flag is not set, this was an interrupt generated by the hardware. The software reads either the INTSET or INTRST registers (they have duplicate information) and then checks each bit to see which timer (or timers) generated the interrupt. If a bit is set the routine associated with that timer is called. To acknowledge that we have processed the interrupts, we have to reset the bits by writing to the INTRST register. If we write to INTRST with the value just read from INTSET, then that clears the bits that we're about to process. Also, this leaves undisturbed the other bits, which themselves may have become set since we read the INTSET register.

Here's something interesting about interrupts and the interrupt flags registers: the presence of non-zero bits in the interrupt flags registers is what causes the interrupt to occur. A simple implication of this is that if we set the register to zero (by writing an $FF to INTRST) then we won't get interrupted again until a new interrupt comes along. A more interesting implication is that we can force an interrupt to occur by writing a non-zero value to the INTSET register! But because the presence of non-zero bits in the Interrupt Status Register causes an interrupt, if a timer times out during the processing of interrupt code, after you've read the interrupt bits and cleared them by writing to INTRST, then the bit will be set in the status register and very soon after you exit your interrupt processing code you will be interrupted again, which is what we want to have happen.

If you want to disable an interrupt, you clear its enable bit. If you want to make sure that you're not interrupted at all by that interrupt, you should also clear its bit in the INTSET/RST register.

Timers and serial are the only things that generate interrupts (other than NMI).

While in interrupt code:

- Don't use the math without allowing the current calculations to finish, saving the result, doing the math, then restoring the old result
- Don't use any of the sprite hardware without checking done it (you can also set a bit which asks sprite engine to stop at end of current sprite)
- In general, we can read or write only unrestricted read/write bytes, and only after checking status bit that controls those fields

You enable the timers by setting the ENABLE bit of the timer's static control byte. You enable the serial port by setting either the Transmitter Interrupt Enable (TXINTEN) or Receiver Interrupt Enable (RXINTEN) bit in the Serial Control Register (SERCTL).

## 2. CPU Sleep

CPU Sleep is good. CPU Sleep is what you should do with the CPU when you've got nothing more to do until some other event occurs. When you've nothing to do, causing the CPU to go to sleep means that battery life is saved and other good effects happen too. The two most common reasons for putting the CPU to sleep: you've done everything you need to do until the next Top Of Frame, so sleep until then; you've got nothing more to do until Suzy is done painting sprites, so sleep until then.

Regarding CPUSLEEP, even if the CPU has set the Interrupt Disable flag in the processor status byte, you'll wake up out of sleep.

In this code sample, we want to sleep until Suzy is done, although we want to continue processing interrupts as they happen. What we need is a way to wake up after interrupts, realize that Suzy isn't done yet, and go back to sleep. We presume the following: the interrupt routine will decrement the byte named Interruptus. We initialize this byte to $00 and then sleep. If we awaken because of an interrupt, the interrupt router routine will decrement Interruptus, setting it to $FF, before we get around to checking it. We can then test whether the byte is still zero and if not then we know that we were awakened at least by an interrupt. What we don't know is whether or not Suzy is finished too. But we can find out: if Suzy has awakened us, then every time we try to sleep again we won't sleep until we write to the Suzy Wakeup Acknowledge register. These are Suzy's rules, so obey! So as we continue looping, sooner or later we'll hit sleep, hit the next instruction, find Interruptus still equal to $00 because no interrupts occurred, at which time we can presume that it was Suzy who woke us up. Finally, we write the Suzy Wakeup Acknowledge register so that Suzy will stop waking us up until she once again has good reason to do so. The only thing that still makes me nervous is whether or not we need at least one NOP after the STZ CPUSLEEP. Glenn feels that we're guaranteed that interrupt code will get control immediately, before the very next CPU instruction can be executed. DaveN wasn't so sure, and I'm standing in the mist. Please clarify for me.

```
SuzySleepyTime

;----   SuzySleepyTime waits for Suzy to finish but allows interrupts
;----   to occur and be processed.
;----   Note that this routine disturbs no registers!

        STZ     Interruptus
        STZ     CPUSLEEP        ;When we write to this location we sleep

;----   When we awaken, either an interrupt came in while we
;----   were sleeping or Suzy finished its sprite work
;----   We check if we are awake due to sleep interruptus by
;----   checking the Interruptus field; if negative, interrupt
;----           occurred so go back to sleep waiting for Suzy to show up
;----           unaccompanied by interrupts

        BIT     Interruptus
        BMI     SuzySleepyTime

;----           If we get here, we were awakened not by interrupts and
;----           therefore it must have been Suzy!!

        STZ     SDONEACK        ; Tell Suzy that we got the Suzy Wakeup call

        RTS
```

Here's a sample piece of code that busy-waits until Suzy is done. This is not the suggested way, as battery power is consumed while waiting, but if you know that it's a very short wait then this may be the best way to get the job done.

```
WaitForSuzyDone:
        LDA     SPRSYS
        AND     #SPRITE_DONE
        BNE     WaitForSuzyDone
```

## 3. Using Interrupts with Handebug

The Handebug monitor (which is the part of Handebug that lives in the 6502) controls hardware and software interrupts.

When a software BRK is executed, the monitor warm-starts and the communication link with the Amiga is reestablished. A BRK might be executed for two reasons: either the programmer put a BRK instruction in his or her program, or Handebug put a BRK as part of the breakpoint implementation.

The monitor traps both software and hardware interrupts, of course, although the monitor doesn't use any of the hardware interrupts. Instead, if a hardware interrupt occurs the monitor vectors to a handler for that interrupt. This is accomplished by jumping indirect through a table of interrupt routine addresses. The name of the table is IntTable and is defined in the file interrupt.i. The first two bytes of this table will contain the address of the routine that will be called if interrupt 0 occurs. The next two bytes are for interrupt 1, et cetera. When Handebug boots up, these vectors are set to point to a "safe" routine. Also, every time the Handebug GO command is selected the vectors are reset to the safe routine. A program that begins to execute can then set any of these vectors to point to an alternate routine, which routine will then be called if the interrupt occurs. If the program executes a BRK or hits a breakpoint, and then the programmer selects the Handebug CONTINUE command the vectors will remain as set by the programmer.

NOTE: currently (31 Aug 88) these vectors are reset every time you select the Handebug CONTINUE command, because GO isn't implemented yet! This means that you can't halt the NMI button and then halt CONTINUE and expect your Interrupt code to be called.

You can cause an interrupt routine of your own to be called by following these steps:

- Save the state of the Interrupt Disable flag
- Set the Interrupt Disable flag
- Set the IntTable interrupt entry to the address of your handler
- Restore the Interrupt Disable flag to its previous state
- Your handler must save and restore A, X and Y if it uses them
- Your handler must end with an RTS
