Written by : Peter Quiring (Oct 27/97)
Updated : Found exact PIT osc. speed (Nov 9/97)
Updated : Osc. speed was wrong. (Jan 7/98) [It's 1,193,181 Hz]
This tutorial will teach you how to use the PIT (programmable interrupt
timers). I must worn you though that using the following code will only
work when it runs under DOS. Windoze will not allow the PIT to
be reprogrammed (although you can still use the PIT at it's
default speed, you just can not change the speed under Win95).
What is the PIT? The PIT is one device that offers 4 timers.
But 3 of the 4 are already used by the system for one reason or another
so the user (you) can only really use one of them. The PIT has 4 channels
So the only channels you may use is #0 and #2. You can redirect #2 to the
PC speaker to generate digital sound reproduction but the quality is
usually pretty bad and very irratable.
- #0 - IRQ Driven counter.
- #1 - RAM Refresh counter. (do not touch!)
- #2 - Speaker counter.
- #3 - NMI generator (PS/2 only)
The main focus is on channel #0, which is connected to IRQ #0. The speed
of this timer can be precisely controlled which you can use to control
the speed of events in your programs (unless you are under Win95 in which
case you can not change the speed of the timer).
The PIT has one data port for each channel and one control port for
all the channels. They are as follows:
Now remember, you can only write to these ports from DOS, if you are in Windoze
(which includes a DOS-Box) all access to these ports is ignored.
- Port 40h : Channel #0
- Port 41h : Channel #1
- Port 42h : Channel #2
- Port 43h : PIT Control port
- Port 44h : Channel #3 (PS/2 only)
Each timer is connected to a crystal speed of about 1,193,181 Hz (exactly).
Each channel has two 16bit "counters".
One counter is the "divisor" counter that you setup. The second counter is
incremented each time with the crystal osc. When this counter reaches
the "divisor" counter then the event associated with that counter happens
(ie: the IRQ is triggered).
I am not going to go into how to use the PS/2 counter #3.
The control port is as follows:
Bits 7-6 : counter select
00 = counter 0
01 = counter 1
10 = counter 2
11 = (do not use)
Bits 5-4 : counter access
00 = (do not use)
01 = read/write "set" counter bits 0-7 only.
10 = read/write "set" counter bits 8-15 only.
11 = read/write "set" counter bits 0-7 first, then 8-15.
Bits 3-1 : counter mode
000 mode 0 select = zero detection interrupt
001 mode 1 select = programmable one shot
x10 mode 2 select = rate generator
x11 mode 3 select = square wave generator
divisor factor 3 not allowed!
100 mode 4 select = software triggered strobe
101 mode 5 select = hardware triggered strobe
Bit 0 : counting style
0 = binary counter 16 bits
1 = BCD counter (4 decades)
By default the "divisor" counter for channel #0 is 65536, and since
the divisor is a 16bit value you must use 0 to represent 65536. So if you
take 1,193,181 divided by 65536 you will get about 18.2. This means
that IRQ #0 occurs 18.2 times a second.
To create a simple timer use Mode 5, Style 0.
The DOS clock
One side effect of changing the speed of the system timer is that the
DOS "time" will warp into the future. So there are a few ways to overcome
A)Within your IRQ handler keep your own counter that will make sure
to call DOS only 18.2 times a second. So for example, if you change the
timer to 36.4 times a second then you could have your IRQ handler call
DOS every other time your IRQ handler is called. But this limits how
many frequencies you can use.
B)You can stop sending any IRQs to DOS so the system clock simply
stops while running your program.
C)Or you use step B and then set the DOS time to the Real-Time circuit when
when your program exits.
; all done!
And that will update the DOS timer to the Real-Time clock. Make sure you
do this after returning the timer to it's normal state (65536).
Let me quickly show how to set up this:
Speed = 60 Hz.
First hook the IRQ handler with your own, do not make your handler branch
on to the old handler. But you still must get the old handler so you
can restore before terminating.
mov eax,1191180 ;PIT osc speed
xor edx,edx ;clear EDX
div ebx ;Eax = divisor counter
mov ecx,eax ;Save Eax for later use
mov al,000111010b ;Channel=0 Mode=5 Style=0
out 43h,al ;Prepare channel for new divisor
Now IRQ #0 will be triggered 60 times a second.
Once you are finished using it use this code to clean-up.
mov ecx,0 ;Default divisor for channel #0 == 65536
mov al,000111010b ;Channel=0 Mode=5 Style=0
out 43h,al ;Prepare channle for new divisor
Then use the DOS Update code (above) to reset the DOS timer to the
real-time timer. And then finally restore the original DOS handler.
Non of this will work under Windoze (or OS/2, or any other multi-tasking
system that needs to use the timer for itself).
Using this can be lead to unstable programs since some system services
require the timer IRQ to do certain tasks, I even think SMARTDRV needs it
but is no threat to it if you mess with it, but there may others...so
The only place I would use the PIT is in an OS on in your own little
I hope this information is accurate.
Copyright © 1995-2005 Nexus Systems