(*  This program uses an 8253 interval timer on an iSBC 86/12A 
board to interrupt the host processor every 10 milliseconds.
It assumes the 86/12A board is set up as a standard Series-III board
is set up, with:
   - counter 0 of the on-board 8253 free, and its interrupt strapped
     to level 2 of the on-board 8259A.
   - The on-board 8259A initialized as on the Series-III, with its
     8 interrupts mapped in from 56 to 63.
*)

program IntervalTimer;

   (*  8253 Port address definitions. *)
   const CountControlPort = 0D6H;
         CountReg0        = 0D0H;
         InitializeReg0   = 030H;

   (* 8259a Port address and control word definitions. *)
         IMaskRegPort     = 0C2H;
         IControlPort     = 0C0H;
         EndofInterrupt   = 020H;

   (* Define a set of interrupts to use to mask the 8259A interrupt mask *)
   (* The 8259A assigns interrupt levels from right to left in its control
      register, the same order that Pascal-86 assigns bits to set elements.
    *)

    type  IntLevels = (I0, I1, I2, I3, I4, I5, I6, I7);
          IntSet    = Set of IntLevels;

    var   CountCounts : LONGINT;  (* Extend timer here. Holds count of
                                     10 msec. intervals recieved. *)
(*----------------------------------------------------------------*)
procedure ResetCount;
       (* This procedure loads the counter value with 12300 
       so that the counter will count down for exactly 10
       milliseconds. ( The iSBC 86/12A clock rate is 1.23MHz.)  *)


(* Use a variant record to map two bytes "on top of" a word.
Note that the low byte precedes the high byte. *)
var   Count : RECORD CASE BOOLEAN OF
               true:( FullWord: WORD);
              false:( Low, High: 0..255)
              END;

begin
    Count.FullWord := 12300;  (* 10 milliseconds @ 1.23 Mhz *)
    OUTBYT(CountReg0,Count.Low);
    OUTBYT(CountReg0,Count.High)
end; (* ResetCount *)

(*----------------------------------------------------------------*)
procedure InitializeChip;  (* Initialize the 8253 by setting the control
       word to select counter 0, read/load low-order byte then high-order
       byte, interrupt on a terminal count, and accept count in binary.
       (control word = 30H)  ResetCount is called to load the counter.*)

    var IMask : IntSet;

begin
    (* Initialize Counter 0. *)
    OUTBYT(CountControlPort,InitializeReg0);
    RESETCOUNT;

    (* Now, enable the interrupt level corresponding to timer 0.
       On the Series-III board, it is mapped at interrupt level 2
       on the 8259A, which maps to the 8086 level 58. *)

    INBYT(IMaskRegPort, IMask);
    (* The expression IMask - [I2]  yields a set of interrupts with
       I2 removed.  This turns off the bit corresponding to interrupt
       level 2 on the 8259, which is our timer.
       (Turning the bit off in the mask register enables the interrupt) *)
    OUTBYT(IMaskRegPort, IMask - [I2]);

end; (* InitializeChip *)

(*----------------------------------------------------------------*)
$INTERRUPT(ServiceInterrupt)
procedure ServiceInterrupt; (* This procedure services interrupt 58 when that
       interrupt occurs.  To make the 
       program more useful, add code to take action before starting the 
       next interval.                                                *)

begin
    ResetCount;
    CountCounts := CountCounts + 1;

    (* Must clear the interrupt on the 8259A. *)
    OUTBYT(IControlPort, EndofInterrupt);
end; (* ServiceInterrupt *)

(*----------------------------------------------------------------*)
begin
    SETINTERRUPT(58,ServiceInterrupt);
    CountCounts := 0;
    InitializeChip;
    while true do
end. (* IntervalTimer *)
