Real Time Programming

This chapter provides an introduction to real time
programming. You’ll learn:

About the timeslice clock and how to use it;

All about interrupts, and how to use them to respond to events.

Intro:
Interrupts, Multitasking and Timely Services

Kernel
Services

Autostart,
initialization, orderly shutdown

Autostarting
(from QED Software Manual)

New C #include
Files to Set and Clear the Priority Autostart

Two new
include-able files are now available in the \fabius\include\mosaic directory to
simplify the setting or clearing of the PRIORITY.AUTOSTART vector. Simply
including the file named

SET_AUTO.H

in the
application's C source code file causes MAIN to be automatically executed each
time the QED Board is powered up or restarted. This is equivalent to typing

CFA.FOR MAIN
PRIORITY.AUTOSTART

from the terminal
after the *.txt file has been sent to the QED Board, as explained in the
documentation (for example, in the Turnkey Application Program chapter of the
Getting Started With C manual).

Similarly,
including the file named

CLR_AUTO.H

in the
application's C source code file erases the priority autostart vector at the
top of page 4. As before, the priority autostart may also be erased by typing

NO.AUTOSTART

from the terminal.

The names of these
files are:

\fabius\include\mosaic\set_auto.h

\fabius\include\mosaic\clr_auto.h

These files are
included in the latest versions of the GUI (Graphical User Interface) Toolkit
and the ATA Flash Card Software Package. Contact Mosaic Industries for more
information.

Changes in
Priority Autostart

To the programmer,
the functionality of priority autostart is the same as before. It allows a
specified function to be automatically executed each time the QED Board powers
up or resets. In the QED-FLASH Board, the C functions PriorityAutostart() and
NoAutostart() and the corresponding Forth functions PRIORITY.AUTOSTART and
NO.AUTOSTART are now "flash-smart". These routines check whether the
DIP switches 3 and 4 are both asserted (which indicates that flash is in socket
S1). If so, they write or erase the autostart pattern at the top of page 4 in
flash. Note that an autostart routine should not include a call to
PriorityAutostart() or PRIORITY.AUTOSTART. This would cause the autostart
pattern to be written to flash upon each startup or reset, which might
eventually exceed the 10,000 write/erase cycle limit of the flash chip.]]]

The Timeslicer and Task Switching

The Built-In Elapsed Time Clock

The QCard Controller’s multitasking executive maintains an
elapsed time clock whenever the timeslicer is active. Please consult the
TIMEKEEP.C program in the \MOSAIC\DEMOS_AND_DRIVERS\MISC\C EXAMPLES
directory for examples of using the elapsed time clock.

Your program can start the timeslice clock by calling the
function:

StartTimeslicer()

The timeslicer increments the long variable named TIMESLICE_COUNT
each timeslice period. The default timeslice period is 5 milliseconds (ms),
and this can be modified by calling

ChangeTaskerPeriod()

as described in the Control-C Glossary. The function

InitElapsedTime()

sets TIMESLICE_COUNT
equal to zero. The function

ReadElapsedSeconds()

returns a long result representing the number of elapsed
seconds since InitElapsedTime()
was called.

To attain the full 5 millisecond resolution of the elapsed
time counter, we can write a simple function that converts the TIMESLICE_COUNT
into elapsed seconds as well as the number of milliseconds since the last
integral second. For example, let’s examine some code from the TIMEKEEP.C
file in the \MOSAIC\DEMOS_AND_DRIVERS\MISC\C
EXAMPLES directory:

#define DEFAULT_TIMESLICE_PERIOD 5 //
{ms}; system default

#define MS_PER_SECOND 1000

static long start_time; // saves
starting count of TIMESLICE_COUNT

_Q void MarkTime(void)

{ start_time = TIMESLICE_COUNT;

}

_Q void PrintElapsedTime(void)

{ long elapsed_ms =

DEFAULT_TIMESLICE_PERIOD*(TIMESLICE_COUNT
- start_time);

long seconds = elapsed_ms / MS_PER_SECOND;

int ms_after_second = elapsed_ms
% MS_PER_SECOND;

printf(“\nTime since mark is: %ld seconds and %d ms.\n”,

seconds, ms_after_second);

}

The MarkTime() function simply stores the TIMESLICE_COUNT
in the start_time
variable. The timeslicer is continually incrementing its counter, and when you
later call PrintElapsedTime(),
start_time
is subtracted from the latest TIMESLICE_COUNT
and multiplied by the timeslice period to calculate the elapsed number of
milliseconds. This is converted into the elapsed seconds by dividing by 1000,
and the remainder is the number of milliseconds since the last integral elapsed
second.

To try it out, use the Mosaic IDE’s editor to open the TIMEKEEP.C
file in the \MOSAIC\DEMOS_AND_DRIVERS\MISC\C
EXAMPLES directory, click on the Make Tool to compile the
program, and use the terminal to send TIMEKEEP.DLF to the QCard.
At your terminal, type:

main

to start the timeslicer and initialize the program. Now
at any time you can mark a starting time by typing at the terminal:

MarkTime( )

and you can print the elapsed seconds and ms since the
last mark by typing:

PrintElapsedTime( )

which will produce a response of the form:

Time since mark is: 3 seconds and 45 ms.

[[[User
Areas

Initializing
and Preserving Variables]]]

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 21 interrupts can enhance the
performance of these facilities. Interrupts allow rapid response to
time-critical events that often occur in measurement and control applications.
For example, you can use an interrupt to create a pulse-width modulated (PWM) output
signal.

[[[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 resources. Interrupts allow rapid
response to time-critical events that often occur in real-time measurement and
control applications.

In this section we
describe how interrupts are implemented on the 68HC11, and how interrupt
service routines are simplified by built-in kernel routines. Using an
interrupt requires four simple steps:

0. Name the
registers related to the interrupt using the REGISTER: utility. These register names are used to
code the interrupt handler.

0. 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.

0. Install the
interrupt handler using the QED-Forth word ATTACH.

0. 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.]]]

68HC11 interrupts fall into two main categories:
nonmaskable and maskable.

Maskable Interrupts

Maskable interrupts may be freely enabled and disabled by
software. They 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 events.

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 and
waiting for a triggering event.

When an interrupt is both recognized and serviced, the
processor pushes the programming registers onto the stack to save the machine
state, and 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) and the
current page 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 CPU then executes the appropriate interrupt handler
routine. It is the programmer’s responsibility to ensure that a valid interrupt
service routine is stored at the address pointed to by the interrupt
vector. The interrupt vectors are near the top of memory in the onboard ROM. The ROM revectors the interrupts (using jump instructions) to point to specified
locations in the EEPROM. The ATTACH() routine installs a call to the
interrupt service routine at the appropriate location in the EEPROM so that the
programmer’s specified service function is automatically executed when the
interrupt is serviced. ATTACH() also supplies the required RTI
(return from interrupt) instruction that unstacks the programming registers and
resumes execution of the previously executing program.

