Chapter 3 - A Hardware Perspective on 68HC11 Interrupts
The on-chip resources of the 68HC11 include an A/D converter, timer system, pulse accumulator, watchdog timer, serial communications port, high speed serial peripheral interface, and general purpose digital I/O. The 68HC11's interrupts can enhance the performance of these facilities. Interrupts allow rapid response to time-critical events that often occur in real-time measurement and control applications.
This chapter describes how interrupts are implemented on the 68HC11, and how interrupt service routines are simplified by QED-Forth. Using an interrupt requires four simple steps. The interrupt-related programming examples presented in this manual follow these steps:
- Name the registers related to the interrupt using the REGISTER: utility. These register names are used to code the interrupt handler.
- Use QED-Forth or assembly code to define an interrupt handler routine which will be executed every time the interrupt occurs. The interrupt handler must reset the interrupt request flag and perform any necessary actions to service the interrupt event.
- Install the interrupt handler using the QED-Forth word ATTACH.
- Write utility words to enable and disable the interrupt.
The summary at the end of this chapter presents a more detailed version of these steps.
Interrupt Recognition and Servicing
68HC11 interrupts fall into two main categories: nonmaskable and maskable. Nonmaskable interrupts are immediately serviced, regardless of the processor's current state. Events which cause nonmaskable interrupts include resets, execution of illegal opcodes, execution of the SWI (software interrupt) instruction, and an active low signal on the nonmaskable interrupt request pin named /XIRQ.
Maskable interrupts, on the other hand, may be freely enabled and disabled by software. Maskable interrupts may be generated as a result of a variety of events, including signal level changes on a pin, completion of predetermined time intervals, overflows of special counting registers, and communications handshaking.
Recognition and servicing of maskable interrupts are controlled by a global interrupt enable bit (the I bit in the condition code register) and a set of local interrupt mask bits in the hardware control registers. If a local interrupt mask bit is not enabled, then the interrupt is "masked" and will not be recognized. If the relevant local mask bit is enabled and the interrupt event occurs, the interrupt is recognized and its interrupt flag bit is set to indicate that the interrupt is pending. It is serviced when and if the global interrupt bit (the I bit) is enabled. An interrupt that is not in one of these states is inactive; it may be disabled, or enabled but waiting for a triggering event.
When an interrupt is both recognized and serviced, the processor automatically globally disables all maskable interrupts by setting the I bit in the condition code register until the service routine is over. Other maskable interrupts can become pending during this time, but will not be serviced until interrupts are again globally enabled when the service routine ends. (The programmer can also explicitly re-enable global interrupts inside an interrupt service routine to allow nesting of interrupts, but this is not recommended in multitasking applications). Non-maskable interrupts (reset, clock monitor failure, COP failure, illegal opcode, software interrupt, and XIRQ) are serviced regardless of the state of the I bit.
When an interrupt is serviced, execution of the main program is halted. The programming registers (CCR, ACCD, IX, IY, PC) are pushed onto the return stack. This saves the state of execution of the main program at the moment the interrupt became serviceable. Next, the processor automatically sets the I bit in the condition code register. This disables interrupts to prevent the servicing of other maskable interrupts. The processor then fetches an address from the "interrupt vector" associated with the recognized interrupt, and starts executing the code at the specified address. It is the programmer's responsibility to ensure that a valid interrupt service routine, or "interrupt handler" is stored at the address pointed to by the interrupt vector. The interrupt vectors are near the top of memory in the QED-Forth kernel ROM. QED-Forth revectors the interrupts (using jump instructions) to point to specified locations in the EEPROM. The ATTACH utility described below installs a call to the interrupt handler at the appropriate location in the EEPROM so that the programmer's specified handler routine is automatically executed when the interrupt is serviced.
An interrupt handler must reset the interrupt flag bit (not the mask bit) and perform any tasks necessary to service the interrupt. When the interrupt handler has finished, it executes the RTI (return from interrupt) assembly code instruction. RTI restores the CPU registers to their prior values based on the contents saved on the return stack. As explained below, this also re-enables interrupts by clearing the I bit in the CCR (condition code register). Thus other interrupts can be serviced after the current interrupt service routine has completed.
Nonmaskable Interrupts
If a nonmaskable interrupt is enabled, it is serviced immediately upon being recognized. The importance of these interrupts is reflected by the fact that most cause a hardware reset when serviced (please see the "Program Development Techniques" chapter in the QED Software Manual for a discussion of the difference between a hardware reset and the accompanying software restart). The following table gives the name of each nonmaskable interrupt and a description of its operation. They are listed in order of priority from highest to lowest:
Interrupt | Description |
---|---|
Reset | Recognized when the /RESET (active-low reset) pin is pulled low, this highest priority nonmaskable interrupt resets the machine immediately upon recognition and executes the standard QED-Forth restart sequence. |
Clock Monitor Failure | Enabled or disabled via the CME (clock monitor enable) bit in the OPTION register, this interrupt is recognized if the E-clock frequency drops below 10 kHz. It resets the processor hardware and executes a user-defined service routine. QED-Forth installs a default service routine for this interrupt that performs the standard restart sequence. |
COP Failure | After enabling the computer operating properly (COP) subsystem, failure to update COP registers within a predetermined timeout period triggers this interrupt which resets the processor and executes a user-defined service routine. QED-Forth installs a default service routine for this interrupt that performs the standard restart sequence. |
Illegal Opcode Trap | This interrupt occurs when the processor encounters an unknown opcode. QED-Forth installs a default service routine for this interrupt that performs the standard restart sequence. |
SWI | Software interrupts are triggered by execution of the SWI opcode. After being recognized, an SWI interrupt is always the next interrupt serviced provided that no reset, clock monitor, COP, or illegal opcode interrupt occurs. SWI requires a user-installed interrupt handler. |
/XIRQ | Enabled by clearing the X bit in the condition code register, an /XIRQ interrupt is recognized when the /XIRQ (active-low nonmaskable interrupt) pin is pulled low. This interrupt is serviced immediately upon recognition. It requires an appropriate user-installed interrupt handler. |
The service routine for the reset interrupt cannot be modified by the programmer. The service routines for the clock monitor, COP failure, and illegal opcode trap interrupts are initialized to perform the restart sequence, but this action may be changed by the programmer. No default actions are installed for the SWI and /XIRQ interrupts, so before invoking these interrupts the user should install an interrupt handler using the simple ATTACH command described below.
Chapter 8 ("External Interrupts, Resets, Operating Modes, and the COP"), describes the Reset, Clock Monitor, COP, /XIRQ, and IRQ interrupts in more detail.
Servicing Maskable Interrupts
Maskable interrupts are controlled by the I bit in the condition code register (HC11 pp.5-22...5-26). When the I bit is set, interrupts are disabled, and maskable interrupts cannot be serviced. When clear, interrupts can be serviced, with the highest priority pending interrupt being serviced first. In sum, a locally enabled maskable interrupt is serviced if:
- it has been recognized, and
- it has the highest priority, and
- the I bit in the condition code register is clear.
If a maskable interrupt meets these criteria, the following steps are taken to service it. First, the programming registers saved on the return stack. Note that the condition code register, CCR, is one of the registers saved, and that the saved value of the I bit in the CCR is 0. Next, the CPU sets the I bit to 1 to temporarily prevent maskable interrupts from being serviced. Control is then passed to the interrupt handler code pointed to by the contents of the interrupt vector. The interrupt handler clears the interrupt flag bit set by the trigger event and performs any tasks necessary to service the interrupt. It terminates with an RTI instruction which restores the saved values to the programming registers. Execution then resumes where it left off.
Recall that when the interrupt service began, the processor's first action was to store the programming registers on the return stack. At that time, the I bit in the CCR equaled 0 indicating that interrupts were enabled, and the bit was stored as 0 on the return stack. After stacking the machine state, the processor set the I bit to disable interrupts during the service routine. When the programming registers are restored to their prior values by RTI, note that the I bit is restored to its prior cleared state, indicating that interrupts are again enabled. In this manner the processor automatically disables interrupts when entering a service routine, and re-enables interrupts when exiting a service routine so that other pending interrupts can be serviced.
Nested Interrupts
The programmer can explicitly clear the I bit inside an interrupt service routine to allow nesting of interrupts. Make sure that the return stack is large enough to accommodate the register contents placed there by the nested interrupts. The default size of the return stack is 768 bytes, and it can be easily resized.
Interrupt Priority
The servicing order of pending interrupts depends on their priority. The following table lists the fifteen available maskable interrupts in order of highest to lowest priority:
Interrupt | Description |
---|---|
/IRQ | /IRQ is an active-low external hardware interrupt which is recognized when the signal on the /IRQ pin of the 68HC11 is pulled low (F1 p.2-4). |
Real Time Interrupt | The RTI provides a programmable periodic interrupt (F1 p.6-8). |
Input Capture 1 | An IC1 interrupt is recognized when a specified signal transition is sensed on port A, pin 2 (F1 p.7-1 ff.). |
Input Capture 2 | An IC2 interrupt is recognized when a specified signal transition is sensed on port A, pin 1 (F1 p.7-1 ff.). |
Input Capture 3 | An IC3 interrupt is recognized when a specified signal transition is sensed on port A, pin 0 (F1 p.7-1 ff.). |
Output Compare 1 | An OC1 interrupt is recognized when the main timer's count becomes equal to OC1's timer compare register (F1 p.7-4 ff.). |
Output Compare 2 | An OC2 interrupt is recognized when the main timer's count becomes equal to OC2's timer compare register (F1 p.7-4 ff.). |
Output Compare 3 | An OC3 interrupt is recognized when the main timer's count becomes equal to OC3's timer compare register (F1 p.7-4 ff.). |
Output Compare 4 | An OC4 interrupt is recognized when the main timer's count becomes equal to OC4's timer compare register (F1 p.7-4 ff.). |
I4O5 | Depending on its configuration, an I4O5 (input capture 4/output compare 5) interrupt is recognized when a specified signal transition is sensed on port A, pin 3, or when I4O5's timer compare register is equal to the main timer's count (F1 p.7-1 ff.). |
Timer Overflow | A TOF interrupt occurs when the free-running count in the TCNT (timer count) register overflows from FFFFH to 0000H (F1 p.7-1). |
Pulse Accum Overflow | A PAOVF interrupt occurs when the PACNT (pulse accumulator count) register overflows from FFH to 00H (F1 p.7-11 ff.). |
Pulse Accum Edge | A PEDGE interrupt occurs after a signal edge is detected on port A, pin 7 (F1 p.7-11 ff.). |
SPI Event Interrupt | An SPI (serial peripheral interface) interrupt occurs after a byte transfer is completed, or a write collision or a mode fault is detected (F1 p.10-1 ff.). |
SCI Event Interrupt | An SCI (serial communications interface) interrupt occurs when the transmit data register is empty, or the transmission is complete, or the receive data register is full, or an idle line is detected. The handler must determine which of these four events caused the interrupt (HC11 p.9-16). |
Elevated Priority
After being recognized, a locally enabled maskable interrupt will be serviced when the I bit is clear, and when it has the highest priority among the pending interrupts.
One maskable interrupt can be elevated to receive highest priority servicing. This is accomplished by configuring four priority-selection bits named PSEL0, PSEL1, PSEL2, and PSEL3 located in the HPRIO (high priority) register (F1 pp.6-15...6-16). The default highest priority maskable interrupt is /IRQ. A table in F1, p.6-16 lists the states of the priority selection bits needed to elevate an interrupt's status to the highest priority. After selecting the appropriate code for an interrupt, the following word can be used to implement its priority status change:
HEX 803C REGISTER: HPRIO : ELEVATE.INTERRUPT.PRIORITY ( interrupt.id.according.to.F1.p6-16 %%--%% ) 0F HPRIO CLEAR.BITS \ clear 4 PSEL bits HPRIO SET.BITS \ desired interrupt gets highest priority ;
For example, to elevate the RTI (real time interrupt) which has a priority code of 07H to have the highest priority, execute:
07 ELEVATE.INTERRUPT.PRIORITY
Interrupt Flag and Mask Bits
Each maskable interrupt enabled and disabled by a local mask bit. An interrupt is enabled when its local mask bit is set. When an interrupt's trigger event occurs, the processor sets the interrupt's flag bit.
The local mask bit, as opposed to the global I bit, should be used to enable and disable individual interrupts. In general, you should avoid setting the I bit unless you are sure that you want to disable all interrupts. Time-critical interrupt service routines such as the time-sliced multitasker cannot perform their functions when interrupts are globally disabled.
Some QED-Forth routines globally disable interrupts for short periods to facilitate multitasking and access to shared resources. These routines are summarized in the "Interrupts and Register Initializations" chapter of the QED Software Manual.
Interrupt trigger events can occur whether or not the interrupt is enabled. For this reason, it is common for flag bits to be set before an interrupt is ready to be used. Unless an interrupt's flag bit is cleared before it is enabled, setting the local mask bit will force the system to recognize an interrupt immediately. Unfortunately, the event which set the interrupt's flag bit occurred at an unknown time before the interrupt was enabled. Depending on the interrupt handler's task, this can cause erratic initial behavior, collection of an incorrect initial data point, or begin an improper sequence of events (for example, cause a phase shift in an output waveform). To avoid these problems, it is recommended that you enable an interrupt by first clearing its flag bit and then immediately setting its mask bit.
An Interrupt Flag Bit Is Cleared By Writing a 1
Although mask bits can be set and cleared by storing the desired value in them, flag bits are unusual. Since flag bits are set by trigger events, it is not possible to set them via software. In order to clear an interrupt flag bit, a one must be stored into the flag bit's location.
To clear a specified flag bit, write a pattern to the flag register with a 1 in the bit position of the flag that must be cleared. All of the other flag bits in the flag register then remain unchanged. See HC11 p.10-14 for examples.
QED-Forth's Handling of Interrupts
How QED-Forth Simplifies Interrupts
With QED-Forth it is easy to define an interrupt service routine and ATTACH it to a specified interrupt. You define your service routine in either assembly code or in high level FORTH. Your routine ends with a normal RTS (return from subroutine) in assembly code or with a normal ; in FORTH (as opposed to an RTI instruction). Thus the service routine can be debugged just like any other FORTH word. You then reference your service routine, specify the interrupt, and execute ATTACH to bind the service routine to the interrupt.
The following QED-Forth words have been defined as identifiers for the 68HC11 interrupts:
Kernel word identifier | Interrupt description |
---|---|
SCI.ID | Serial communications interface |
SPI.ID | Serial peripheral interface |
PULSE.EDGE.ID | Pulse accumulator edge detection |
PULSE.OVERFLOW.ID | Pulse accumulator overflow |
TIMER.OVERFLOW.ID | Timer overflow |
IC4\OC5.ID | Timer input capture 4/output compare 5 |
OC4.ID | Timer output compare 4 |
OC3.ID | Timer output compare 3 |
OC2.ID | Timer output compare 2 |
OC1.ID | Timer output compare 1 |
IC3.ID | Timer input capture 3 |
IC2.ID | Timer input capture 2 |
IC1.ID | Timer input capture 1 |
RTI.ID | Real-time interrupt |
IRQ.ID | IRQ external pin |
XIRQ.ID | IRQ external pin (pseudo-nonmaskable) |
SWI.ID | Software interrupt |
ILLEGAL.OPCODE.ID | Illegal opcode trap |
COP.ID | COP failure (reset) |
CLOCK.MONITOR.ID | Clock monitor failure (reset) |
The kernel word ATTACH expects the 32-bit extended code field address (xcfa) of your service routine under an interrupt identifier on the stack, and it sets up the interrupt vector in EEPROM so that subsequent interrupts will execute the specified service routine. The code installed by ATTACH includes the RTI instruction that terminates the interrupt service sequence.
For example, if you define a word called TIMER.SERVICE to respond to the timer output compare #4 interrupt, you simply execute
CFA.FOR TIMER.SERVICE OC4.ID ATTACH
to vector the output compare 4 interrupt so that it will call the TIMER.SERVICE routine.
Note that the OC2 interrupt is used as the multitasker's timeslice clock. Before using this interrupt for another purpose, make sure that you don't need the services provided by the timeslicer which supports the multitasking executive, the elapsed time clock, and the BENCHMARK: utility (see the glossary entry for START.TIMESLICER).
QED-Forth Routines that Temporarily Disable Interrupts
Certain QED-Forth routines temporarily disable interrupts by setting the I bit in the condition code register. These routines are summarized in the "Interrupts and Register Initializations" chapter of the QED Software Manual. A review of that list will assist you in planning the time-critical aspects of your application.
Interrupt Latency
The time required between the processor's initiation of interrupt servicing and the execution of the first byte of the specified service routine is called the interrupt latency. Most of the 68HC11's interrupts have an inherent latency of 12 machine cycles during which the registers are saved on the return stack and the interrupt vector is fetched. This corresponds to 6 microseconds (abbreviated as usec) if the board is clocked at 8 MHz, and of course this time is halved if the board is clocked at 16 MHz. QED-Forth's interrupt latency is longer because the interrupts are re-vectored via the EEPROM to allow the programmer to modify the vectors, and because the page must be changed. The latency of service routines installed with ATTACH is 34 machine cycles, or 17 usec with an 8 MHz crystal. That is, the first opcode of the user's service routine is executed 17 usec after interrupt service begins. After the service routine's concluding RTS executes, an additional 20 cycles (10 usec) lapses before the originally interrupted program resumes execution. 12 of these cycles are accounted for by the RTI instruction, and the other 8 cycles are required to restore the original page.
Summary
Maskable interrupts have a local mask bit which enables and disables the interrupt, and a flag bit which is set when a trigger event occurs. For maskable interrupts, an interrupt triggering event is recognized when the flag and mask bits are both set. In order to avoid premature recognition of a maskable interrupt, it should be enabled by first clearing its flag bit and then setting its mask bit. Once an interrupt has been recognized, it will be serviced if it is not masked by the I bit (or, for the /XIRQ interrupt, by the X bit) in the CCR. Multiple pending interrupts are serviced in the order determined by their priority. Interrupts have a fixed priority, except that the programmer may elevate one interrupt to have the highest priority. Nonmaskable interrupts always have the highest priority when they are recognized, and are immediately serviced.
When an interrupt is serviced, the machine state (specified by the programming registers) is saved and the I bit in the CCR register is set. This prevents other pending interrupts from being serviced. The CPU then executes the appropriate interrupt handler routine. The interrupt handler is responsible for clearing the interrupt flag bit. For most interrupts this is accomplished by writing a one to the flag bit. After completing its tasks, the interrupt handler executes an RTI instruction to restore the machine state, subsequently clearing the I bit in the CCR. The CPU is now ready to service the next, highest priority, pending interrupt. If there is none, processing of the main program continues.
QED-Forth simplifies the details of using interrupts. Implementing an interrupt using QED-Forth tools is a 4 step process:
- Define constants representing registers related to the interrupt being implemented. Also define bit masks that are useful for setting and clearing flag, mask, and other relevant bits.
- Write an interrupt handler using Forth or assembly code. The handler should use the constants and masks defined in step 1 to clear the interrupt's flag bit, and should end with a ; or RTS instruction.
- Write an installation word for the interrupt handler. This word will use the ATTACH command which causes QED-Forth to install your interrupt handler into the appropriate interrupt vector in EEPROM. The code installed by ATTACH supplies the RTI (return from interrupt) instruction that correctly terminates the service routine.
- Write words to enable and disable the interrupt. Enabling the interrupt is accomplished by clearing the interrupt's flag bit and then setting its mask bit. It may also be necessary to clear the I bit in the CCR to globally enable interrupts. This can be accomplished by executing the kernel word ENABLE.INTERRUPTS.
The following chapters explain how to define and install interrupt service routines that enhance the usefulness of many of the 68HC11's hardware features.