[comp.lang.pascal] Turbo Pascal 4.0/5.0 COMM port spooler

Mannie@cup.portal.com (William Allison Guynes) (02/12/89)

I got such a response for this Turbo Pascal spooler code that I decided to
share it with everyone.  Too many people have been wanting it and it is easier
just to upload the source.

Also included is a small "dumb terminal" program to show you how
ASYNC operates.

If you have any questions.  Leave me a message.

----- cut here and all above -----

{ This version of Michael Quinlan's ASYNC.PAS is compatible with IBM PC and
Compatibles.  It gives interrupt-driven buffered communications capabilities to
Turbo Pascal programs written for the IBM PC.  It is heavily dependent on that
hardware.

The Async_ITR routine was taken from N. Arley Dealey's Async4 procedures, to
make this set of routines work with version 4.0 of Turbo Pascal.

The following example routines are public domain programs that have been
uploaded to our Forum on CompuServe.  As a courtesy to our users that do not
have immediate access to CompuServe, Technical Support distributes these
routines free of charge.

However, because these routines are public domain programs, not developed by
Borland International, we are unable to provide any technical support or
assistance using these routines.  If you need assistance using these routines,
or are experiencing difficulties, we recommend that you log onto CompuServe and
request assistance from the Forum members that developed these routines. }

UNIT Async;

INTERFACE

USES DOS;
{-----------------------------------------------------------------------------}
{                                  ASYNC.INC                                  }
{                                                                             }
{   Async Communication Routines                                              }
{   by Michael Quinlan                                                        }
{   with a bug fixed by Scott Herr                                            }
{   with Async_ISR update to 4.0 by N. Arley Dealey substituted               }
{                                by Keith Hawes                               }
{   made PCjr-compatible by W. M. Miller                                      }
{   Highly dependent on the IBM PC and PC DOS 2.0 or later                    }
{                                                                             }
{   based on the DUMBTERM program by CJ Dunford                               }
{   in the January 1984                                                       }
{   issue of PC Tech Journal.                                                 }
{                                                                             }
{   Entry points:                                                             }
{-----------------------------------------------------------------------------}

PROCEDURE Async_Init;
{-----------------------------------------------------------------------------}
{   Performs initialization.                                                  }
{                                                                             }
{-----------------------------------------------------------------------------}

FUNCTION Async_Open (ComPort      : Word;
                     BaudRate     : Word;
                     Parity       : Char;
                     WordSize     : Word;
                     StopBits     : Word) : Boolean;
{-----------------------------------------------------------------------------}
{   Sets up interrupt vector, initialize the COM port for processing, sets    }
{   pointers to the buffer.  Returns FALSE if COM port not installed.         }
{-----------------------------------------------------------------------------}

FUNCTION Async_Buffer_Check (var C : Char) : Boolean;
{-----------------------------------------------------------------------------}
{   If a character is available, returns TRUE and moves the character from    }
{   a buffer to the parameter.  Otherwise, returns FALSE.                     }
{-----------------------------------------------------------------------------}

PROCEDURE Async_Send (C : Char);
{-----------------------------------------------------------------------------}
{    Transmits the character.                                                 }
{-----------------------------------------------------------------------------}

PROCEDURE Async_Send_String (s : string);
{-----------------------------------------------------------------------------}
{   Calls Async_Send to send each character of S.                             }
{-----------------------------------------------------------------------------}

PROCEDURE Async_Close;
{-----------------------------------------------------------------------------}
{   Turns off the COM port interrupts.  ** MUST ** BE CALLED BEFORE EXITING   }
{   YOUR PROGRAM; otherwise you will see some really strange errors and to    }
{   re-boot.                                                                  }
{-----------------------------------------------------------------------------}

PROCEDURE Async_Change (BaudRate    : Word;
                        Parity      : Char;
                        WordSize    : Word;
                        StopBits    : Word);
{-----------------------------------------------------------------------------}
{   Changes communication parameters "on the fly".  You cannot use the BIOS   }
{   routines because they drop DTR.                                           }
{-----------------------------------------------------------------------------}

VAR
  Async_Buffer_Overflow : Boolean;     { True if buffer overflow has happened }
  Async_Buffer_Used     : Word;
  Async_MaxBufferUsed   : Word;

IMPLEMENTATION                                          { global declarations }

