Category : Files from Magazines
Archive   : DDJ0691.ZIP
Filename : CLOCK_P.ASC

 
Output of file : CLOCK_P.ASC contained in archive : DDJ0691.ZIP

_USING THE REAL-TIME CLOCK_
by Kenneth Roach

[TURBO PASCAL VERSION]

[LISTING ONE]

(*
** TIMELIB.PAS
** (C) Copyright 1990 by Kenneth Roach
** This module contains procedures similar to Turbo Pascal's GetTime and
** GetDate procedures, but which are based on use of the AT class of
** system's real time clock. Additionally, procedures and functions are
** provided to enable and disable periodic interrupts from the real time
** clock along with an interrupt handler for same. Interrupts from the
** real time clock are provided at a rate of 1024 per second, and a
** function is provided to return the number of interrupts received in the
** current second. Also provided are emulations of the C language's
** time(), ctime() and clock() functions.
*)

Unit TimeLib;

Interface

Uses Dos;

Type
TimeString = String[24];
TimeStrPtr = ^TimeString;

Function RtcClock : LongInt;
Function MilliCount : Integer;
Function CTime2(Time : LongInt) : TimeStrPtr;
Procedure RtcTime(Var Where : LongInt);
Procedure Time2(Var Result : LongInt);
Procedure EnableRtcInts;
Procedure DisableRtcInts;
Procedure GetRtcTime(Var Hr,Mn,Sc,Hn : Word);
Procedure GetRtcDate(Var Yr,Mo,Dy : Word);


Implementation

Type
ShortString = String[3];
OldVec = Procedure;

Const
CLI = $FA;
STI = $FB;
MASK_24 = $02;
BCD_MASK = $04;
CMOSFLAG = $70;
CMOSDATA = $71;
SECONDS_REQ = $00;
MINUTES_REQ = $02;
HOURS_REQ = $04;
STATUSA = $0A;
DATE_REQ = $07;
MONTH_REQ = $08;
YEAR_REQ = $09;
CENTURY_REQ = $32;
UPDATE = $80;
HINIBBLE = $F0;
LONIBBLE = $0F;

SECS_PER_MIN = 60;
SECS_PER_HOUR = 3600;
SECS_PER_DAY = 86400;
SECS_PER_YEAR = 31536000;
MINS_PER_HOUR = 60;
DAYS_PER_YEAR = 365;
BASE_YEAR = 1980;
DAYS_PER_WEEK = 7;
TUESDAY = 3; { day of week for 1-1-1980 }
APRIL = 4;
JUNE = 6;
SEPTEMBER = 9;
NOVEMBER = 11;
FEBRUARY = 2;

RTC_VEC = $70;
IMR2 = $A1;
CMD1 = $20;
CMD2 = $A0;
EOI = $20;
RTC_MASK = $FE;
STATUSB = $0B;
STATUSC = $0C;
RTC_FLAG = $40;

Months : Array[1..12] of ShortString =
('Jan','Feb','Mar','Apr','May','Jun',
'Jul','Aug','Sep','Oct','Nov','Dec');
Days : Array[1..7] of ShortString =
('Sun','Mon','Tue','Wed','Thu','Fri','Sat');

Var
Bcd : Boolean;
RtcCount : Integer;
TickCount : LongInt;
OldRtcVec : Pointer;
OldCall : OldVec;
OldMask : Byte;
TimeStr : TimeString;

(*
** emulation of the C language clock() function. RtcClock returns
** a value corresponding to the number of periodic interrupts which
** have occurred since interrupts from the real time clock were
** enabled. The value will remain positive for some 24 days from
** initialization.
*)

Function RtcClock : LongInt;
Begin
RtcClock := TickCount;
End;

(*
** MilliCount returns the real time clock periodic interrupt count for
** the current second. Range of value is 0 to 1023.
*)

Function MilliCount : Integer;
Begin
MilliCount := RtcCount;
End;

(*
** real time clock interrupt handler
*)

