Programmable Interval Timer
The Programmable Interval Timer (PIT) or 8253/8254 chip basically consists of an oscillator, a prescaler and 3 independent frequency dividers.It has 3 channels , one for timer interrupter,one for system use, and the last one for the CPU speaker. IRQ 0 is the interrupt signalled by the PIT.
Channels:
Channel 0 is connected directly to IRQ 0, it generates interrupts at a fixed time.Channel 1 is not used now a days, also recent chips do not have channel 1, it is obsolete. Channel 2 is connected to the PC speaker, it would help in setting the speaker in and out.We could make beeps using Channel 2.
Channel 0
This channel's output is connected to the Programmable Interrupt Controller Chip, as this channel generates IRQ 0.The defualt output frequency for channel 0 is 18.2065 Hz which is set by the BIOS.This channel fires an interrupt at a time which helps us in keeping track of time. As the PIT is programmable we can change its frequency to any frequency of our choice.This channel's data port is "0x40" .Refer to the example codes at the end of the tutorial.
Channel 1
This was used in the earlier personal computer's for refreshing the DRAM.On mordern computers this is a totally a useless feature of the PIT. PIT channel 1 might be not used at all or may lose its support from the chip.This cannel's data port is "0x41".
Channel 2
This output of channel 2 is connected to the CPU/PC Speaker.This is commonly used to make a beep to a given frequency.Changing the frequency of this channel will also affect the sound produced by the speaker.This is the only channel where the gate input can be controlled by the Operating System.This channel's data port is "0x42".
The command register of the PIT is at port 0x43.Command Register of PIT is meant to be written, reading command register is ingnored.
Examples of Channel 0 and 2 are given at the end of this tutorial.
Reqirenments for the code:
Examples x86(C Language):
You need to define the data ports and the command register port of the PIT Chip.
Channel 1 = 0x40 = Timer or IRQ 1
Channel 2 = 0x41 =DRAM Refresher
Channel 3 = 0x42 =CPU/PC Speaker
Command Register = 0x43 =PIT Command Register
#define PIT_CHANNEL0 0x40 //PIT Channel 0's Data Register Port
#define PIT_CHANNEL1 0x41 //PIT Channels 1's Data Register Port, we wont be using this here
#define PIT_CHANNEL2 0x42 //PIT Channels 2's Data Register Port
#define PIT_CMDREG 0x43 //PIT Chip's Command Register Port
=====================================================================================
Setting the frequency's of the PIT Channels 0 and 2
The code below writes to the particular channel's data register and sets the frequency of that channel.Channel 1 is not mentioned because Channel 1 of the PIT Chip has nothing to do with the operating system.
===============================================================================================
void speaker_set(int hz)
{
int divisor = 1193180 / hz;
outportb(PIT_CMDREG, 0xb6);
outportb(PIT_CHANNEL2, (unsigned char) (divisor) );
outportb(PIT_CHANNEL2, (unsigned char) (divisor >> 8));
}
void timer_set(int hz)
{
int divisor = 1193180 / hz;
outportb(PIT_CMDREG , 0x36);
outportb(PIT_CHANNEL0, divisor & 0xFF);
outportb(PIT_CHANNEL0, divisor >> 8);
}
=====================================================================================
Using the PIT Channel 0
The PIT fires an interrupt on the frequency which is at the channel 0's Data Register.You can change this frequency using "timer_set" function shown above.This requires your operating system to have a function to register an interrupt.What I mean is a function which can call the given function when ever a particular interrupt is fired. Here, the interrupt which the PIT Channel 0 fires is IRQ 0.
===============================================================================================
void timer_handler(struct regs *registers)
{
counter++; //Increments the variable by 1 every interrupt fired by the PIT
}
void init_timer()//This must be called in the main to make the timer and it's dependent functions)wait) to work.
{
register_irq(0,timer_handler);//Registers timer_handler to IRQ 0
}
=====================================================================================
Using PIT Channel 2
This function sends the command to the PIT Channel 2 and set in or out of the speaker.The frequency of beep depends on the frequency set on the Channel 2 Data Register.If you need a 1000 frequency beep ,you need to set the channel 2 data register's frequency using "speaker_set(1000)" function shown earlier, and the function will keep beeping until the given number of beeps are set in the function.
===============================================================================================
void beep(unsigned int wait_time,unsigned int times )
{
unsigned char tempA =inportb(0x61);
unsigned char tempB = (inportb(0x61) & 0xFC);
unsigned int count;
for(count =0,count ==times,count++)
{
if (tempA != (tempA | 3))
{
outportb(0x61, tempA | 3);
}
wait(wait_time);//Wait is one more PIT function
outportb(0x61, tempB);
}
}
=====================================================================================
Making a delay function:
This simple function works just by adding the current tick count and the tick to a new variable which will have a new tick count.Till the tick count becomes equal to the new variable, the loop will keep running preventing from going further.
NOTE:This function will work only if the init_timer is running, because that function will be updating our counter.
int counter; //This is the variable which is to be updated by the 'timer_handler'.
void wait(int count)
{
unsigned long wait_till;//This is addition of count and the current no of ticks
wait_till = counter + count;
while(counter < wait_till);//loops until the given period has been reached
}
=====================================================================================
Troubleshooting:
"counter" variable not getting updated
This happens because you might have forgotten to call 'init_timer' in your main, as init_timer set's up the timer_handler with its respective Interrupt.Also note that "timer_handler" is the function which keeps updating the "counter".
CPU Speaker not working in virtual machines:
This is something silly, the beep code does not work on most of the virtual machines but will work on real computer hardware.The reason behind this is not known yet.The beep code has been tested on real hardware.
Channels:
Channel 0 is connected directly to IRQ 0, it generates interrupts at a fixed time.Channel 1 is not used now a days, also recent chips do not have channel 1, it is obsolete. Channel 2 is connected to the PC speaker, it would help in setting the speaker in and out.We could make beeps using Channel 2.
Channel 0
This channel's output is connected to the Programmable Interrupt Controller Chip, as this channel generates IRQ 0.The defualt output frequency for channel 0 is 18.2065 Hz which is set by the BIOS.This channel fires an interrupt at a time which helps us in keeping track of time. As the PIT is programmable we can change its frequency to any frequency of our choice.This channel's data port is "0x40" .Refer to the example codes at the end of the tutorial.
Channel 1
This was used in the earlier personal computer's for refreshing the DRAM.On mordern computers this is a totally a useless feature of the PIT. PIT channel 1 might be not used at all or may lose its support from the chip.This cannel's data port is "0x41".
Channel 2
This output of channel 2 is connected to the CPU/PC Speaker.This is commonly used to make a beep to a given frequency.Changing the frequency of this channel will also affect the sound produced by the speaker.This is the only channel where the gate input can be controlled by the Operating System.This channel's data port is "0x42".
The command register of the PIT is at port 0x43.Command Register of PIT is meant to be written, reading command register is ingnored.
Examples of Channel 0 and 2 are given at the end of this tutorial.
Reqirenments for the code:
- IDT Setup
- ISR's Setup
- IRQ Setup
Examples x86(C Language):
You need to define the data ports and the command register port of the PIT Chip.
Channel 1 = 0x40 = Timer or IRQ 1
Channel 2 = 0x41 =DRAM Refresher
Channel 3 = 0x42 =CPU/PC Speaker
Command Register = 0x43 =PIT Command Register
#define PIT_CHANNEL0 0x40 //PIT Channel 0's Data Register Port
#define PIT_CHANNEL1 0x41 //PIT Channels 1's Data Register Port, we wont be using this here
#define PIT_CHANNEL2 0x42 //PIT Channels 2's Data Register Port
#define PIT_CMDREG 0x43 //PIT Chip's Command Register Port
=====================================================================================
Setting the frequency's of the PIT Channels 0 and 2
The code below writes to the particular channel's data register and sets the frequency of that channel.Channel 1 is not mentioned because Channel 1 of the PIT Chip has nothing to do with the operating system.
===============================================================================================
void speaker_set(int hz)
{
int divisor = 1193180 / hz;
outportb(PIT_CMDREG, 0xb6);
outportb(PIT_CHANNEL2, (unsigned char) (divisor) );
outportb(PIT_CHANNEL2, (unsigned char) (divisor >> 8));
}
void timer_set(int hz)
{
int divisor = 1193180 / hz;
outportb(PIT_CMDREG , 0x36);
outportb(PIT_CHANNEL0, divisor & 0xFF);
outportb(PIT_CHANNEL0, divisor >> 8);
}
=====================================================================================
Using the PIT Channel 0
The PIT fires an interrupt on the frequency which is at the channel 0's Data Register.You can change this frequency using "timer_set" function shown above.This requires your operating system to have a function to register an interrupt.What I mean is a function which can call the given function when ever a particular interrupt is fired. Here, the interrupt which the PIT Channel 0 fires is IRQ 0.
===============================================================================================
void timer_handler(struct regs *registers)
{
counter++; //Increments the variable by 1 every interrupt fired by the PIT
}
void init_timer()//This must be called in the main to make the timer and it's dependent functions)wait) to work.
{
register_irq(0,timer_handler);//Registers timer_handler to IRQ 0
}
=====================================================================================
Using PIT Channel 2
This function sends the command to the PIT Channel 2 and set in or out of the speaker.The frequency of beep depends on the frequency set on the Channel 2 Data Register.If you need a 1000 frequency beep ,you need to set the channel 2 data register's frequency using "speaker_set(1000)" function shown earlier, and the function will keep beeping until the given number of beeps are set in the function.
===============================================================================================
void beep(unsigned int wait_time,unsigned int times )
{
unsigned char tempA =inportb(0x61);
unsigned char tempB = (inportb(0x61) & 0xFC);
unsigned int count;
for(count =0,count ==times,count++)
{
if (tempA != (tempA | 3))
{
outportb(0x61, tempA | 3);
}
wait(wait_time);//Wait is one more PIT function
outportb(0x61, tempB);
}
}
=====================================================================================
Making a delay function:
This simple function works just by adding the current tick count and the tick to a new variable which will have a new tick count.Till the tick count becomes equal to the new variable, the loop will keep running preventing from going further.
NOTE:This function will work only if the init_timer is running, because that function will be updating our counter.
int counter; //This is the variable which is to be updated by the 'timer_handler'.
void wait(int count)
{
unsigned long wait_till;//This is addition of count and the current no of ticks
wait_till = counter + count;
while(counter < wait_till);//loops until the given period has been reached
}
=====================================================================================
Troubleshooting:
"counter" variable not getting updated
This happens because you might have forgotten to call 'init_timer' in your main, as init_timer set's up the timer_handler with its respective Interrupt.Also note that "timer_handler" is the function which keeps updating the "counter".
CPU Speaker not working in virtual machines:
This is something silly, the beep code does not work on most of the virtual machines but will work on real computer hardware.The reason behind this is not known yet.The beep code has been tested on real hardware.