SLTF Consulting
Technology with Business Sense

 Home | Bio | Contact Us | Site Map


 

Interrupts might seem basic, but many programmers still avoid them

Scott Rosenthal
May, 1995

When I first started working with microcontrollers, interrupts seemed like a magical mechanism that only a rare few really understood. Intimidated, I religiously avoided them and stuck with polled I/O. After a while, though, I decided to become one of the few, the proud, the programmers that not only used interrupts but exploited all their capabilities. Just like taking apart a toaster oven to see how it works, I disassembled all the interrupt-driven code I could find. Now, interrupts are easy, fun and above everything else, extremely necessary for today's embedded systems.

In case you're wondering why I'm taking a column to talk about something so basic, the answer is twofold: First, in the past few months, I've examined (and chased bugs in) several examples of code by people who were supposed to know what they were doing with interrupts, yet they consistently got part of the implementation wrong. Second, in my company the one part of an instrument's operating system that I always receive questions on is how it manages interrupts. Therefore, it's time for a review.

The basics

Obviously, each microcontroller and microprocessor handles interrupts somewhat differently, yet they all share some common functionality. Basically, an interrupt causes a program to suspend its current operation and branch to a location elsewhere in memory. Then, after the program handles the event that caused the interrupt, the interrupt service routine (ISR) must restart the program from where it had previously been suspended.

Electrically, an interrupt is a digital signal into a CPU that indicates some event has happened. For example, receiving serial information, pressing a key or a timer expiring can all generate an interrupt. Expanding on the keypress example, a common way of generating an interrupt is with the venerable 74C923 20-position keypad encoder. When you press a key, this chip's debounced Data Available (DA) signal goes High and stays there until you release the key. By tying this signal to one of the CPU's interrupt inputs, the processor can sense a keypress as soon as it happens.

Interrupts come in three flavors: edge-triggered, level-triggered and a combination of both. As the name indicates, edge-triggered interrupts happen on the transition of a signal from one state to another, primarily from a Zero to a One. This type of interrupt is useful for a fleeting signal that doesn't last long enough for the processor to recognize it using polled I/O or for when the signal can last a long time, but the significant event is when that signal first goes active. Again, the keypress is an excellent example of an application that calls for an edge-triggered interrupt. From an interrupt's viewpoint, the amount of time you hold the key down doesn't matter. All that's important is detecting when the event first happens.

By contrast, level-triggered interrupts are in some ways like polling except that the CPU manages the polling without program intervention. Typically, the processor samples the interrupt input at predefined times during each bus cycle such as state T2 for the Z80 microprocessor. If the interrupt isn't active when the processor samples it, the CPU doesn't see it. One possible use for this type of interrupt is to minimize spurious signals from a noisy interrupt line.

The last interrupt type is a hybrid, where the hardware not only looks for a transition, but it also verifies that the interrupt signal stays active for a certain period of time. A common hybrid interrupt is the NMI (non-maskable interrupt) input. Because NMIs generally signal major-or even catastrophic-system events, a good implementation of this signal tries to ensure that the interrupt is valid by verifying that it remains active for a period of time. This 2-step approach helps to eliminate false interrupts from affecting the system.

Processing an interrupt

Okay, now you have an interrupt signal at the CPU, the next step is to see how it affects the processor's operation. No single technique describes all possible forms of interrupt processing, so I'll start with a simple description and work up to more complicated ones.

Interrupt processing has some basic requirements from the CPU. Before it can respond to an interrupt, the processor must wait for an "interruptible" state in its processing. For example, if the processor's writing to memory, it must wait until the write is done before processing the interrupt. Once the CPU detects the interrupt, its first action is to save all the information it will need to resume normal processing once the interrupt is over. At a minimum, the chip saves the Program Counter (PC). This process is analogous to you placing a finger in a book when someone interrupts you while you're reading. After the interruption goes away, you know exactly where to continue.

After saving this "bookmark" information, the CPU changes the PC value to a fixed location in the processor's memory that contains a pointer to the instructions-called an interrupt service routine (ISR)-that tells the processor how to deal with the interrupt. When the ISR finishes, the processor restores the original PC and merrily continues on as if nothing had happened.

An NMI is an excellent example of this most-basic form of interrupt. Many processors have a special interrupt pin reserved for flagging such catastrophic events as a power failure. Using this interrupt might allow you a couple of milliseconds in which to save crucial operating information or shut down a system before power dies completely.

Beyond these basics, interrupts often sport some refinements that make them even more useful. For example, one common feature is to multiplex one interrupt input by allowing the interrupt source to send an identifying code along with the signal that allows the processor to tell which interrupt occurred. This code might take the form of an address where the processor can find the ISR for that particular interrupt, or an index into a table where the processor can look up the ISR's address. The code typically goes into the processor on the data lines.

When the interrupt occurs, the processor also sends out an acknowledge signal analogous to the CPU memory read signal that other parts of the system use to place information onto the processor's data bus. I've seen processors issue anywhere from one to four of these acknowledge signals for each interrupt. Such multiple signals allow the processor to get additional information about the interrupt from the hardware.

Depending on the microprocessor, this information can take the form of either a vector address or an executable opcode. The latter can lend itself to some rather peculiar implementations. I once worked on a very early floppy-disk system-big 8" disks that held all of 90k bytes-that required considerable horsepower from the processor. As each new byte came off the disk every 35 µsec, the processor had to store the data away as it came in. At the time, this speed was much too fast for an ISR, so instead we implemented a halt-interrupt technique that involved executing a halt instruction and then waiting for an interrupt-on this processor the only way to exit the halt. When the next data byte became available, the hardware issued an interrupt. The interrupt acknowledge read a NOP (no operation) opcode that the processor executed, and then the processor started executing the next instruction after the halt. This technique allowed us to quickly read all the data while staying synchronized-but just try to document the technique in the code comments!

ISR basics

Finally, no interrupt primer would be complete without a discussion of what goes on inside an ISR. Basically there are two fundamental rules. First, the ISR must save then restore all CPU, memory and I/O resources that it uses. For example, this data might include CPU registers or memory data such as the scratch area for a floating-point package (many of which aren't reentrant). Second, it must get back out of the ISR as quickly as possible. The reason for this rule is that ISRs should generally block new interrupts until after the ISR has completed running. Therefore, an ISR should do as little as possible so that interrupts are off for as little time as possible. For the processors I typically use, I like to see ISRs run in less than 40 µsec. Obviously, a 100-MHz Pentium and a 4-MHz 8051 have very different time scales, but the concept's still the same-get out quickly! PE&IN



About Us | What We Do | SSM | MiCOS | Search | Designs | Articles

Copyright © 1998-2014 SLTF Consulting, a division of SLTF Marine LLC. All rights reserved.