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

    {  +-------------------------------------------------------------------+
       |  Project  : Generic             |  Filename    : TIMERINT.PAS     |
       |  Coded By : Lost (Jacob Lister) |  Version     : 1.0              |
       |--========-------------+---------+--========-----------------------|
       |  Date     : 05/07/97  |  Created                                  |
       |             06/07/97  |  Got it working                           |
       |             24/07/97  |  Interrupt handler now saves state of     |
       |                       |  flags                                    |
       |             21/08/97  |  Cleaned up for SWAG release              |
       |--========-------------+--========---------------------------------|
       |  Purpose  : General purpose timer interrupt functions, allow many |
       |             handlers to be strung onto the one timer, and         |
       |             re-programs the clock rate as nessacary               |
       |  Notes    : There are some very messy fiddles used here to get the|
       |             interrupt handler working with the sub-fuction record |
       |             table, this is OK because the crappy code is confined |
       |             to this unit.  Take care of what procedures you use   |
       |             this unit to call, for example 'writeln' will behave  |
       |             unstabily if it is being called by both an IRQ and    |
       |             other code at the same time, this goes for most       |
       |             procedures.                                           |
       |  Wish List: Optimize the interrupt handler (timer cirtical)       |
       |--========---------------------------------------------------------|
       |  Credits  : Some interrupt programming info from SWAG             |
       +--========---------------------------------------------------------+  }
    { Usage : to install a handler, call the 'install_handler' with a pointer
              to your procedure and the interval rate in Hz that it is to be
              called.  You handlers MUST be far code, force this by adding
              the directive 'far' to the procedure header, eg
                 procedure mytest; far;
              Your procedures are free to use all CPU registers as the unit
              handles preserving these.
              I beleive it is possible for procedures in different units to
              use different data segments, if this is the case for your code,
              your handlers must point DS to their own data segment, to do
              this, put these lines at the start of your handler:
                 asm
                    mov ax, seg @data
                    mov ds, ax
                 end;
              You can de-install handlers by calling 'remove_handler',
              alternativly the program will handle all de-installation on
              exit }

    unit timerint;

    INTERFACE

    procedure install_handler(address : pointer; rate : longint);
    procedure remove_handler(address : pointer);

    IMPLEMENTATION
    uses dos;
    const USE32 = $66;                      {32 bit instruction prefix}

    const clock_rate = 1192755;             {Timer rate (Hz)}
          dos_rate = 63356;                 {The rate dos service call is called}
    var tick_rate : longint;                {The current timer rate}
        dos_tick  : longint;                {number of ticks since last dos
                                             timer service called }
        old_handler : pointer;              {Variable to save old int 8 handler}
        old_exit  : pointer;                {save for exit chain}

    type timer_int_function = record
       address : pointer;                   {Pointer to the handler}
       rate    : longint;                   {Required rate in timer ticks}
       ticks   : longint;                   {ticks since last call}
       active  : boolean;                   {Is this handler active?}
    end;

    const t_address  = $0;                  {Address values for the timer record}
          t_rate     = $4;                  {Needed, as they cannot be directly}
          t_ticks    = $8;                  {derived for the inline ASM}
          t_active   = $C;
          t_size     = $D;

    const num_handlers = 5;
    var timer_ints : array[1..num_handlers] of timer_int_function;

    {Variables for interrupt service rountine.}
    var call_adr : pointer;

    {Re-programs the timer to genererate an interrupt every 'ticks' ticks }
    procedure set_clock_rate(ticks : longint);
    begin
       port[$43] := $36;
       port[$40] := lo(ticks);
       port[$40] := hi(ticks);
       tick_rate := ticks;
    end;

    {  +-------------------------------------------------------------------+
       |  Procedure : find_clock_rate                                      |
       |  Callers   : install_handler, remove_handler                      |
       |  Purpose   : Finds the maximum clock rate needed, and sets the    |
       |              timer to run at that rate                            |
       +--========---------------------------------------------------------+
       |  Input     : nothing                                              |
       |  Output    : nothing                                              |
       |  Destroys  : nothing                                              |
       +--========---------------------------------------------------------+
       |  Notes     :                                                      |
       +--========---------------------------------------------------------+   }
    procedure find_clock_rate;
    var i : byte;
        max_rate : longint;
    begin
       max_rate := dos_rate;
       for i:=1 to num_handlers do
         if (timer_ints[i].active = true) and (timer_ints[i].rate < max_rate)
            then max_rate := timer_ints[i].rate;
       set_clock_rate(max_rate);
    end;

    {  +-------------------------------------------------------------------+
       |  Procedure : install_handler                                      |
       |  Callers   : Anything                                             |
       |  Purpose   : Installs an interrupt handling funtion on to the     |
       |              interrupt.  If the requested rate cannot be handled  |
       |              by the current clock rate, the clock rate is changed |
       +--========---------------------------------------------------------+
       |  Input     : address - Pointer to code to run on interrupt        |
       |              rate    - the rate at which the hander is to run     |
       |                        (in Hz)                                    |
       |  Output    :                                                      |
       |  Destroys  : nothing                                              |
       +--========---------------------------------------------------------+
       |  Notes     :                                                      |
       +--========---------------------------------------------------------+   }
    procedure install_handler(address : pointer; rate : longint);
    var handler_tick_rate : longint;
        i                 : byte;
    begin
       port[$43] := $36;                    {lower clock rate so interrupts don't}
       port[$40] := 0;                      {occur which we are installing handler}
       port[$40] := 0;

       handler_tick_rate := clock_rate div rate;

       for i:=1 to num_handlers do begin
          if(timer_ints[i].active = false) then begin
             timer_ints[i].active   := true;
             timer_ints[i].address  := address;
             timer_ints[i].rate     := handler_tick_rate;
             timer_ints[i].ticks    := 0;
             break;
          end;
       end;
       find_clock_rate;
    end;

    {  +-------------------------------------------------------------------+
       |  Procedure : remove_handler                                       |
       |  Callers   : Anything                                             |
       |  Purpose   : Searches for the a specified interrupt handler, and  |
       |              removes it, then re-adjusts the clock rate if        |
       |              nessecary                                            |
       +--========---------------------------------------------------------+
       |  Input     : address - pointer to handler routine to remove       |
       |  Output    : nothing                                              |
       |  Destroys  : info for specified handler                           |
       +--========---------------------------------------------------------+
       |  Notes     : Destroys the first found case of routine.  If more   |
       |              than one instance of the same handler function, only |
       |              the first will be destroyed.                         |
       +--========---------------------------------------------------------+   }
    procedure remove_handler(address : pointer);
    var i : integer;
    begin
       port[$43] := $36;                    {lower clock rate so interrupts don't}
       port[$40] := 0;                      {occur which we are removing handler}
       port[$40] := 0;
       for i:=1 to num_handlers do
          if timer_ints[i].address = address then
             timer_ints[i].active := false;
       find_clock_rate;
    end;

    {  +-------------------------------------------------------------------+
       |  Procedure : interrupt_handler                                    |
       |  Callers   : interrupt 8                                          |
       |  Purpose   : Dispacthes many timer functions off the one timer    |
       |              interrupt as required.  Calls the original DOS IRQ8  |
       |              handler as required                                  |
       +--========---------------------------------------------------------+
       |  Input     : nothing                                              |
       |  Output    : nothing                                              |
       |  Destroys  : nothing                                              |
       +--========---------------------------------------------------------+
       |  Notes     :                                                      |
       +--========---------------------------------------------------------+   }
    procedure interrupt_handler; assembler;
    asm
       pushf
       db USE32; pusha
       push ds
       push es
       push ss

       mov ax, seg @data
       mov ds, ax

       mov di, OFFSET timer_ints            {Point to our handlers}
       mov cl, num_handlers                 {Get the number of handlers}
     @@L0:
       mov al, 0
       cmp [di+t_active], al
       jz @@Next_Handle                     {Check if handle is active}

       db USE32; mov ax, [di+t_ticks]
       db USE32; add ax, word ptr tick_rate
       db USE32; cmp ax, [di+t_rate]

       jl @@Next_Handle                     {Is it time for the next handle call?}
       db USE32; sub ax, [di+t_rate]        {reset our tick count}

       db USE32; mov bx, [di+t_address]
       db USE32; mov word ptr call_adr, bx

       push ax
       push cx
       push di
       push ds
       CALL call_adr
       pop ds
       pop di
       pop cx
       pop ax

     @@Next_Handle:
       db USE32; mov [di+t_ticks], ax       {Save back the handle tick info}
       add di, t_size
       dec cl
       jnz @@L0

       mov al,$20
       out $20,al

       db USE32; mov ax, word ptr dos_tick
       db USE32; add ax, word ptr tick_rate
       db USE32; mov word ptr dos_tick, ax
       db USE32; cmp ax, 0000; dw 1         {next dos call?}
       jl @@NoDos
       db USE32; sub ax, 0000h; dw 1
       db USE32; mov word ptr dos_tick, ax  {reset dos rate counter}

       pushf
       call [old_handler]                   {never returns}

      @@NoDos:

       pop ss
       pop es
       pop ds
       db USE32; popa
       popf
       iret
    end;

    { Clean up code runs when program finishes, resets the timer interrupt
      handler, and resets the clock }
    procedure timer_exit; far;
    begin
       asm cli end;
       ExitProc := old_exit;
       port[$43] := $36;
       port[$40] := 0;
       port[$40] := 0;
       setintvec(8, old_handler);
       asm sti end;
    end;

    var i : byte;
    begin
       for i:=1 to num_handlers do timer_ints[i].active := false;
       tick_rate := 65536;
       dos_tick := 0;

       getintvec(8,old_handler);
       setintvec(8,@interrupt_handler);
       old_exit := ExitProc;
       ExitProc := @timer_exit;
    end.

    {---------------------------------- CUT HERE -------------------------------}

    program timertst;

    uses timerint, crt;

    procedure toggle_light(x, y: byte);
    const screen = $B800;
          lchr   = $0CFE;
          dchr   = $04FE;
    var   adr    : word;
    begin
       adr := (y*80+x)*2;
       if(memW[screen:adr] <> lchr) then memW[screen:adr] := lchr
          else memW[screen:adr] := dchr;
    end;

    procedure test1; far;
    begin
       toggle_light(5,5);
    end;

    procedure test2; far;
    begin
       toggle_light(13,5);
    end;

    procedure test3; far;
    begin
       toggle_light(21,5);
    end;

    procedure test4; far;
    begin
       toggle_light(29,5);
    end;

    procedure test5; far;
    begin
       toggle_light(37,5);
    end;

    begin
       clrscr;
       writeln('+------------------------------------------+');
       writeln('|           TIMERINT UNIT EXAMPLE          |');
       writeln('+------------------------------------------+');
       writeln('|   1 Hz    2 Hz    5 Hz    10 Hz   100 Hz |');
       writeln('|   +-+     +-+     +-+     +-+     +-+    |');
       writeln('|   | |     | |     | |     | |     | |    |');
       writeln('|   +-+     +-+     +-+     +-+     +-+    |');
       writeln('+------------------------------------------+');
       install_handler(@test1, 1);
       install_handler(@test2, 2);
       install_handler(@test3, 5);
       install_handler(@test4, 10);
       install_handler(@test5, 100);

       repeat
       until keypressed;
    end.

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