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

{$O-} {This unit may >>>>NOT<<<< be overlaid}
{$X+} {Extended syntax is ok}
{$F+} {Allow far calls}
{$A+} {Word Align Data}
{$G+} {286 Code optimization - if you're using an 8088 get a real computer}
{$R-} {Disable range checking}
{$S+} {Enable Stack Checking}
{$I-} {Disable IO Checking}
{$Q-} {Disable Overflow Checking}
{$D-} {Turn off debugging - use only if you modify this unit and get a bug}
{$L-} {Turn off local symbols - again this unit has been thouroughly debuged}
{$V-} {Turn off strict VAR strings}
{$B-} {Allow short circuit boolean evaluations}
{$T-} {Turn off typed @ operators}

Unit Async;

{This unit was written by Patrick Hunlock, 10/19/1994     }
{Copyright @ 1994 by Patrick Hunlock - all rights reserved}

{Software License Agreement: If you use this unit you are bound by it's terms}

{You may use this unit in your programs without royalties. If you use this
 unit my name and the copyright notice must appear beneath your name and
 copyright notice even if you have made significant alterations to the
 source code in this unit.  This source code may only be transmitted via
 the telephone system.  It may not be distributed on any magnetic, optical,
 or any future computer media format without prior consent of the copyright
 holder.  No charge may ever be levied for the receipt of this source code
 other than normal phone charges and/or normal connect charges on a BBS which
 charges for access.  Any charges above and beyond what would be considered
 normal access charges to download this source is considered a SALE of this
 source code which is expressly prohibited by this License Agreement.
 You may distribute this source code via the telephone system (I.E.
 BBS or computer network) in it's original form only, you may not add or
 subtract to/from it in any way shape or form.}

{This ASYNC unit implements a >COMPLETE< multi-port serial interface wrapped
 in a turbo pascal object.  Baud rates up to 110,000 are supported.  Up to
 four ports may be opened at a time, up to two ports may be active at any
 given time (four can be active so long as they do not share interrupts).
 Some considerations.  A com port which shares the same interrupt as a
 serial mouse will over-ride the mouse driver for the duration of the
 program.  If you have a mouse on Com1, avoid using Com3 in your program.
 Likewise if you have a mouse on Com2, avoid using Com4 in your program.
 A mouse on a BUS card will not be affected by this unit.  Default addresses
 for the 4 com ports dictate that com1 & com3, and com2 & com4 share an
 interrupt on the system bus.  As such while you can open both ports, make
 sure that only - ONE of the ports on the shared interrupt has been .ENABLED
 at any one time.  See the .ENABLE and .DISABLE procedures for more



   Com_Port      = Object
                         CPort   : Byte;          {Com Port for this port}

                      {Initializes the buffers and the object}
                      Function Init(ComPort: Byte; RBufSize,TBufSize: Word): Byte;
                      {Sets the baud,parity,wordsize, and stopbits}
                      Procedure SetParam(Baud: Longint; WordSize: Byte;
                                         Parity: Char; StopBits: Byte);
                      {Used for shared interrupts}
                      Procedure Disable;
                      {Enable a com port for use}
                      Procedure Enable;
                      {Returns true if there is data waiting}
                      Function Waiting: Boolean;
                      {Returns a character waiting in the buffer}
                      Function Read: Char;
                      {Waits for a character if necessary}
                      Function Readw: Char;
                      {Writes a character to the transmit buffer}
                      Procedure Write(C: CHar);
                      {Passes a string to the transmit buffer}
                      Procedure WriteS(S: String);
                      {Returns true if modem is "on-line" re DCD status}
                      Function OnLine: Boolean;
                      {Disconnects the modem connection}
                      Procedure Hangup;
                      {Sends a Break Signal to the other computer}
                      Procedure Break;
                      {Terminates the comm port}
                      Procedure Done;


Uses DOS,       {Turbo Pascal's DOS Unit}
     CRT;       {Turbo Pascal's CRT Unit}

   PortBases : Array[1..4] Of Word = ($3F8,$2F8,$3E8,$2E8);
   Interrupts: Array[1..4] Of Byte = (   4,   3,   4,   3);

   Disable_Interrupts = $FA;       {Used in INLINE statements}
   Enable_Interrupts  = $FB;       {Used in INLINE statements}

   {Buf Type is never actually >declared< in this unit.  It is created only
    as a pointer to an area of memory which can actually only be 10 to 64000
    bytes long.  However by creating the pointer in this way, the contents
    of the pointer memory can be accessed like an array of chars simplifying
    the coding of this unit.  If you only create a 10000 byte buffer then
    it's valid to read BUFTYPE[0] through BufTYPE[10000] anything higher
    is dangerous since no memory was set aside for this buffer.  However
    the unit knows how much memory was set asside and it never references
    any data outside of the "safe" range}
   BufType = Array[0..64000] Of Char;

   {ComBufferType is the actual buffer used by the COM_ISR routine and the
    record which actually makes COM_PORT work.  In reality COM_PORT is just
    a shell which is wrapped around this record to give the illusion of
    OOP.  Take away COM_PORT, write your own procedures to reference
    ComBufferType and your program will work just fine}

   ComBufferType = Record
                      Active       : Boolean;  {True if this buffer is active}
                      R_Buffer     : ^BufType; {Recieve buffer pointer       }
                      R_Head,R_Tail: Word;     {Buffer Head and Buffer Tail  }
                      R_Size       : Word;     {Size of the recieve buffer   }
                      T_Buffer     : ^BufTYpe; {Transmit Buffer Pointer      }
                      T_Head,T_Tail: Word;     {Transmit Buffer Head & Tail  }
                      T_Size       : Word;     {Size of the transmit buffer  }
                      UART_Data    : Word;     {Uart data address            }
                      UART_IER     : Word;     {Uart interrupt enable registr}
                      UART_IIR     : Word;     {Uart interrupt identification}
                      UART_LCR     : Word;     {UArt Line Control Register   }
                      UART_MCR     : Word;     {UArt Modem COntrol Register  }
                      UART_LSR     : Word;     {UArt Line Status Register    }
                      UART_MSR     : Word;     {UArt Modem Status Register   }
                      OLD_MCR      : Byte;     {Old Modem control register   }
                      Org_Vector   : Pointer;  {Original interrupt vector    }

   Bufs: Array[1..4] Of ComBufferType; {This declares 4 com buffers for  }
                                       {coms 1 - 4                       }

Procedure COM_ISR; Interrupt;

{COM_ISR is the main interrupt procedure which handles all the serial IO.
 This procedure is called >AUTOMATICALLY< by DOS whenever data arrives at
 the com port - or when it is clear to send data.  >SEVERE< restrictions
 as to what you can and can not add to this procedure apply.  You can not
 use WRITELN.  You can not reference any turbo pascal objects.  This unit
 may not be overlaid.  Etc, Etc, Etc}

Const Ktr: Byte = 0;  {These are STATIC variables so pascal doesn't }
      IIR: Byte = 0;  {constantly have to redeclare them on the heap}

   For Ktr:= 1 to 4 Do begin
      With Bufs[Ktr] Do Begin
         If Active Then Begin
            iir:= Port[UART_IIR];
            While Not Odd(IIR) Do Begin
               Case (iir SHR 1) Of
                  0: iir:= Port[UART_MSR]; {Modem status change}
                  1: If T_Head = T_Tail Then Begin    {Ok to transmit      }
                        {Transmit buffer empty - disable transmit interrupt}
                        Port[UART_IER]:= Port[UART_IER] And Not 2;
                     End Else Begin
                        Port[UART_DATA]:= Byte(T_Buffer^[T_Head]);
                        If T_Head > T_Size Then T_Head:= 0;
                  2: Begin                  {Recieve buffer}
                        R_Buffer^[R_Tail]:= Char(Port[Uart_Data]);
                        If R_Tail > R_Size Then R_Tail:= 0;
                        If (R_Tail = R_Head) Then Begin
                           Inc(R_Head); {Overflow}
                           If R_Head > R_Size Then R_Head:= 0;
                  3: iir:= Port[UART_LSR]; {Line status change}
               iir:= Port[UART_IIR];
   Port[$20]:= $20;  {We're done processing the interrupt}

Function Com_Port.Init(ComPort: Byte; RBufSize,TBufSize: Word): Byte;

{Init is the standard object initialization routine. You must pass the
 comport (1-4) you want the object to be associated with and the buffer
 size (10-64000 bytes) you want the buffer to be.  Init returns the following
 codes based upon it's success or failure......

    0 - Com Port Initialized
    1 - ComPort is out of range - not 1, 2, 3, or 4.
    2 - ComPort is already active and in use.
    3 - Buffer size is either to small (10 bytes or more) or to large
        (greater than 64000) - Recieve Buffer
    4 - Transmit buffer size out of range (See #3)

 Init only sets up the buffers and prepares the interrupt vector.  You must
 follow this with a call to setparams (set baud rate, parity, etc), then
 a call to enable.  See enable and disable especially if you are going to
 use multiple comm ports simultaniously.

Var InUse: Boolean;     {Scratch variable to check for active interrupts}
    Ktr  : Byte;        {Counter Variable                               }

   {Set the initial state of the return code to OK}
   Init:= 0;
   {Check the comport validity}
   If (ComPort < 1) Or (ComPort > 4) Then Begin
      Init:= 1;
   {Check to see if the comport is already in use by another object}
   If Bufs[ComPort].Active Then Begin
      Init:= 2;
   {Check to make sure the buffer size is valid}
   If (RBufSize > 64000) Or (RBufSize < 10) Then Begin
      Init:= 3;
   If (TBufSize > 64000) Or (TBufSize < 10) Then Begin
      Init:= 4;

   {Begin main setup}

   CPort:= ComPort;                        {Store the comport for future use}
   Getmem(Bufs[ComPort].R_Buffer,RBufSize);{Allocate memory for the buffer  }
   Bufs[ComPort].R_Size:= RBufSize;        {Store the size of the buffer    }
   GetMem(Bufs[ComPort].T_Buffer,TBufSize);{Allocate transmit buffer memory }
   Bufs[ComPort].T_Size:= TBufSize;        {Store the size of the buffer    }

   {This next section sets up the PORT addresses used by the comport
    requested.  The base addresses are stored in PORTBASES, a constant
    declared at the top of the implenetation section of this unit.  Your
    program may need to change the address and/or interrupts found in
    that section for serial cards with unusual addresses and interrupts.
    Since PortBases and Interrupts are typed constant arrays you can
    easilly add an object method to change the address or interrupt for
    a given comm port}

    Bufs[ComPort].UART_DATA:= PortBases[ComPort]+0;
    Bufs[ComPort].UART_IER := PortBases[ComPort]+1;
    Bufs[ComPort].UART_IIR := PortBases[ComPort]+2;
    Bufs[ComPort].UART_LCR := PortBases[ComPort]+3;
    Bufs[ComPort].UART_MCR := PortBases[ComPort]+4;
    Bufs[ComPort].UART_LSR := PortBases[ComPort]+5;
    Bufs[ComPort].UART_MSR := PortBases[ComPort]+6;

   {This next section sees if there is already an interrupt vector set for
    a shared interrupt com port.  For instance COM1 and COM3 both use
    interrupt 4 and COM2 and COM4 use interrupt 3.  If we are setting up
    COM1, but COM3 is already up and running we don't need to do anything
    since the interrupt service routine (Procedure COM_ISR) will
    automatically check the com ports on it's interrupt}

   InUse:= False;
   For Ktr:= 1 to 4 Do
      If (Interrupts[Ktr] = Interrupts[ComPort]) And Bufs[Ktr].Active Then
         InUse:= True;

   {This next section is run if, and only if a shared interrupt is not
    currently running.}


   If Not InUse Then Begin
      {Get the old DOS interrupt vector, save it then change it to point
       to the COM_ISR procedure in this unit}
      Port[$21] := Port[$21] Or (1 SHL Interrupts[ComPort]);
      Port [$21] := Port [$21] AND NOT (1 SHL Interrupts[ComPort]);

   Bufs[ComPort].Old_MCR:= Port[Bufs[ComPort].UART_MCR]; {Store MCR        }
   Port[Bufs[ComPort].UART_LCR]:= 3; {Parity to none and turn off the break}
   PORT[Bufs[ComPort].UART_IER]:= 1; {Enable data recieved interrupts      }


   Bufs[ComPort].Active:= True;      {Let COM_ISR know to check this port  }

Procedure Com_Port.SetParam(Baud: Longint; WordSize: Byte;
                             Parity: Char; StopBits: Byte);

      MaxBaud      = 115200;  {Maximum baud rate                            }

Var Divisor: Word;
    lcr    : Byte;

{Sets the baud rate, wordsize, parity, and stop bits used by the port.  The
 most common setting especially with high speed modems is 38400 baud, word
 size is 8 bits (one byte), 'N' or no parity, and one stop bit.  Compuserve
 uses 7 bits and even parity.}


   {This next section sets the baud rate based on the divisor of MAXBAUD}

   If Baud < 50 Then Baud:= 50;
   If Baud > MaxBaud Then Baud:= MaxBaud;
   Divisor:= MaxBaud Div Baud;
   Port [Bufs[CPort].uart_lcr ]:= Port[Bufs[Cport].uart_lcr] Or $80;
   Portw[Bufs[CPort].uart_Data]:= divisor;
   Port [Bufs[CPort].uart_lcr] := Port[Bufs[CPort].uart_lcr] And NOT $80;

   {This next section sets the parity}

   Case upcase(Parity) Of
      'N': lcr:= $00 or $03;
      'E': lcr:= $18 or $02;
      'O': lcr:= $08 Or $02;
      'S': lcr:= $38 Or $02;
      'M': lcr:= $28 OR $02;
         Lcr:= $00 or $03;
   If StopBits = 2 Then lcr:= Lcr OR $04;

   Port[Bufs[CPort].Uart_lcr]:= Port[Bufs[CPort].uart_lcr] And $40 Or LCR;

Procedure Com_Port.Done;

{Use this procedure when you are done with your program.  You >MUST< run
 this procedure for each COM variable you have initialized.  If you don't
 if any data comes in to the comm port DOS will still try to go to the
 place where the COM_ISR >>>USED<<< to be!  Meaning your computer >COULD<
 crash.  Since this is an OOP approach exit-proc wasn't used since there
 could be any number of variables open and running.  Therefore it is YOUR
 responsibility to call this procedure for each COM_PORT object you have

Var InUse: Boolean;     {Scratch variable to test for shared interrupt}
    Ktr  : byte;        {Counter variable                             }

   {Check for shared interrupt usage}

   InUse:= False;
   For Ktr:= 1 to 4 Do
      If (Interrupts[Ktr] = Interrupts[CPort]) And Bufs[Ktr].Active Then
         InUse:= True;


   {Restore the old Modem Control Register and disable incomming data
   Port[Bufs[CPort].UART_MCR] := Bufs[CPort].Old_MCR;
   Port[Bufs[CPort].UART_IER] := 0;

   If Not InUse Then Begin
      {Remove the interrupt only if another object is not using it}
      Port[$21] := Port[$21] Or ($01 SHR Interrupts[CPort]);


   CPort:= 0;                {Set CPort variable to 0     }

   {Release the buffer memory and set the active flag to false}
   Bufs[CPort].Active:= False;

Function Com_Port.Read: Char;

{If a character is available on the buffer, this procedure will return the
 char.  If there is no character, char 0 (#0) will be returned.  If you
 expect #0 to be passed as part of the data call this routine only if
 function waiting returns true, or use readw - which will wait for
 a character from the com port if nothing is available}

   With Bufs[CPort] Do Begin
      If R_Head = R_Tail Then Begin  {Nothing in the buffer}
         Read:= #0;
      End Else Begin                 {Data waiting in the buffer}
         Read:= R_Buffer^[R_Head];          {Get the waiting character}
         Inc(R_Head);                       {Increment the queue pointer}
         If R_Head > R_Size Then R_Head:= 0; {Check for out of range}

Function Com_Port.Readw: Char;

{Waits for a character from the comm port if none are available.  Passes
 back the first character it finds in the recieve buffer}

    With Bufs[CPort] Do Begin
       While R_Head = R_Tail Do;
       Readw:= R_Buffer^[R_Head];
       If R_Head > R_Size THen R_Head:= 0;

Function Com_Port.Waiting: Boolean;

{This function returns TRUE if there is data waiting in the recieve buffer}

   Waiting:= (Bufs[CPort].R_Head <> Bufs[CPort].R_Tail);

Procedure Com_Port.Enable;

{This should be the third command you run after .INIT and .SETPARAM.  Enable
 and Disable are provided for multi-port use.  If you are using com1 and
 com2 together you can leave both ports enabled at the same time, likewise
 with com3 and com4.  However ports that share interrupts (Com1 & Com3)
 (com2 & com4) can not both be enabled at the same time.  This is the reason
 I wrote this unit because the most popular pascal com libraries
 (particularly the async libraries by rising sun which are otherwise
 extrodinarilly compitent packages) could not handle shared interrupts.
 This unit can, but it cheats by allowing you to "suspend" one of the ports
 on the shared interrupt.  While a port is suspended (disabled) you can not
 send or recieve data from that port.  Other considerations - a mouse on
 com1 will not work with this package when you use com3, likewise a mouse
 on com2 will not work when you run com4 this is because this package
 installs it's own interrupts - overwriting the mouse ports (although the
 mouse will begin working again once you call .DONE}

   Port[Bufs[CPort].Uart_MCR]:= 11;

Procedure Com_Port.Disable;

{Call this procedure only if you are about to enable another port which
 uses the same interrupt, see COM_PORT.Enable for more information}

   Port[Bufs[CPort].Uart_MCR]:= 3;

Procedure Com_Port.Write(C: Char);

{This procedure places a character on the transmit buffer}

   With Bufs[CPort] Do Begin
      T_Buffer^[T_Tail]:= C;
      If T_Tail > T_Size Then T_Tail:=0;
      If (T_Tail = T_Head) Then Begin
         Inc(T_Head); {Overflow}
         If T_Head > T_Size Then T_Head:=0;
      {Tell the modem to alert us when it is OK to send data}
      Port[UART_IER]:= Port[UART_IER] Or 2;

Procedure Com_Port.WriteS(S: String);

{Passes a string to the transmit buffer}

Var Ktr: Byte;

   For Ktr:= 1 to Length(S) Do

Procedure Com_Port.Break;

{This procedure sends a >BREAK< signal down the line.  It is usefull
 primarilly for mainframe and unix based systems, DOS based machines
 do not look for or respond to the break signal.}

   Org_Data: byte;

   With Bufs[CPort] Do Begin
      Org_Data:= Port[UART_LCR];   {Save the contents of the LCR            }
      Port[UART_LCR]:= 255;        {Load up the Line Control Register       }
      Delay(3);                    {CRT unit - Delay 3 thousands of a second}
      Port[UART_LCR]:= Org_Data;   {Restore the Line Control Register       }

Function Com_Port.OnLine: Boolean;

{This function returns TRUE if the Modem Status Register indicates a Data
 carrier detect signal.  Note that some modems always return true even when
 not connected, an AT command is needed to force DCD to return the true state
 of the modem.  Also note that some direct serial connections (I.E. no modem
 but hardwired to another machine, may not return the correct DCD stat or
 may be false even when connected - this is particularly true of three wire
 direct serial connections (pins 2, 3, and 7 wired all others unwired)}

    OnLine := (Port[Bufs[CPort].UART_MSR] And $80) > 0;

Procedure Com_Port.Hangup;

{This procedure disconects the modem by lowering the DTR signal.  Note that
 some modems may not be affected by this procedure based on their AT
 configurations.  Direct serial lines are not usually affected by this
 signal.  Your best bet is to issue this command then send '+++' to the modem
 and wait five seconds and then issue 'ATH<return>'}

Var Org_MCR: Byte;  {Scratch var to store the original MCR stuff}

  With Bufs[CPort] Do Begin
     Org_Mcr := Port[UART_MCR];
     Port[UART_MCR]:= Org_MCR Or $FE;  {Lower the DTR signal  }
     Delay(100);                       {Delay 100 ms          }
     Port[UART_MCR]:= Org_MCR;         {Restore the DTR Signal}

Var Ktr: Byte;


   {Initialize the 4 comm buffers to an inactive state, this code is run
    the moment you start the program, automatically}

   For Ktr:= 1 to 4 Do Begin
      Bufs[Ktr].Active  := False;
      Bufs[Ktr].R_Buffer:= Nil;
      Bufs[Ktr].R_Head  := 0;
      Bufs[Ktr].R_Tail  := 0;
      Bufs[Ktr].R_Size  := 0;
      Bufs[Ktr].T_Buffer:= Nil;
      Bufs[Ktr].T_Head  := 0;
      Bufs[Ktr].T_Tail  := 0;
      Bufs[Ktr].T_Size  := 0;

{This area is ignored by turbo pascal}

Sample program:

Program Sample;

Uses Async,CRT;

   C: Char;
   Com1: Com_Port;

   Com1.Init(1,10000,10000);            {Set up the buffers & Interrupt    }
   Com1.SetParam(38400,8,'N',1);        {Set up the baud,ws,parity,&stopbts}
   Com1.Enable;                         {Enable the com port               }
   C:= ' ';                             {Initialize the scratch variable   }
   WriteLn('Press ESC to exit dumb terminal');
      If Keypressed Then Begin
        C:= Readkey;
        If C <> #27 Then Com1.Write(C);
     If Com1.Waiting Then Write(Com1.Read);
  Until C = #27;

{An explination of the UART data areas   The PORT address is offset by the
 numbers.  So if you're on com1 and com1 as at 3f8, the uart_IER address is
 at port address 3f8+1, uart_iir is 3f8+2, etc}

      uart_data    = 0;       {Uart Data Offset                             }
                                 {Transmit/Recieve Data area                }
      uart_ier     = 1;       {Uart Interrupt Enable Register               }
                                 {Bits 7-4 always 0                         }
                                 {Bit    3  1 = enable change in modem stat }
                                 {Bit    2  1 = enable line-status interrupt}
                                 {Bit    1  1 = enable transmit reg empty   }
                                 {Bit    0  1 = data available interrupt    }
      uart_iir     = 2;       {Uart Interrupt Identification Register       }
                                 {Bits 7-3 always 0                         }
                                 {Bits 2-1 01 = transmit - register empty   }
                                 {         10 = data available              }
                                 {         11 = line status                 }
                                 {Bit    0  1 = No Interrupt pending        }
                                 {          0 = Interrupt Pending           }
      uart_lcr     = 3;       {Uart Line Control Register}
                                 {Bit    7  0 = Normal, 1=Address Baud rate }
                                 {Bit    6  0 = break disabled, 1 enabled   }
                                 {Bit    5  0 = Don't force parity          }
                                 {          1 = if bit 4-3 = 01 parity = 1  }
                                 {              if bit 4-3 = 11 parity = 0  }
                                 {              if bit   3 = 0 no parity    }
                                 {Bit    4  0 = odd parity,1=even parity    }
                                 {Bit    3  0 = no parity, 1=parity         }
                                 {Bit    2  0 = 1 stop bit                  }
                                 {          1 = 1.5 stop bits if 5bits/char }
                                 {              or 2 stop bits if 6-8 bits  }
                                 {Bits 1-0 00 = 5 bits/character            }
                                 {         01 = 6 bits/character            }
                                 {         10 = 7 bits/character            }
                                 {         11 = 8 bits/character            }
      uart_mcr     = 4;       {Uart Modem Control Register                  }
                                 {Bits 7-5 always 0                         }
                                 {Bit    4  0=normal,1=loop back test       }
                                 {Bit    3  1=interrupts to system bus      }
                                 {Bit    2  user designated output          }
                                 {Bit    1  1=active rts                    }
                                 {Bit    0  1=active dtr                    }
      uart_lsr     = 5;       {Uart line status register                    }
                                 {Bit    7  always 0                        }
                                 {Bit    6  1=transmit shift reg is empty   }
                                 {Bit    5  1=transmit hold reg is empty    }
                                 {Bit    4  1=break recieved                }
                                 {Bit    3  1=framing error recieved        }
                                 {Bit    2  1=parity error recieved         }
                                 {Bit    1  1=overrun error recieved        }
                                 {Bit    0  1=data received                 }
      uart_msr     = 6;       {Uart Modem Status Register                   }
                                 {Bit    7  1=recieve line signal detect    }
                                 {Bit    6  1=ring indicator                }
                                 {Bit    5  1=data signal ready             }
                                 {Bit    4  1=clear to send                 }
                                 {Bit    3  1=recieve line signal change    }
                                 {Bit    2  1=ring indicator has changed    }
                                 {Bit    1  1=dsr has changed state         }
                                 {Bit    0  1=cts has changed state         }

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