CONST
  UART_THR = $00;             { offset from base of UART Registers for IBM PC }
  UART_RBR = $00;
  UART_IER = $01;
  UART_IIR = $02;
  UART_LCR = $03;
  UART_MCR = $04;
  UART_LSR = $05;
  UART_MSR = $06;
  I8088_IMR = $21;              { port address of the Interrupt Mask Register }

CONST
  Async_Buffer_Max     = 4095;
VAR
  Async_Interrupt_Save : Pointer;
  Async_ExitProc_Save  : Pointer;
  Async_Buffer         : ARRAY [0..Async_Buffer_Max] OF Char;
  Async_Open_Flag      : Boolean;
  Async_Port           : Word;            { current open port number (1 or 2) }
  Async_Base           : Word;                   { base for current open port }
  Async_Irq            : Word;                    { irq for current open port }
                                       { Async_Buffer is empty if Head = Tail }
  Async_Buffer_Head    : Word;        { Locn in Async_Buffer to put next char }
  Async_Buffer_Tail    : Word;        { Locn in Async_Buffer to get next char }
  Async_Buffer_NewTail : Word;
  Async_BIOS_port_table : ARRAY [1..2] OF Word absolute $40:0;
{-----------------------------------------------------------------------------}
{   This table is initialized by BIOS equipment determination code at boot    }
{   time to contain the base addresses for the installed async adapters.  A   }
{   value of 0 means "not installed."                                         }
{-----------------------------------------------------------------------------}

CONST
  Async_Num_Bauds = 8;
  Async_Baud_Table : ARRAY [1..Async_Num_Bauds] OF record
                                                     Baud,Bits : Word;
                                                   END
                             =((Baud:110; Bits:$00),
                               (Baud:150; Bits:$20),
                               (Baud:300; Bits:$40),
                               (Baud:600; Bits:$60),
                               (Baud:1200; Bits:$80),
                               (Baud:2400; Bits:$A0),
                               (Baud:4800; Bits:$C0),
                               (Baud:9600; Bits:$E0));

PROCEDURE BIOS_RS232_Init (Comport,Comparm : Word);
{-----------------------------------------------------------------------------}
{   Issue Interrupt $14 to initialize the UART format of ComParm:             }
{   (From IBM Technical Reference)                                            }
{                                                                             }
{   7     6     5     4     3     2         1     0                           }
{   --Baud Rate--     -Parity     StopBit   Word Len                          }
{   000   =   110     x0 = None   0 = 1     10 = 7                            }
{   001   =   150     01 = Odd    1 = 2     11 = 8                            }
{   010   =   300     11 = Even                                               }
{   011   =   600                                                             }
{   100   =  1200                                                             }
{   101   =  2400                                                             }
{   110   =  4800                                                             }
{   111   =  9600                                                             }
{                                                                             }
{-----------------------------------------------------------------------------}
VAR
  Regs : Registers;
BEGIN
  WITH Regs DO
    BEGIN
      ax := ComParm and $00FF;   { AH=0; AL=ComParm }
      dx := ComPort;
      intr($14,regs)
    END;
END;

{-----------------------------------------------------------------------------}
{                     ISR - Interrupt Service Routine                         }
{-----------------------------------------------------------------------------}

