Interrupts on the 68HC11

by Karl Lunt

You can't write robot software of any complexity without running into interrupts. If
you haven't had to confront them yet, you might as well get introduced now. Besides, if
you use them properly, interrupts can solve some pretty tough problems for you.

An interrupt causes the microcontroller (MCU) to drop what it's doing, namely running
your main program, and start up a different, higher priority task. Usually, this task is
also part of your program, and when that task finishes, the interrupt processing ends and
the MCU picks up where it left off in the main program.

The above paragraph hits several important points. First, you are responsible for
writing both the main program and any interrupt code required. Second, the MCU generally
spends the bulk of its time running your main program. Finally, when the MCU does need to
drop what its doing and service an interrupt, the interrupt code must eventually end or
the MCU will never return to your main program.

Let's take a short look at a typical interrupt in the 68hc11 MCU, so you can see how
these ideas work. Even though the 68hc11 supports about a dozen different interrupt
sources, I'll use one of the simplest as my example, the real-time interrupt, or RTI.

Often, your main program has to time an event. For example, suppose you need your code
to sample a bumper switch several times, to make sure the switch is really closed. Suppose
further that you want each sample to take place after a delay of 25 msecs. You could write
your main program so it sits in a busy loop, counting down cycles until each 25 msec chunk
has elapsed, but that's inelegant and wasteful. After all, the MCU might have to keep
track of some other tasks as well, such as looking for keypresses on a wireless joystick
or changes in light on a photocell.

Regardless, the simplest way to count out fixed-duration delays is by using the RTI.
This is a part of the 68hc11 timer subsystem that can generate an interrupt at precise
intervals, based on the crystal that drives the MCU. If your 68hc11 system uses the
typical 8 MHz crystal, and your software makes no other changes to the RTI timing
subsystem, the 68hc11 is capable of generating an RTI interrupt every 4.096 msecs.

Those of you who have already written a few non-RTI programs for the 68hc11 are
probably wondering what happened in your code when the RTI interrupt went off. The answer
is nothing, since the RTI interrupt never fired. The 68hc11 comes out of reset with all
makable interrupts disabled, since the I-bit in the CCR is set. Additionally, all
subsystems that could generate an interrupt have their interrupts shut off as well. In
other words, before any interrupt can occur, your software must enable interrupts in
general, by clearing the I-bit in the CCR, and enable the specific interrupt you want to
happen.

In the case of the RTI, your software must modify two 68hc11 registers before RTI
interrupts can trigger. The first register, TFLG2 at $1025, contains the bit RTIF, which
is bit 6. If this bit is a 1, the RTI interrupt has triggered. This doesn't mean that the
interrupt was serviced or that the interrupt actually occurred; it only means that the RTI
subsystem reached a point that a real-time interrupt could have happened, and the
subsystem sets this flag so your software can determine that.

The other register involved is TMSK2 at $1024. Bit 6 of this register, known as RTII,
acts as a mask. If this bit is clear, the 68hc11 will not generate an RTI interrupt, even
if the RTI subsystem triggers one. The 68hc11 always clears this bit when coming out of
reset, which explains why you may well have never seen an RTI before. Setting this bit,
however, unmasks the RTI interrupt and allows the RTI subsystem to generate an interrupt
when necessary.

Even now, however, your main program is still shielded from RTI interrupts when coming
out of reset. This is because the 68hc11 always starts up with all maskable interrupts
disabled, owing to the initial state of the I-bit in the CCR. Until your program clears
the I-bit using a CLI opcode, no maskable interrupts can occur.

Therefore, your code must do all of the three following tasks, and generally do them in
this order, before the 68hc11 will generate an RTI interrupt:

1. Set the RTII mask bit in TMSK2 (bit 6 of $1024),
2. Clear the RTIF bit in TFLG2 (bit 6 of $1025),
3. Clear the interrupt mask bit in the CCR using a CLI opcode.

Note that setting the mask bit in TMSK2 is straightforward, but clearing the flag bit
in TFLG2 looks odd. The code seems like it should end up setting the bit, rather than
clearing it, but that is how the mechanism built into the 68hc11 works.

With these tasks out of the way, the 68hc11 will generate an RTI sometime within the
next 4.096 msecs. When that happens, the MCU will cease execution of your main program and
immediately start processing your RTI interrupt service routine (ISR). It does this by
treating the address $fff0 as a vector and reading the 16-bit value at that address. The
MCU then passes control to that address. Obviously, you had better have stored the address
of your ISR at $fff0, or the MCU will head into the tall weeds on the first RTI interrupt.
You can set up a vector to your ISR in SBasic by using the INTERRUPT command. Here is an
example of a simple RTI ISR in SBasic:

This little block of code, while quite small, actually handles all of the tasks needed
for properly servicing an RTI. The INTERRUPT command tells SBasic to store the address of
this code at $fff0, essentially setting up the RTI interrupt vector for you. The next
three lines are the body of the ISR. Here, the code checks the variable WAIT and, if that
variable is not 0, decrements it. Finally, the POKEB command clears the flag bit in TFLG2
before exiting the ISR via the END statement.

The above code turns WAIT into a "magical" variable; that is, any time it
contains a value other than 0, it automatically begins decrementing at a rate of one count
every 4.096 msecs. The final POKEB command is essential to proper operation of the ISR. If
the ISR didn't clear the flag bit in TFLG2, the RTI would not trigger 4.096 msecs later.
The flag bit must be cleared in order to permit the next RTI to occur!

Finally, we can return to our original problem, that of delaying for a fixed interval
using the RTI interrupt. Before your code sets up the RTI, as described above, it needs to
write a 0 into variable WAIT. When the code enables RTI interrupts, each interrupt will
check WAIT, see that it is already 0, rearm the RTI interrupt, and exit. WAIT will remain
stuck at 0.

To delay for 25 msecs, your code should set WAIT to 6, which will cause a delay of
4.096 msecs times 6, or 24.576 msecs, which is generally good enough for debounce delays.
WAIT will begin decrementing once every 4.096 msecs until it finally hits 0, at which
point it will remain unchanged. Your main code can do anything you want it to do,
occasionally checking the value in WAIT. When WAIT finally hits 0, your code knows the
proper delay has elapsed, and can set up the next task.

Note that there is some uncertainty in this technique. Your program cannot know exactly
how long it will take before the RTI ISR decrements WAIT for the first time. For example,
your code might load a 6 into WAIT a few cycles before the next RTI interrupt occurs. In
that case, WAIT will actually count out five full delays of 4.096 msecs and an intial
delay of just a few microseconds. For this reason, you cannot use RTI to schedule reliable
delays of only one or two intervals. Still, for significantly longer delays, this
technique works quite well.

This is one of the simpler interrupts in the 68hc11 arsenal, yet it still offers a lot
of power and I use it in almost all of my programs. For more information on this and other
available 68hc11 interrupts, consult the "pink book," also known as the 68hc11
Reference Manual, available free from Motorola. Be warned that this isn't an easy book to
dig through, but it's better than most reference manuals and has all of the information
you need. If you get really stuck, just ask around the SRS; many of us use the 68hc11 and
will be glad to help you out.