An interrupt handler must reset the interrupt flag bit
(not the mask bit) by writing a 1 to it, 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

Six of the 68HC11F1’s 21 interrupts are nonmaskable,
meaning that they are serviced regardless of the state of the global interrupt
mask (the I bit in the CCR). Events that cause nonmaskable interrupts include
resets, clock monitor failure (triggered when the E-clock frequency drops below
10 kHz), Computer-Operating-Properly (COP) failure (triggered when a
programmer-specified timeout condition has occurred), execution of illegal
opcodes, execution of the SWI (software interrupt) instruction, and an active
low signal on the nonmaskable interrupt request pin named /XIRQ.

Three types of interrupts initiate a hardware reset of the
68HC11:

Power-on or activation of the reset button

Computer-Operating-Properly (COP) timeout

Clock monitor failure

These are the highest priority interrupts, and are
nonmaskable. Serviced immediately, they initialize the hardware registers and
then execute a specified interrupt service routine. QED-Forth sets the
interrupt vectors of these interrupts so that they execute the standard startup
sequence. The service routines for all but the main reset interrupt may be
changed by the programmer with the Attach() utility.

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:

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 (see the
Glossary entry for InitVitalIRQsOnCold() for more details). No default
actions are installed for the SWI and /XIRQ interrupts, so before invoking
these interrupts the user should install an appropriate interrupt service
routine using the ATTACH() command.

Servicing Maskable Interrupts

Maskable interrupts are controlled by the I bit in the
condition code register (M68HC11 Reference Manual, mc68hc11rm.rev4.1.pdf,
Sections.5.7 and 5.8).
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 and
page are automatically 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 automatically sets the I bit to 1 to
temporarily prevent maskable interrupts from being serviced. Control is then
passed to the interrupt handler code, which you must provide and post using ATTACH().
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

While the programmer can explicitly clear the I bit inside
an interrupt service routine to allow nesting of interrupts, this is not
recommended as it can cause crashes in multitasking applications.

Interrupt Priority

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 using the
HIPRIO register. Nonmaskable interrupts always have the highest priority when
they are recognized, and are immediately serviced. The following table lists the
fifteen available maskable interrupts in order of highest to lowest priority:

Table 6‑2 Maskable Interrupts, from Highest to Lowest Priority.

Interrupt Name

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 (MC68HC11F1 Technical Data Manual, p.2-5).

