This document describes the Handy interrupts and CPU Steep capability. It contains the following sections:
When a hardware interrupt occurs or the CPU executes a BRK
instruction, the software routine pointed to by the BREAK
vector is called atter 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 exzecuting 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 fhe 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:
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).
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 1o 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, betore 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 Suzyl! 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 powered 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
The Handebug monitor (which is the part of Handebug that
lives in the 6502) controls hardware and software
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 inerrupt 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: