Programmable Interval Timer: Difference between revisions
[unchecked revision] | [unchecked revision] |
Content deleted Content added
m Bot: Replace deprecated source tag with syntaxhighlight |
|||
(26 intermediate revisions by 20 users not shown) | |||
Line 1:
The '''Programmable Interval Timer''' ('''PIT''') chip (
== 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
The original PC used a single "base oscillator" to generate a frequency of 14.31818 MHz because this frequency was commonly used in television circuitry at the time. This base frequency was divided by 3 to give a frequency of 4.77272666 MHz that was used by the CPU, and divided by 4 to give a frequency of 3.579545 MHz that was used by the CGA video controller. By logically ANDing these signals together a frequency equivalent to the base frequency divided by 12 was created. This frequency is 1.1931816666 MHz (where the 6666 part is recurring). At the time it was a brilliant method of reducing costs, as the 14.31818 MHz oscillator was cheap due to mass production and it was cheaper to derive the other frequencies from this than to have several oscillators. In modern computers, where the cost of electronics is much less, and the CPU and video run at much higher frequencies the PIT lives on as a reminder of "the good ole' days".
Line 9:
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.
The PIT has only 16 bits that are used as frequency divider, which can represent the values from 0 to 65535. Since the frequency can't be divided by 0 in a sane way, many implementations use 0 to represent the value 65536 (or 10000 when programmed in BCD mode).
The PIT chip has three separate frequency dividers (or 3 separate channels) that are programmable, in that the value of the "reset counter" is set by software (the OS). Software also specifies an action to be taken when the counter reaches zero on each individual channel. In this way, each channel can be used in one of several "modes"
Each PIT channel also has a "gate input" pin which can be used to control whether the input signal (the 1.19MHz one) gets to the channel or not. For PIT channels 0 and 1, the associated gate input pin is not connected to anything. The PIT channel 2 gate is controlled by IO port 0x61, bit 0.
Line 22:
=== Channel 0 ===
The output from 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 65535 or 0 (which translates to 65536), which gives an output frequency of 18.2065 Hz (or an IRQ every 54.9254
When choosing [[#Operating Modes|an operating mode]], below, it is useful to remember that the IRQ0 is generated by the ''rising edge'' of the Channel 0 output voltage (ie. the transition from "low" to "high", only).
=== 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.
On
=== Channel 2 ===
Line 48:
Bits Usage
The "Select Channel" bits select which channel is being configured, and must always be valid on every write of the mode/command register, regardless of the other bits or the type of operation being performed. The "read back" (both bits set) 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). The "read back" command is discussed later.
The "Access Mode" bits tell the PIT what access mode you wish to use for the selected channel, and also specify [[#Counter Latch Command|the "counter latch" command]] to the CTC
The "Operating Mode" bits specify which mode the selected PIT channel should operate in. For the "read back" command and the "counter latch" command, these bits have different meanings (see the information corresponding to these commands below). There are 6 different [[#Operating Modes|operating modes]]. Each operating mode will be discussed separately later.
The "BCD/Binary" bit determines if the PIT channel will operate in binary mode or BCD mode (where each 4 bits of the counter represent a decimal digit, and the counter holds values from
== Operating Modes ==
Line 87:
: The current counter value is always either decremented or reset to the reload value on the ''falling'' edge of the (1.193182 MHz) input signal.
; '''Current Counter Reload'''
: In modes where the current count is decremented when it is reloaded, the current count is not decremented on the same input clock pulse as the reload
=== Mode 0
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, to begin the countdown. 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).
When the current count decrements from one to zero, the output goes high and remains high until another mode/command register is written or the reload register is set again. The current count will wrap around to 0xFFFF (or 0x9999 in BCD mode) and continue to decrement until the mode/command register or the reload register are set, however this will not
The reload value can be changed at any time. In "lobyte/hibyte" access mode counting will stop when the first byte of the reload value is set. Once the full reload value is set (in any access mode), the next falling edge of the (1.193182 MHz) input signal will cause the new reload value to be copied into the current count, and the countdown will continue from the new value.
Line 98:
Note: despite the misleading name of this mode, it only generates interrupts on channel 0.
=== Mode 1
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).
When the mode/command register is written the output signal goes high and the PIT waits for the reload register to be set by software. After the reload register has been set the PIT will wait for the next rising edge of the gate input. Once this occurs, the output signal will go low and 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.
When the current count decrements from one to zero, the output goes high and remains high until another mode/command register is written or the reload register is set again. The current count will wrap around to 0xFFFF (or 0x9999 in BCD mode) and continue to decrement until the mode/command register or the reload register are set, however this will not
If the gate input signal goes low during this process it will have no effect. However, if the gate input goes high again it will cause the current count to be reloaded from the reload register on the next falling edge of the input signal, and restart the count again (the same as when counting first started).
Line 109:
The reload value can be changed at any time, however the new value will not affect the current count until the current count is reloaded (on the next rising edge of the gate input). So if you want to do this, clear and then reset bit 0 of IO port 0x61, after modifying the reload value.
=== Mode 2
This mode operates as a frequency divider.
Line 118:
If the gate input goes low, counting stops and the output goes high immediately. Once the gate input has returned high, the next falling edge on input signal will cause the current count to be set to the reload value and operation will continue.
The reload value can be changed at any time, however the new value will not
A reload value (or divisor) of one must ''not'' be used with this mode.
Line 126:
Typically, OSes and BIOSes use mode 3 (see below) for PIT channel 0 to generate IRQ 0 timer ticks, but some use mode 2 instead, to gain frequency accuracy (frequency = 1193182 / reload_value Hz).
=== Mode 3
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 142:
On channel 2, if the gate input goes low, counting stops and the output goes high immediately. Once the gate input has returned high, the next falling edge on input signal will cause the current count to be set to the reload value and operation will continue (with the output left high).
The reload value can be changed at any time, however the new value will not
A reload value (or divisor) of one must ''not'' be used with this mode.
=== Mode 4
Mode four operates as a retriggerable delay, and generates a pulse when the current count reaches zero.
Line 157:
The reload value can be changed at any time. When the new value has been set (both bytes for "lobyte/hibyte" access mode) it will be loaded into the current count on the next falling edge of the (1.193182 MHz) input signal, and counting will continue using the new reload value.
=== Mode 5
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).
When the mode/command register is written the output signal goes high and the PIT waits for the reload register to be set by software. After the reload register has been set the PIT will wait for the next rising edge of the gate input. Once this occurs, 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.
When the current count decrements from one to zero, the output goes low for one cycle of the input signal (0.8381 uS). The current count will wrap around to 0xFFFF (or 0x9999 in BCD mode) and continue to decrement until the mode/command register or the reload register are set, however this will not
If the gate input signal goes low during this process it will have no effect. However, if the gate input goes high again it will cause the current count to be reloaded from the reload register on the next falling edge of the input signal, and restart the count again (the same as when counting first started).
Line 169:
== 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 main benefit of the latch command is that it allows both bytes of the current count to be read without inconsistencies. For example, if you didn't use the latch command, then the current count may decrease from 0x0200 to 0x01FF after you've read the low byte but before you've read the high byte, so that your software thinks the counter was 0x0100 instead of 0x0200 (or 0x01FF).
Line 181:
Bits Usage
Note: Be careful with bits 4 and 5
Bits 1 to 3 of the read back command select which PIT channels are affected, and allow multiple channels to be selected at the same time.
Line 202:
Bit/s Usage
The bottom six bits return the values that where programmed into the mode/command register when the channel was last initialized.
Line 231:
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:
<
unsigned read_pit_count(void) {
unsigned count = 0;
// Disable interrupts
mov al, 00000000b ; al = channel in bits 6 and 7, remaining bits clear▼
cli();
outb(0x43,0b0000000);
count = inb(0x40); // Low byte
count |= inb(0x40)<<8; // High byte
return count;
}
</syntaxhighlight>
== Setting The Reload Value ==
Line 251 ⟶ 253:
For the "lobyte/hibyte" access mode you need to send the low 8 bits followed by the high 8 bits. You must prevent other code from setting the PIT channel's reload value or reading its current count once you've sent the lowest 8 bits. Disabling interrupts works for single CPU computers. For example:
<
void set_pit_count(unsigned count) {
// Disable interrupts
cli();
outb(0x40,(count&0xFF00)>>8); // High byte
return;
}
</syntaxhighlight>
It should be noted 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.
Line 270 ⟶ 272:
The idea is to provide a single routine to initialize PIT channel 0 for any (possible) frequency and use IRQ 0 to accurately keep track of real time in milliseconds since the PIT was configured.
For the sake of accuracy, the initialization code will calculate the number of whole milliseconds to add to the "system timer tick" each IRQ, and the number of "fractions of a millisecond" to avoid drift. This may be important, for example if the PIT is set for 700 Hz it'd work out to (roughly) 1.42857
Hopefully, everyone is familiar with fixed point mathematics. For example, with the "32.32" notation I'll be using, if the high
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.
<
section .bss
system_timer_fractions: resd 1 ; Fractions of 1
IRQ0_fractions: resd 1 ; Fractions of 1
IRQ0_frequency: resd 1 ; Actual frequency of PIT
PIT_reload_value: resw 1 ; Current PIT reload value
section .text
</syntaxhighlight>
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).
<
IRQ0_handler:
push eax
Line 295 ⟶ 297:
mov eax, [IRQ0_fractions]
mov ebx, [
add [system_timer_fractions], eax ; Update system timer tick fractions
adc [
mov al, 0x20
Line 305 ⟶ 307:
pop eax
iretd
</syntaxhighlight>
Now for the tricky bit
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).
<
;Input
; ebx Desired PIT frequency in Hz
Line 374 ⟶ 376:
; Note: The basic formula is:
; time in ms = reload_value / (3579545 / 3) * 1000
; This can be rearranged in the
; time in ms = reload_value * 3000 / 3579545
; time in ms = reload_value * 3000 / 3579545 * (2^42)/(2^42)
Line 387 ⟶ 389:
shr edx,10 ;edx:eax = reload_value * 3000 * (2^42) / 3579545 / (2^10)
mov [
mov [IRQ0_fractions],eax ;Set fractions of 1
Line 408 ⟶ 410:
popad
ret
</syntaxhighlight>
Note: you also need to install an IDT entry for IRQ 0, and unmask it in the PIC chip (or I/O APIC).
Line 416 ⟶ 418:
==Uses for the Timer IRQ==
===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
<
volatile uint32_t CountDown;
</syntaxhighlight>
Next, every time the timer interrupt is called, decrement this variable until 0 is stored.
<syntaxhighlight lang="asm">
section .text
TimerIRQ:
TimerDone:
</syntaxhighlight>
Finally, create a function <tt>
<syntaxhighlight lang="c">
void sleep(uint32_t millis) {
CountDown
while
halt();
▲ MOV CountDown, EAX
}
</syntaxhighlight>
▲ OR EAX, EAX
▲ STI
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:
<
#define COUNTDOWN_DONE_MSG 1
struct TimerBlock {
EXCHANGE e;
} timerblocks[20];
void TimerIRQ(void) /* called from Assembly */
{
for (i = 0; i < 20; i++)
Line 481 ⟶ 468:
}
void Sleep(
{
struct TimerBlock *t;
Line 490 ⟶ 477:
WaitForMessageFrom(t->e = getCrntExch());
}
</syntaxhighlight>
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
<
Sleep(100);
</syntaxhighlight>
to sleep for a single second.
Line 506 ⟶ 493:
=== Articles ===
* [[Time And Date]]
* [[RTC]]
=== Threads ===
Line 511 ⟶ 499:
*[[wikipedia:Programmable Interval Timer | Programmable Interval Timer]] on Wikipedia
*[http://www.osdever.net/bkerndev/Docs/pit.htm The PIT: A System Clock] on osdever
[[Category:Common Devices]]
[[Category:Timers]]
[[de:Programmable_Interval_Timer]]
|