Device Control


Using 1KHz Interrupts On AT Clones

Paul A. Cornelius


Paul Cornelius is manager of software engineering at Coherent, Inc., a supplier of software controlled laser systems to the scientific research community. He can be reached at Coherent Laser Group, 3210 Porter Dr., Palo Alto, CA 94303.

Timing Periodic Events With The 1KHz AT Interrupt

The PC compatibles family is widely used for data acquisition and device control. One problem that plagues the PC programmer in this application domain is the measurement of time intervals and the generation of periodic events. The difficulties are especially acute if the requirement is to manage time intervals in a hardware-independent fashion across a variety of CPU speeds and types. Most PC programmers are familiar with the BIOS Time of Day interrupt, which "ticks" at 18.2 Hz on all PCs. However, the resolution afforded by this service is inadequate for many data acquisition and control applications. Alternatively, the 1024 Hz Real Time Clock Interrupt on AT clones (not on true PCs or XTs) provides millisecond time resolution. The use of this interrupt presents some practical obstacles, however, owing to the limited support available in the BIOS and the generally poor documentation of this feature.

The first part of this article describes a simple method for using the Real Time Clock to time events with millisecond precision. The second part of the article presents C+ + classes that manage a list of periodic event handlers, driven by the 1024 Hz interrupt.

Theory Of Real-Time Clock Interrupt 70h

The Real-Time Clock interrupt (70h) can be enabled through BIOS INT 15h, functions 83h and 86h. Function 86h causes the executing program to be suspended; it is not useful for periodic timing and will not be discussed further. See Table 1.

BIOS INT 0x15 Function 0x83 Description

BIOS INT 15h, function 83h (SET INTERVAL) uses the Real Time Clock to time an interval. During the interval, the calling program continues to execute and the Real Time Clock Interrupt (70h) is enabled. When the interval ends, the BIOS signals the calling program by setting a bit in memory and disables the interrupt. See Table 2.

To invoke the SET INTERVAL service, the CPU registers are loaded according to Table 1 and an INT 15h call is made. The BIOS copies both the interval count and the wait flag pointer to specific RAM locations (see Table 2) . Then the BIOS enables the Real Time Clock Interrupt 70h and returns control to the calling program.

Once enabled, INT 70h occurs every 976 microseconds (1024 Hz). For each interrupt event, a BIOS-installed INT 70h handler subtracts 976 from the "Interval microseconds remaining" at address 40h:9Ch. When the subtraction causes the value to pass through zero, the BIOS sets bit 7 in the wait flag address and disables the Real Time Clock Interrupt.

The largest interval that can be timed with this service is FFFFFFFFh microseconds, which is just under 72 minutes. To make the service useful for general periodic timing applications, this eventual timeout must be circumvented.

Periodic Timing Based On BIOS INT 15h Function 83h

My first goal is to write a set of low-level drivers that facilitate the use of the Real Time Clock Interrupt. I wish to exploit the BIOS as much as possible, both to maximize the portability of the implementation and also to simplify the code.

The approach is to install a custom handler for the Real Time Clock Interrupt, then to enable the Real Time Clock using the BIOS SET INTERVAL function. We will show a simple method of defeating the timeout. Three low-level functions are defined: the interrupt driver itself, an installation function that enables the clock interrupt, and a de-installation function that disables the clock and restores the original BIOS interrupt vector.

The implementation is in C++ and has been compiled and tested with Version 2.1 of the Zortech MS-DOS C++ compiler. Though the code necessarily relies on non-ANSI library functions, conversion to other platforms should be relatively straightforward. The drivers can also be modified easily for a C, rather than C+ +, environment.

Library Support

The Zortech library supports interrupts through the function int_intercept, which installs a standard C function as an interrupt handler. The previous interrupt vector is saved; control can be chained to the old handler by returning a value of 0 from the new handler. The companion function int_restore puts the old vector back in place.

Driver Implementation

The interrupt driver int_70 is shown in Listing 1 and Listing 2. Circumvention of the SET INTERVAL timeout is accomplished by a single executable statement: a value greater than 976 is poked directly into the RAM interval count address each time the driver is called. This allows the process to run forever by preventing the timer count from decrementing through zero.

The interrupt driver itself does not do anything useful, but calls a function handle_tick. The intention of this design is to keep the driver as general as possible; each specific application will define its own function handle_tick to carry out any desired real-time processing. The simplest possible example of a handle_tick function is a counter that increments at 1024 Hz:

volatile long unsigned count;
void handle_tick() //elementary
        //counter {    count++; }
After handle_tick has done its work, the driver chains to the original BIOS handler by returning 0. (Note that this is a feature of the Zortech implementation and not a general property of interrupt handlers!) The BIOS handler manages all of the hardware details concerned with interrupt termination.