Procedure Rtc; Interrupt;
Begin
Inline(CLI);
Port[CMOSFLAG] := STATUSC; { determine cause of interrupt }
If (Port[CMOSDATA] and $40) <> 0 Then { is it for us? }
Begin
Inc(RtcCount); { update number of times ISR called this second }
Inc(TickCount); { update total number of times called }
If RtcCount = 1024 Then { if start of new second then }
RtcCount := 0 { reset RtcCount }
Else
Begin
Port[CMOSFLAG] := STATUSA; { check it again for accuracy }
If (Port[CMOSDATA] and UPDATE) <> 0 Then
RtcCount := 0;
End;
Port[CMD1] := EOI; { signal end of interrupt to primary 8259 }
Port[CMD2] := EOI; { signal end of interrupt to chained 8259 }
End
Else
OldCall; { not for us, so call bios ISR }
Inline(STI);
End;

(*
** turn on interrupts from the real time clock
*)

Procedure EnableRtcInts;
Begin
RtcCount := 0; { reset ISR counter values }
TickCount := 0;
GetIntVec(RTC_VEC,OldRtcVec);
Move(OldRtcVec^,OldCall,Sizeof(Pointer)); { fake out Pascal... }
SetIntVec(RTC_VEC,@Rtc); { point to interrupt handler }
Port[IMR2] := Port[IMR2] and RTC_MASK; { enable clock interrupt }
Port[CMOSFLAG] := STATUSB;
OldMask := Port[CMOSDATA]; { get rtc mask register }
Port[CMOSFLAG] := STATUSB;
Port[CMOSDATA] := OldMask or RTC_FLAG; { enable periodic interrupts }
End;

(*
** turn off interrupts from the real time clock
*)

Procedure DisableRtcInts;
Begin
Port[CMOSFLAG] := STATUSB;
Port[CMOSDATA] := OldMask; { turn off periodic interrupts }
Port[IMR2] := Port[IMR2] and (not RTC_MASK); { reset 8259 mask }
SetIntVec(RTC_VEC,OldRtcVec); { remove our ISR }
End;

(*
** emulation of the C language's ctime() function
*)

Function CTime2(Time : LongInt) : TimeStrPtr;
Var
Hr,Mn,Sc : Word;
Yr,Mo,Dy : Word;
Bias,Dw,T : Word;
Junk,S : Byte;
Temp : LongInt;
Begin
Temp := Time mod SECS_PER_DAY; { get seconds left for this day }
Hr := Temp div SECS_PER_HOUR; { determine hours this day }
Temp := Temp mod SECS_PER_HOUR; { lose hours this day }
Mn := Temp div MINS_PER_HOUR; { determine minutes this hour }
Sc := Temp mod SECS_PER_MIN; { determine seconds this minute }

Inline(CLI);
Repeat { duplicate a bit of code for speed }
Port[CMOSFLAG] := STATUSA; { wait until not in update mode }
Until (Port[CMOSDATA] and UPDATE) = 0;
Port[CMOSFLAG] := CENTURY_REQ; T := Port[CMOSDATA]; { get century }
Port[CMOSFLAG] := YEAR_REQ; Bias := Port[CMOSDATA]; { get year }
Port[CMOSFLAG] := MONTH_REQ; Mo := Port[CMOSDATA]; { get month }
Port[CMOSFLAG] := DATE_REQ; Dy := Port[CMOSDATA]; { get day }
Inline(STI);
If Bcd Then { convert from BCD to binary as required }
Begin
T := ((T and HINIBBLE) shr 4) * 10 + (T and LONIBBLE);
Bias := ((Bias and HINIBBLE) shr 4) * 10 + (Bias and LONIBBLE);
Mo := ((Mo and HINIBBLE) shr 4) * 10 + (Mo and LONIBBLE);
Dy := ((Dy and HINIBBLE) shr 4) * 10 + (Dy and LONIBBLE);
End;
Inc(Bias,T * 100);

