The interrupt concept is easy enough to understand; the program
executes a dedicated task, services the interrupt when it occurs
and then resumes its task. Implementation however is often
considered a mystery to be used by "elite" programmers and avoided
by others. As such, one often avoids interrupt use with polling and
ad hoc dummy delay loop techniques. Although limited,
performance with such techniques may be acceptable and interrupt
use is further avoided. But often applications requiring faster
performance, like analog-to-digital conversion or incremental
encoder pulse counting, benefit greatly from interrupt use. This
tutorial features wiring up an switch (seen in the above
photo) to the ISA bus' IRQ line and programming an interrupt
service routine (ISR) to count switch toggles. Through this
example, you might find implementing interrupts easier than
you thought.
What use are the IRQ2-IRQ7 lines on the ISA bus? Why would I care to use IRQ2-IRQ7? When do I need to use IRQ2-IRQ7? What is a hardware interrupt? What's an ISR? What's an IRQ? I want a practical, implementable example with schematics and code!This tutorial's audience is characterized by people asking questions like the above. Perhaps you have been overwhelmed by books and webpages that immerse you in minutia like the 8259 programmable interrupt controller (PIC), control and command words. Perhaps you find the software interrupt literature aggravating, which details BIOS handlers and the like, detouring your search for answers.
This tutorial takes a focused approach. It endeavors to serve you with answers through a simple hardware example, often absent in the existing literature. The tutorial presents a debounce switch wired to the ISA bus' IRQ3 line. Turbo C code is written that counts switch toggles by an interrupt service routine (ISR). This focused approach yields quick answers to the above questions and it serves both as a stepping stone to understanding the existing literature and to bigger applications like interrupt driven analog-to-digital converters and incremental encoders. So let's get started!
This tutorial is broken down as follows:
PART DESCRIPTION | VENDOR | PART | PRICE (1999) | QTY | |
PC ISA BUS PROTOTYPING CARD | JAMECO | 21531 | 17.95 | 1 | |
7400 QUAD NAND GATE | JAMECO | 46252 | 0.25 | 1 | |
10 KOHM RESISTORS | JAMECO | 29911 | 0.99/100 | 2 | |
14-PIN WIREWRAP SOCKET | JAMECO | 37217 | 0.75 | 1 | |
SPDT TOGGLE SWITCH | JAMECO | 26315 | 1.15 | 1 | |
SMALL (2 SQ.IN) BREADBOARD | JAMECO | 105099 | 2.75 | 1 |
If you live in the US or Canada, all these parts are sold at your neighborhood Radio Shack. Most mail-order/surplus/hobby electronics retailers have these parts. Headers, housings and crimps were further used for quick and professional looking connections. Example Digikey part numbers are WM4002-ND (headers), WM2002-ND (housings), WM2200-ND (crimps) and WM2312-ND for crimp tool. Of course, you can choose to wirewrap/solder your circuit without these parts.
irqSchematic072800.pdf is the Acrobat file of the same schematic. You will need Adobe's free Acrobat reader to view it.
The schematic is relatively straight-forward. One possible (and common) confusing point with ISA bus circuits is the use of the prefix "A". An ISA card has a component and solder side, often called "A" and "B" respectively. There are 62 edge tabs: 31 on the component side (A1-to-A31) and 31 on the solder side (B1-to-B31). However, address lines often use the prefix "A" too. On the PC there are 20 address lines (A0-to-A19).
To avoid this prefix confusion, this tutorial uses the lower case "a" to refer to the component side's 31 edge tabs. For example a11 is the AEN pin. "b" refers to the solder side's 31 edge tabs. For example b25 identifies the IRQ3 pin. The schematic above includes the ISA 62 pinout for referencing connections to debounce switch.
The ISA prototyping card (left) fits into your PC's motherboard just like
a modem or sound card would. As mentioned before such a card
has 31 pads on each side. The schematic only requires wiring
up three pins b1, b3 and b25 to your debounce
switch's GND, +5V and Q pinouts respectively.
Understandably, the ISA prototyping card is pricey ($17.95 at Jameco) for just three connections. However, you can later use your ISA prototyping card to build future circuits, like the 8255 PC Interface Card featured in another tutorial (photo below). The IRQ header post is zoomed and lies near the component-side tabs.
Recall that the task at hand is to build a simple circuit to demonstrate hardware interrupts. As such, only 3 connections are required. Perhaps one can justify using this somewhat pricey prototyping board with the ambition of replacing one's debounce switch with an interrupt-driven analog-to-digital card or a incremental encoder pulse. This is touched upon briefly in the Final Words section.
With that said, you tether your debounce switch to your motherboard-installed ISA prototyping card, with wire. This wire tether can be a 3-wire ribbon cable four (or up to 25) feet long. The net result of your circuit building is a debounce switch that when toggled on, rises IRQ3 high (+5V) or when toggled off, lowers IRQ3 to low (GND). By programming an interrupt service routine, you can count the number of switch toggles by servicing IRQ3 and detailed next.
The code to appreciate this follows. In it, main() has the simple loop task of repeatedly incrementing a variable, j, and printing its value. main() continues this loop until a key is pressed (kbhit).
However when you toggle your debounce switch, the "something" executed is the ISR you named countToggle. Program execution transfers from main() to the ISR, which increments a variable, i, representing the number of switch toggles. The transfer from main() to ISR and back happens so quickly that it seems transparent.
/* FILE: toggle.c AUTH: P.OH DESC: ISR on IRQ3 for counting toggle switches */ #include< stdio.h > #include< stdlib.h > #include< dos.h > #include< conio.h > void interrupt (*oldIrq3)(void); void interrupt countToggle(void); /* GLOBALS */ int i = 0; long j = 0; #define IRQ3 0x0B /* IRQ3 address */ int main(void) { window(5,5,50,75); clrscr(); gotoxy(1,3); cprintf("Do-while loop iteration # "); oldIrq3 = getvect(IRQ3); /* save the old interrupt vector */ setvect(IRQ3, countToggle); /* install the new interrupt handler */ /* Unmask (i.e. enable) IRQ3. This requires turning bit 3 to 0 */ /* and bits 0,1,2,4,5,6,7 to 1. Hence 11110111 binary = F7 hex */ outportb(0x21, ( inportb(0x21) & 0xF7 ) ); /* Unmask (Enable) IRQ3 */ do { j++; gotoxy(27,3); cprintf("%ld\n", j); } while(!kbhit()); /* Key hit, so exit main. But first, be nice and return things back */ /* to original state */ setvect(IRQ3, oldIrq3); outportb(0x21, (inportb(0x21) | 0x08) ); /* disable IRQ3 */ printf("\nswitch presses i = %d\n", i); printf("j = %ld\n", j); return 0; } /* end of main */ /* this ISR should execute each time IRQ3 goes high */ void interrupt countToggle(void) { disable(); i++; outportb(0x20, 0x20); /* send EOI signal */ enable(); }
IRQ3 is #defined as 0B hex (11 in decimal). This is the address defined for the ISA bus' IRQ3 pin in PC's. The Appendix gives some more addresses for IRQ2-IRQ7 in case you'd rather wirewrap and service a different IRQ pin.
The statements:
oldIrq3 = getvect(IRQ3); setvect(IRQ3, countToggle);use Turbo C's interrupt functions getvect() and setvect(). Because IRQ number 3 may actually be assigned by your PC for other purposes (e.g. your modem or mouse), it's important to "bookmark" its value before assigning it to your desired task (counting switch toggles). getvect() does this "bookmarking" for you and saves the value to a pointer you named oldIrq3. setvect() assigns IRQ3 with a new function, i.e. the interrupt service routine (ISR) you named countToggle.
This "re-assignment" of IRQ 3 is completed with the statement:
outportb(0x21, ( inportb(0x21) & 0xF7 ) );0x21 is a special address called the Operation Control Word. Its value, inportb(0x21), holds the current settings of IRQ's 2 through 7. Its actual value is not important to us. Rather, we want to ensure that IRQ number 3 is configured to recognize high's (+5V) and low's (GND) on ISA bus pin b25. To achieve this, you must set the bit position corresponding to IRQ3 to 0. This is the reason for the logical bit-wise AND'ing of 0x21's current settings with F7 hex (or 11110111 binary). This is further emphasized diagramatically:
As you can see, all bit positions remain the same, save IRQ3 which is now 0.
The do-while loop increments and prints the variable j until you press a key. Now whenever you toggle your debounce switch, the ISR countToggle:
void interrupt countToggle(void) { disable(); i++; outportb(0x20, 0x20); /* send EOI signal */ enable(); }is executed. disable() is a Turbo C function that tells your PC to ignore any other interrupts. The toggle counting variable, i is then incremented. outporb(0x20, 0x20); is the statement responsible for telling your PC that the ISR has finished servicing your interrupt. This is also known as an end-of-interrupt (EOI) and gives your PC permission to re-dedicate itself to servicing main(). Before countToggle exits, the Turbo C function enable() tells your PC to heed any interrupt.
When you run toggle.c you will see do-while loop iterations faithfully printing. Switch toggles will not seem to disturb this printing. Once your press a key, the number of toggles is then printed.
It is important to restore the "bookmarked" value, oldIrq3 to IRQ3 before exiting the program. This is achieved with:
setvect(IRQ3, oldIrq3); outportb(0x21, (inportb(0x21) | 0x08) ); /* disable IRQ3 */The bit wise logical OR'ing with 08 hex sets IRQ3's 0x21 bit position to 1. In other words IRQ3 is disabled.
This accomplishment however begs answers to the first three questions. At this point, you might be saying to yourself, "big deal!" Perhaps, the simplicity of the exercise obscures the accomplishment. First let's revisit the first three questions with answers.
What use are the IRQ2-IRQ7 lines on the ISA bus?IRQ2-IRQ7 (six hardware interrupts) empower you with the means to immediately get the attention of your PC's central processing unit (CPU). They are ready for the taking, existing off the ISA bus. A rising high signal (+5V) on any of them will request the CPU to drop whatever it's doing and attend to another task. By programming a hardware interrupt (like IRQ3), you define the task (through an ISR) for the CPU to handle when that hardware interrupt goes high.
Why would I care to use IRQ2-IRQ7?Most probably you came upon this tutorial because you have PC interfacing interests. Perhaps your experiences and ambitions lie in PC control of motors, relays, temperature measurements and the like. If you've achieved such tasks without interrupts, you most probably did so with polling. Here, you would wait and monitor bit(s). By waiting, your task is at the "mercy" of your CPU's scheduler. Without hardware interrupts, the CPU is prioritorized refreshing the screen, checking key presses, powering up/down disk motors and the like. If the CPU decides it has time to handle your task, it will. IRQ2-IRQ7 gives you the power to place the CPU scheduler in your favor. Your task is given priority so that it doesn't idly wait.
When do I need to use IRQ2-IRQ7?When fast performance is required for your PC interfacing endeavors consider using IRQ's. For example, PC interfaced analog-to-digital converters (ADC) are typically used to acquire physical parameters like temperature and sound. ADC performance is characterized by sampling time (bandwidth), and for most applications, the shorter the better. IRQ's give your ADC the ability to demand the CPU's attention and process the acquired sample. Without IRQ's, your ADC's sampling time is dictated by the program cycle time, of which most is wasted time due to polling.
You have a working hardware interrupt driven PC interfaced debounce switch, an ISR program and answers to all five questions. Running toggle.c, you'll observe that toggle switching does not appear to slow down printing. It illustrates your power to dictate task handling on your own terms, not the CPU's.
Replacing your toggle switch with the output of an incremental encoder, toggle.c can be used for measuring disk rotation. For example, the infrared (IR) encoder tutorial on this site, has a one-hole disk. The IR output uses a Schmitt trigger for a clean +5V transition. This circuit can be wired to IRQ3 and toggle.c will report the number of disk rotations. It is possible to use this number over a fixed time and calculate motor RPM.
As such, this tutorial's simple circuit and ISR code endeavored to get you past the interrupt minutia common in the existing literature. It provides a stepping stone to bigger picture applications like ADC and motor RPM measurements. A future tutorial on a hardware interrupt driven ADC is in the works to illustrate what can be leveraged from what was presented here.
Click here to email me
IRQ NAME | HEX ADDRESS | TYPICAL USE |
IRQ2 | 0A | RE-DIRECTION |
IRQ3 | 0B | SERIAL COM2/COM4 |
IRQ4 | 0C | SERIAL COM1/COM3 |
IRQ5 | 0D | SOUND CARD |
IRQ6 | 0E | FLOPPY DISK CONTROLLER |
IRQ7 | 0F | PARALLEL PORT |