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.
Here is a snippet of SBasic code that will perform the above tasks:
pokeb tmsk2, peekb(tmsk2) or $40 ' unmask RTI interrupts
pokeb tflg2, $40 ' clear RTI flag
interrupts on ' allow interrupts
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:
interrupt $fff0 ' RTI ISR if wait <> 0 wait = wait - 1 endif pokeb tflg2, $40 ' clear RTI flag end
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.
Keep on keeping on...