Programmable Interval Timer: Difference between revisions
Added source tags for assembly and C source code.
[unchecked revision] | [unchecked revision] |
(→See Also: Add Wikipedia external link.) |
(Added source tags for assembly and C source code.) |
||
Line 1:
The '''Programmable Interval Timer''' ('''PIT''') chip (also called 8253/8254 chip) basically consists of an oscillator, a prescaler and 3 independent frequency dividers. Each frequency divider has an output, which is used to allow the timer to control external circuitry (for example, IRQ 0).
== The Oscillator ==
The oscillator used by the PIT chip runs at (roughly) 1.193182 MHz. The reason for this requires a trip back into history (to the latter half of the 1970's)...
Line 9 ⟶ 7:
== Frequency Dividers ==
The basic principle of a frequency divider is to divide one frequency to obtain a slower frequency. This is typically done by using a counter. Each "pulse" from the input frequency causes the counter to be decreased, and when that counter has reached zero a pulse is generated on the output and the counter is reset. For example, if the input signal is 200 Hz and the counter is reset to a value of ten each time, then the output frequency would be 200/10, or 20 Hz.
Line 19 ⟶ 16:
== PIT Timer Accuracy ==
The accuracy of the PIT timer depends on the quality of the oscillator used, and is typically accurate to within +/- 1.73 seconds per day. There are many causes for this inaccuracy, however because of this there isn't much point in specifying times or frequencies to more than five or six digits. Despite this, I like to be as accurate as possible!
== Outputs ==
Each channel (or frequency divider) has an output signal that is used for a different purpose.
=== Channel 0 ===
The output for PIT channel 0 is connected to the PIC chip, so that it generates an "IRQ 0". Typically during boot the BIOS sets channel 0 with a count of 65536, which gives an output frequency of 18.2065 Hz (or an IRQ every 54.9254 mS). Channel 0 is probably the most useful PIT channel, as it is the only channel that is connected to an IRQ.
=== Channel 1 ===
The output for PIT channel 1 was once used (in conjunction with the DMA controller's channel 0) for refreshing the DRAM (Dynamic Random Access Memory) or RAM. Typically, each bit in RAM consists of a capacitor which holds a tiny charge representing the state of that bit, however (due to leakage) these capacitors need to be "refreshed" periodically so that they don't forget their state.
Line 37 ⟶ 30:
=== Channel 2 ===
The output of PIT channel 2 is connected to the PC speaker, so that frequency of the output determines the frequency of the sound produced by the speaker. This is the only channel where the gate input can be controlled by software (via. bit 0 of I/O port 0x61), and the only channel where it's output can be read by software (via. bit 5 of I/O port 0x61). Details of how to program the PC speaker can be found [[PC_Speaker|here]].
== I/O Ports ==
The PIT chip uses the following I/O ports:
Line 85 ⟶ 76:
== Operating Modes ==
While each operating mode behaves differently, some things are common to all operating modes. This includes:
Line 98 ⟶ 88:
=== Mode 0 - Interrupt On Terminal Count ===
For this mode, when the mode/command register is written the output signal goes low and the PIT waits for the reload register to be set by software. After the reload register has been set, the current count will be set to the reload value on the next falling edge of the (1.193182 MHz) input signal. Subsequent falling edges of the input signal will decrement the current count (if the gate input is high on the preceding rising edge of the input signal).
Line 106 ⟶ 95:
=== Mode 1 - Hardware Re-triggerable One-shot ===
This mode is similar to mode 0 above, however counting doesn't start until a rising edge of the gate input is detected. For this reason it is not usable for PIT channels 0 or 1 (where the gate input can't be changed).
Line 118 ⟶ 106:
=== Mode 2 - Rate Generator ===
This mode operates as a frequency divider.
Line 134 ⟶ 121:
=== Mode 3 - Square Wave Generator ===
For mode 3, the PIT channel operates as a frequency divider like mode 2, however the output signal is fed into an internal "flip flop" to produce a square wave (rather than a short pulse). The flip flop changes its output state each time its input state (or the output of the PIT channel's frequency divider) changes. This causes the actual output to change state half as often, so to compensate for this the current count is decremented twice on each falling edge of the input signal (instead of once), and the current count is set to the reload value twice as often.
Line 151 ⟶ 137:
=== Mode 4 - Software Triggered Strobe ===
Mode four operates as a retriggerable delay, and generates a pulse when the current count reaches zero.
Line 163 ⟶ 148:
=== Mode 5 - Hardware Triggered Strobe ===
Mode 5 is similar to mode 4, except that it waits for the rising edge of the gate input to trigger (or re-trigger) the delay period (like mode 1). For this reason it is not usable for PIT channels 0 or 1 (where the gate input can't be changed).
Line 175 ⟶ 159:
== Counter Latch Command ==
To prevent the current count from being updated, it is possible to "latch" a PIT channel using the latch command. To do this, send the value CC000000 (in binary) to the mode/command register (I/O port 0x43), where 'CC' corresponds to the channel number. When the latch command has been sent, the current count is copied into an internal "latch register" which can then be read via. the data port corresponding to the selected channel (I/O ports 0x40 to 0x42). The value kept in the latch register remains the same until it has been fully read, or until a new mode/command register is written.
Line 183 ⟶ 166:
== Read Back Command ==
The read back command is a special command sent to the mode/command register (I/O port 0x43). The "read back" is not supported on the old 8253 chips but should be supported on all AT and later computers except for PS/2 (i.e. anything that isn't obsolete will support it).
Line 235 ⟶ 217:
== Reading The Current Count ==
To read the current count using the "lobyte only" or "hibyte only" access modes, you can just do an "in al,0x40" (for PIT channel 0) without problems. For frequencies higher than 4.7 KHz it can be easiest to set the high byte of the reload value to zero, and then use the "lobyte only" access mode to minimize hassles.
For the "lobyte/hibyte" access mode you need to send the latch command (described above) to avoid getting wrong results. If any other code could try set the PIT channel's reload value or read its current count after you've sent the latch command but before you've read the highest 8 bits, then you have to prevent it. Disabling interrupts works for single CPU computers. For example, to read the count of PIT channel 0 you could use something like:
<source lang="asm">
read_PIT_count:
pushfd
cli
mov al, 00000000b ; al = channel in bits 6 and 7, remaining bits clear
out 0x43, al ; Send the latch command
</source>
== Setting The Reload Value ==
To set the reload value, just sent the value/s to the corresponding data port. For the "lobyte only" or "hibyte only" access modes this only takes a single "out 0x40,al" (for PIT channel 0).
For the "lobyte/hibyte" access mode you need to send the lowest 8 bits, then the highest 8 bits. If any other code could try set the PIT channel's reload value or read its current count after you've sent the lowest 8 bits but before you've sent the highest 8 bits, then you have to prevent it. Disabling interrupts works for single CPU computers. For example:
<source lang="asm">
set_PIT_count:
pushfd
cli
out
rol ax, 8
out
rol ax, 8 ; al = low byte, ah = high byte (ax = original reload value)
popfd
ret
</source>
It should be notes that a reload value of zero can be used to specify a divisor of 65536. This is how the BIOS gets an IRQ 0 frequency as low as 18.2065 Hz.
== PIT Channel 0 Example Code ==
To complete this page I've to included the following example code. This code was written for NASM, but hasn't been tested.
Line 283 ⟶ 266:
To begin with, this following code contains all of the data used by this example. It is assumed that the ".bss" section is filled with zeros.
<source lang="asm">
section .bss
PIT_reload_value: resw 1 ; Current PIT reload value
section .text
</source>
Next is the handler for IRQ 0. It's fairly simple (all it does it add 2 64 bit fixed point values and send an EOI to the PIC chip).
<source lang="asm">
IRQ0_handler:
push ebx
</source>
Now for the tricky bit - the initialization routine. It should be noted that the PIT can't generate some frequencies, for example if you want 8000 Hz then you've got a choice of 8007.93 Hz or 7954.544 Hz. In this case the following code will find the nearest possible frequency. Once it has calculated the nearest possible frequency it will reverse the calculation to find the actual frequency selected (rounded to the nearest integer, intended for display purposes only).
Line 314 ⟶ 301:
For some extra accuracy, I also use "3579545 / 3" instead of 1193182 Hz. This is mostly pointless due to inaccurate hardware (I just like being correct).
<source lang="asm">
;Input
; ebx Desired PIT frequency in Hz
Line 410 ⟶ 398:
popad
ret
</source>
I should point out that you'd also need to install an IDT entry for IRQ 0, and unmask it in the PIC chip (or I/O APIC).
Line 418 ⟶ 407:
===Using the IRQ to Implement <tt>sleep</tt>===
The PIT's generating a hardware interrupt every ''n'' milliseconds allows you to create a simple timer. Start with a global variable x that contains the delay:
<source lang="asm">
SEGMENT .DATA
CountDown DD 0
Line 458 ⟶ 448:
POP EBP
RETN 8
</source>
In a multitasking system, consider using a linked list or array of these CountDown variables. If your multitasking system supports interprocess communication, you can also store the semaphore/exchange where two processes can talk to, have the interrupt send a message to the waiting process when the timer is done, and have the waiting process block all execution until that message comes:
<source lang="c">
#define COUNTDOWN_DONE_MSG 1
struct TimerBlock {
EXCHANGE e;
u32int CountDown;
} timerblocks[20];
void TimerIRQ(void) /* called from Assembly */
{
u8int
for (i = 0; i < 20;
if (timerblocks[i].CountDown == 0)
SendMessage(timerblocks[i].e, COUNTDOWN_DONE_MESSAGE);
}
}
void Sleep(u32int delay)
{
struct TimerBlock *t;
if ((t = findTimerBlock()) == nil)
return;
t->CountDown = delay;
WaitForMessageFrom(t->e = getCrntExch());
}
</source>
In your documentation, note the interval of the timer. For example, if the timer interval is 10 milliseconds per tick, tell the programmer to issue
<source lang="c">
Sleep(100);
</source>
to sleep for a single second.
Line 500 ⟶ 498:
=== Threads ===
=== External Links ===
[[wikipedia:Programmable Interval Timer | Programmable Interval Timer]] on Wikipedia
|