PIT Programming

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 as follows:
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.
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.
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.

Control Port

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 this.
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.
Here's how:
mov ah,2
int 1ah
mov al,ch
call _1
mov ch,al
mov al,cl
call _1
mov cl,al
mov al,dh
call _1
mov dh,al
mov dl,0
mov ah,2dh
int 21h
; all done!

push ax
shr al,4
mov bl,10
mul bl
pop bx
and bl,15
add al,bl
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).

Complete Example

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
  mov ebx,60
  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
  mov al,cl
  out 40h,al
  mov al,ch
  out 40h,al
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
  mov al,cl
  out 40h,al
  mov al,ch
  out 40h,al
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.

Final Notes

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 be warned.
The only place I would use the PIT is in an OS on in your own little demos.
I hope this information is accurate.


Copyright © 1995-2005 Nexus Systems Privacy
SourceForge.net Logo