An SPI (serial peripheral interface) interrupt occurs
after a byte transfer is completed, or a write collision or a mode fault is
detected (MC68HC11F1 Technical Data Manual, p.8-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 (M68HC11
Reference Manual, Section 9.5.2).

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. Note that interrupts are not necessarily
serviced in the order in which they are recognized, but in order of priority
among those pending.

You can elevate one maskable interrupt at a time 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 (MC68HC11F1 Technical Data Manual, p.5-7). The
default highest priority maskable interrupt is /IRQ. A table in MC68HC11F1
Technical Data Manual, p.5-8 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:

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 is 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 should be used to enable and disable
individual interrupts. In general, you should avoid setting the global I bit
in the condition code register (CCR) using DISABLE_INTERRUPTS()
unless you are sure that you want to disable all interrupts. Time-critical
interrupt service routines such as the timesliced multitasker cannot perform
their functions when interrupts are globally disabled.

Some of the QCard Controller’s library functions globally
disable interrupts for short periods to facilitate multitasking and access to
shared resources. A list of these functions is presented in the Control-C
Glossary document.

[[[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 to it !

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 logical one must be stored into the flag bit’s location –
that clears it to zero!

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 M68HC11
Reference Manual Section 10.4.4 for examples.

Two external interrupts, /IRQ (active-low interrupt
request) and /XIRQ (active-low nonmaskable interrupt request) allow external
hardware to interrupt the 68HC11F1 (M68HC11 Reference Manual, Section 2.4.6).
The / prefix to each of these names indicates that the signals are active-low.
Pull-up resistors on the QCard Controller hold these signals high during normal
operation, and an interrupt is recognized when either signal is pulled low by
an external source. The /IRQ input is maskable and is not serviced unless the
I bit in the condition code register is clear. If the CPU is servicing an interrupt
when the /IRQ line goes low, the external interrupt will not be recognized
until the interrupt being serviced has been handled. Unlike all the other
maskable interrupts, /IRQ does not have a local interrupt mask. The /XIRQ
external interrupt is not available on the QCard.

The /IRQ pin is accessed and controlled via the Wildcard
Port Header (for pin locations see Appendix A). It operates as an active-low
input to the processor. An external device can drive the line LOW to signal an
interrupt. Alternatively, several open-collector devices can be wired together
on the same line, so that any one of them can interrupt the processor by
pulling the request line low. This is called “wired-or” operation. In either
case, the external device must pull the line low long enough to be detected by
the CPU.

Note that the PORTA input capture lines can also be
configured to interrupt the processor when an external event occurs.

Configuring /IRQ Interrupts

In its default state, after each reset or restart, the /IRQ
pin is configured as an edge-triggered input. In this mode, the 68HC11 latches
the falling edge, causing an interrupt to be recognized. This frees peripheral
devices from having to hold the /IRQ line low until the CPU senses the
interrupt, and prevents multiple servicing of a single external event.

The disadvantage of this configuration is that multiple
edge-triggered interrupts cannot be reliably detected when used with wired-OR
interrupt sources. If you are using multiple wire-or /IRQ inputs, you can
specify level-sensitive interrupt recognition by clearing a bit named IRQE (IRQ
edge-sensitive) in the OPTION register (M68HC11 Reference Manual, Section
5.8.1). IRQE is a “protected bit” in OPTION that must be written within the
first 64 E cycles after a reset. The QED-Forth word InstallRegisterInits()
(described in the glossary) may be used to specify a value that is
automatically stored into OPTION upon each reset.

Using /IRQ

To use the /IRQ external interrupt, define an interrupt
handler and install it using the pre-defined identifier IRQ.ID and the
interrupt Attach
utility, as described in the Glossary entry for Attach.

In QED-Forth you
would execute:

CFA.FOR <name
of your /IRQ handler> IRQ.ID ATTACH

If interrupts have not yet been enabled globally, then
execute:

ENABLE.INTERRUPTS

ENABLE_INTERRUPTS

Whenever /IRQ is pulled low, your interrupt handler will
be executed. Note that there is no local interrupt mask for the /IRQ
interrupt, so your interrupt handler routine need not clear an interrupt
request flag.

CFA.FOR <name
of your /XIRQ handler> XIRQ.ID ATTACH

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).

Certain kernel routines temporarily disable interrupts by
setting the I bit in the condition code register. These routines are
summarized in the “Words that Disable
Interrupts”“Library Functions that Disable
Interrupts” chapter of the QED-ForthControl-C Glossary. 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 3 microseconds (µs). 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 8.5 µs. That is,
the first opcode of the user’s service routine is executed 8.5 µs after
interrupt service begins. After the service routine’s concluding RTS
executes, an additional 20 cycles (5 µs) 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.

Interrupt
Latency

Time to
Enter an Interrupt Service Routine: 8.5 µsec
Time to Leave an Interrupt Service Routine: 5.0 µsec

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 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.

To use interrupts you need to
create and post an interrupt service routine using the ATTACH() macro. We’ll look at this process in detail, then discuss
how interrupts are implemented on the 68HC11.

To use an interrupt to respond
to events, follow these four steps:

1. Use #define to name all required bit masks related to servicing the
interrupt, and look in the Motorola 68HC11F1 documentation and the QEDREGS.H file (in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory) to find the names of all registers that relate
to the interrupt. These bit mask and register names will simplify the creation
of a readable service routine.

2. Use C or assembly code to
define an interrupt service routine which will be executed every time the
interrupt occurs. The function must have a void
stack picture; it cannot return a value or expect input parameters. This
function must reset the interrupt request flag (by writing a 1 to it!) and
perform any necessary actions to service the interrupt event. Note that the
service routine is a standard function; it is not defined using the _interrupt keyword.

3. Write a function that
installs the interrupt service routine using the ATTACH() command. ATTACH() initializes the interrupt vector in EEPROM to call the
specified service routine, and ATTACH() also supplies the RTI
(return from interrupt) instruction that correctly terminates the service
routine.

4. Write functions to enable and disable the
interrupt. Enabling the interrupt is accomplished by clearing the interrupt’s
flag bit by writing a 1 to it, 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 ENABLE_INTERRUPTS().

ATTACH() Makes It Simple

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 C. Thus the service routine can be debugged
just like any other C function. You then call ATTACH() to bind the service
routine to the interrupt.

The following constants have been defined as identifiers
for the 68HC11 interrupts in the INTERUPT.H file in the \MOSAIC\FABIUS\INCLUDE\MOSAIC
directory:

The ATTACH() macro expects as inputs a function pointer
to your service routine, and an interrupt identifier. 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. The StartFunctionTimer()
routine presented earlier shows how ATTACH() is called.

Implementation Details

The interrupt vectors near the top of memory are in ROM;
locations that cannot be modified by the programmer. The contents of these
locations point to a series of locations in the EEPROM (at AE20-AEBFH) which
can be modified and, if desired, write-protected using the BPROT register. ATTACH()
writes some code at the EEPROM locations corresponding to the specified
interrupt. This code loads the code field address of the user’s service
function into registers and jumps to a routine that saves the current page, changes
the page to that of the user’s service function, and calls the service function
as a subroutine. When the user-defined service function returns, the code installed
by ATTACH()
restores the original page and executes RTI (return from interrupt)
to complete the interrupt service process. This calling scheme ensures that
the interrupt service will be properly called no matter which page the
processor is operating in when the interrupt occurs. And because the interrupt
calling routine which is installed by ATTACH() ends with an RTI,
your service routine can end with a standard RTS, return or } which
makes debugging much easier.

QED-Forth
simplifies the details of using interrupts. Implementing an interrupt using
QED-Forth tools is a 4 step process:

1. 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.

2. 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.

3. 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.

4. 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 sections explain how to define and install
interrupt service routines that enhance the usefulness of many of the 68HC11’s
hardware features.

The following example
illustrates how to write and use an interrupt service routine.

Many times a program needs to
execute a specified action every X milliseconds, where X is a specified time
increment. We can use an output compare interrupt to accomplish this. We’ll
set up an interrupt service routine that executes once per millisecond (ms),
and maintains a ms_counter variable that is incremented every millisecond. The
variable time_period specifies the time increment, in ms, between calls to the
specified function which is named TheFunction(). For this simple example, TheFunction() inverts the contents of the static variable named action_variable.

// This interrupt service routine is called by an OC3-based
clock interrupt and

// simply calls TheFunction periodically.

{ ms_counter++;

if(ms_counter == next_execution_time)

{ TheFunction();

next_execution_time = next_execution_time
+ time_period;

}

TOC3 += ONE_MS; // set OC3 count for next
interrupt in 1 ms

TFLG1 = OC3_MASK; // reset the oc3 interrupt
flag by writing a 1

}

_Q void StopFunctionTimer(void)

{ TMSK1 &= ~OC3_MASK; // clear OC3I to
locally disable OC3

}

_Q void StartFunctionTimer(void)

// inits variables and locally enables OC3 interrupt;

// does not globally enable interrupts!

{ StopFunctionTimer(); // locally disable OC3 while we
set it up

ATTACH(FunctionTimer, OC3_ID); // post the
interrupt service routine

ms_counter = 0;

time_period = next_execution_time
= DEFAULT_TIME_PERIOD; // 1/second

action_variable = 0; // state is toggled by
TheFunction()

TOC3 = TCNT + ONE_MS; // start after a 1 ms
delay

TFLG1 = OC3_MASK; // clear interrupt
flag OC3F

TMSK1 |= OC3_MASK; // set OC3I to locally
enable OC3

}

In this program, we define a bit
mask named OC3_MASK
which has bit 5 set and all other bits clear. From inspection of the register
summary in the Motorola 68HC11F1 booklet, we see that this mask isolates the
Output Compare 3 (OC3) mask bit in the TMSK1 register, and isolates the OC3
interrupt flag bit in the TFLG1 register. The
other relevant registers are the 16 bit free-running counter register named TCNT which increments every 2 microseconds, and the Timer
Output Compare 3 register named TOC3.
If the OC3 interrupt is enabled by setting its mask bit = 1 in TMSK1 and by globally enabling interrupts using ENABLE_INTERRUPTS() or the assembly instruction CLI,
then an interrupt occurs when the count in TCNT
matches the count in TOC3. Thus we can control
when the next interrupt occurs by writing a specified count to TOC3.

TheFunction() is our
prototypical function that simply toggles the action_variable between the values 0 and 1. The goal of our interrupt
service routine is to call TheFunction()
exactly once per second.

FunctionTimer() is the OC3 interrupt service routine. It increments the ms_counter variable, and checks if ms_counter equals next_execution_time. If so, it calls TheFunction() and updates next_execution_time by adding time_period to it. Then FunctionTimer() increments
the contents of the TOC3 register to set up the
next interrupt in 1 ms, and clears the interrupt request flag by writing a 1 to
the OC3 flag bit in the TFLG1
register. Note that FunctionTimer() does not
have any input parameters or a return value. Moreover, it is not defined using
the _interrupt keyword which would insert an RTI (return from interrupt) instruction at the end of the
function. Rather, ATTACH() will supply the RTI instruction for us. Because FunctionTimer() does not end with an RTI,
we can easily test the FunctionTimer()
service routine using our standard interactive debugging techniques.

StopFunctionTimer() simply
clears the local OC3 interrupt mask bit in
the TMSK1 register to disable the OC3
interrupt.

StartFunctionTimer() first
locally disables OC3 to prevent an interrupt
while the service routine is being posted. Then it calls:

ATTACH(FunctionTimer, OC3_ID);

to ensure that FunctionTimer() is called every time the OC3
interrupt occurs; ATTACH() also installs a
return sequence that supplies the required RTI
(return from interrupt) opcode. ATTACH() is described in detail later in this chapter. Note that
its input parameters are a pointer to the interrupt service routine FunctionTimer, and a pre-defined constant named OC3_ID that identifies the interrupt. All of the interrupt
identifier constants are summarized in Table 6‑3.

After calling ATTACH(), StartFunctionTimer()
initializes the timing variables, and initializes TOC3
so that the first interrupt will occur in 1 ms. It then clears the interrupt
flag by writing a 1 to the OC3F flag bit in the
TFLG1 register using the statement:

TFLG1 = OC3_MASK;

Clearing the interrupt flag bit
before enabling the interrupt is a highly recommended procedure that ensures
that all prior pending OC3 interrupts are
cleared before the interrupt occurs. Finally, StartFunctionTimer() locally enables the OC3
interrupt by setting the mask bit in TMSK1
with the statement:

TMSK1 |= OC3_MASK;

To start the interrupt, main() simply calls StartFunctionTimer() followed by ENABLE_INTERRUPTS(). After you compile and download the TIMEKEEP.C program and type:

main

from your terminal, the OC3 interrupt is running in the background. To monitor
the state of the action_variable, interactively type at your terminal:

See( )

and you will see the variable’s
value change from 0 to 1 exactly once per second. Type any key to terminate
the See(
) function.

This short program provides a
template that shows how a function can be periodically called with a period
specified by the variable time_period. Of course, in your application the called function would perform a
more useful action than does TheFunction() in this simple example. You could make other
enhancements; for example, a foreground task could manipulate the contents of time_period to change the frequency at which TheFunction() is called, and you could use ms_counter to measure elapsed time with 1 ms resolution.

Note that, to maintain timing
accuracy, the interrupt service routine should have a worst-case execution time
of under 2 ms; otherwise the FunctionTimer() will miss the interrupt when TCNT
matches TOC3, and an extra delay of 131 ms will occur while the TCNT timer rolls over. In general, interrupt service routines
should be short and simple. Complex calculations should be done by foreground
tasks, and the interrupt routines should perform the minimum actions necessary
to service the time-critical events. An example of this approach is presented
in the Turnkeyed Application Program.

Cautions and Restrictions

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 and the elapsed time clock.

The main restriction on interrupt service routines is that
they must not call _forth (kernel) library functions unless the
instructions in the next section are followed. The Glossary document and the
header files in the \MOSAIC\FABIUS\INCLUDE\MOSAIC directory specify
which functions are of the _forth type.

Summary

Using interrupts requires:

coding an interrupt service routine;

using ATTACH() to bind it to the appropriate
interrupt; and,

enabling its local interrupt mask.

[[[The Turnkeyed Application
Program described in the next chapter presents another detailed example of how to
use interrupts in a measurement and control application.]]]

All About
Interrupts and Interrupt Latency

How the
68HC11 Handles Interrupts

The 68HC11
processor has 21 interrupts including input capture and output compare timers,
a pulse accumulator, synchronous and asynchronous serial I/O, maskable and
non-maskable external interrupt signals, computer-operating-properly and clock
monitor failures, software and real-time interrupts, and the master reset
interrupt. The Motorola 68HC11 manuals and the QED Hardware Manual describe
the interrupt system in detail; a few basic aspects of the system are discussed
here.

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. 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 only
serviced, however, if the global interrupt bit is enabled.

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; however, this can cause hard-to-diagnose problems
in multitasking application programs). Non-maskable interrupts (reset, clock
monitor failure, COP failure, illegal opcode, and XIRQ) are serviced regardless
of the state of the I bit.

When an interrupt
is serviced, the processor globally disables interrupts, pushes the contents of
the 68HC11’s programming registers onto the return stack, and then fetches the
address of the service routine from a memory location (called an “interrupt
vector”) near the top of memory that is associated with that interrupt. In
QED-Forth the interrupt vectors (which are in non-modifiable ROM) point to
modifiable locations in the EEPROM so that the interrupt service routines can
be specified by the user. The service routine must, at a minimum, perform two
duties:

1. Reset
the interrupt flag bit (not the mask bit) so that the interrupt is no longer
pending. The flag bit, oddly enough, is reset by writing a 1 to it.

2. Execute
an RTI (return from interrupt) instruction which pops the saved registers from
the return stack and re-enables the global interrupt mask bit.

In addition there
are several minor constraints on the interrupt service routine:

1. Local
variables should not be used in interrupt service routines or in any code
called by the interrupt service routine. See LOCALS{ in the glossary.

2. If
floating point operations are used in an interrupt service routine then FP.PUSH
and FP.POP must be used. See glossary entries for FP.PUSH and FP.POP.

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.

A set of kernel
words has been defined as identifiers for each interrupt except the hardware
reset which is not revectorable. The revectorable interrupts are:

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 (used by timeslicer)

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
(nonmaskable)

ILLEGAL.OPCODE.ID

Illegal opcode
trap (nonmaskable)

COP.ID

COP failure
(causes reset; nonmaskable)

CLOCK.MONITOR.ID

Clock failure
(causes reset; nonmaskable)

The kernel word
ATTACH makes it easy to specify an action which is invoked by a given
interrupt. 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 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. The
QED Hardware Manual describes some examples of interrupt service routines.

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 (see the glossary entry for START.TIMESLICER).

Implementation
Details

The interrupt
vectors near the top of memory are in ROM locations that cannot be modified by
the programmer. The contents of these locations point to a series of locations
in the EEPROM (at AE20-AEBFH) which can be modified and, if desired,
write-protected using the BPROT register. ATTACH writes some code at the
EEPROM locations corresponding to the specified interrupt. This code loads the
code field address of the user’s service word into registers and jumps to a routine
that saves the current page, changes the page to that of the user’s service
word, and calls the service word as a subroutine. When the user-defined
service word returns, the code installed by ATTACH restores the original page
and executes RTI (return from interrupt) to complete the interrupt service
process. This calling scheme ensures that the interrupt service will be
properly called no matter which page the processor is operating in when the
interrupt occurs. And because the interrupt calling routine which is installed
by ATTACH ends with an RTI, your service routine can end with a standard RTS or
; which makes debugging much easier.

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.

Interrupt
Latency 1 (Getting Started C v31)

Interrupt
Latency Resulting from Clock Stretching

The PIA
(Peripheral Interface Adapter), the LCD display, and the battery-backed
real-time clock have timing specifications that are not wholly consistent with
16 MHz read/write operations. To solve this problem, the V3.0 software uses the
"clock stretching" feature of the 68HC11F1 processor to slow down the
interactions with these devices by inserting wait states. The slow I/O
accesses are targeted only at these three devices and do not slow down the
normal operations of the QED Board. Interrupts are globally disabled while
clock stretching is active; however, the interrupts are typically disabled for
less than 10 microseconds so the effect on most applications is minimal.

Nested Interrupts

More About
Interrupts 1 (Getting Started C v31)

Interrupt Mask
Bits and Flag Bits 1 (Getting Started C v31)

Nonmaskable
Interrupts 1 (Getting Started C v31)

An Interrupt Flag
Bit Is Cleared By Writing a 1 1 (Getting Started C v31)

Interrupt
Recognition and Servicing 1 (Getting Started C v31)

ATTACH() Makes It
Simple 1 (Getting Started C v31)

Implementation
Details 1 (Getting Started C v31)

Cautions and
Restrictions 1 (Getting Started C v31)

Interrupt Priority
1 (Getting Started C v31)

Available
Interrupts

Kernel
Services That Disable Interrupts

Certain QED-Forth
routines temporarily disable interrupts by setting the I bit in the condition
code register. These routines are summarized here to assist you in planning
the time-critical aspects of your application.

The kernel
provides a set of uninterruptable memory operators that disable interrupts for
a few microseconds during the memory access. These are very useful in
applications where several tasks or interrupt routines must access a shared
memory location. The glossary entries for these words detail the length of
time that interrupts are disabled.

(CHANGE.BITS) (CLEAR.BITS) (SET.BITS) (TOGGLE.BITS)

CHANGE.BITS CLEAR.BITS SET.BITS TOGGLE.BITS

|2!| |F!| |X!|

|2@| |F@| |X@|

The multitasker
mediates access to shared resources and ensures smooth transfer of information
among tasks. The routines that manage resource variables and mailboxes must
disable interrupts for short periods of time to ensure proper access to shared
resources and messages. Consequently, the following routines temporarily
disable interrupts:

?GET ?RECEIVE ?SEND

GET RECEIVE RELEASE SEND

Consult their
glossary entries for details.

The following
routines temporarily disable interrupts to ensure that a new task is not
corrupted while it is being built:

BUILD.STANDARD.TASK BUILD.TASK

These routines
disable interrupts to ensure that the elapsed time clock is not updated while
it is being read:

READ.ELAPSED.SECONDS READ.ELAPSED.TIME

The multitasker is
charged with smoothly transferring control among tasks via timeslicing or cooperative
task switching. The timeslicer is an interrupt service routine associated with
output compare#2. It disables interrupts for the duration of a task switch
which requires 58 microseconds plus 6.5 microseconds for each ASLEEP task
encountered (these times are halved if the processor is clocked at 16 MHz).
The cooperative task switch routine

PAUSE

disables
interrupts for 31 microseconds plus 6.5 microseconds for each ASLEEP task
encountered, and again these times are halved if the processor is clocked at 16
MHz.

The PAUSE routine
(which temporarily disables interrupts) is called by the following built-in
device drivers:

EMIT EMIT1 EMIT2

KEY KEY1 KEY2

?KEYPAD KEYPAD

These routines as
well as the following device driver routines GET and RELEASE resource variables,
and so disable interrupts for short periods of time:

?KEY ?KEY1 ?KEY2 ?KEYPRESS

>DAC

A/D12.MULTIPLE A/D12.SAMPLE

A/D8.MULTIPLE A/D8.SAMPLE

$>DISPLAY

CHAR>DISPLAY

CLEAR.DISPLAY

COMMAND>DISPLAY

DISPLAY.OPTIONS

PUT.CURSOR

UPDATE.DISPLAY

UPDATE.DISPLAY.LINE

INIT.DISPLAY

The battery-backed
real-time clock option shares the RAM socket on the QED Board. While the
“watch” is being read or set by the routines

READ.WATCH SET.WATCH

the RAM cannot be
accessed, so interrupts cannot be properly serviced. Therefore these routines
disable interrupts for approximately 1 msec (or 0.5 msec with a 16 MHz crystal)
while the watch is being accessed.

All of the
routines that write to the EEPROM disable interrupts for 20 msec per programmed
byte. This results from the 68HC11’s design which prohibits any EEPROM
locations from being read while other EEPROM locations are being modified.
Since all interrupts are vectored through EEPROM, interrupts cannot be serviced
while an EEPROM storage operation is in progress. The following fundamental
EEPROM storage routines

(EEC!) (EE!) (EEX!) (EEF!) (EE2!)

disable interrupts
for 20 msec per programmed byte. These routines are smart enough to avoid
programming a byte that already has the correct contents. The following
routines may modify EEPROM locations:

ATTACH AUTOSTART

COLD.ON.RESET DEFAULT.REGISTER.INITS

INIT.VITAL.IRQS.ON.COLD INSTALL.MULTITASKER

INSTALL.REGISTER.INITS NO.AUTOSTART

SAVE SERIAL1.AT.STARTUP

SERIAL2.AT.STARTUP STANDARD.RESET

START.TIMESLICER

The following
routines disable interrupts and do not re-enable them:

DISABLE.INTERRUPTS SEI

COLD WARM

DISABLE.INTERRUPTS
and its assembly language counterpart SEI explicitly set the I bit in the
condition code register. The routines ENABLE.INTERRUPTS and CLI clear the I
bit to globally enable interrupts. The restart routines COLD and WARM disable
interrupts so that the initialization process is not interrupted.

Coding
Interrupt Service Routines

There is a special consideration
when calling_forth
library functions from interrupt service routines. Fortunately, this
restriction can be overcome by simply including the FORTHIRQ.C file with your source code, and following the simple
example presented in the file. FORTHIRQ.C is present in the \MOSAIC\DEMOS_AND_DRIVERS\MISC\C
EXAMPLES directory.

The method is very simple: just
place a call to the function

BeginForthInterrupt();

at the top of your interrupt
service routine (or, at the minimum, before any _forth
functions are called). Before the final exit point of the interrupt service
routine, place a call to the function

EndForthInterrupt();

That's all there is to it. The ability to call _forth
library functions from interrupt service routines makes it easier to manage
page-mapped I/O devices on an event-driven basis.External Hardware and Reset-Type Interrupts

The RESET
interrupt is the most important interrupt in the 68HC11. It is invoked when
the processor is powered up or when the reset line is pulled low (which occurs
when the reset button is pushed). When a reset occurs, the processor
initializes its hardware registers to their specified reset conditions and
calls QED-Forth’s restart routine. RESET is the only interrupt that cannot be
revectored by the programmer.

As explained in
Chapter 3, a reset is a hardware initialization sequence, and a restart is a
software-controlled initialization sequence. QED-Forth’s restart routine
initializes the system using either a COLD or WARM restart sequence, and
commences execution of the operation program. The program decides whether to
perform a COLD or WARM restart based on the contents of a variable in the user
area. If this user variable is properly initialized, a warm restart is
performed; if not, a cold startup occurs. A WARM restart initializes the
minimum number of hardware registers and user variables necessary to run
QED-Forth. It clears the stacks and ensures that there is one awake task
running. A COLD restart is more thorough: it completely re-initializes the
user area to default values.

In addition to the
RESET interrupt, two other interrupts also cause hardware resets that
initialize the processor’s registers: the computer operating properly (COP)
failure and clock monitor failure. These interrupts then execute a specified
service routine. QED-Forth initializes the vectors of these interrupts so that
they execute the same program that the standard reset does. These interrupts
are vectored via the EEPROM so that the user can change the interrupt service
routine if necessary.

The illegal opcode
trap interrupt does not force a hardware reset. But it is very important that
this interrupt’s vector is initialized at all times to perform a proper
restart. Otherwise, a crash which invokes this interrupt could cause another
crash, causing an infinite loop of calls to the un-initialized interrupt that
can only be exited by resetting the processor. The default service routine for
this interrupt is the standard startup routine like the other three interrupts
just described. This default service routine can be changed by the programmer.

Initialization
of the Vital Interrupts

QED-Forth treats
the RESET, computer operating properly (COP) failure, clock monitor failure,
and illegal opcode trap interrupts as “vital interrupts” whose vectors should
always be properly initialized. The RESET interrupt vector is in ROM and can
never be changed. The other three vital interrupts are revectored via the
EEPROM. QED-Forth’s cold restart routine checks to make sure that these vital
interrupts are properly initialized to their default values. If they are
already initialized, it does nothing; if not, the cold restart routine writes
to their EEPROM vectors to initialize them. The warm restart routine does not
do this.

If you wish to
maintain customized service routines for these interrupts, install the service
routines (using ATTACH, for example) and then execute

NO.VITAL.IRQ.INIT

This installs a
pattern in a special EEPROM location which informs the cold startup routine not
to initialize the illegal opcode trap, COP and clock monitor failure interrupts.
Be careful with custom service routines for COP and clock monitor failure
interrupts. Because these interrupts are associated with hardware resets, the
four protected registers INIT, OPTION, BPROT, and TMSK2 must be initialized in
the first 64 cycles of operation. Make sure that your service routine can
accomplish the initializations within the allotted time.

To revert to the
default initialization of the vital interrupts, simply execute

INIT.VITAL.IRQS.ON.COLD

Forcing
Cold Restarts

Some applications
are more reliable if any reset condition (power-up, reset button, or COP or
clock monitor failure) causes a cold restart which completely initializes the
user area and system parameters. This is especially important in embedded
systems applications where the main program has been configured to
automatically execute after each restart. Such main programs typically perform
a set of initializations that configure the system to perform the application
program. The combination of a cold (as opposed to a warm) restart and the main
program’s initializations establishes a well-known starting state for the
system.

To force a cold
restart every time a reset occurs, execute

COLD.ON.RESET

which installs a
pattern in a specified location in the EEPROM. The startup routine checks this
location and, if the proper pattern is present, always performs a cold
startup. Because the pattern is in EEPROM, it need not be re-installed after
each restart. To revert to the standard warm/cold startup behavior, execute

STANDARD.RESET

which removes the
pattern from the location in the EEPROM.

Initializing
the Protected Registers

There are four
“protected” 68HC11 registers that contain bits that can be modified only during
the first 64 machine cycles after a startup. These are the INIT, OPTION,
BPROT, and TMSK2 registers. The QED-Forth restart routine initializes these
registers to default values. You have the option of installing customized
initialization values for OPTION, BPROT, TMSK2, and BAUD; initializing the BAUD
register allows you to specify a serial communications baud rate that is set
every time the processor restarts.

The INIT register
specifies the memory locations of the hardware registers and on-chip RAM. The
protected portion of the OPTION register controls the edge sensitivity of the
external IRQ signal, STOP mode exit delay, and COP timer rate selection. BPROT
can write-protect sections of the EEPROM and the CONFIG register. Two
protected bits (bits 0 and 1) of TMSK2 control the frequency of the free-running
counter. BAUD controls the baud rate of the serial communications interface.

The default
initializations of these special registers are as follows:

INIT is set to B8H
to place the on-chip RAM at B000H-B3FFH and the hardware registers at
8000H-805FH. INIT must have this value for QED-Forth to operate properly.

OPTION is
initialized to 33H which configures the A/D converter to use the E-clock to
power its charge pump, keeps the 8-bit A/D converter off at startup, makes the
IRQ input edge sensitive, sets a 4 msec delay after exiting the STOP mode,
disables the clock monitor circuit, and sets the COP timer rate to 1.049 sec.
The A/D power-up bit, the clock/charge-pump select bit, and the clock monitor
enable bit can be set at any time. The other bits can only be modified within
the first 64 machine cycles after a reset.

The 2 protected
bits in TMSK2, called PR0 and PR1, control the frequency of the main timer.
The other bits in the register are interrupt mask bits that are not protected
(they can be modified at any time); they are initialized to 0 so that 4
interrupts associated with the timer subsystem are not enabled after a reset.
PR0 and PR1 are set such that the main timer has a period of 2 microseconds
(usec), and the timer “rolls over” to 0 after just over 131 msec. If the QED
Board is clocked at 8 MHz the register is initialized to 01H which drives the
main timer at 1/4 the E-clock frequency, and if the QED Board is clocked at 8
MHz the register is initialized to 02H which drives the main timer at 1/8 the
E-clock frequency. (For experts and the curious: A flag in the kernel ROM at
location FFC0H tells QED-Forth the crystal frequency.)

BPROT is
initialized to 10H which enables writes to all EEPROM cells and disables writes
to the CONFIG register.

BAUD is
initialized such that the baud rate is 9600 bits per second. If the onboard
crystal frequency is 8 MHz, the BAUD register is initialized to 30H, and if the
onboard crystal frequency is 16 MHz, the BAUD register is initialized to 31H.
The BAUD register may be modified at any time.

The default
register initialization values are summarized in Figure 11.1.

Register Register Default
Value Default Value

Name Address @ 8
MHz @ 16 MHz

OPTION 8039H 33H 33H

TMSK2 8024H 01H 02H

BPROT 8035H 10H 10H

BAUD 802BH 30H 31H

Figure 11.1 Default
values of the registers initialized by INSTALL.REGISTER.INITS. These are the
values that take effect after DEFAULT.REGISTER.INITS has been executed.

The kernel word
INSTALL.REGISTER.INITS specifies initialization values for OPTION, BPROT, BAUD,
and the lowest 2 bits of TMSK2. The specified values take effect upon the next
reset, and the registers are appropriately initialized each time the processor
resets. INSTALL.REGISTER.INITS expects the value of OPTION under the value of
TMSK2 under the value of BPROT under the value of BAUD on the stack. After
ANDing the specified value of TMSK2 with 03H so that only the lowest 2
(protected) bits are retained, INSTALL.REGISTER.INITS writes a pattern followed
by the specified bytes in a special area of the EEPROM. The restart routine
checks this pattern after every reset and, if it is present, initializes the
registers during the first 64 cycles to the specified values.

For example,
suppose that your board is clocked by an 8 MHz crystal, and you want to speed
up the main free-running timer to run at 2 MHz (its default value is 500 kHz).
This requires setting the value of TMSK2 to 00H (see the description of TMSK2
in the Motorola 68HC11 manuals). To install this value of TMSK2 while
maintaining the default values for the INIT, OPTION, and BAUD registers,
execute:

HEX

33 \ default
value for OPTION

00 \ new value
for TMSK2

10 \ default
value for BPROT

30 \ default
value for BAUD at 8MHz, yields 9600 baud

INSTALL.REGISTER.INITS

The specified
values will take effect upon the next reset.

To return to the
default initializations for the five registers, execute

DEFAULT.REGISTER.INITS

which erases the
pattern that INSTALL.REGISTER.INITS put in the EEPROM. After executing
DEFAULT.REGISTER.INITS the registers will be initialized to the default values
shown in Figure 11.1 each time the processor executes its restart sequence.

External
Hardware Interrupts /IRQ and /XIRQ (from QED Hardware)

Reset
Interrupts (from QED Hardware)

A Hardware
Perspective on 68HC11 Interrupts (from QED Hardware)

Interrupt
Recognition and Servicing (from QED Hardware)

Interrupt
Flag and Mask Bits (from QED Hardware)

QED-Forth's
Handling of Interrupts (from QED Hardware)

Summary
(from QED Hardware)

Changing
the CONFIG Register

The CONFIG
register is a special hardware register implemented as an EEPROM cell. It
controls the position of the 512-byte EEPROM, determines whether the EEPROM is
enabled, and enables or disables the computer operating properly (COP)
feature. The CONFIG register is writable as long as bit 4 of the BPROT
register is cleared. QED-Forth’s default value for BPROT has this bit set so
that the CONFIG register is unwritable. This prevents inadvertent
modifications which could crash the system. If you wish to modify CONFIG (for
example, to turn on the COP feature), first use INSTALL.REGISTER.INITS to set a
value for BPROT with bit 4 cleared. If you have an 8 MHz crystal, you could
execute

HEX

33 \ default
value for OPTION

01 \ default
value for TMSK2 at 8 MHz (use 02 at 16 MHz)

00 \ new value
for BPROT enabling writes to CONFIG

30 \ default
value for BAUD at 8 MHz (use 31H at 16 MHz)

INSTALL.REGISTER.INITS

Now issue a reset
so that the new value of BPROT takes effect, and CONFIG becomes writable. Then
use (EEC!) to set the desired value of the CONFIG register, whose address is
803FH. For example, to enable the COP while keeping the EEPROM enabled at
AE00-AEFFH, execute

HEX AB 803F (EEC!)

Before executing a
reset, the CONFIG register should be write-protected again by specifying a
value for BPROT with bit 4 clear. This can be accomplished by another
INSTALL.REGISTER.INITS command or by executing DEFAULT.REGISTER.INITS. If the
COP has been enabled, make sure that an autostart routine has been installed
that can service the COP before its intermittent time-out. Only then should a
reset be issued so that the new value of CONFIG takes effect. The chapter
titled “External Interrupts, Resets, Operating Modes, and the COP” in the QED
Hardware Manual defines a step-by-step sequence to assist you in configuring
the COP.

These routines
disable interrupts just before reading the memory contents and restore the
prior state of the interrupt flag (enabled or disabled) after writing to the
specified address. Interrupts remain disabled for only 10 to 16 cycles,
corresponding to 5 to 8 microseconds if the crystal frequency is 8 MHz.
Consult the glossary for detailed descriptions of these operators.

Returning to our
example, Task#1 can set the least significant bit in port PPA by executing

01 PPA SET.BITS

Because SET.BITS
is uninterruptable, we can be sure that there will not be a task switch during
the read/modify/write operation. Likewise, Task#2 can set the most significant
bit in PPA by executing

HEX 80 PPA
SET.BITS

These instructions
work properly in a multitasked system.

Similar problems
can arise when one task writes to a floating point or other 4-byte variable,
and a second task needs to read the saved value. The data that is read may be
invalid if the read or the write is interrupted between the time of the
writing/reading of the first 16 bits and the writing/reading of the second 16
bits. The SEND and RECEIVE mailbox operators described earlier are one
solution to this problem. Another solution is to use one of QED-Forth’s
uninterruptable 32-bit operators denoted by the | (“bar”) character are in the
kernel. The uninterruptable storage operators are

|2!| |F!| |X!|

and the
uninterruptable fetch operators are

|2@| |F@| |X@|

The assembly
instructions for 8 bit and 16 bit read and write operations are themselves
uninterruptable. Thus, with the unlikely exception a 16 bit variable that
straddles a page boundary, the C@, C!, @, and ! operators are already
uninterruptable and can be used to share data among tasks.

Designing
Re-Entrant Code

For a multitasking
system to operate at its full potential, the kernel routines in the system must
be “re-entrant”. A re-entrant routine functions properly even if it is
“re-entered” while it is executing. Routines that are not re-entrant will fail
under these circumstances. There are two contexts where this is important:
recursion and multitasking.

A recursive
routine is one that calls itself; to ensure “re-entrancy with respect to
recursion”, a routine may modify only stack-based quantities such as data stack
items and local variables (which are kept on the return stack).

Routines that
modify only stack or task-private memory locations are “re-entrant with respect
to multitasking”. “Task-private” locations are those that can be accessed by
one task only. A user variable is a task-private memory location. Routines
that modify variables that are shared by more than one task are not re-entrant
with respect to multitasking. They may work properly if used by only one task,
but are prone to failure if they are called by multiple tasks.

A routine that is
re-entrant with respect to recursion is automatically re-entrant with respect
to multitasking, but the converse is not true. The rest of this discussion
will focus on re-entrancy with respect to multitasking. If you are designing
recursive routines, keep in mind that the rules for re-entrancy are stricter
than those presented below.

Re-entrant
and Non-re-entrant Definitions

The following
definition (originally presented in Chapter 2) is re-entrant. The word
calculates the volume and cross-sectional area of a cylinder, and modifies only
the data stack and local variables (which are maintained on the return stack):

:
CYLINDER.STATISTICS (
r1\r2 -- r3\r4 )

\ r1 = radius, r2
= height, r3 = volume, r4 = cross-sectional area

LOCALS{
f&height f&radius | f&area }

f&radius FDUP
F* PI F* \
cross-sectional area = πR**2

TO f&area

f&area
f&height F* (
-- volume = height * area )

f&area (
-- volume\area )

;

The following
version of this word is not re-entrant:

REAL:
CYLINDER.AREA \
define a self-fetching variable to hold area

:
CYLINDER.STATISTICS (
r1\r2 -- r3\r4 )

\ r1 = radius, r2
= height, r3 = volume, r4 = cross-sectional area

LOCALS{
f&height f&radius }

f&radius FDUP
F* PI F* \
cross-sectional area = πR**2

TO CYLINDER.AREA

CYLINDER.AREA
f&height F* (
-- volume = height * area )

CYLINDER.AREA (
-- volume\area )

;

This version uses
a global self-fetching variable named CYLINDER.AREA. Assume that
CYLINDER.STATISTICS is called by task #1, and that the multitasker’s timeslicer
transfers control to task#2 just before the final statement of the definition
(that is, before the final invocation of CYLINDER.AREA). If task #2 now puts a
radius and height on the stack and calls CYLINDER.STATISTICS, the value in the
variable CYLINDER.AREA will be changed. When control returns to task#1, the
area placed on the stack will be incorrect for that task. This shows how
non-re-entrant code causes errors in multitasking systems.

Techniques
for Ensuring Re-entrancy

Each task has its
own user area, data and return stacks, POCKET, PAD, and TIB. To ensure
re-entrancy, each task that uses the heap must have its own heap area. These
memory areas are called “task-private” because only one task has access to
them. To ensure re-entrancy, words should modify only stack-based quantities
(including local variables) and task-private memory. Non-task-private memory
such as the contents of variables and self-fetching variables should not be
modified if the word is to be called by more than one task.

To ensure
re-entrancy, data structures such as temporary matrices that hold intermediate
results within a word must reside wholly on a stack or within a task-private
heap. Recall that arrays and matrices have parameter fields that hold
dimensioning information and a handle to the heap memory block. The parameter
field of a globally defined array or matrix is allotted in the variable area
when the data structure is defined. Because a function that uses such a
globally defined temporary matrix could be called from multiple tasks, a
parameter field in the variable area would make the routine non-re-entrant.

Stack
Frames

Stack frames solve
the problem of how to create a parameter field for a temporary array or matrix
while preserving re-entrancy. The temporary array/matrix parameter field can
be created and kept on the data stack as a “stack frame”, and using stack-based
items preserves re-entrancy.

The kernel word
STACK.FRAME allows a structure that has been defined with the structure defining
words (see the previous chapter) to be instantiated on the data stack.
STACK.FRAME expects a size (in bytes) of the structure to be placed on the data
stack, allocates room on the stack, and returns on the top of the stack the
extended address of the base of the stack frame. The word FRAME.DROP is used
to remove the stack frame from the stack before the calling word finishes
executing. The word PF.STACK.FRAME (“parameter-field-stack-frame”) performs
the operation of STACK.FRAME and then stores 0\0 into the first 4 bytes of the
stack frame in the position where the heap xhandle will reside. Initializing
the xhandle to zero is good programming practice, and PF.STACK.FRAME makes this
easy to accomplish when allocating stack-based parameter fields.

A Stack
Frame Example

For example, let’s
define a re-entrant version of a word that places the transpose of a source
matrix into a destination matrix, even if the specified source and destination
are the same. If the source and destination matrices are the same we must
dimension a temporary matrix, place the transpose into it, and then copy the
transpose back to the source. In the Chapter 6 discussion of arrays and
matrices we defined the word #2.TRANSPOSED which can transpose a matrix, but
only if the source and the destination are different matrices. We can use
#2.TRANSPOSED to perform the actual transposition, but we’ll add the capability
of dimensioning a temporary data matrix to handle the case when the source and
the destination are the same.

: #3.TRANSPOSED (
src.xpfa\dest.xpfa -- )

\ places the
transpose of the source in the destination;

\ the destination
can be the same as the source

LOCALS{ x&dest
x&src | x&temporary }

x&dest
x&src X= (
source = destination?--)

IF \
if source = destination...

MATRIX.PF
PF.STACK.FRAME \
make room on stack for pf

TO x&temporary \
save temporary xpfa

x&src
x&temporary #2.TRANSPOSED \
transpose is in temp matrix

x&dest
x&temporary SWAP.MATRIX \
now x&dest has the answer

x&temporary
DELETED \
delete the temporary matrix

MATRIX.PF
FRAME.DROP \
clear frame off data stack

ELSE \
else if source not= dest...

x&src
x&dest #2.TRANSPOSED \
...just do it

ENDIF

;

The first line of
the definition loads the source and destination xpfa’s into 32-bit local
variables, and creates an un-initialized local variable. The next line checks
if the source and destination are equal. If they are not, the ELSE part of the
conditional executes, and #2.TRANSPOSED can perform the operation directly. If
the source equals the destination, however, #2.TRANSPOSED will not work, and we
have to create a temporary destination matrix. The next line allocates space
on the data stack for the parameter field of the temporary matrix. MATRIX.PF
puts the size of the parameter field on the stack, and PF.STACK.FRAME allocates
space for the parameter field on the data stack and initializes its heap handle
to 0\0. The base xaddress of the stack frame is loaded into the local variable
x&temporary, and the source and temporary matrix xpfa’s are passed to
#2.TRANSPOSED, which dimensions the temporary matrix and transposes the source
into it. Next, SWAP.MATRIX interchanges the contents of the temporary and
destination parameter fields so that x&dest is the transposed matrix and
x&temporary points to the original matrix. Next the temporary matrix is
deleted from the heap, and FRAME.DROP clears the temporary parameter field off
the data stack.

This final
definition is very similar to the actual definition of TRANSPOSED in
QED-Forth. Many of the matrix words need temporary matrices to allow the
source and destination matrices to be the same, and temporary matrices are
always implemented using the stack-frame technique to ensure re-entrant
operation of all of the kernel words. Thus all of the mathematics routines may
be used in multitasking applications as described in the next chapter.

Low Power
Mode

Low Power Modes
(from QED Hardware)

Operating Modes of
the 68HC11F1 CPU (from QED Hardware)

The Special
Cleanup Mode

If a buggy program
has been installed as an autostart routine, an infinite series of crashes may
result: the program crashes, which causes a restart, which calls the autostart
program, which crashes, etc... Another sticky situation arises if an improper
value is written to the non-volatile CONFIG register which controls the
location of the EEPROM and the enabling of the COP interrupt. Or, if
INSTALL.REGISTER.INITS has been used to set a value for the BPROT register that
write-protects the EEPROM, it will not be possible to use
DEFAULT.REGISTER.INITS to undo the initialization, because the initializations
themselves are stored in the EEPROM.

The special
cleanup mode allows you to recover from any of these situations. Simply set DIP
switch #5 “on” and reset the board by toggling DIP switch #6. This puts the
processor in the “special test mode”. QED-Forth’s special “cleanup routine” is
automatically called in this mode. It removes any installed autostart
patterns, initializes the CONFIG register, sets all of the options specified by
EEPROM locations to their default values, and performs a COLD restart to enter
the QED-Forth monitor. Note that the cleanup mode cannot remove a
PRIORITY.AUTOSTART vector if page 4 is write-protected or PROM. If a buggy
PRIORITY.AUTOSTART routine is installed in PROM, remove the PROM to fix the
problem. If a buggy PRIORITY.AUTOSTART routine is installed in
write-protected RAM, turn DIP switch#1 OFF before entering the special cleanup
mode so that the autostart pattern can be erased.

After using the
cleanup mode, set DIP switch #5 to its standard “off” position and reset the
processor to re-establish the normal operating mode and continue programming.