Programmable Interval Timer: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
(convert exotic assembler syntax to NASM)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(7 intermediate revisions by 5 users not shown)
Line 92:
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 effectaffect the output pin state.
 
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 103:
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 effectaffect the output pin state.
 
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 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 effectaffect the current count until the current count is reloaded (when it is decreased from two to one, or the gate input going low then high). When this occurs counting will continue using the new reload value.
 
A reload value (or divisor) of one must ''not'' be used with this mode.
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 effectaffect the current count until the current count is reloaded (when it is decreased from two to zero, or the gate input going low then high). When this occurs counting will continue using the new reload value.
 
A reload value (or divisor) of one must ''not'' be used with this mode.
Line 162:
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 effectaffect the output state.
 
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 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:
 
<sourcesyntaxhighlight lang="asmc">
unsigned read_pit_count(void) {
read_PIT_count:
unsigned count = 0;
pushfd
cli
// Disable interrupts
mov al, 00000000b ; al = channel in bits 6 and 7, remaining bits clear
cli();
out 0x43, al ; Send the latch command
mov al, 00000000b ;// al = channel in bits 6 and 7, remaining bits clear
in al, 0x40 ; al = low byte of count
outb(0x43,0b0000000);
mov ah, al ; ah = low byte of count
in al, 0x40 ; al = high byte of count
count = inb(0x40); // Low byte
rol ax, 8 ; al = low byte, ah = high byte (ax = current count)
count |= inb(0x40)<<8; // High byte
popfd
ret
return count;
</source>
}
</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:
 
<sourcesyntaxhighlight lang="asmc">
void set_pit_count(unsigned count) {
set_PIT_count:
// Disable interrupts
pushfd
cli();
out 0x40, al ; Set low byte of reload value
rol// ax, 8 ; al = high byte, ah =Set low byte
out outb(0x40, al count&0xFF); // Set highLow byte of reload value
outb(0x40,(count&0xFF00)>>8); // High byte
rol ax, 8 ; al = low byte, ah = high byte (ax = original reload value)
return;
popfd
}
ret
</syntaxhighlight>
</source>
 
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 276 ⟶ 278:
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.
 
<sourcesyntaxhighlight lang="asm">
section .bss
system_timer_fractions: resd 1 ; Fractions of 1 ms since timer initialized
Line 285 ⟶ 287:
PIT_reload_value: resw 1 ; Current PIT reload value
section .text
</syntaxhighlight>
</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).
 
<sourcesyntaxhighlight lang="asm">
IRQ0_handler:
push eax
Line 305 ⟶ 307:
pop eax
iretd
</syntaxhighlight>
</source>
 
Now for the tricky bit – the initialization routine. 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 311 ⟶ 313:
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).
 
<sourcesyntaxhighlight lang="asm">
;Input
; ebx Desired PIT frequency in Hz
Line 408 ⟶ 410:
popad
ret
</syntaxhighlight>
</source>
 
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 x that contains the delay:
<sourcesyntaxhighlight lang="asmC">
volatile uint32_t CountDown;
section .data
</syntaxhighlight>
CountDown: dd 0
</source>
Next, every time the timer interrupt is called, decrement this variable until 0 is stored.
<sourcesyntaxhighlight lang="asm">
section .text
global TimerIRQ
Line 428 ⟶ 429:
push eax
mov eax, [CountDown]
ortest eax, or eax ; quick way to compare to 0
jz TimerDone
mov eax, [CountDown]
dec eax
mov [CountDown], eax
Line 436:
pop eax
iretd
</syntaxhighlight>
</source>
Finally, create a function <tt>sleep</tt> that waits the interval, in milliseconds.
<sourcesyntaxhighlight lang="asmc">
void sleep(uint32_t millis) {
[GLOBAL sleep]
CountDown: dd= 0millis;
sleep:
while push(CountDown ebp> 0) {
mov ebp, esp halt();
push eax}
}
mov eax, [ebp + 8] ; eax has value of sole argument
</syntaxhighlight>
mov [CountDown], eax
SleepLoop:
cli ; can't be interrupted for test
mov eax, [CountDown]
or eax, eax
jz SleepDone
sti
nop ; nop a few times so the interrupt can get handled
nop
nop
nop
nop
nop
jmp SleepLoop
SleepDone:
sti
pop eax
pop ebp
ret
</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:
 
<sourcesyntaxhighlight lang="c">
#define COUNTDOWN_DONE_MSG 1
struct TimerBlock {
Line 496 ⟶ 477:
WaitForMessageFrom(t->e = getCrntExch());
}
</syntaxhighlight>
</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
 
<sourcesyntaxhighlight lang="c">
Sleep(100);
</syntaxhighlight>
</source>
 
to sleep for a single second.
Line 520 ⟶ 501:
 
[[Category:Common Devices]]
[[Category:TimeTimers]]
[[de:Programmable_Interval_Timer]]