199 lines
5.4 KiB
Plaintext
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.
|