UNIT WCom;

{ -- Asychronous communications routines for Turbo Pascal  --
   Copyright (c) 1995 by Wayne Hoxsie. Revision date 5-27-95

   You may use this unit as you please as long as you mention
   me in your documentation.

}
INTERFACE

PROCEDURE AsyncTransmit(ch1:char);
FUNCTION  AsyncRecieve:char;
PROCEDURE AsyncFlushComm;
FUNCTION  AsyncCharWaiting:BOOLEAN;
FUNCTION  AsyncCDetect:boolean;
PROCEDURE AsyncInit(port_num:byte);
PROCEDURE AsyncShutdown;

IMPLEMENTATION

USES DOS;

CONST
(****************************************************************************
  These are the common base addresses and IRQ's for the 4 COM ports.  These
  will need to be edited if you use non-standard addresses or IRQ's
*)

  port_base_id : array[0..4] of word = (0,$3f8,$2f8,$2e8,$3e8);
  port_IRQ : array[0..4] of byte = (0,4,3,4,3);


(****************************************************************************
  These are jusr the mneumonics for the UART registers which are accesses
  relative to the com port's base address.
*)
  IER=1;
  FCR=2;
  IIR=2;
  LCR=3;
  MCR=4;
  LSR=5;
  MSR=6;

VAR
(****************************************************************************
  The head and tail are mearly to keep track of how many characters are in
  the buffer.  If they are not equal, then there are characters waiting to
  be processed.  Since it is a circular buffer, there may be characters still
  in the buffer which have already been processed.
*)

  buf_head,buf_tail : word;
  com_buf : array[0..1024] of byte;

(****************************************************************************
  These are the variables which will hold the current com port info.
*)

  async_irq,port_base : word;

(****************************************************************************
  These are just the ASM CLI and RTI instructions used to prevent another
  interrupt during the processing of data directly related to the interrupt
  handler.
*)

PROCEDURE Disable; INLINE($FA);
PROCEDURE Enable;  INLINE($FB);

(****************************************************************************
  This is the actual interrupt handler.  When a character is recieved by the
  com port, it signals the 8259 PIC which flags the processor to run the
  following code.  It grabs the character from the UART register and puts it
  into our circular buffer to be processed by the program at a later time.
  This is the essence of "Interrupt Driven Communications."
*)

{$F+}                                 { Make it a FAR function }
PROCEDURE async_isr; INTERRUPT;
BEGIN
  com_buf[buf_head]:=port[port_base];
  inc(buf_head);
  IF buf_head = 1024 THEN
    buf_head := 0;
  port[$20]:=$20;
END;
{$F-}

(****************************************************************************
  This procedure mearly sends a character out the com port.
*)
PROCEDURE AsyncTransmit(ch1:CHAR);
VAR
  temp : BYTE;
BEGIN

(*
  First we poll the LSR until there are no characters waiting in the
  transmitter holding register.  The bit we check (bit 5) is the THRE
  (Transmit Holding Register Empty) bit.
*)

  repeat
    temp:=port[port_base + LSR] AND $20;
  until temp<>0;

(*
  Now we can place a character in the register for the UART to send out.
*)

  port[port_base]:=integer(ch1);
END;

(****************************************************************************
  This function just checks to see if a character is waiting to be processed.
  It doesn't effect the character at all.
*)
FUNCTION  AsyncCharWaiting:BOOLEAN;
BEGIN
  asynccharwaiting := buf_head<>buf_tail;
END;

(****************************************************************************
  This function pulls a character out of our buffer and increments the
  pointers appropriately so that the interrupt handler can put the next
  character at the right place.  If no character is available, then we
  return a 0.
*)

FUNCTION  AsyncRecieve:char;
VAR
  c1 : char;
BEGIN
(* First make sure a char is waiting *)
  if buf_head <> buf_tail then
  BEGIN
    disable;
    c1 := char(com_buf[buf_tail]);
    inc(buf_tail);
    if buf_tail = 1024 then
      buf_tail := 0;
    enable;
    asyncrecieve:=c1;
  END  else
    asyncrecieve:=#0;
END;

(****************************************************************************
  This flushes all characters out of the buffer and resets the pointers
*)

PROCEDURE AsyncFlushComm;
BEGIN
  disable;
  buf_head :=0;
  buf_tail :=0;
  enable;
END;

(****************************************************************************
  This procedure sets the DTR pin to the desired value--ON or OFF--to tell
  the program on the other end that we are ready or not-ready.
*)

PROCEDURE dtr(i : BOOLEAN);
VAR
  i1 : word;
BEGIN
  i1 := port[port_base + MCR] AND $00FE;
  if i then
  port[port_base + MCR]:= i1+1 else
  port[port_base + MCR] := i1;
END;

(****************************************************************************
  This tells us if a carrier is detected or not.
*)

FUNCTION  AsyncCDetect:boolean;
BEGIN
  asynccdetect := (port[port_base + MSR] AND $0080)<> 0;
END;

(****************************************************************************
  This is the procedure that must be called to set up our com port for
  operation.  This installs the interrupt and sets the registers to the
  desired values.
*)

PROCEDURE AsyncInit(port_num : byte);
VAR
  temp : word;
BEGIN
  port_base := port_base_id[port_num];
  async_irq := port_IRQ[port_num];
  setintvec(8 + async_irq, addr(async_isr));
  buf_head :=0;
  buf_tail := 0;
  port[port_base + LCR]:= $03;          { set N81 parameters }
  disable;
  temp := port[port_base + LSR];        { Read LSR to reset all bits }
  temp := port[port_base];              { clear any character still pending }
  temp := port[$21];                    { read the PIC mask }
  temp := temp AND((1 SHL async_irq)XOR $00FF);  {set appropriate bit }
  port[$21]:=temp;                      {set the PIC mask appropriately}
  port[port_base + IER]:=1;
  temp:=port[port_base + MCR];
  port[port_base + MCR]:= temp OR $0A;  {Set MCR }
  port[port_base + FCR]:=$c0;           {enable FIFO's on 16550A's }
  enable;
  dtr(TRUE);                            {tell the world, "We're ready!" }
END;

(****************************************************************************
  This resets all the stuff we set up in the first place.
*)
PROCEDURE AsyncShutdown;
VAR
  temp : integer;
  oldvec : pointer;
BEGIN
  if port_base<>0 then BEGIN
    disable;
    temp := port[$21];
    temp := temp OR ((1 SHL async_irq));  { unmask the PIC }
    port[$21]:= temp;
    port[port_base + IIR]:=0;             { shutdown the interrupts }
    port[port_base + MCR]:=3;
    getintvec(8,oldvec);
    setintvec(async_irq+8,oldvec);
    enable;
    port_base:=0;
  END;
END;

END.

{End of Unit==Cut here}

program dumb_terminal;

uses dos,wcom,crt;

var
  temp : char;
  done : boolean;
  i,code : integer;

begin
  if(paramcount<1) then
    i:=1
  else
    val(paramstr(1),i,code);
  done:=false;
  asyncinit(2);
  repeat begin
    if keypressed then begin
      temp:=readkey;
      if temp=#27 then done:=true;
      asynctransmit(temp);
    end;
    if asynccharwaiting then
      write(asyncrecieve);
  end;
  until done;
  asyncshutdown;
end.