The installation of the driver and activation of the Real Time Clock Interrupt are likewise quite straightforward. The function start_timer accomplishes this as follows:

De-installation of the driver and termination of the Real Time Clock Interrupts are done by stop_timer. First the original interrupt vector is recovered by calling int_restore. The BIOS SET INTERVAL routine resumes normal operation and will time out in 64k microseconds (the value that int_70 poked into the counter). On expiration of the count, bit 7 of dummy will be set. To test for proper termination, stop_timer delays for 200 milliseconds and then tests the value of dummy, which will be nonzero if all is well.

C++ Classes For Periodic Events

Using Listing 1 and Listing 2 as a foundation, the 1024 Hz interrupt can be built into applications with as much ease as the more commonly used 18.2 Hz Time of Day Clock. In this section, I illustrate the use of the driver by developing a set of C++ classes for periodic event control. The code is an example of the often underrated value of C++ for device control applications. Furthermore, the classes are not strongly tied to the particulars of the Real Time Clock and could be adapted easily to other hardware environments.

Class RepRate: Periodic Event Handlers

The class RepRate associates a function pointer - the "user function" — with a desired repetition rate in Hz. Once the Real Time Clock Interrupt is activated, the user function will be called as an interrupt process at the desired repetition rate. Several features of the implementation of RepRate are noteworthy (see Listing 3 and Listing 4) .

The design of the class RepRate allows for many RepRate objects having different repetition rates. The class RepRateList, of which there can be only one instance, is responsible for managing a list of active RepRate objects and coupling them to the "ticks" of the Real Time Clock. Each time the clock ticks, RepRateList invokes the member function HandleTick() for every active RepRate object. HandleTick() has been made private to enforce the fact that it can only be invoked through the friend class RepRateList.

For each RepRate object, the desired repetition rate is achieved as closely as possible by a simple "counting down" of the 1024 Hz clock. For example, to obtain a 20 Hz rate the 1024 Hz clock will be divided by 51, achieving a true repetition rate of 20.0784 Hz. The instance variable tick_interval, the value of which is computed during the constructor call, holds the divide-by count. A second instance variable, count_now, is incremented each time the member function HandleTick() is called. When count_now reaches the value of tick_interval, the user function is called and count_now is reset to zero.

Class RepRateList: A List Of Event Handlers

A RepRate object can be in either an active or an inactive state. Active objects are those whose HandleTick() function is being called periodically. A RepRate object is made active when it is linked to the RepRateList. It becomes inactive when it is linked out of the list.

RepRateList has a simple interface: a constructor, a function to link in a RepRate object, and a function to link out a RepRate object. As soon as the first RepRate object is linked in, RepRateList turns on the Real Time Clock Interrupt. When the last RepRate object is linked out (or when the RepRateList destructor is called) the Real Time Clock is turned off. The implementation makes use of the Zortech C+ + Toolkit class for doubly linked lists.

Example Program

Listing 5 shows a trivial but complete program using all of the facilities developed here. Two RepRate objects are created, one having a repetition rate near 100 Hz (actually 102.4 Hz) and another near 1 Hz (1.024 Hz). The user functions display their total accumulated count on the screen; for amusement the 100 Hz object also sounds a click on the PC speaker.

Until the user presses a key, neither of the RepRate objects is linked to the RepRateList. The Real Time Clock is not activated; nothing happens on the screen. After the first keypress, the 100 Hz object is linked in and begins to count. The second keypress links in the 1 Hz object, at which point the two objects count at their respective rates. A third keypress links the 1 Hz object out again, and its count stops. A fourth keypress terminates the program.

Limitations Of The Design

The design shown here has certain limitations, the seriousness of which depends on the intended application:

If the worst-case execution time of the RepRate list exceeds 976 microseconds, the consequences are "unpredictable" — the programmer's euphemism for possible disaster.

For all but the first object in the RepRateList, there can be jitter in the latency between the Real Time Clock tick and the invocation of the user function.

The linked list of RepRate objects must be traversed for every clock tick; the resulting performance overhead may be unacceptable.

While it is beyond the scope of this article to address these concerns, I hope to have shown the possibilities inherent in this little-used PC feature. I also hope that the code given here will stimulate interest in the potential of C++ for device control.

Bibliography

System BIOS for IBM PC/XT/AT Computers and Compatibles, Phoenix Technologies Ltd., Addison-Wesley, Reading, Massachusetts 1989.

Programmer's Problem Solver for the IBM PC, XT, and AT, Robert Jourdain, Prentice Hall, New York, New York 1986.

C++ Function Reference V2.1, Zortech Inc., Arlington, Massachusetts 1991.