
{
>     Ok... I have fifty million different modem units, sources, docs
> I have not been able to get any where with any of them....  All i want

I remember being in your shoes a long time ago, my friend.  I haven't
a Unit, per se, but here is _WORKING_ Pascal source (excuse the
documentation style, I originally wrote the code in C) that compiles under
TP 6.0.

  Written by Dave Jarvis.
  
  The purpose of this program is to do a simple communications protocol and 
  demonstrate how Serial communications can be Serially driven. 
} 
 
USES DOS, Crt; 
 
CONST 
  COM1      = $3F8;    { Communications port 1 address.     } 
  COM2      = $2F8;    { Communications port 2 address.     } 
  THR       = $00;     { Transmitter holding register.      } 
  RDR       = $00;     { Receiver data register.            } 
  BRDL      = $00;     { Baud rate low divisor register.    } 
  BRDH      = $01;     { Baud rate high divisor register.   } 
  IER       = $01;     { Interrupt enable register.         } 
  IIR       = $02;     { Interrupt identIFication register. } 
  LCR       = $03;     { Line control register.             } 
  MCR       = $04;     { Modem control register.            } 
  LSR       = $05;     { Line status register.              } 
  MSR       = $06;     { Modem status register.             } 
 
  SET_BAUD  = $80;     { DLAB.                              } 
  CTS_DSR   = $30;     { Check for DSR and CTS in MSR.      } 
  THREMPTY  = $20;     { Check for THR empty in LSR.        } 
 
  WORD7      = $02;    { Bits 0 and 1 when setting LCR.     } 
  WORD8      = $03; 
  BIT1       = $00;    { Bit 3 when setting LCR.            } 
  BIT2       = $04; 
  NONE       = $00;    { Bits 4, 5, and 6 when setting LCR. } 
  EVEN       = $30; 
  ODD        = $20; 
 
  INT_ENABL  = $0B;    { Tell UART to perform interrupts.   } 
  PIC        = $20;    { Address of PIC.                    } 
  PIC_CNTL   = $21;    { Address of PIC control register.   } 
  IRQ4_MASK  = $EF;    { Mask for IRQ4.                     } 
  COM_INT    = $0C;    { Communications interrupt to tap.   } 
  EOI        = $20;    { End of interrupt signal for PIC.   } 
  DATA_REC   = $01;    { Interrupt on data received.        } 
  WRITE_CH   = $0E;    { Write character function.          } 
 
  XON_CH     = #$11;   { XON protocol control character.    } 
  XOFF_CH    = #$13;   { XOFF protocol control character.   } 
  EOT        = #$04;   { End of transmission character.     }

  MAX_BUFF   = 256;     { Maximum characters in the buffer.  }
  BUFF_FULL  = 0.75;    { Buffer full @ 75% of MAX_BUFF.     }
  BUFF_EMPT  = 0.50;    { Buffer empty @ 50% of MAX_BUFF.    }

  ERR        = -1;

TYPE
  RecLinkPtr = ^Receive;

  Receive = RECORD
              rec_char : CHAR;
              Next     : RecLinkPtr;
            End;

VAR
  rec_buff,                  { Global linked list. } 
  to_write   : RecLinkPtr; 
  buff_Count : INTEGER;      { Number of characters in buffer.    } 
  xon        : BOOLEAN;      { Enable send ability.               } 
 
PROCEDURE ShowUsage; 
Begin 
   WriteLn( 'Usage : TERMINAL <baud> <parity> <data bits> <stop bits>' ); 
   WriteLn( 'Where : <baud> is any of 300, 1200, 2400, 9600;' ); 
   WriteLn( '        <parity> is any of N, O, E;' ); 
   WriteLn( '        <data bits> is either 7 or 8;' ); 
   WriteLn( '        <stop bits> is either 1 or 2.' ); 
 
   Halt( 0 );

End; 
 
PROCEDURE setup( baud, parity, data_bits, stop_bits : INTEGER ); 
VAR 
  setup : INTEGER; 
 
Begin 
  setup := parity;

 
  { Set DLAB such that baud rate can be changed/set. } 
  Port[ COM1 + LCR ] := SET_BAUD; 
 
  CASE baud OF 
     300 : Begin 
             Port[ COM1 + BRDL ] := $80; 
             Port[ COM1 + BRDH ] := $01; 
           End; 
    1200 : Begin 
             Port[ COM1 + BRDL ] := $60; 
             Port[ COM1 + BRDH ] := $00; 
           End; 
    2400 : Begin 
             Port[ COM1 + BRDL ] := $30; 
             Port[ COM1 + BRDH ] := $00; 
           End; 
    9600 : Begin 
             Port[ COM1 + BRDL ] := $0C; 
             Port[ COM1 + BRDH ] := $00; 
           End;
    ELSE
      ShowUsage;
  End;

  CASE data_bits OF
    7  : setup := setup OR WORD7;
    8  : setup := setup OR WORD8;
    ELSE
      ShowUsage;
  End;

  CASE stop_bits OF
    1  : setup := setup OR BIT1;
    2  : setup := setup OR BIT2;
    ELSE
      ShowUsage;
  End;

  { Send final (calculated) setup Value to the communications port. }
  Port[ COM1 + LCR ] := setup;