PROCEDURE Async_ISR ; INTERRUPT;
{-----------------------------------------------------------------------------}
{   Interrupt Service Routine                                                 }
{   Invoked when the USART has received a byte of data from the comm line     }
{   re-written 9/10/84 in machine language ; original source left as          }
{   comments re-written 1987 to work under Turbo Pascal Version 4.0           }
{-----------------------------------------------------------------------------}
BEGIN
  inline($FB/                                                           { STI }
    { get the incoming character }
    { Async_Buffer[Async_Buffer_Head] := CHR(port[Async_Base+UART_RBR]); }
    $8B/$16/Async_Base/                                         { MOV DX,Base }
    $EC/                                                           { IN AL,DX }
    $8B/$1E/Async_Buffer_Head/                            { MOV BX,BufferHead }
    $88/$87/Async_Buffer/                                 { MOV Buffer[BX],AL }

    { Async_Buffer_NewHead := SUCC(Async_Buffer_Head); }
    $43/                                                             { INC BX }

    { IF Async_Buffer_NewHead > Async_Buffer_Max
                              THEN Async_Buffer_NewHead := 0; }
    $81/$FB/Async_Buffer_Max/                              { CMP BX,BufferMax }
    $7E/$02/                                                       { JLE L001 }
    $33/$DB/                                                      { XOR BX,BX }

    { IF Async_Buffer_NewHead = Async_Buffer_Tail THEN overflow := TRUE   }
    { L001: }
    $3B/$1E/Async_Buffer_Tail/                          { CMP BX,cbuffer_Tail }
    $75/$08/                                                       { JNE L002 }
    $C6/$06/Async_Buffer_Overflow/$01/                       { MOV Overflow,1 }
    $90/                                                   { NOP generated by }
                                                           { Assembler        }
    $EB/$16/                                                 { JMP SHORT L003 }
    { ELSE                                                }
    {   BEGIN                                             }
    {     Async_Buffer_Head := Async_Buffer_NewHead;      }
    {     Async_Buffer_Used := SUCC(Async_Buffer_Used);   }
    {     IF Async_Buffer_Used > Async_MaxBufferUsed THEN }
    {       Async_MaxBufferUsed := Async_BufferUsed       }
    {   END;                                              }
    { L002: }
    $89/$1E/Async_Buffer_Head/                            { MOV BufferHead,BX }
    $FF/$06/Async_Buffer_Used/                              { INC cbufferUsed }
    $8B/$1E/Async_Buffer_Used/                           { MOV BX,cbufferUsed }
    $3B/$1E/Async_MaxBufferUsed/                      { CMP BX,cmaxbufferused }
    $7E/$04/                                                       { JLE L003 }
    $89/$1E/Async_MaxBufferUsed/                      { MOV cmaxbufferused,BX }
    { L003: }

    $FA/                                                                { CLI }

                                                     { issue non-specific EOI }
    { port[$20] := $20; }
    $B0/$20/                                                     { MOV AL,20h }
    $E6/$20                                                      { OUT 20h,AL }
    )
  END { Async_ISR };

PROCEDURE Async_Init;
{ initialize variables }
BEGIN
  Async_Open_Flag := FALSE;
  Async_Buffer_Overflow := FALSE;
  Async_Buffer_Used := 0;
  Async_MaxBufferUsed := 0;
END { Async_Init };

PROCEDURE Async_Close;
{ reset the interrupt system when UART interrupts no longer needed }
VAR
  i,m : word;
