Programmable Interval Timer: Difference between revisions

m
Bot: Replace deprecated source tag with syntaxhighlight
[unchecked revision][unchecked revision]
m (typos)
m (Bot: Replace deprecated source tag with syntaxhighlight)
 
(5 intermediate revisions by 3 users not shown)
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="c">
unsigned read_pit_count(void) {
unsigned count = 0;
Line 246:
return count;
}
</syntaxhighlight>
</source>
 
== Setting The Reload Value ==
Line 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="c">
void set_pit_count(unsigned count) {
// Disable interrupts
Line 263:
return;
}
</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 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 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 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 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 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 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 430 ⟶ 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 438 ⟶ 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 498 ⟶ 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 522 ⟶ 501:
 
[[Category:Common Devices]]
[[Category:TimeTimers]]
[[de:Programmable_Interval_Timer]]