End;

PROCEDURE add_char( ch : CHAR );
Begin 
  { IF the buffer is full, then sound the speaker twice -- toss char. } 
  IF( buff_Count = MAX_BUFF ) THEN 
  Begin 
    Sound( 1000 ); 
    Sound( 900 ); 
    NoSound; 
 
    Exit; 
  End; 
 
  { Store character in buffer. } 
  rec_buff^.rec_char := ch; 
 
  { Point to next storage position. } 
  rec_buff := rec_buff^.next; 
 
  { Increment number of characters in buffer. } 
  INC( buff_Count ); 
End; 
 
{$F+} 
PROCEDURE receive_ch; INTERRUPT; 
VAR 
  ch : CHAR; 
 
Begin 
  ch := CHAR(Port[ COM1 + RDR ]); 
 
  IF( ch = XON_CH ) THEN 
    xon := TRUE 
  ELSE IF( ch = XOFF_CH ) THEN 
    xon := FALSE 
  ELSE 
    add_char( CHAR(ch) ); 
 
  { Send End of interrupt signal to PIC chip. } 
  Port[ PIC ] := EOI; 
End; 
{$F-} 
 
PROCEDURE xmit( ch : CHAR ); 
Begin 
  Port[ COM1 + THR ] := INTEGER(ch); 
End;

FUNCTION can_xmit : BOOLEAN;
Begin
  {  IF input characters can be sent, and the DSR, CTS and THREMPTY are
     all set high, then the character read from keyboard can be sent.
  }
  IF( xon AND ((Port[ COM1 + MSR ] AND CTS_DSR)  = CTS_DSR) AND
             ((Port[ COM1 + LSR ] AND THREMPTY) = THREMPTY) ) THEN
    can_xmit := TRUE
  ELSE
    can_xmit := FALSE;

End;

PROCEDURE writech;
Begin
  Write( to_write^.rec_char );

  { Decrement the number of actual elements in the buffer. }
  DEC( buff_Count );

  { Point to the next character to write (IF any are left). }
  to_write := to_write^.next;
End;

PROCEDURE send_string( s : STRING );
VAR 
  Count : INTEGER; 
 
Begin 
  FOR Count := 1 TO Length( S ) DO 
  Begin 
    WHILE( NOT can_xmit ) DO 
      ; 
 
    xmit( s[ Count ] ); 
  End; 
End; 
 
PROCEDURE Serial; 
VAR 
  ch       : CHAR;       { Character read from the keyboard.    } 
  done,                  { Loop until done = TRUE.              } 
  send_xon : BOOLEAN;    { TRUE IF XON character has been sent. } 
 
Begin 
  done     := FALSE; 
  send_xon := TRUE; 
 
  Repeat 
    {  IF a character is in the keyboard buffer, and it can be sent to the 
       UART, then read it from the keyboard buffer, and transmit it. 
    } 
    IF( can_xmit AND KeyPressed ) THEN 
    Begin 
      ch := ReadKey; 
 
      IF( ch = EOT ) THEN 
        done := TRUE 
      ELSE 
        xmit( ch ); 
    End; 
 
    IF( buff_Count > 0 ) THEN 
    Begin 
      { Display a character from the buffer. } 
      writech; 
 
      { IF the buffer is more than 75% full, then send XOFF char ASAP. } 
      IF( (buff_Count / (MAX_BUFF * 1.0)) > BUFF_FULL ) THEN 
      Begin
        { Wait until a character can be sent. } 
        WHILE( NOT can_xmit ) DO 
          ; 
 
        { Send the XOFF control code. } 
        xmit( XOFF_CH ); 
 
        { Indicate that an XON can be sent anytime. } 
        send_xon := FALSE; 
      End; 
 
      { IF the buffer is less than 50% full, then send XON char ASAP. } 
      IF( ((buff_Count / (MAX_BUFF * 1.0)) < BUFF_EMPT) AND 
           (NOT send_xon) ) THEN 
      Begin 
        { Wait until a character can be sent. } 
        WHILE( NOT can_xmit ) DO 
          ; 
 
        { Send the XON control code. } 
        xmit( XON_CH ); 
 
        { An XON control code has been sent. } 
        send_xon := TRUE; 
      End; 
    End; 
  Until( done ); 