BEGIN
  IF Async_Open_Flag THEN
    BEGIN
      { disable the IRQ on the 8259 }
      Inline($FA);                                       { disable interrupts }
      i := Port[I8088_IMR];                 { get the interrupt mask register }
      m := 1 shl Async_Irq;                  { set mask to turn off interrupt }
      Port[I8088_IMR] := i or m;

      { disable the 8250 data ready interrupt }
      Port[UART_IER + Async_Base] := 0;

      { disable OUT2 on the 8250 }
      Port[UART_MCR + Async_Base] := 0;
      Inline($FB);                                        { enable interrupts }

      { re-initialize our data areas so we know the port is closed }
      Async_Open_Flag := FALSE;

      { Version 4 support by Keith Hawes next 2 lines }
      SetIntVec(Async_IRQ+8,@Async_Interrupt_Save);   { Restore old interrupt }
      ExitProc := Async_ExitProc_Save;               { Restore Exit{roc chain }
    END
END { Async_Close };

FUNCTION Async_Open (ComPort    : Word;
                     BaudRate   : Word;
                     Parity     : Char;
                     WordSize   : Word;
                     StopBits   : Word);
{ open a communications port }
VAR
  ComParm : Word;
  i,m : Word;
BEGIN
  IF Async_Open_Flag then Async_Close;

  IF (ComPort = 2) and (Async_BIOS_Port_Table[2] <> 0)
    THEN Async_Port := 2
    ELSE Async_Port := 1;                                   { default to COM1 }
  Async_Base := Async_BIOS_Port_Table[Async_Port];
  Async_Irq := Hi(Async_Base) + 1;

  IF (Port[UART_IIR + Async_Base] and $00F8) <> 0
    THEN
      Async_Open := FALSE
    ELSE
      BEGIN
        Async_Buffer_Head := 0;
        Async_Buffer_Tail := 0;
        Async_Buffer_Overflow := FALSE;

        { Build the ComParm for RS232_Init }
        { See Technical Reference Manual for description }

        ComParm := $0000;

        { Set up the bits for the baud rate }
        i := 0;
        REPEAT
          i := i + 1;
        UNTIL (Async_Baud_Table[i].Baud = BaudRate)
           OR (i = Async_Num_Bauds);
        ComParm := ComParm or Async_Baud_Table[i].Bits;

        IF Parity in ['E','e']
          THEN ComParm := ComParm or $0018
          ELSE IF Parity in ['O','o']
            THEN ComParm := ComParm or $0008
            ELSE ComParm := ComParm or $0000;          { default to no parity }
        IF WordSize = 7
          THEN ComParm := ComParm or $0002
          ELSE ComParm := ComParm or $0003;          { default to 8 data bits }
        IF StopBits = 2
          THEN ComParm := ComParm or $0004
          ELSE ComParm := ComParm or $0000;

        { use the BIOS ROM port initialization routine to save typing }
        { the code                                                    }

        BIOS_RS232_Init(Async_Port - 1,ComParm);
        GetIntVec(Async_Irq + 8,Async_Interrupt_Save);
                                                       { version 4 support KH }
        Async_ExitProc_Save := ExitProc;               { version 4 support KH }
        ExitProc := @Async_Close;                      { version 4 support KH }
        SetIntVec(Async_Irq + 8,@Async_Isr);           { version 4 support KH }

        { read the RBR and reset any possible pending error conditions.   }
        { First turn off the Divisor Access Latch Bit to allow access to  }
        { RBR, etc.                                                       }

        Inline($FA);                                     { disable interrupts }

        Port[UART_LCR + Async_Base] := Port[UART_LCR + Async_Base] and $7F;
        { read the Line Status Register to reset any errors it indicates  }
        i := Port[UART_LSR + Async_Base];
        { read the receiver buffer register in case it contains a character }
        i := Port[UART_RBR + Async_Base];

        { enable the irq on the 8259 controller }
        i := Port[I8088_IMR];               { get the interrupt mask register }
        m := (1 shl Async_Irq) xor $00FF;
        Port[I8088_IMR] := i and m;

        { enable the data ready interrupt on the 8250 }
        Port[UART_IER + Async_Base] := $01;
        { enable data ready interrupt }

        { enable OUT2 on 8250 }
        i := Port[UART_MCR + Async_Base];
        Port[UART_MCR + Async_Base] := i or $08;
        Inline($FB);                                      { enable interrupts }
        Async_Open_Flag := TRUE
      END;
END { Async_Open };

FUNCTION Async_Buffer_Check (VAR C : Char) : Boolean;
{ see if a character has been received; return it if yes }
BEGIN
  IF Async_Buffer_Head = Async_Buffer_Tail
    THEN Async_Buffer_Check := FALSE
    ELSE
      BEGIN
        C := Async_Buffer[Async_Buffer_Tail];
        Async_Buffer_Tail := Async_Buffer_Tail + 1;
        IF Async_Buffer_Tail > Async_Buffer_Max
          THEN Async_Buffer_Tail := 0;
        Async_Buffer_Used := Async_Buffer_Used - 1;
        Async_Buffer_Check := TRUE
      END
END { Async_Buffer_Check};

PROCEDURE Async_Send (C : Char);                       { transmit a character }
VAR
  i,m,Counter : Word;
BEGIN
  Port[UART_MCR + Async_Base] := $0B;            { turn on OUT2, DTR, and RTS }

                                                               { wait for CTS }
  Counter := MaxInt;
  WHILE (Counter <> 0) AND ((Port[UART_MSR+Async_Base] and $10) = 0)
    DO Counter := Counter - 1;

                               { wait for Transmit Hold Register Empty (THRE) }
  IF Counter <> 0 THEN Counter := MaxInt;
  WHILE (Counter <> 0) AND ((Port[UART_LSR+Async_Base] and $20) = 0)
    DO Counter := Counter - 1;
  IF Counter <> 0
    THEN
      BEGIN                                              { send the character }
        Inline($FA);                                     { disable interrupts }
        Port[UART_THR + Async_Base] := Ord(C);
        Inline($FB);                                      { enable interrupts }
      END
    ELSE WriteLn('<<<TIMEOUT>>>');
END { Async_Send };

PROCEDURE Async_Send_String (s : string);                 { transmit a string }
VAR
  i : Word;
BEGIN
  FOR i := 1 TO Length(S)
    DO Async_Send(S[i])
END { Async_Send_String };

PROCEDURE Async_Change (BaudRate    : Word;
                        Parity      : Char;
                        WordSize    : Word;
                        StopBits    : Word);
{ change communications parameters "on the fly" you cannot use the BIOS   }
{ routines because they drop DTR                                          }
CONST num_bauds = 15;
  divisor_table : ARRAY [1..num_bauds] OF record
                                            baud,divisor : word
                                          END
      = ((Baud:50;   Divisor:2304),
         (Baud:75;   Divisor:1536),
         (Baud:110;  Divisor:1047),
         (Baud:134;  Divisor:857),
         (Baud:150;  Divisor:768),
         (Baud:300;  Divisor:384),
         (Baud:600;  Divisor:192),
         (Baud:1200; Divisor:96),
         (Baud:1800; Divisor:64),
         (Baud:2000; Divisor:58),
         (Baud:2400; Divisor:48),
         (Baud:3600; Divisor:32),
         (Baud:4800; Divisor:24),
         (Baud:7200; Divisor:16),
         (Baud:9600; Divisor:12));

VAR
  i : Word;
  dv : Word;
  lcr : Word;
BEGIN
  { Build the Line Control Register and find the divisor                  }
  { (for the baud rate)                                                   }

  { Set up the divisor for the baud rate                                  }
  i := 0;
  REPEAT
    i := i + 1
  UNTIL (Divisor_Table[i].Baud = BaudRate) OR (i = num_bauds);
  dv := Divisor_Table[i].divisor;

  lcr := 0;
  CASE Parity OF
    'E' : lcr := lcr or $18;                                    { even parity }
    'O' : lcr := lcr or $08;                                     { odd parity }
    'N' : lcr := lcr or $00;                                      { no parity }
    'M' : lcr := lcr or $28;                                    { Mark parity }
    'S' : lcr := lcr or $38;                                   { Space parity }
  ELSE
    lcr := lcr or $00;                                 { default to no parity }
  END;

  CASE WordSize OF
    5 : lcr := lcr or $00;
    6 : lcr := lcr or $01;
    7 : lcr := lcr or $02;
    8 : lcr := lcr or $03;
  ELSE
    lcr := lcr or $03;                               { default to 8 data bits }
  END;

  IF StopBits = 2
    THEN lcr := lcr or $04
    ELSE lcr := lcr or $00;                           { default to 1 stop bit }

  lcr := lcr and $7F;                         { make certain that DLAB is off }

  Inline($FA);                                           { disable interrupts }

  { turn on DLAB to access the divisor }
  Port[UART_LCR + Async_Base] := Port[UART_LCR + Async_Base] or $80;

  { set the divisor }
  Port[Async_Base] := Lo(dv);
  Port[Async_Base + 1] := Hi(dv);

  { turn off the DLAB and set the new comm. parameters }
  Port[UART_LCR + Async_Base] := lcr;

  Inline($FB);                                            { enable interrupts }

END { Async_Change };
END. { Unit }

*************************    split here    ************************************
{   Test program... place in a seperate file   }

PROGRAM tty;
USES Crt,Async;
VAR
  c : Char;

BEGIN
  Async_Init;                                          { Initialize variables }
  IF NOT Async_Open(2,1200,'E',7,1) THEN           { open communications port }
    BEGIN
      Writeln('**ERROR: Async_Open failed');
      Halt
    END;
  Writeln('TTY Emulation begins now...');
  Writeln('Press ESC key to terminate...');

  REPEAT
    IF Async_Buffer_Check(c) THEN
      CASE c OF
        #000 : ;                                       { strip incoming nulls }
        #010 : ;                                  { strip incoming line feeds }
        #012 : ClrScr;                          { clear screen on a form feed }
        #013 : Writeln                      { handle carriage return as CR/LF }
      ELSE
        Write(c)                     { else write incoming char to the screen }
      END { CASE };
    IF KeyPressed THEN
      BEGIN
        c := ReadKey;
        IF c = #027 THEN                                       { Trap Esc Key }
          BEGIN
            Async_Close;                   { reset the interrupt system, etc. }
            Writeln('End of TTY Emulation...');
            Halt;                                     { terminate the program }
          END
        ELSE
          Async_Send(c)
      END;
  UNTIL FALSE;
END.