Temp := Time div SECS_PER_DAY; { get number of days for this value }
Yr := Temp div DAYS_PER_YEAR; { now convert it to years }
Bias := (Bias - BASE_YEAR) shr 2; { get leap year days for value }
Dy := Temp - Yr * DAYS_PER_YEAR - Bias; { get unprocessed days }
Inc(Dy); { add back 'today' }
Inc(Yr,BASE_YEAR); { now add in the 1980 start date }
Dw := Time div SECS_PER_DAY + TUESDAY; { 1-1-80 was a Tuesday }
Dw := Dw mod DAYS_PER_WEEK; { determine weekday }

Mo := 1; S := 1; { now determine the month's name }
While S <> 0 Do { process total remaining days for year }
Begin
Junk := 0;
Case S of
APRIL,
JUNE,
SEPTEMBER,
NOVEMBER: If Dy >= 30 Then { month has 30 days in it }
Junk := 30;
FEBRUARY: If (Yr shr 2) = 0 Then { special case february }
If Dy >= 29 Then
Junk := 29
Else
Else If Dy >= 28 Then
Junk := 28;
Else If Dy >= 31 Then
Junk := 31; { else month has 31 days }
End;
If Junk <> 0 Then
Begin
Inc(Mo); { account for month just processed }
Inc(S); { bump case index }
Dec(Dy,Junk); { subtract days just processed }
End
Else
S := 0; { Dy is less than 1 month, clear while var }
End;

TimeStr[1] := Days[Dw][1]; { now convert all values to a string }
TimeStr[2] := Days[Dw][2]; { done inline for speed }
TimeStr[3] := Days[Dw][3];
TimeStr[4] := ' ';
TimeStr[5] := Months[Mo][1];
TimeStr[6] := Months[Mo][2];
TimeStr[7] := Months[Mo][3];
TimeStr[8] := ' ';
TimeStr[9] := Chr(Dy div 10 + Ord('0'));
TimeStr[10] := Chr(Dy mod 10 + Ord('0'));
TimeStr[11] := ' ';
TimeStr[12] := Chr(Hr div 10 + Ord('0'));
TimeStr[13] := Chr(Hr mod 10 + Ord('0'));
TimeStr[14] := ':';
TimeStr[15] := Chr(Mn div 10 + Ord('0'));
TimeStr[16] := Chr(Mn mod 10 + Ord('0'));
TimeStr[17] := ':';
TimeStr[18] := Chr(Sc div 10 + Ord('0'));
TimeStr[19] := Chr(Sc mod 10 + Ord('0'));
TimeStr[20] := ' ';
TimeStr[21] := Chr(Yr div 1000 + Ord('0')); Yr := Yr mod 1000;
TimeStr[22] := Chr(Yr div 100 + Ord('0')); Yr := Yr mod 100;
TimeStr[23] := Chr(Yr div 10 + Ord('0'));
TimeStr[24] := Chr(Yr mod 10 + Ord('0'));
TimeStr[0] := Chr(24);
CTime2 := @TimeStr;
End;

(*
** replacement for Turbo Pascal's GetTime procedure
*)

Procedure GetRtcTime(Var Hr,Mn,Sc,Hn : Word);
Begin
Inline(CLI);
Repeat
Port[CMOSFLAG] := STATUSA; { wait until not in update cycle }
Until (Port[CMOSDATA] and UPDATE) = 0;
Port[CMOSFLAG] := SECONDS_REQ; Sc := Port[CMOSDATA]; { get seconds }
Port[CMOSFLAG] := MINUTES_REQ; Mn := Port[CMOSDATA]; { get minutes }
Port[CMOSFLAG] := HOURS_REQ; Hr := Port[CMOSDATA]; { get hour }
Inline(STI);
If Bcd Then { convert from BCD to binary as required }
Begin
Sc := ((Sc and HINIBBLE) shr 4) * 10 + (Sc and LONIBBLE);
Mn := ((Mn and HINIBBLE) shr 4) * 10 + (Mn and LONIBBLE);
Hr := ((Hr and HINIBBLE) shr 4) * 10 + (Hr and LONIBBLE);
End;
Hn := RtcCount div 10; { RtcCount goes to 1024 }
If Hn > 75 Then { correct for values to 102 each second }
Dec(Hn,3)
Else If Hn > 50 Then
Dec(Hn,2)
Else If Hn > 25 Then
Dec(Hn);
End;


(*
** replacement for Turbo Pascal's GetDate procedure
*)

Procedure GetRtcDate(Var Yr, Mo, Dy : Word);
Var T : Integer;
Begin
Inline(CLI);
Repeat
Port[CMOSFLAG] := STATUSA; { wait until not in update mode }
Until (Port[CMOSDATA] and UPDATE) = 0;
Port[CMOSFLAG] := CENTURY_REQ; T := Port[CMOSDATA]; { get century }
Port[CMOSFLAG] := YEAR_REQ; Yr := Port[CMOSDATA]; { get year }
Port[CMOSFLAG] := MONTH_REQ; Mo := Port[CMOSDATA]; { get month }
Port[CMOSFLAG] := DATE_REQ; Dy := Port[CMOSDATA]; { get day }
Inline(STI);
If Bcd Then { convert time from BCD to binary as required }
Begin
T := ((T and HINIBBLE) shr 4) * 10 + (T and LONIBBLE);
Yr := ((Yr and HINIBBLE) shr 4) * 10 + (Yr and LONIBBLE);
Mo := ((Mo and HINIBBLE) shr 4) * 10 + (Mo and LONIBBLE);
Dy := ((Dy and HINIBBLE) shr 4) * 10 + (Dy and LONIBBLE);
End;
Inc(Yr,T * 100); { add in century }
End;

(*
** emulation of the C language's time() function
*)

Procedure RtcTime(Var Where : LongInt);
Var
Hr : LongInt;
T,S,B,Yr,Sc,Mn,Mo,Dy : Word;
Begin
Inline(CLI); { following code is duplicated for speed }
Repeat
Port[CMOSFLAG] := STATUSA;
Until (Port[CMOSDATA] and UPDATE) = 0;
Port[CMOSFLAG] := SECONDS_REQ; Sc := Port[CMOSDATA]; { get seconds }
Port[CMOSFLAG] := MINUTES_REQ; Mn := Port[CMOSDATA]; { get minutes }
Port[CMOSFLAG] := HOURS_REQ; Hr := Port[CMOSDATA]; { get hour }
Port[CMOSFLAG] := CENTURY_REQ; T := Port[CMOSDATA]; { get century }
Port[CMOSFLAG] := YEAR_REQ; Yr := Port[CMOSDATA]; { get year }
Port[CMOSFLAG] := MONTH_REQ; Mo := Port[CMOSDATA]; { get month }
Port[CMOSFLAG] := DATE_REQ; Dy := Port[CMOSDATA]; { get day }
Inline(STI);
If Bcd Then { convert time from BCD to binary as required }
Begin
Sc := ((Sc and HINIBBLE) shr 4) * 10 + (Sc and LONIBBLE);
Mn := ((Mn and HINIBBLE) shr 4) * 10 + (Mn and LONIBBLE);
Hr := ((Hr and HINIBBLE) shr 4) * 10 + (Hr and LONIBBLE);
T := ((T and HINIBBLE) shr 4) * 10 + (T and LONIBBLE);
Yr := ((Yr and HINIBBLE) shr 4) * 10 + (Yr and LONIBBLE);
Mo := ((Mo and HINIBBLE) shr 4) * 10 + (Mo and LONIBBLE);
Dy := ((Dy and HINIBBLE) shr 4) * 10 + (Dy and LONIBBLE);
End;

Inline(STI);
Mn := Mn * SECS_PER_MIN + Sc; { convert today's values to seconds }
Hr := Hr * SECS_PER_HOUR + Mn;
Inc(Yr,T * 100); { account for century }
Dec(Yr,BASE_YEAR); { keep years since 1980 }
Inc(Dy,(Yr shr 2)); { check leap years }
S := 1;
While S < Mo Do { add days for this year }
Begin
Case S of
APRIL,
JUNE,
SEPTEMBER, { month has 30 days in it }
NOVEMBER: Inc(Dy,30);
FEBRUARY: If (Yr shr 2) = 0 Then { is this year a leap year? }
Inc(Dy,29) { yes }
Else
Inc(Dy,28); { no }
Else Inc(Dy,31); { else month has 31 days }
End;
Inc(S);
End;
Dec(Dy); { lose today... }
Where := Yr * SECS_PER_YEAR + { return final value }
Dy * SECS_PER_DAY + Hr;
End;

(*
** Pascal substitute for Turbo-C's time() function, based on calls to
** GetDate, GetTime. Provided for use on systems not equipped with a
** real time clock.
*)

Procedure Time2(Var Result : LongInt);
Var
H : LongInt;
S,Hr,Yr,Sc,Mn,Mo,Dy : Word;
Begin
GetTime(Hr,Mn,Sc,S); { get time from Turbo Pascal }
Mn := Mn * 60 + Sc; { convert to seconds }
H := Hr * 3600 + Mn;
GetDate(Yr,Mo,Dy,S); { get date from Turbo Pascal }
Dec(Yr,1980); { get years since 1980 }

Inc(Dy,Yr shr 2); { check leap years }
S := 1;
While S < Mo Do { add days for this year }
Begin
Case S of
APRIL,
JUNE,
SEPTEMBER,
NOVEMBER: Inc(Dy,30); { month has 30 days in it }
FEBRUARY: If (Yr shr 2) = 0 Then { is this year a leap year? }
Inc(Dy,29) { yes }
Else
Inc(Dy,28); { no }
Else Inc(Dy,31); { else month has 31 days }
End;
Inc(S);
End;
Result := (Yr * SECS_PER_YEAR + { return final value }
Dy * SECS_PER_DAY + H);
End;

(*
** unit initialization
*)

Begin
Port[CMOSFLAG] := STATUSB;
Bcd := (Port[CMOSDATA] and BCD_MASK) = 0; { check for BCD mode }
Port[CMOSFLAG] := STATUSB;
Port[CMOSDATA] := Port[CMOSDATA] or MASK_24; { force 24 hour mode }
RtcCount := 0;
TickCount := 0;
End.


[LISTING TWO]

(*
** TIME_PAS
** (C) Copyright 1990 by Kenneth Roach
** This program uses the time and date functions provided by Turbo Pascal
** compiler, as well as similar functions contained in the module TIMELIB.PAS.
** TIME_PAS calls each function for five seconds, counting the number of
** times the function in question was called. It then compares the number
** of times each function was called and displays the results. Following
** this, it displays the current date and time obtained from the
** GetRtcTime function, and as reported and converted by the RtcTime
** and CTime2 functions.
*)

Program TimePas;

Uses Dos,Crt,TimeLib;

Const
TEST_TIME = 5120; { 5 seconds * 1024 ticks per second }

Var
GrtCount : LongInt; { counter for GetRtcTime calls }
GtCount : LongInt; { counter for GetTime calls }
GrdCount : LongInt; { counter for GetRtcDate calls }
GdCount : LongInt; { counter for GetDate calls }
TCount : LongInt; { counter for Time calls }
RtCount : LongInt; { counter for RtcTime calls }
CtCount : LongInt; { counter for CTime2 calls }
Timer1 : LongInt; { used in Time, RtcTime testing }
Temp : LongInt;
Hr,Mn,Sc,Hn : Word; { used in calls to GetTime, GetRtcTime }
Yr,Mo,Dy,Dw : Word; { used in calls to GetDate, GetRtcDate }
St : TimeStrPtr; { used in CTime2 testing }

(*
** test performance of real time clock based time functions
*)

Procedure TestRtc;
Begin

Writeln;
Write('Testing GetRtcTime...');
Temp := RtcClock; { get current time tick count }
Repeat
GetRtcTime(Hr,Mn,Sc,Hn);
Inc(GrtCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

Writeln;
Write('Testing GetRtcDate...');
Temp := RtcClock;
Repeat
GetRtcDate(Yr,Mo,Dy);
Inc(GrdCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

Writeln;
Write('Testing RtcTime...');
Temp := RtcClock;
Repeat
RtcTime(Timer1);
Inc(RtCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

Writeln;
Write('Testing CTime2...');
Temp := RtcClock;
Repeat
St := CTime2(Timer1);
Inc(CtCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

End;

(*
** test performance of Turbo Pascal/DOS based time functions
*)

Procedure TestPas;
Begin

Writeln;
Write('Testing GetTime...');
Temp := RtcClock;
Repeat
GetTime(Hr,Mn,Sc,Hn);
Inc(GtCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

Writeln;
Write('Testing GetDate...');
Temp := RtcClock;
Repeat
GetDate(Yr,Mo,Dy,Dw);
Inc(GdCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

Writeln;
Write('Testing Time2...');
Temp := RtcClock;
Repeat
Time2(Timer1);
Inc(TCount);
Until (RtcClock - Temp) = TEST_TIME; { count for 5 seconds }

End;

(*
** determine percentage one value represents of another
*)

Function Percent(Count1,Count2 : LongInt) : LongInt;
Var Temp : LongInt;
Begin
Temp := (Count1 * 100) div Count2;
If ((Count1 * 100) mod Count2) >= 50 Then
Inc(Temp);
Percent := Temp;
End;

(*
** show results of timing tests
*)

Procedure DisplayResults;
Begin
Writeln;
Writeln('Test Summary:');
Writeln;
Writeln('GetTime called ',GtCount,' times');
Writeln('GetRtcTime called ',GrtCount,' times');
If GrtCount > GtCount Then
Writeln('GetRtcTime was ',Percent(GrtCount,GtCount),
'% the speed of GetTime')
Else
Writeln('GetTime was ',Percent(GtCount,GrtCount),
'% the speed of GetRtcTime');

Writeln;
Writeln('GetDate called ',GdCount,' times');
Writeln('GetRtcDate called ',GrdCount,' times');
If GrdCount > GdCount Then
Writeln('GetRtcDate was ',Percent(GrdCount,GdCount),
'% the speed of GetDate')
Else
Writeln('GetDate was ',Percent(GdCount,GrdCount),
'% the speed of GetRtcDate');

Writeln;
Writeln('Time2 called ',TCount,' times');
Writeln('RtcTime called ',RtCount,' times');
If TCount > RtCount Then
Writeln('Time2 was ',Percent(TCount,RtCount),
'% the speed of RtcTime')
Else
Writeln('RtcTime was ',Percent(RtCount,TCount),
'% the speed of Time2');

Writeln;
Writeln('CTime2 called ',CtCount,' times');
End;

Begin
GrtCount := 0; { initialize counter variables }
GtCount := 0;
GrdCount := 0;
GdCount := 0;
TCount := 0;
RtCount := 0;
CtCount := 0;

EnableRtcInts;

ClrScr;

TestRtc; { test the functions using the real time clock }
TestPas; { test the normal Pascal/DOS based time functions }

DisplayResults;

Writeln;
Writeln('End of test.');
Writeln('Start time display.');
Writeln('Depress any key to stop');
Writeln;
While not KeyPressed Do
Begin
GetRtcTime(Hr,Mn,Sc,Hn);
RtcTime(Timer1);
Write(Chr(13),Hr:2,':',Mn:2,':',Sc:2,'.',Hn:2,
' ',CTime2(Timer1)^);
End;

DisableRtcInts;
End.