Using the 1024Hz CMOS Timer Interrupt

Copyright © 1997 by Mark Feldman


Important Note: I've recently received a few e-mails from people claiming that the information in this file doesn't work for them. I'm still trying to track down the exact cause of the problems, until I do the information in this file should be considered incomplete. It will not work on all machines.

Introduction

I was originally going to write an article for the ill-fated PCGPE 2 on using the CMOS timer interrupt, but since I no longer support MS-DOS that article will not be written. Instead, I've written this short HTML page showing how to enable and use it for anyone who's interested.

What is It?

The CMOS timer is a chip on the motherboard responsible for keeping track of the time. It's connected to the motherboard battery, which is why your computer keeps the correct time even when you unplug it from the wall (unless of course you disconnect the battery). When the computer is first booted the appropriate BIOS function reads the time from the CMOS chip and sets the appropriate variables in system memory. BIOS also initializes the PIT chip, which then periodically (ie 18.2 times a second) generates an interrupt to update the time variables. Messing with the PIT chip's frequency will affect the computer's time, but it will automatically be set again the next time the user boots and BIOS reads the CMOS chip again. You can also read the time from the CMOS chip directly if you want to set the correct time afterwards (details on how to do this are in HelpPC).

One very useful feature of the CMOS timer is that it's capable of generating regular interrupts. The frequency is fixed at 1024Hz, but that's is more than enough for most applications, and in my experiments it has a negligable affect on overall system performance. Best of all, it leaves the PIT chip free to do other stuff. In fact, if you develop your application carefully you probably won't have to use the PIT chip at all (unless you need an extremely high resolution timer such as that required for CPU instruction timing)! I've found the CMOS timer to be safer, easier to implement and more reliable that PIT (particularly when running under a Win95 DOS shell).

Ok, So How Do I Enable It?

The following code will enable the interrupts:

outp(0x70, 0x0B);

outp(0x71, inp(0x71) | 0x40);

The following code will disable the interrupts:

outp(0x70, 0x0B);

outp(0x71, inp(0x71) &0xBF);

When the timer is enabled it will trigger interrupt 0x70 at a rate of 1024Hz. Place a custom interrupt handler there to trap it. As always, make sure you install your custom handler before enabling the interrupt, get the old handler beforehand and be sure to call it in your own, and make sure you clean up after yourself when you're done.

(Details on what the above registers actually do can be found in HelpPC).

How Would I Use This in a Real Application?

I'm sure there are many ways to use this, here is one I would suggest. I'll assume that your application is doing real-time sound mixing and needs accurate timing for animation control.

First of all I'd declare the following two variables:

volatile unsigned LONG time; // indicates elapsed time
int mixing=0;                // a boolean variable

The volatile keyword is used to indicate to the compiler that it should always read the contents of the variable before using it (Most compilers often cache variables in registers in order to improve performance. This is unacceptable since the interrupt handler may change the value of the variable at any time).

Next I would write an interrupt handler which looked something like this:

void interrupt handler(...) 
{
 time++;               // Update time value
 oldhandler();         // call the old handler
 // If we are not already mixing then we should try and mix some more
 if (!mixing)
 {
  mixing = 1;    // Indicate that we are now mixing
  _asm sti;      // Enable interrupts
  MixSound();    // Go mix
  mixing = 0;    // No longer mixing
 }
}

This code starts by incrementing the time variable. Thus, the main application can simply read this variable at any time and divide by 1024 to get the number of seconds elapsed (or divide by 1.024 to get he number of milliseconds). The handler should then calls the old handler so that the interrupt is acknowledged (can also be accomplished with the outp(0x20,0x20) instruction). The handler then does sound mixing.

You can see that the interrupt handler I've designed calls a function called MixSound(). I assume that this function determines whether there is any sound to be mixed into the playback buffer, and if so does whatever it needs to do. Now if this function mixes 1/2 a second of sound (say) at a time, then quite a few int 0x70's may be triggered while it is mixing. Normally our handler wouldn't be called for these, and we'd see a slight "glitch" in animation" since the time variable would have skipped over a few interrupts.. For this purpose it is important that interrupts are enabled while the mixing is in progress, so that further int 0x70's interrupt the sound mixing function and are still processed (hence the _asm sti statement). The problem though is that this will cause the MixSound() function to be called again when it's already in the middle of mixing sound - the result of which is potentially disastrous. For this reason I maintain the mixing variable, so that while mixing is in progress the variable is set and the handler doesn't try to call the mixing routine again.

I haven't actually tried using the CMOS timer for tripple buffering, but I imagine it would be pretty straightforward. If you are in a 60Hz display mode then retraces would occurr every 1024/60=17.0666 ticks or so (make sure you stay synched to the retrace, I'd probably start polling the VGA card after about 15 ticks or so just to be safe). I'd use a loop similar to the MixSound() loop above, which would instead poll the vertical retrace bit waiting for the retrace to start. It should then read the current time value, calculate when the next retrace is due to start (current time + 15) and then go do whatever it has to do.

One last thing I'd like to point out is that the time variable I use is a 32-bit unsigned long. This means that it's value would wrap around to 0 after 4294967296 ticks, which is about once every seven weeks. I can't imagine very many programs which would be left running for this long, but it's best to keep it in mind anyway.


Back to the PCGPE Home Page