[Back to TSR SWAG index]  [Back to Main SWAG index]  [Original]

Unit Clock;
{
  Author:  Trevor J Carlsen 
  Purpose: Demonstrate a simple "on screen" clock.
  
  This demo Unit works by "hooking" the timer interrupt ($1c). This
  interrupt is called by the hardware interrupt ($08) approximately 18.2
  times every second and normally consists of a simple return instruction
  unless some other application has already hooked it.
  
  Because the routine is called roughly 18 times every second it is
  important that any processing it contains is optimised as much as
  possible.  Obviously the best way to do this is by assembly language but
  in this demo I have used almost pure Turbo Pascal and have set up a
  counter Variable and any processing is only done every 6 calls.  This is
  more than sufficient and minimises processing. The routine is by no
  means perfect - there will be a minor irregularity For the final 10
  seconds of each day and For about half a second each hour. Better this
  than to waste valuable processing time in the interrupt by coding it
  out.
  
  Because Dos is not re-entrant it is also important that the routine make
  no calls to any Procedure or Function that makes use of Dos For its
  operation. Thus no Writeln, Write can be used.  To display the time on
  screen an Array is addressed directly to screen memory.  Any changes in
  this Array are thus reflected on the screen.  The downside to this is
  that on older CGAs this would cause a "snow" effect and code would be
  needed to eliminate this. It also means that the TP Procedure GetTime
  cannot be used.  So the time is calculated from the value stored at the
  clock tick counter location.
  
  To display an on-screen clock all that is required is For a Programmer
  to include this Unit in the Uses declaration of the Program.}
  
Interface

Const
  DisplayClock : Boolean = True;

Implementation
{ Everything is private to this Unit }

Uses Dos;

Const
  line          = 0;  { Change as required For position of display on screen }
  column        = 72;                               { Top left corner is 0,0 }
  ScreenPos     = (line * 160) + (column * 2);
  Colour        = $1f;                                       { White on Blue }
  ZeroChar      = Colour shl 8 + ord('0'); 
  Colon         = Colour shl 8 + ord(':');
Type
  timestr       = Array[0..7] of Word;
  timeptr       = ^timestr;
Var
  time          : timeptr;
  OldInt1c      : Pointer;
  ExitSave      : Pointer;

{$F+}
 Procedure Int1cISR; interrupt;
  { This will be called every clock tick by hardware interrupt $08 }
  Const
    count       : Integer = 0;                  { To keep track of our calls }
  Var
    hr          : Word Absolute $40:$6e;
    ticks       : Word Absolute $40:$6c; 
                  { This location keeps the number of clock ticks since 00:00}
    min,
    sec         : Byte;
    seconds     : Word;
  begin
    Asm cli end;
    if DisplayClock then begin
      inc(count);
      if count = 6 then { ticks to update the display } begin
        count       := 0;  { equality check and assignment faster than mod 9 }
        seconds     := ticks * LongInt(10) div 182;       { speed = no Reals }
        min         := (seconds div 60) mod 60;
        sec         := seconds mod 60;

      { The following statements are what actually display the on-screen time}

        time^[0]    := ZeroChar + (hr div 10);         { first Char of hours }
        time^[1]    := ZeroChar + (hr mod 10);        { second Char of hours }
        time^[2]    := Colon;
        time^[3]    := ZeroChar + (min div 10);      { first Char of minutes }
        time^[4]    := ZeroChar + (min mod 10);     { second Char of minutes }
        time^[5]    := Colon;
        time^[6]    := ZeroChar + (sec div 10);      { first Char of seconds }
        time^[7]    := ZeroChar + (sec mod 10);     { second Char of seconds }
      end;  { if count = 6 }
    end;  { if DisplayClock }
    Asm         
      sti
      pushf                                  { push flags to set up For IRET }
      call OldInt1c;                              { Call old ISR entry point }
    end;
  end; { Int1cISR }

Procedure ClockExitProc;
  { This Procedure is VERY important as you have hooked the timer interrupt  }
  { and therefore if this is omitted when the Unit is terminated your        }
  { system will crash in an unpredictable and possibly damaging way.         }
  begin
    ExitProc := ExitSave;
    SetIntVec($1c,OldInt1c);               { This "unhooks" the timer vector }
  end;
{$F-}

Procedure Initialise;
  Var
    mode : Byte Absolute $40:$49;
  begin
    if mode = 7 then                                { must be a mono adaptor }
      time := ptr($b000,ScreenPos)
    else                                       { colour adaptor of some kind }
      time := ptr($b800,ScreenPos);      
    GetIntVec($1c,OldInt1c);              { Get old timer vector and save it }
    ExitSave := ExitProc;                          { Save old Exit Procedure }
    ExitProc := @ClockExitProc;                 { Setup a new Exit Procedure }
    SetIntVec($1c,@Int1cISR);   { Hook the timer vector to the new Procedure }
  end;  { Initialise }  
  
begin 
  Initialise;
end.


[Back to TSR SWAG index]  [Back to Main SWAG index]  [Original]