End; 

FUNCTION Value( NumS : STRING ) : LONGINT;
VAR 
  O, M, S, C : LONGINT; 
 
Begin 
  S := 0; 
  M := 1; 
 
  FOR C := Length(NumS) DOWNTO 1 DO 
  Begin 
    O := Ord( NumS[C] ); 
 
    IF NumS[C] IN ['0'..'9'] THEN 
    Begin 
      INC( S, M * (O - 48) ); 
      M := M * 10; 
    End; 
  End; 
 
  Value := S; 
End; 
 
FUNCTION UCase( S : STRING ) : STRING; 
VAR 
  C : BYTE; 
 
Begin 
  FOR C := 1 TO Length(S) DO 
    S[C] := UpCase( S[C] ); 
 
  UCase := S; 
End; 
 
VAR 
  Count,                { Simple Counter.                    } 
  baud,                 { Baud rate.                         } 
  parity,               { Parity    - NONE, EVEN, ODD.       } 
  data_bits,            { Data bits - 7, 8.                  } 
  stop_bits  : INTEGER; { Stop bits - 1, 2.                  } 
  temp, 
  current    : RecLinkPtr; 
  SecParam   : STRING; 
  save_int   : POINTER; 
 
Begin 
  buff_Count := 0;
  xon        := TRUE;

  { 4 command line arguments (include program name) are required. }
  IF( ParamCount <> 4 ) THEN
    ShowUsage;

  { The first command line argument is specified to be the baud rate. }
  baud := Value( ParamStr( 1 ) );

  { Convert second argument to upper case so first letter can be checked
    for parity. }
  SecParam := ParamStr( 2 );
  SecParam := UCase( SecParam );

  { Check first character of 2nd command line parameter for parity. }
  CASE SecParam[1] OF
    'N' : parity := NONE;
    'O' : parity := ODD;
    'E' : parity := EVEN;
  ELSE
    ShowUsage;
  End;

  rec_buff := NIL;
  {  Allocate enough memory for MAX_BUFF characters (New is not re-entrant).
  } 
  FOR Count := 0 TO MAX_BUFF - 1 DO 
  Begin 
    New( temp ); 
 
    temp^.next     := NIL; 
    temp^.rec_char := #0; 
 
    IF( rec_buff = NIL ) THEN 
      rec_buff := temp 
    ELSE 
    Begin 
      current := rec_buff; 
 
      WHILE( current^.next <> NIL ) DO 
        current := current^.next; 
 
      current^.next := temp; 
    End; 
  End; 
 
  {  Create a circular buffer by pointing the last element in the list to 
     the start (head) of the list. 
  } 
  temp^.next := rec_buff; 
 
  { Point to the first character to write within the buffer. } 
  to_write := rec_buff; 
 
  data_bits := Value( ParamStr( 3 ) );

  stop_bits := Value( ParamStr( 4 ) ); 
 
  getintvec( COM_INT, save_int ); 
 
  { Set vector = $0C to new interrupt routine. } 
  SetIntVec( COM_INT, Addr( receive_ch ) ); 
 
  { Initialize the modem according to the command line parameters. } 
  setup( baud, parity, data_bits, stop_bits ); 
 
  { Interrupt on received character. } 
  Port[ COM1 + IER ] := DATA_REC; 
 
  { Enable interrupts }
  Port[ COM1 + MCR ] := INT_ENABL; 
 
  { Set PIC control register to enable IRQ4. } 
  Port[ PIC_CNTL ] := Port[ PIC_CNTL ] AND IRQ4_MASK; 
 
  { Set MSR such that CTS and DSR are high. } 
  Port[ COM1 + MSR ] := CTS_DSR; 
 
  ClrScr; 
  WriteLn( 'Type Control-D at any time to quit.' ); 
 
  { Repeat Serial communications. } 
  Serial; 
 
  { Disable interrupts } 
  Port[ COM1 + IER ] := 0; 
 
  { Set PIC control register to disable IRQ4. } 
  Port[ PIC_CNTL ] := Port[ PIC_CNTL ] AND (NOT IRQ4_MASK); 
 
  { Set vector = $0C to old interrupt routine. } 
  SetIntVec( COM_INT, save_int ); 
 
  { Deallocate all the memory used in the buffer. } 
  FOR Count := 0 TO MAX_BUFF - 1 DO 
  Begin 
    temp     := rec_buff; 
    rec_buff := rec_buff^.next; 
 
    Dispose( temp ); 
  End; 
End. 
