
  TITLE : Interrupt handler
PRODUCT : Turbo Pascal
VERSION : All
     OS : Ms DOS 1.00 or greater
   DATE : 2/1/85

{----------------------------------------------------------------------------
      DumbTerm is an example program written to demonstrate the use of both
 interrupt routines and com port communication.  There are some inline
 instructions that are used in the interrupt routine that do not appear in
 the Turbo manual.  This is because the Turbo manual contains incomplete
 inline coding.  When making your own interrupt handlers be sure to save
 all registers,  as has been done in this example.  There is also a
 restoration of the DS register in the handler.  When an interrupt occurs
 all segment registers are set to the code segment of the interrupt routine
 this disallows the use of global variables,  because of a destroyed DS
 register.  To counter act this affect,  the DS is restored via an absolute
 variable "segment".  This program was

                                                Written by,
                                                  Jim McCarthy
                                                  Technical Support
                                                  Borland International

                                                and
                                                  Andy Batony
                                                  Teleware Incorporated

-----------------------------------------------------------------------------}

PROGRAM dumbterm;

  CONST
    hex        : string[16] = '0123456789ABCDEF'; { Constant used to convert }
                                                  { decimal to hex           }
    irq4       = $30;                         { Interrupt vector address for }
                                              { COM1.                        }
    irq3       = $2C;                         { Vector for COM2.             }
    eoi        = $20;                         {                              }
    com1base   = $03F8;                       { Port address of COM1.        }
    com2base   = $02F8;                       { Port address of COM2.        }
                                              { Offset to add to com#base for}
    intenreg   = 1;                           {   Interrupt enable register  }
    intidreg   = 2;                           {   Interrupt id register      }
    linectrl   = 3;                           {   Line control register      }
    modemctrl  = 4;                           {   Modem control register     }
    linestat   = 5;                           {   Line status register       }
    modemstat  = 6;                           {   Modem status register      }
    buffsize   = 1024;                        { Size of the ring buffer      }

  TYPE                                        { Type declarations            }
    str4       = string[4];
    str80      = string[80];
    ratetype   = (rate300,rate1200,rate4800,rate9600);
    comtype    = (com1,com2);
    bytechar   = record case boolean of
                   true :(o:byte);
                   false:(c:char)
                 end;









    regrec     = record
                   ax,bx,cx,dx,bp,di,si,ds,es,flags : integer;
                 end;

  VAR
    segment      : integer absolute cseg:$00A0;  { Address for storing DS    }
    intbuffer    : array [0..buffsize] of bytechar;    { Ring buffer         }
    oldvecseg,                                { Segment of DOS set           }
    oldvecoff,                                { Offset of DOS set com int.   }
    head,                                     { Index to the head of the     }
                                              { ring buffer.                 }
    tail,                                     { Tail index of the ring buff  }
    comport,                                  { Comport address              }
    i            : integer;                   { Counter                      }
    comp         : comtype;                   { Used to specify which comport}
    ch,                                       { Temperary character buffer   }
    kch          : char;                      { Char keyed in from the key-  }
                                              { board                        }
    temp         : string[80];                { Temperary buffer             }
    tbyte,
    lbyte        : byte;
    showok       : boolean;
    registers    : regrec;                    { Registers used in DOS call   }

{----------------------------------------------------------------------------
      This is the interrupt handler for the COM1 or COM2 comports.  Notice
 the restoration of the DS register through a move to the AX from address
 CS:00A0.  The absolute variable "segment" is initialized at the begining
 of the program to contain the value of "DSEG".  The inline statments should
 replace the current ones in the Turbo reference manual.
----------------------------------------------------------------------------}

  PROCEDURE IntHandler;

    BEGIN
      inline( $50            { push ax        }
             /$53            { push bx        }
             /$51            { push cx        }
             /$52            { push dx        }    { Save all the registers }
             /$57            { push di        }
             /$56            { push si        }
             /$06            { push es        }
             /$1E            { push ds        }
             /$2E            { cs:            }
             /$A1 /$A0 /$00  { mov ax, [00A0] }    { Get the Current data    }
             /$50            { push ax        }    { segment                 }
             /$1F            { pop ds         } ); { Restore the DS register }
      tbyte := port[ comport ];               { Get the character in the port}
      lbyte := port[ comport + linestat ];    { Get the status of the port   }
      If ( head < buffsize ) then             { Check bounds of the ring     }
        head := head + 1                      { buffer,  and if smaller then }
      else                                    { increment by one otherwise   }
        head := 0;                            { set to the first element     }
      intbuffer[ head ].o := tbyte;           { Load the buffer w/ the char. }
      port[$20] := $20;                       {                              }
      inline( $1F            { pop ds         }
             /$07            { pop es         }
             /$5E            { pop si         }








             /$5F            { pop di         }
             /$5A            { pop dx         }
             /$59            { pop cx         }   { Restore all registers  }
             /$5B            { pop bx         }
             /$58            { pop ax         }
             /$5D            { pop     bp     }   { Reset the stack to its }
             /$89 /$EC       { mov     sp,bp  }   { proper position        }
             /$5D            { pop     bp     }
             /$CF );         { iret           }   { Return                 }
    END;

{-----------------------------------------------------------------------------
      The procedure AskCom gets the comport to comunicate through.
-----------------------------------------------------------------------------}

  PROCEDURE AskCom( var comp : comtype );

    VAR
      ch : char;

    BEGIN
      write( 'What port is the modem in ( 1 or 2 ) : ' );  { Write prompt }
      Repeat
        read( kbd,ch );                       { Get the character and        }
      Until ( ch in ['1','2'] );              { check bounds                 }
      If ( ch = '1' ) then
        Begin
          writeln( 'COM1:' );                 { Set to COM1                  }
          comp := com1;
        End
      else
        Begin
          writeln( 'COM2:' );
          comp := com2;                       { Set to COM2                  }
        End;
    END;

{-----------------------------------------------------------------------------
      This procedure sets the baud rate of the comport to either 300, 1200,
 4800, or 9600 baud.  The Divisor latches are set according to the table in
 the IBM hardware technical reference manual ( p. 1-238 ).
-----------------------------------------------------------------------------}

  PROCEDURE SetRate(r:ratetype);

    VAR
      tlcr,                                   { Line control register        }
      tdlmsb,                                 { Divisor latch MSB            }
      tdllsb    : byte;                       { Divisor latch LSB            }

    BEGIN
      tdlmsb:=0;                              { Set DL MSB to 0 for 1200,    }
                                              { 4800 and 9600 baud           }
      case r of                               { Use case to check baud rate  }
        rate300 :  begin                      { Check for 300 baud           }
                     tdlmsb:=1;               { Set DL MSB to 01             }
                     tdllsb:=$80;             { Set DL LSB to 80             }
                   end;                       { for a total of 0180          }








        rate1200 : tdllsb:=$60;               { 1200 set LSB to 60           }
        rate4800 : tdllsb:=$18;               { 4800 set LSB to 18           }
        rate9600 : tdllsb:=$0c;               { 0C for 9600 baud             }
      end;
      tlcr:=port[comport+linectrl];           { Get the Line control register}
      port[comport+linectrl]:=tlcr or $80;    { Set Divisor Latch Access Bit }
      port[comport]:=tdllsb;                  { in order to access divisor   }
      port[comport+1]:=tdlmsb;                { latches, then store the      }
                                              { values for the desired baud  }
                                              { rate                         }
      port[comport+linectrl]:=tlcr and $7f;   { then clear the DLAB in order }
                                              { to access to the receiver    }
                                              { buffer                       }
    END;

{----------------------------------------------------------------------------
      WhatRate is the input procedure that uses SetRate to set the correct
 baud rate.
----------------------------------------------------------------------------}

  PROCEDURE WhatRate;

    BEGIN
      writeln;                                { Display prompt             }
      write('what baud rate ([3]00,[1]200,[4]800,[9]600) ');
      read(kbd,kch);                          { Read in the baud rate      }
      case kch of
        '3':SetRate(rate300);                 { Set the corrisponding rate }
        '1':SetRate(rate1200);                {             .             }
        '4':SetRate(rate4800);                {             .              }
        '9':SetRate(rate9600);                {             .              }
      end;
      writeln(kch);
    END;

{-------------------------------------------------------------------------
      The procedure IntOn sets up the interrupt handler vectors,  and
 communication protocal.
-------------------------------------------------------------------------}

  PROCEDURE IntOn(com:comtype);

    CONST
      bits5=0;
      bits6=1;
      bits7=2;
      bits8=3;
      stopbit1=0;                             { These are constants used     }
      stopbit2=4;                             { to define parity, stop bits, }
      noparity=0;                             { data bits, etc.              }
      parity=8;
      evenparity=16;
      dtrtrue=1;
      rtstrue=2;
      bit3true=8;

    VAR
      tbyte   : byte;                         { Temperary byte buffer        }








      i       : integer;                      { counter                      }

    BEGIN
      head:=0;                                { Initialize the ring buffer   }
      tail:=0;                                { indexes                      }
      case com of
        com1:comport:=com1base;               { Set the com port to talk to  }
        com2:comport:=com2base;
      end;
      tbyte := port[ comport ];               { Read the ports to clear any  }
      tbyte := port[ comport + linestat ];    { error conditions             }
      WhatRate;                               { Get the baud rate            }
      port[ comport + linectrl ] := bits7 + stopbit1 + noparity;
                                              { Set the protocall            }
      port[ comport + modemctrl ] := dtrtrue + rtstrue + bit3true;
      port[ comport + intenreg ] := 1;        { Enable com port interrupts   }
      tbyte := port[$21];                     {                              }
      with registers do
        begin
          ax:=$2500;                          { Load the function number for }
                                              { redefining an interrupt      }
          ds:=cseg;                           { Get and set the segment and  }
          dx:=ofs(IntHandler);                { offset of the handler        }
        end;
      case com of
        com1: begin
                oldvecoff:=memw[0000:irq4];   { Save the segment and offset  }
                oldvecseg:=memw[0000:irq4+2]; { of the DOS interrupt handler }
                registers.ax:=registers.ax+$0c; { Use the COM1: interrupt    }
                intr($21,registers);          { Call DOS to reset INT 0C     }
                port[$21]:=tbyte and $ef;     {                              }
              end;
        com2: begin
                oldvecoff:=memw[0000:irq3];   { Same as above                }
                oldvecseg:=memw[0000:irq3+2]; { Same as above                }
                registers.ax:=registers.ax+$0b; { Use the COM2: interrupt    }
                intr($21,registers);          { Call DOS                     }
                port[$21]:=tbyte and $f7;     {                              }
              end;
      end;
      inline($fb);                            { Enable interrupts            }
    END;

{-----------------------------------------------------------------------------
      This procedure restores the original system values to what they
 were before the interrupt handler was set into action.
-----------------------------------------------------------------------------}

  PROCEDURE IntOff;

    VAR
      tbyte:byte;

    BEGIN
      inline($FA);  { CLI }                   { Disable interrupts           }
      tbyte:=port[$21];                       {                              }
      port[comport+intenreg]:=0;              { Disable COM interrupts       }
      If comport=$3f8 then                    { If using COM1: then          }








        begin
          port[$21]:=tbyte or $10;            {                              }
          memw[0000:irq4]:=oldvecoff;         { Restore the DOS interrupt    }
          memw[0000:irq4+2]:=oldvecseg;       { handler                      }
        end
      else
        begin
          memw[0000:irq3]:=oldvecoff;         { Restore the DOS interrupt    }
          memw[0000:irq3+2]:=oldvecseg;       { handler                      }
          port[$21]:=tbyte or $08;            {                              }
        end;
    END;

{-----------------------------------------------------------------------------
      If the ring buffer indexes are not equal then ReadCom returns the
 char from either the COM1: or COM2: port.  The character is read from the
 ring buffer and is stored in the FUNCTION result.
-----------------------------------------------------------------------------}

  FUNCTION ReadCom : char;

    BEGIN
      If ( head <> tail ) then           { Check for ring buffer character   }
        begin
          If ( tail < buffsize ) then    { Check the limits of the ring      }
            tail := tail + 1             { and set tail accordingly          }
          else
            tail := 0;
          ReadCom := intbuffer[tail].c;  { Get the character                 }
        end;
    END;

{----------------------------------------------------------------------------
      This procedure outputs directly to the communications port the byte
 equivilent of the character to be sent.
----------------------------------------------------------------------------}

  PROCEDURE WriteCom( ch : char );

    VAR
      tbyte:byte;

    BEGIN
      tbyte:=ord(ch);                { Change to byte format                }
      port[comport]:=tbyte;          { Output the character                 }
    END;

{---------------------------------------------------------------------------
      When the interrupt routine is called because of a com port interrupt
 the head index is incremented by one,  but does not increment the tail
 index.  This causes the two indexes to be unequal,  and ModemInput to
 become true.
---------------------------------------------------------------------------}

  FUNCTION ModemInput:boolean;

    begin
      ModemInput:=(head<>tail);








    end;

BEGIN
  segment := dseg;                { segment is an absolute variable used  }
                                  { by the interrupt routine to restore   }
                                  { the DS register to point to the DSEG  }
  AskCom( comp );                 { Get the com port to use               }
  IntOn( comp );                  { Set up the interrupt routine          }
  ch:=' ';                        { Initialize ch for the loop            }
  Repeat
    If keypressed then            { If a key is pressed on the keyboard   }
      begin
        read(kbd,kch);            { then the program reads it in and      }
        kch := Upcase(kch);
        write( kch );
        If ( kch = chr(13)) then writeln;
                                  { checks if the program should be ended }
        WriteCom(kch);            { Write the character to the com port   }
      end;
    If ModemInput then            { If something was placed in the ring   }
      begin                       { buffer then                           }
        write('[');
        ch:=ReadCom;              { it is read in and printed to the      }
        write(ch);                { screen                                }
        If ch=chr(13) then writeln;
        write(']');
      end;
  Until kch=chr(27);              { This loops ends when <esc> is hit     }
  IntOff;                         { Restore the enviornment               }
END.       { Main program }

   