fruit-popper/GDLIB/GDTIMER.PAS
2021-07-07 17:10:18 -04:00

199 lines
5.4 KiB
Plaintext

{ GDlib Higher Frequency Timer Utilities
Gered King, 2021 }
{$A+,B-,F-,G+,I-,N+,P-,Q-,R-,S-,T-,V-,X+}
unit GDTimer;
interface
const
{ count of the number of times the timer interrupt has been raised.
this will be incremented 'freq' times per second once InitTimer
has been called. }
TimerTicks : longint = 0;
function InitTimer(freq: word) : boolean;
function CloseTimer : boolean;
function GetTimerFrequency : word;
function MarkTimer : longint;
procedure WaitForTime(delay : word);
implementation
uses Dos;
const
PIC_CTRL_PORT = $20;
CHANNEL_0_PORT = $40;
COMMAND_PORT = $43;
TIMER_FREQ_SET_COMMAND = $36; { mode 2 (rate generator),
read/write lo-byte of counter,
read/write hi-byte of counter }
TIMER_CLOCK_RATE = 1193180; { 1.19318 mhz }
ORIGINAL_TIMER_FREQ = 18.2065;
_timerInstalled : boolean = false;
_timerFreq : word = 0;
_lastMarkedAt : longint = 0;
var
_oldTimerCounter : longint;
_oldTimerTriggerAt : longint;
_oldTimerInterrupt : pointer;
procedure SetTimerFrequency(freq: word);
{ configures the PC 8253 timer to trigger interrupt 8 at the given frequency.
the value provided here should simply be just that: the number of times
per second that interrupt 8 should be triggered per second. }
var
counter0 : word;
begin
if freq = 0 then
counter0 := 0
else
counter0 := TIMER_CLOCK_RATE div freq;
{ calculate the number of timer interrupt ticks that will need to elapse
(at our new timer frequency) before our custom interrupt should call the
original timer interrupt handler to ensure it is still called at the
original 18.2hz frequency }
_oldTimerTriggerAt := round(freq / ORIGINAL_TIMER_FREQ);
port[COMMAND_PORT] := TIMER_FREQ_SET_COMMAND;
port[CHANNEL_0_PORT] := Lo(counter0);
port[CHANNEL_0_PORT] := Hi(counter0);
_timerFreq := freq;
end;
procedure TimerHandler;
{ custom timer (interrupt 8) handler.
this has been written as a 'raw' assembler procedure instead of using
a more typical pascal 'interrupt' procedure just to keep this as lean
as possible since it will potentially be called hundreds (or more)
times per second }
far;
assembler;
asm
push ds
db $66; push ax
mov ax, seg @Data { restore DS so that we can access pascal vars }
mov ds, ax
db $66; inc word ptr [TimerTicks]
{ house-keeping to ensure the original interrupt 8 handler is still
called at the rate it should be (18.2hz) }
db $66; inc word ptr [_oldTimerCounter]
{ if _oldTimerCounter < _oldTimerTriggerAt, then skip calling the
original interrupt 8 handler }
db $66; mov ax, word(_oldTimerCounter)
db $66; cmp ax, word(_oldTimerTriggerAt)
jl @done
{ otherwise (if _oldTimerCounter >= _oldTimerTriggerAt), then,
reset the counter back to zero and call the original interrupt 8
handler }
db $66; xor ax, ax
db $66; mov word(_oldTimerCounter), ax
pushf
call [_oldTimerInterrupt]
@done:
{ tell the PIC that we're done }
mov al, $20
out PIC_CTRL_PORT, al
db $66; pop ax
pop ds
iret
end;
function InitTimer(freq: word) : boolean;
{ installs a custom timer interrupt handler (interrupt 8). returns false if
the timer interrupt handler could not be installed for some reason, or if
the custom handler was already installed. }
begin
InitTimer := false;
if _timerInstalled then exit;
TimerTicks := 0;
_oldTimerCounter := 0;
_lastMarkedAt := 0;
asm cli end;
SetTimerFrequency(freq);
GetIntVec(8, _oldTimerInterrupt);
SetIntVec(8, @TimerHandler);
asm sti end;
_timerInstalled := true;
InitTimer := true;
end;
function CloseTimer : boolean;
{ removes a previously installed custom timer interrupt handler. }
begin
CloseTimer := false;
if not _timerInstalled then exit;
asm cli end;
SetTimerFrequency(0); { resets back to the normal 18.2hz }
SetIntVec(8, _oldTimerInterrupt);
asm sti end;
TimerTicks := 0;
_oldTimerCounter := 0;
_oldTimerInterrupt := nil;
_timerInstalled := false;
CloseTimer := true;
end;
function GetTimerFrequency : word;
{ returns the frequency that the installed custom timer interrupt handler
is being triggered at. if no custom timer interrupt handler is installed,
returns 0 }
begin
GetTimerFrequency := _timerFreq;
end;
function MarkTimer : longint;
{ used to calculate time differences between subsequent calls to this
function. the very first time it is called, the return value should
probably be ignored (will be the ticks since the timer subsystem was
initialized). }
var
newMarkedAt : longint;
begin
newMarkedAt := TimerTicks;
MarkTimer := newMarkedAt - _lastMarkedAt;
_lastMarkedAt := newMarkedAt;
end;
procedure WaitForTime(delay : word);
{ waits indefinitely for specified number of ticks to elapse }
var
startedAt : longint;
begin
if not _timerInstalled then exit;
startedAt := TimerTicks;
while (TimerTicks - startedAt) < delay do begin
end;
end;
begin
if Test8086 < 2 then begin
writeln('The GDTIMER unit requires a 386 cpu or higher!');
halt;
end;
end.