Interrupt Request (IRQ) and PC interfacing

Keywords: IRQ, hardware interrupts, interrupt service routine, ISR, triggering an interrupt

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.

Motivation and Audience

       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:

Parts List and Sources

US-based vendors include Jameco, Digikey, JDR and Radio Shack. Note: Boondog has no association with these vendors.


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.

Circuit Construction

A combination of wirewrapping and soldering was used to construct a PC interfaced debounce switch. A debounce switch is characterized by a ripple-free transition between on and off states. A switch is mechanical and when toggled, its wiper slams into position. This slamming results in ripples that take a short but finite time to settle. Because, electronic circuits are fast, they interpret these ripples as a sequence of (unwanted) on's and off's. To ensure that toggling the switch once results in one-and-only-one toggle, a debounce switch is typically used. A debounce switch can be quickly built using a 7400 NAND gate and a resistor pair. This is featured in the schematic.


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.


Begin by wirewrapping/soldering up your debounce switch on a small breadboard (photo below). Part placement is not critical. The +5V, GND and Q pins of your debounce switch need to be wired up the ISA prototyping card.

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 analog-to-digital/digital-to-analog 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.

Interrupt Software Programming

Rising an IRQ pin (IRQ3 in the schematic) high would provoke your PC to do "something". This "something" is defined in a section of code called an interrupt service routine (ISR). ISR's can be loosely thought of as functions that get executed when an IRQ pin is raised to +5V. Once your PC completes the "something", it returns to its main program and happily continues until the next time the IRQ pin goes high.

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.

DOS Turbo C code
Note: download toggle.c rather than cutting and pasting from below.

   FILE: toggle.c
   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);

int  i = 0;
long j = 0;

#define IRQ3  0x0B   /* IRQ3 address */

int main(void)
  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 {
    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)
  outportb(0x20, 0x20); /* send EOI signal */

Fuller Code Description

For those unfamiliar with Turbo C syntax, cprintf is defined in conio.h and allows one to print to a defined place (gotoxy()) on screen. As such, j's value is always printed at the same spot in the screen.

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)
        outportb(0x20, 0x20); /* send EOI signal */

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.

Results and Observations

Re-visiting the five questions in the Motivation and Audience section gives stock of what you accomplished. You implemented a simple hardware interrupt circuit by interfacing a debounce switch to your PC's IRQ lines. In particular you used IRQ3 (ISA bus pin b25) and wrote an ISR that monitored it; toggling high brought IRQ3 high and your ISR incremented the count. Your accomplishments exercised your pursuit for answers to the last two of the five questions.

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.

Final Words

Frankly, interfacing a toggle switch to your PC off the ISA bus' IRQ3 line is not very exciting. To appreciate the speed advantages hardware interrupts offer, replace the toggle switch with a device capable of generating higher speed +5V/GND transitions. This can be a 555-based or commerical square wave generator. Running toggle.c with a 40 MHz 386-SX was able to safely count square-wave toggles at 10 kHz! For an analog-to-digital converter circuit, this suggests a 0.1 msec sampling time is safely possible.

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 return to Main Page

Click here to email me


There are 6 IRQ's available, namely IRQ2 through IRQ7, on all PC's. AT class machines, 286's and above provide additional IRQ's (IRQ8-IRQ15), but this is beyond this tutorial's scope. IRQ3 was used in the tutorial, but one can also use other IRQ's. The table below gives the addresses for the IRQ's that off the ISA bus as reference: