Kenneth is an engineer for Unisys. He can be contacted at P.O. Box 2271, Manteca, CA 95336.
When recently faced with the need to perform processing based on seconds elapsed, I was disappointed at the types of time-related functions Turbo Pascal provided. What I needed was a routine which would return the elapsed time in seconds since a base date. That is, a Pascal routine similar to the time function as defined for ANSI C. This was not available, and I was faced with either doing calls to the Turbo Pascal GetTime procedure and manipulating the value returned, or inventing an equivalent of the time function for Turbo Pascal.
I quickly wrote a Pascal version of the time function that proved satisfactory, though tests indicated that its performance was less so. The Time procedure was called around 8000 times in a five-second period on the system I was using--a 25-MHz 80386 PC running under MS-DOS. I then began efforts to improve the procedure's performance. The first attempt involved eliminating as many long integer calculations as possible; some remained, however, because the value returned is a long integer. This improved performance, though it still seemed that more processing time than should have been was required.
The system I was using had an AT-compatible real-time clock, so it seemed reasonable to test usage of this clock with the Time procedure. Calls to the Pascal GetTime and GetDate procedures were replaced with direct reads of the values maintained by the real-time clock. The performance improvement was startling. Using the real-time clock, the newly created Pascal Time function was faster than the compiler's own GetTime procedure, even though this Time function had much more to do.
After some thought, this made sense. Turbo Pascal is designed to generate programs which can be run on any DOS-based system, including those using the 8088 processor. It makes no assumptions about the type of hardware available. Instead, it relies on standard MS-DOS time and date information provided by the 8253 timer chip, regardless of what might be available. Performance suffers, it seems, for the sake of compatibility.
Later tests with Turbo C provided similar results. In a five-second period, the standard C time function was called some 11,000 times, and the gettime function around 34,000 times. These counts are very similar to those of Turbo Pascal, so it seems that they, too, obtain time and date information via the 8253 timer chip.
Because of the real-time clock's superior performance, I decided to create a set of Turbo Pascal (and Turbo C) routines that use this clock.
The real-time clock function is provided by a Motorola MC146818 processor located on the motherboard. Information from this clock is stored in battery-backed memory. This memory is accessible by programs through port addresses $70 and $71. Locations in the memory relating to the real-time clock are described in Table 1. To read a memory location, it is necessary to first place the location's address into register $70 and then read the data from register $71. Writing to the memory is similar. The location to be written to is first placed into register $70, and the data to be written is then placed into register $71.
Location Description
-------------------------------------------------------------------------
$00: Current time (second)
$01: Alarm time (second)
$02: Current time (minute)
$03: Alarm time (minute)
$04: Current time (hour)
$05: Alarm time (hour)
$06: Day of week
$07: Day of month
$08: Month (1-12)
$09: Year, relative to century
$32: Century
$0a: Status Register A:
Bit 7: Indicates update of time is in progress if set.
Bit 6-4: Time frequency. Default is 010, or 32,786 KHz.
Bit 3-0: Interrupt frequency. Default is 0110, or 1.024 KHz.
$0b: Status Register B:
Bit 7: Set clock: If set, the program can initialize the
14 time-bytes. No updates will occur until the bit is reset.
Bit 6: Periodic Interrupt Enable. If set, enables interrupt
according to the parameters in register A.
Bit 5: Alarm Interrupt Enable. If set, enables alarm
interrupt at time specified in registers $01,
$03 and $05.
Bit 4: Update Ended Interrupt Enabled. If set, enables interrupt
at clock update interval.
Bit 3: N/A.
Bit 2: If set, indicates time information is in binary,
else time information is in BCD.
Bit 1: If set, indicates clock is operating in 24-hour
mode, else clock is in 12-hour mode.
Bit 0: If set, enables daylight savings mode.
$0c: Status Register C:
Bit 7: Interrupt identification.
Bit 6: Periodic interrupt occurred.
Bit 5: Alarm interrupt occurred.
Bit 4: Update interrupt occurred.
Bit 3-0: N/A.
$0d: Status Register D:
Bit 7: If not set, indicates that the real-time clock has
lost power.
Bit 6-0: N/A.
Most of the memory locations are straightforward and explained adequately in Table 1. Some deserve additional comment, however:
The real-time clock uses IRQ 8, which is handled through the second 8259 using interrupt vector $70. The periodic interrupt is enabled by setting bit 6 of status register B. When interrupts occur, the interrupt service routine must examine status register C to determine the cause of the interrupt. This presents a problem because an application can be involved in reading this memory prior to and after the interrupt. The problem is compounded by the fact that register $70 is a read-only register. Other interrupts should not be allowed to occur while accessing the real-time clock.
If the clock interrupt occurred due to something other than the periodic interrupt, the interrupt service routine must pass the interrupt through to the normal ISR. If the ISR was called due to the periodic interrupt, any other processing required should be done without calling the normal ISR. Following this, an end-of-interrupt must be generated for both the primary and secondary 8259 interrupt controllers.
The real-time clock can also be requested to generate an interrupt at a specific time (see Table 1, locations $01, $03, and $05). While MS-DOS does not provide a mechanism for enabling or handling the periodic interrupt, it does support enabling the alarm interrupt through interrupt $1a. When enabled through MS-DOS, the BIOS will generate interrupt $4a. While this is an acceptable mechanism, and perhaps preferred in some cases, an application can handle the interrupt a bit more directly by processing IRQ 8 itself.
Listing One (page 88) shows the set of time and date functions using the real-time clock I developed for Turbo Pascal. These routines include replacements for the GetTime and GetDate procedures, as well as routines emulating the C language's ctime and clock routines. Because Turbo Pascal does not have a time function of its own, a version of the Time procedure that does not use the real-time clock is provided along with one which does. Routines to enable and disable periodic interrupts from the real-time clock are provided as well, along with the necessary interrupt service routine for the clock. Finally, a function is provided to return the periodic interrupt count for the current second.
Listing Two (page 91) is a simple test program written to measure the performance of these routines versus the performance of previously existing routines. The test program repeatedly calls each of the routines for a five-second period, with a counter incremented for each of the calls.
(I also developed a set of time functions for C that is similar to that for Pascal. Due to space constraints, however, these functions are only available electronically; see "Availability" on page 3. The C code includes real-time clock-based replacements for C's time, gettime, and getdate functions, as well as a replacement for the time function that does not use the real-time clock. Interestingly, the corresponding Turbo Pascal procedure was found to be faster than the one provided with Turbo C. Consequently, a replacement for C's ctime function is provided as well.)
Timing results can be expected to vary from system to system, depending on the processor type, resident software installed, and so on. A sample of the Pascal test program's results is shown in Figure 1.
Test Summary:
GetTime called 34601 times
GetRtcTime called 108846 times
GetRtcTime was 334% faster than
GetTime
GetDate called 16397 times
GetRtcDate called 53894 times
GetRtcDate was 330% faster than
GetDate
Time called 13250 times
RtcTime called 30755 times
RtcTime was 233% faster than Time
During the course of refining the real-time clock routines, I made a few additional discoveries which can make using the real-time clock routines all the more attractive.
The system being used for testing was connected to a local area network and, for convenience, was running in file server mode about half the time, allowing remote access to the system. Large differences were noted in performance between tests on different occasions, and these differences were ultimately traced to whether or not the LAN server program was in memory or not. Further testing found that the overhead added to the MS-DOS date and time functions by the server is considerable. When the LAN server program was running, the MS-DOS date and time functions performed about a third as fast as when the server program was not running, while the routines that used the real-time clock performed only about 5-10 percent slower.
I also discovered that performing the above tests with a 386 memory management program loaded added considerably to the overhead of the MS-DOS time and date functions. The test results shown in Figure 1 are, in fact, those output by the program when a memory management program was in use. When the memory manager was disabled and the test repeated, the output in Figure 2 was obtained. As can be seen, when the 386 memory manager was not in use, a substantial improvement in the performance of the MS-DOS date and time functions was observed. However, not only did the MS-DOS-based functions improve in performance, the routines based on usage of the real-time clock improved as well.
Test Summary:
GetTime called 50898 times
GetRtcTime called 129415 times
GetRtcTime was 255% faster than
GetTime
GetDate called 25605 times
GetRtcDate called 65650 times
GetRtcDate was 257% faster than
GetDate
Time called 18237 times
RtcTime called 37101 times
RtcTime was 254% faster than Time
The reason for lessened performance with the 386 memory management software relates to the fact that the particular memory manager in use runs the system as a virtual 8086 task. When this is done, all interrupt processing is filtered by a virtual 8086 task management program. According to Intel documentation, this can add as much as 300 clock ticks to each interrupt performed, and more than 200 clock ticks to each return from an interrupt. Much of the processing performed by the real-time clock versions of the routines is not affected by interrupt processing.
While use of a 386 memory management program does increase overhead for virtually all things done, it is unlikely that many of us would be willing to give up memory management programs at the present time. Such programs perform valuable services, including emulation of expanded memory (EMS), remapping of ROM and RAM in the upper areas of the first megabyte of memory, and often the ability to load TSR programs and device drivers into this upper range of the first megabyte of memory.
Because 386 memory managers will likely continue to be used, usage of the real-time clock-based routines will circumvent any performance problems relating to time and date processing on systems using such. The complete set of time and date routines for Turbo Pascal are shown in Table 2.
Pascal EnableRtcInts; (Procedure) C void enable_rtc_ints( )
Enables interrupts from the real-time clock, which will be handled by Rtc in Pascal or rtc in C. Note that in both languages, the routines that return the current time rely on interrupts from the real-time clock to calculate hundredths of seconds. If interrupts are not enabled, hundredths will not be returned.
Enabling interrupts from the clock will cause additional processing time to be used while servicing them, so clock interrupts should not be enabled unless there is a need for time information at greater than a 1-second resolution.
Pascal DisableRtcInts; (Procedure) C void disable_rtc_ints( )
If clock interrupts have been enabled, this routine must be called prior to terminating your program to disable interrupts from the clock.
Pascal None C void init_time( )
Used in the C version of the library to determine whether daylight savings time is in effect. Should be called once prior to using the other time routines in the C library to assure accuracy of results. Daylight savings time is not a factor in the Pascal version.
Pascal GetRtcTime(Var Hr, Mn, Sc, Hn:Word); (Procedure) C void get_rtc_time(timep *time)
A direct replacement of the original GetTime routine. Note that variable Hn (hundredths of second) will always be set to zero unless interrupts from the real-time clock have been enabled.
Pascal GetRtcDate(Var Yr,Mo,Dy:Word); (Procedure) C void get_rtc_date(datep *date)
A direct replacement of the original GetDate, with the exception that day of week is not calculated.
Pascal RtcTime (Var Result: LongInt); (Procedure) C time rtc_time(time_t *result)
An addition to Turbo Pascal. Emulates C's time function, though with these differences: First, C time function both returns a value and stores that value at an address which is passed to it. RtcTime is a procedure, not a function. Therefore, it simply stores a time value at the address of the Result variable. Second, C's time function returns elapsed time in seconds, since 00:00:00, January 1, 1970, Greenwich Mean Time. Because there was no preexisting Time procedure in Pascal, GMT information is not available, and the real-time clock has provided the same value since January 1, 1980, the Pascal RtcTime procedure returns elapsed seconds since 1980 instead of 1970, without regard to GMT. Because C provides a precedent for the time function, rtc_time behaves exactly as the original time function does. It returns a value and stores it at the address specified, and returns a value representing elapsed time since 00:00:00 Jan 1, 1970, GMT.
Pascal Time2(Var Result : LongInt); (Procedure) C time_t time2(time_t *result);
For completeness, the Pascal Time2 procedure is provided for systems which are not equipped with a real-time clock. The Pascal Time2 procedure was observed to be faster than Turbo C's own time function, for unknown reasons. Because of this, I include a replacement for Turbo C's time function, called time2, to avoid duplicating the name.
Pascal Clock : LongInt; (Function) C clock_t clock( );
Only usable when interrupts from the real-time clock have been enabled, and only if those interrupts remain enabled between successive calls. Clock returns a value representing the number of periodic interrupts generated since such interrupts were first enabled (the interrupt handler is called 1024 times per second). It can be called multiple times to determine the number of clock ticks which have elapsed between two events. The value returned will go negative should the program run for 24 days or so, which was not considered a problem here.
Pascal MilliCount : Integer; (Function) C int milli_count( );
When interrupts from the real-time clock are enabled, this function returns the number of periodic interrupts generated for the current second.
Pascal CTime2(Time : LongInt): TimeStrPtr; (Function) C char *ctime2(time_t *t);
CTime2 was first written in Turbo Pascal because TP does not provide an equivalent. As written, it processes values returned by the Time2 procedure (based on Jan 1, 1980). Like C's ctime, CTime2 for Pascal returns a pointer instead of a string. Tests indicated that the Pascal CTime2 was around twice as fast as Turbo C's own ctime, so a version was developed for C as well.
Pascal Rtc; Interrupt; (Procedure) C void interrupt rtc( );
Process the periodic interrupts from the real-time clock when enabled.
A necessary limitation of most of these routines is that an AT-compatible real-time clock is required for them to function. When a given program will be used on 8088 systems, as well as newer ones, the presence of this real-time clock cannot be guaranteed. In these cases, the processor type can be detected and the time and date routines which do not use the real-time clock can be used.
How these routines will effect the performance of your programs depends on their need to sample the date and time and/or format it for output. The tighter the loop, the greater the need for performance, and the better these routines may help.
_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.
>
_USING THE REAL-TIME CLOCK_
by Kenneth Roach
[TURBO C VERSION]
[TIME_C.C]
/*
** TIME_C
** (C) Copyright 1990 by Kenneth Roach
** Version date: 3 November, 1990
**
** This program uses the time and date functions provided by the Turbo-C
** compiler, as well as similar functions contained in the module TIMELIB.C.
** TIME_C 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
** get_rtc_time function, and as reported and converted by the rtc_time
** and ctime2 functions.
*/
#include <stdio.h>
#include <dos.h>
#include <time.h>
#include "timelib.h"
long grt_count = 0L; /* counter for get_rtc_time() calls */
long grd_count = 0L; /* counter for get_rtc_date() calls */
long rt_count = 0L; /* counter for rtc_time() calls */
long gt_count = 0L; /* counter for gettime() calls */
long gd_count = 0L; /* counter for getdate() calls */
long t_count = 0L; /* counter for time() calls */
long t2_count = 0L; /* counter for time2() calls */
long ct2_count = 0L; /* counter for ctime2() calls */
long ct_count = 0L; /* counter for ctime() calls */
struct time t; /* used in testing of gettime, get_rtc_time */
struct date d; /* used in testing of getdate, get_rtc_date */
char *str; /* used in testing ctime, ctime2 */
time_t timer; /* used in testing time, time2, rtc_time */
long temp;
#define TEST_TIME 5120L /* 5 seconds * 1024 interrupts per */
/*
** test performance of real time clock based time functions
*/
void test_rtc()
{
printf("\nTesting get_rtc_time...");
temp = rtc_clock();
do {
get_rtc_time(&t);
++grt_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting get_rtc_date...");
temp = rtc_clock();
do {
get_rtc_date(&d);
++grd_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting rtc_time...");
temp = rtc_clock();
do {
rtc_time(&timer);
++rt_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting ctime2...");
temp = rtc_clock();
do {
str = ctime2(&timer);
++ct2_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
}
/*
** test performance of C's DOS based time functions
*/
void test_c()
{
printf("\nTesting gettime...");
temp = rtc_clock();
do {
gettime(&t);
++gt_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting getdate...");
temp = rtc_clock();
do {
getdate(&d);
++gd_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting time...");
temp = rtc_clock();
do {
time(&timer);
++t_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting time2...");
temp = rtc_clock();
do {
time2(&timer);
++t2_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
printf("\nTesting ctime...");
temp = rtc_clock();
do {
str = ctime(&timer);
++ct_count;
} while(rtc_clock() - temp < TEST_TIME); /* count for 5 seconds */
}
/*
** determine percentage one value represents of another
*/
long percent(long count1,long count2)
{
long temp;
temp = (count1 * 100L) / count2;
if(((count1 * 100L) % count2) >= 50L)
++temp;
return(temp);
}
/*
** show results of timing tests
*/
void display_results()
{
printf("\nTest Summary:\n");
printf("\ngettime() called %6ld times\n",gt_count);
printf("get_rtc_time() called %6ld times\n",grt_count);
if(grt_count > gt_count)
printf("get_rtc_time() was %02ld%% the speed of gettime()\n",
percent(grt_count,gt_count));
else
printf("gettime() was %02ld%% the speed of get_rtc_time()\n",
percent(gt_count,grt_count));
printf("\ngetdate() called %6ld times\n",gd_count);
printf("get_rtc_date() called %6ld times\n",grd_count);
if(grd_count > gd_count)
printf("get_rtc_date() was %02ld%% the speed of getdate()\n",
percent(grd_count,gd_count));
else
printf("getdate() was %02ld%% the speed of get_rtc_date()\n",
percent(gd_count,grd_count));
printf("\ntime() called %6ld times\n",t_count);
printf("time2() called %6ld times\n",t2_count);
printf("rtc_time() called %6ld times\n",rt_count);
if(rt_count > t_count)
printf("rtc_time() was %02ld%% the speed of time()\n",
percent(rt_count,t_count));
else
printf("time() was %02ld%% the speed of rtc_time()\n",
percent(t_count,rt_count));
printf("\nctime() called %6ld times\n",ct_count);
printf("ctime2() called %6ld times\n",ct2_count);
if(ct2_count > ct_count)
printf("ctime2() was %02ld%% the speed of ctime()\n",
percent(ct2_count,ct_count));
else
printf("ctime() was %02ld%% the speed of ctime2()\n",
percent(ct_count,ct2_count));
}
void main()
{
enable_rtc_ints();
clrscr();
test_rtc(); /* test the functions using the real time clock */
test_c(); /* test the normal C/DOS based time functions */
display_results();
printf("\nEnd of test.\nStart time display.\nDepress any key to stop\n\n");
while(!kbhit())
{
get_rtc_time(&t);
rtc_time(&timer);
printf("\r%02.2d:%02.2d:%02.2d.%02.2d %-24.24s",
t.ti_hour,t.ti_min,t.ti_sec,t.ti_hund,ctime(&timer));
}
disable_rtc_ints();
}
[TIMELIB.C}
/*
** TIMELIB.C
** (C) Copyright 1990 by Kenneth Roach
** Version date: 3 November, 1990
**
** This module contains functions similar to ANSI C's time(), gettime() and
** getdate(), and clock() functions, but which are based on use of the AT
** class of system's real time clock. Additionally, functions are provided
** to enable and disable periodic interrupts from the real time clock along
** with an intterupt 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 is a replacement for the C language's ctime() function which is
** modestly faster.
*/
#pragma inline
#include <stdio.h>
#include <dos.h>
#include <time.h>
#include "timelib.h"
#define CMOSFLAG 0x70
#define CMOSDATA 0x71
#define SECONDS_REQ 0x00
#define MINUTES_REQ 0x02
#define HOURS_REQ 0x04
#define STATUSA 0x0a
#define STATUSB 0x0b
#define STATUSC 0x0c
#define DATE_REQ 0x07
#define MONTH_REQ 0x08
#define YEAR_REQ 0x09
#define CENTURY_REQ 0x32
#define UPDATE 0x80
#define BCD 0x04
#define MASK_24 0x02
#define HINIBBLE 0xf0
#define LONIBBLE 0x0f
#define APRIL 4
#define JUNE 6
#define SEPTEMBER 9
#define NOVEMBER 11
#define FEBRUARY 2
#define RTC_VEC 0x70
#define IMR2 0xa1
#define CMD1 0x20
#define CMD2 0xa0
#define EOI 0x20
#define RTC_MASK 0xfe
#define RTC_FLAG 0x40
#define SECS_PER_DAY 86400L
#define SECS_PER_YEAR 31536000L
#define BIAS_10_YEARS 315532800L /* difference between 1970 and 1980 */
#define BASE_YEAR 1980
#define SECS_PER_MIN 60
#define SECS_PER_HOUR 3600
#define MINS_PER_HOUR 60
#define DAYS_PER_YEAR 365
#define DAYS_PER_WEEK 7
#define TUESDAY 3 /* day of week for 1-1-1980 */
#define bcd_bin(x) (bcd) ? ((((x & HINIBBLE) >> 4)\
* 10) + (x & LONIBBLE)) : (x)
char months[12][4] = {"Jan","Feb","Mar","Apr","May","Jun",
"Jul","Aug","Sep","Oct","Nov","Dec"};
char days[7][4] = {"Sun","Mon","Tue","Wed","Thu","Fri","Sat"};
extern long timezone;
volatile int rtc_count = 0;
volatile long tick_count = 0L;
void interrupt (*old_rtc_vec)();
int func_init = 0;
int bcd = 0;
int dst = 0;
unsigned int old_mask;
char time_str[26];
/*
** replacement for the Turbo-C clock() function. rtc_clock 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.
*/
clock_t rtc_clock()
{
return(tick_count);
}
/*
** millicount returns the real time clock periodic interrupt count for
** the current second. Range of value is 0 to 1023.
*/
int milli_count()
{
return(rtc_count);
}
/*
** real time clock interrupt handler
*/
void interrupt rtc()
{
asm cli;
outportb(CMOSFLAG,STATUSC); /* get interrupt register identification */
if((inportb(CMOSDATA) & 0x40) != 0) /* if a "periodic" interrupt */
{
if(++rtc_count == 1024) /* update nbr times ISR called this sec */
rtc_count = 0; /* if start of new second, reset rtc_count */
else
{
outportb(CMOSFLAG,STATUSA); /* check it again for accuracy */
if(inportb(CMOSDATA) & UPDATE)
rtc_count = 0;
}
++tick_count; /* update total number of times called */
outportb(CMD1,EOI); /* signal end of interrupt to primary 8259 */
outportb(CMD2,EOI); /* signal end of interrupt to chained 8259 */
}
else
(*old_rtc_vec)();
asm sti;
}
/*
** turn on interrupts from the real time clock
*/
void enable_rtc_ints()
{
rtc_count = 0;
tick_count = 0L;
old_rtc_vec = getvect(RTC_VEC);
setvect(RTC_VEC,rtc); /* point to interrupt handler */
outportb(IMR2,inportb(IMR2) & RTC_MASK); /* enable clock interrupt */
outportb(CMOSFLAG,STATUSB);
old_mask = inportb(CMOSDATA); /* get rtc mask register */
outportb(CMOSFLAG,STATUSB);
outportb(CMOSDATA,old_mask | RTC_FLAG); /* enable 1k interrupts */
}
/*
** turn off interrupts from the real time clock
*/
void disable_rtc_ints()
{
outportb(CMOSFLAG,STATUSB);
outportb(CMOSDATA,old_mask); /* turn off periodic interrupts */
outportb(IMR2,inportb(IMR2) & ~RTC_MASK); /* diable RTC interrupts */
setvect(RTC_VEC,old_rtc_vec); /* restore old interrupt vector */
}
/*
** replacement for the C language's ctime() function
*/
char *ctime2(time_t *t)
{
unsigned int hr,mn,sc;
unsigned int yr,mo,dy;
unsigned int bias,dw;
int junk,s,tp;
long temp;
time_t time;
time = *t - BIAS_10_YEARS;
if(dst)
time -= 3600L; /* compensate for daylight savings */
time -= timezone;
temp = time % SECS_PER_DAY; /* get seconds left for this day */
hr = temp / SECS_PER_HOUR; /* determine hours this day */
temp %= SECS_PER_HOUR; /* lose hours this day */
mn = temp / MINS_PER_HOUR; /* determine minutes this hour */
sc = temp % SECS_PER_MIN; /* determine seconds this minute */
asm cli;
do /* following code duplicated for speed */
outportb(CMOSFLAG,STATUSA); /* wait until not in update cycle */
while(inportb(CMOSDATA) & UPDATE);
outportb(CMOSFLAG,CENTURY_REQ); s = inportb(CMOSDATA); tp = bcd_bin(s);
outportb(CMOSFLAG,YEAR_REQ); s = inportb(CMOSDATA); bias = bcd_bin(s);
outportb(CMOSFLAG,MONTH_REQ); s = inportb(CMOSDATA); mo = bcd_bin(s);
outportb(CMOSFLAG,DATE_REQ); s = inportb(CMOSDATA); dy = bcd_bin(s);
asm sti;
bias = bias + tp * 100 - BASE_YEAR;
temp = time / SECS_PER_DAY; /* get number of days for this value */
yr = temp / DAYS_PER_YEAR; /* now convert it to years */
bias >>= 2; /* get leap year days for value */
dy = temp - yr * DAYS_PER_YEAR - bias; /* get unprocessed days */
yr += BASE_YEAR; /* now add in the 1980 start date */
dw = time / SECS_PER_DAY + TUESDAY; /* 1-1-80 was a Tuesday */
dw %= DAYS_PER_WEEK; /* determine weekday */
--dw;
s = 1; /* now determine the month's name */
mo = 0;
while(s) /* process total remaining days for year */
{
junk = 0;
switch(s)
{
case APRIL: /* first do months with 30 days */
case JUNE:
case SEPTEMBER:
case NOVEMBER: if(dy >= 30)
junk = 30; break;
case FEBRUARY: if((yr >> 2) == 0) /* special case february */
if(dy >= 29)
junk = 29; /* process leap year */
else
;
else if(dy >= 28) /* not a leap year */
junk = 28; break;
default: if(dy >= 31)
junk = 31; /* else month has 31 days */
}
if(junk)
{
++mo; /* account for month just processed */
++s; /* bump case index */
dy -= junk; /* subtract days just processed */
}
else
s = 0; /* Dy is less than 1 month, clear while var */
}
time_str[0] = days[dw][0]; /* now convert all values to a string */
time_str[1] = days[dw][1]; /* avoid call to sprintf for speed */
time_str[2] = days[dw][2];
time_str[4] = months[mo][0];
time_str[5] = months[mo][1];
time_str[6] = months[mo][2];
time_str[8] = dy / 10 + '0';
time_str[9] = dy % 10 + '0';
time_str[11] = hr / 10 + '0';
time_str[12] = hr % 10 + '0';
time_str[14] = mn / 10 + '0';
time_str[15] = mn % 10 + '0';
time_str[17] = sc / 10 + '0';
time_str[18] = sc % 10 + '0';
time_str[20] = yr / 1000 + '0'; yr %= 1000;
time_str[21] = yr / 100 + '0'; yr %= 100;
time_str[22] = yr / 10 + '0';
time_str[23] = yr % 10 + '0';
time_str[24] = '\n';
time_str[25] = 0;
time_str[3] = time_str[7] = time_str[10] = time_str[19] = ' ';
time_str[13] = time_str[16] = ':';
return(time_str);
}
/*
** replacement for Turbo-C's gettime() function
*/
void get_rtc_time(struct time *timep)
{
int h,m,s;
if(!func_init)
init_time(); /* assure we have info we need */
asm cli;
do
outportb(CMOSFLAG,STATUSA); /* wait until not in update cycle */
while(inportb(CMOSDATA) & UPDATE);
outportb(CMOSFLAG,HOURS_REQ); /* get hours */
h = inportb(CMOSDATA); timep->ti_hour = bcd_bin(h);
outportb(CMOSFLAG,MINUTES_REQ); /* get minutes */
m = inportb(CMOSDATA); timep->ti_min = bcd_bin(m);
outportb(CMOSFLAG,SECONDS_REQ); /* get seconds */
s = inportb(CMOSDATA); timep->ti_sec = bcd_bin(s);
asm sti;
s = rtc_count / 10; /* rtc_count goes to 1024 */
if(s > 75) /* correct for values to 102 each second */
s -= 3;
else if(s > 50)
s -= 2;
else if(s > 25)
--s;
timep->ti_hund = s;
}
/*
** replacement for Turbo-C's getdate() function
*/
void get_rtc_date(struct date *datep)
{
int d,m,y,t,s;
if(!func_init)
init_time(); /* assure we have info we need */
asm cli;
do
outportb(CMOSFLAG,STATUSA); /* wait until not in update cycle */
while(inportb(CMOSDATA) & UPDATE);
outportb(CMOSFLAG,CENTURY_REQ); /* get century */
s = inportb(CMOSDATA); t = bcd_bin(s);
outportb(CMOSFLAG,YEAR_REQ); /* get year */
y = inportb(CMOSDATA); datep->da_year = bcd_bin(y);
outportb(CMOSFLAG,MONTH_REQ); /* get month */
m = inportb(CMOSDATA); datep->da_mon = bcd_bin(m);
outportb(CMOSFLAG,DATE_REQ); /* get day */
d = inportb(CMOSDATA); datep->da_day = bcd_bin(d);
asm sti;
datep->da_year = datep->da_year + t * 100; /* add in century */
}
/*
** replacement for Turbo-C's time() function
*/
time_t rtc_time(time_t *result)
{
time_t hr;
unsigned s,b,yr,sc,mn,mo,dy;
if(!func_init)
init_time(); /* assure we have info we need */
asm cli; /* following code is duplicated for speed */
do
outportb(CMOSFLAG,STATUSA); /* wait until not update cycle */
while(inportb(CMOSDATA) & UPDATE);
outportb(CMOSFLAG,SECONDS_REQ); /* get seconds */
s = inportb(CMOSDATA); sc = bcd_bin(s);
outportb(CMOSFLAG,MINUTES_REQ); /* get minutes */
s = inportb(CMOSDATA); mn = bcd_bin(s);
outportb(CMOSFLAG,HOURS_REQ); /* get hours */
s = inportb(CMOSDATA); hr = bcd_bin(s);
outportb(CMOSFLAG,YEAR_REQ); /* get year */
s = inportb(CMOSDATA); yr = bcd_bin(s);
outportb(CMOSFLAG,CENTURY_REQ); /* get century */
s = inportb(CMOSDATA); b = bcd_bin(s);
outportb(CMOSFLAG,MONTH_REQ); /* get month */
s = inportb(CMOSDATA); mo = bcd_bin(s);
outportb(CMOSFLAG,DATE_REQ); /* get day */
s = inportb(CMOSDATA); dy = bcd_bin(s);
asm sti;
mn = mn * 60 + sc; /* convert minutes to seconds */
hr = hr * 3600 + mn + timezone; /* convert hours to seconds */
yr = yr + b * 100 - 1980; /* get years since 1980 */
dy = dy + (yr >> 2); /* correct days for leap years */
s = 1;
while(s < mo) /* add days for this year */
switch(s++)
{
case APRIL: /* months with 30 days */
case JUNE:
case SEPTEMBER:
case NOVEMBER: dy += 30L; break;
case FEBRUARY: dy += ((yr >> 2) == 0) ? 29L : 28L; break;
default: dy += 31L; /* else month has 31 days */
}
if(dst)
hr -= 3600L; /* compensate for daylight savings */
return(*result = (yr * SECS_PER_YEAR + /* return final value */
dy * SECS_PER_DAY +
hr + BIAS_10_YEARS)); /* 10 yr bias for difference */
/* between 1970 and 1980 (secs) */
}
/*
** replacement for Turbo-C's time() function
*/
time_t time2(time_t *result)
{
time_t hr;
unsigned s,yr,mn,mo,dy;
struct date d;
struct time t;
asm cli;
getdate(&d);
gettime(&t);
mn = t.ti_min * 60 + t.ti_sec; /* convert minutes to seconds */
hr = t.ti_hour * 3600 + mn + timezone; /* convert hours to seconds */
yr = d.da_year - 1980; /* get years since 1980 */
dy = d.da_day + (yr >> 2); /* correct days for leap years */
s = 1;
mo = d.da_mon;
while(s < mo) /* add days for this year */
switch(s++)
{
case APRIL: /* months with 30 days */
case JUNE:
case SEPTEMBER:
case NOVEMBER: dy += 30L; break;
case FEBRUARY: dy += ((yr >> 2) == 0) ? 29L : 28L; break;
default: dy += 31L; /* else month has 31 days */
}
if(dst)
hr -= 3600L; /* compensate for daylight savings */
asm sti;
return(*result = (yr * SECS_PER_YEAR + /* return final value */
dy * SECS_PER_DAY +
hr + BIAS_10_YEARS)); /* 10 yr bias for difference */
/* between 1970 and 1980 (secs) */
}
/*
** initialize variables for rtc time and date functions
*/
void init_time()
{
struct tm *cur_time;
time_t timer;
time(&timer); /* kick start TC's time code */
cur_time = localtime(&timer); /* check for daylight savings time */
dst = cur_time->tm_isdst;
outportb(CMOSFLAG,STATUSB); /* get mode the clock is in */
bcd = (inportb(CMOSDATA) & BCD) == 0; /* (binary or BCD) */
outportb(CMOSFLAG,STATUSB);
outportb(CMOSDATA,inportb(CMOSDATA) | MASK_24);/* force 24 hour mode */
func_init = 1;
}
[TIMELIB.H]
/*
** TIMELIB.H
**
** prototype declarations for TIMELIB.C
*/
clock_t rtc_clock();
int milli_count();
void enable_rtc_ints();
void disable_rtc_ints();
void get_rtc_time(struct time *timep);
void get_rtc_date(struct date *datep);
time_t rtc_time(time_t *result);
time_t time2(time_t *result);
void init_time();
char *ctime2();
Copyright © 1991, Dr. Dobb's Journal