Programmable Interval Timer: Difference between revisions
Jump to navigation
Jump to search
[unchecked revision] | [unchecked revision] |
Content deleted Content added
m Simplified the PIT Channel 0 Example Code |
Added information on using the timer IRQ. |
||
Line 414: | Line 414: | ||
Of course it's easier to configure the PIT to a fixed value, but where's the fun in that? :-) |
Of course it's easier to configure the PIT to a fixed value, but where's the fun in that? :-) |
||
==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: |
|||
SEGMENT .DATA |
|||
CountDown DD 0 |
|||
Next, every time the timer interrupt is called, decrement this variable until 0 is stored. |
|||
SEGMENT .TEXT |
|||
[GLOBAL TimerIRQ] |
|||
TimerIRQ: |
|||
PUSH EAX |
|||
MOV EAX, CountDown |
|||
OR EAX, EAX ; quick way to compare to 0 |
|||
JZ TimerDone |
|||
DEC CountDown |
|||
TimerDone: |
|||
POP EAX |
|||
IRETD |
|||
Finally, create a function <tt>Sleep</tt> that waits the interval, in milliseconds. I assume Pascal calling convention - the called function cleans the stack. |
|||
[GLOBAL _Sleep] |
|||
_Sleep: |
|||
PUSH EBP |
|||
MOV EBP, ESP |
|||
PUSH EAX |
|||
MOV EAX, [EBP + 8] ; EAX has value of sole argument |
|||
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: |
|||
POP EAX |
|||
POP EBP |
|||
RETN 8 |
|||
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; |
|||
u32int CountDown; |
|||
} timerblocks[20]; |
|||
void TimerIRQ(void) /* called from Assembly */ |
|||
{ |
|||
u8int i; |
|||
for (i = 0; i < 20; i++) |
|||
if (timerblocks[i].CountDown > 0) { |
|||
timerblocks[i].CountDown--; |
|||
if (timerblocks[i].CountDown == 0) |
|||
SendMessage(timerblocks[i].e, COUNTDOWN_DONE_MESSAGE); |
|||
} |
|||
} |
|||
void Sleep(u32int delay) |
|||
{ |
|||
TimerBlock *t; |
|||
if ((t = findTimerBlock()) == nil) |
|||
return; |
|||
asm("cli"); |
|||
t->CountDown = delay; |
|||
WaitForMessageFrom(t->e = getCrntExch()); |
|||
} |
|||
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); |
|||
to sleep for a single second. |
|||
===Using the IRQ for Preemptive Multitasking=== |
|||
The timer IRQ can also be used to perform preemptive multitasking. To give the currently running task some time to run, set a threshold, for example of 3 ticks. Use a global variable like the one before but go up from 0, and when that variable hits 3, switch tasks. How you do so is up to you. |
|||
[[Category:Common Devices]] |
[[Category:Common Devices]] |