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

    {
    > I have a Turbo Pascal "interrupt" routine which "catches" incoming
    > characters from a COM port and stashes them in a circular buffer.
    > While it seems to work OK most of the time, occasionally it misses
    > a character (it can NOT keep up with 600 baud, but Kermit does quite
    > well at 9600 baud, so I know it can be "fixed").  Here is the code:
    > (Please ignore the BEGINPROCEDURE, ENDIF, etc.; I use a pre-processor
    > to translate Pascal-as-it-ought-to-be-IMHO into Turbo Pascal.)
    >
    I don't know what the trouble is with your interrupt routine, but I wrote one
    about 6 months ago for a friend to use and it works fine on my machine (386
    33) at 2400 and on my friends machine (486 66) at 9600.  Here it is, hope it
    helps.

    This unit is an array implementation of a queue, used to store incoming
    characters.  An array is used instead of a linked list because I believed it
    would be faster, and less overhead.
    }
    UNIT QPak;
    {$R-}
    {Range checking must be turned off, so as to permit the little trick with
    the array}

    INTERFACE

    TYPE
      ElementType = Char;

      ElementArray = ARRAY[0..0] OF Char;

      QUEUE   = RECORD
        Front,
        Rear  : Word;
        EL    : ^ElementArray;
        Size  : Word;
        Count : Word;
      END;

    PROCEDURE MakeQueueEmpty(VAR Q : Queue;
                             QSize : Word);

    FUNCTION  QueueIsEmpty(Q : Queue) : Boolean;

    FUNCTION  QueueIsFull(Q : Queue) : Boolean;

    PROCEDURE Enqueue(VAR Q   : Queue;
                      Element : ElementType);

    PROCEDURE Dequeue(VAR Q       : Queue;
                      VAR Element : ElementType);

    IMPLEMENTATION

    PROCEDURE MakeQueueEmpty(VAR Q : Queue; QSize : Word);

    BEGIN
      GetMem(Q.EL,QSize);
      Q.Front := 1;
      Q.Rear  := 0;
      Q.Size  := QSize;
      Q.Count := 0;
    END;

    FUNCTION QueueIsEmpty(Q : Queue) : Boolean;

    BEGIN
      QueueIsEmpty := (Q.Count = 0);
    END;

    FUNCTION QueueIsFull(Q : Queue) : Boolean;

    BEGIN
      QueueIsFull := (Q.Count = Q.Size);
    END;

    PROCEDURE Enqueue(VAR Q : Queue; Element : ElementType);

    BEGIN
      WITH Q Do BEGIN
        Rear := (Rear + 1) MOD Size;
        EL^[Rear] := Element;
        Inc(Count);
      END;
    END;

    PROCEDURE Dequeue(VAR Q : Queue; VAR Element : ElementType);

    BEGIN
      WITH Q DO BEGIN
        Element := EL^[Front];
        Front := (Front + 1) MOD Size;
        Dec(Count);
      END;
    END;

    END.
    {
    -----------------------CUT HERE--------------------

    Here is the com unit.  I've commented about everyline (since it was for a
    friend) so hopefilly my comments are understandable.

    -----------------------CUT HERE---------------------
    }
    UNIT ComUnit;

    INTERFACE

    USES DOS, CRT, QPak;

    PROCEDURE InitPort(ComPort,
                       Parity,
                       Stop,
                       WLength : Byte;
                       Speed   : Word);

    FUNCTION CharReady(ComPort : Byte) : Boolean;

    {This procedure  writes a char to desired port}
    PROCEDURE SendChar(Ch : Char; ComPort : Byte);

    {This function reads a char from the serial port by dequeueing and element}
    FUNCTION GetChar(ComPort : Byte) : Char;

    PROCEDURE ShutDown(ComPort : Byte);

    TYPE
      UART = RECORD
         THR : Integer; {Transmit Holding Register}
         RBR : Integer; {Receive Holding Register}
         IER : Integer; {Interrupt enable Regeister}
         LCR : Word;    {Line Control Register}
         MCR : Integer; {Modem Control Register}
         LSR : Integer; {Line Status Register}
         MSR : Integer; {Modem Status Register}
         IRQ : Integer;
         DLL : Word;
         DLM : WOrd;
      END;

      {This array holds the buffers for each port}
      BufferArray  = ARRAY[1..4] OF Queue;
      {Here is where we save the old interrupt vectors}
      PointerArray = ARRAY[1..4] OF Pointer;

    CONST
    {The following are constants used in initialization, and for port adressing}
      COM1 = 1;
      COM2 = 2;
      COM3 = 3;
      COM4 = 4;

    {Baud rate divisors}
      B600  = 192;
      B1200 = 96;
      B2400 = 48;
      B4800 = 24;
      B9600 = 12;
      B19200 = 6;
      B38400 = 3;  {If your really feeling frisky}

    {Parity masks}
      NoParity   = 0;
      OddParity  = $8;
      EvenParity = $18;

    {Stop bit masks}
      OneStopBit = 0;
      TwoStopBit = 2;

    {OR-Mask to set divisor latch in line control register}
      DLatch      = $80;

    {Port address for interrupt mask port of 8259A}
      IntMaskPort = $21;

    {Port address for 8259 interrupt control, used to send EOI}
      IntCtlPort  = $20;

    {Masks for different word lengths}
      Word5 = 0;
      Word6 = 1;
      Word7 = 2;
      Word8 = 3;

    IMPLEMENTATION

    CONST
    {Typed constant that contains all registers addresses for Com1..Com4}
      RS232 : ARRAY[1..4] OF UART =

    ((THR:$3F8;RBR:$3F8;IER:$3F9;LCR:$3FB;MCR:$3FC;LSR:$3FD;MSR:$3FE;IRQ:4;DLL:$3F8
    ;
    LM:$3F9),

    (THR:$2F8;RBR:$2F8;IER:$2F9;LCR:$2FB;MCR:$2FC;LSR:$2FD;MSR:$2FE;IRQ:3;DLL:$2F8;
    LM:$2F9),

    (THR:$3E8;RBR:$3E8;IER:$3E9;LCR:$3EB;MCR:$3EC;LSR:$3ED;MSR:$3EE;IRQ:4;DLL:$3E8;
    LM:$3E9),

    (THR:$2E8;RBR:$2E8;IER:$2E9;LCR:$2EB;MCR:$2EC;LSR:$2ED;MSR:$2EE;IRQ:3;DLL:$2E8;
    LM:$2E9));

    VAR
      Buffers     : BufferArray;
      IntVecsSave : PointerArray;

    {Inline Macros}
    PROCEDURE DisableInterrupts ;   inline( $FA {cli} ) ;
    PROCEDURE EnableInterrupts ;    inline( $FB {sti} ) ;

    {Here is the interrupt procedure for com3, its address is put int the int
     Vec table by InitPort}
    PROCEDURE Com13ISR; INTERRUPT;

    BEGIN
    {Read the character from the port and put it in the queue}
      Enqueue(Buffers[Com3],Char(Port[RS232[Com3].RBR]));
      Port[IntCtlPort] := $20;  {Non-specific EOI}
    END;

    PROCEDURE Com24ISR; INTERRUPT;

    BEGIN
    {Read the character from the port and put it in the queue}
      Enqueue(Buffers[Com3],Char(Port[RS232[Com3].RBR]));
      Port[IntCtlPort] := $20;  {Non-specific EOI}
    END;

    {---------------------------------------------------------------}
    {                        +++InitPort+++                         }
    {                                                               }
    {  ComPort: A byte specifying the comport to use Range 1..4     }
    {  Speed  : This is really the baud rate divisor The predefined }
    {           constants are the correct divisors for those speeds }
    {  Parity,                                                      }
    {  Stop,                                                        }
    {  WLength: These are all bit-masks used to build               }
    {           the line format byte                                }
    {---------------------------------------------------------------}
    PROCEDURE InitPort(ComPort,
                       Parity,
                       Stop,
                       WLength : Byte;
                       Speed   : Word);

    VAR
      LineFormat : Byte;

    BEGIN
      MakeQueueEmpty(Buffers[ComPort],2048);
      LineFormat := 0;
    {Build the line format byte}
      LineFormat := LineFormat OR WLength OR Stop OR Parity;
    {Set divisor latch so we can set baud rate}
      Port[RS232[ComPort].LCR] := LineFormat AND DLatch;
    {Now we set baud rate, least sig part of divisor sent first then most sig}
      Port[RS232[ComPort].DLL] := Low(Speed);
      Port[RS232[ComPort].DLM] := Hi(Speed);
    {Now set line format}
      Port[RS232[ComPort].LCR] := LineFormat;
    {Must set out2 of modem control reg for interrupts, so we do it here}
      Port[RS232[ComPort].MCR] := $0B;
    {Save interrupt vector so we can restore it later, then set vector to
     point at our ISR}

    {Now we must unmask appropriate int line in 8259A interrupt controller
     We are using IRQ4 for com1 and 3, and IRQ3 for com2 and 4, use of any
     other IRQ line will require changes to the code}
      IF ODD(ComPort) THEN BEGIN
        GetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
        SetIntVec(RS232[ComPort].IRQ+8,@Com13ISR);
        Port[IntMaskPort] := Port[IntMaskPort] AND $EF
      END ELSE BEGIN
        GetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
        SetIntVec(RS232[ComPort].IRQ+8,@Com24ISR);
        Port[IntMaskPort] := Port[IntMaskPort] AND $F7;
      END;
    {Here we tell 8250 UART to interrupt on received chars}
      DisableInterrupts;
        Port[Rs232[ComPort].IER] := 1;
      EnableInterrupts;

    END;

    {This function returns true if there are any chars in the buffer}
    FUNCTION CharReady(ComPort : Byte) : Boolean;

    BEGIN
      CharReady := NOT QueueIsEmpty(Buffers[ComPort]);
    END;

    {This procedure  writes a char to desired port}
    PROCEDURE SendChar(Ch : Char; ComPort : Byte);

    BEGIN
      {Loop until transmit holding register empty}
      WHILE (Port[RS232[ComPort].LSR] AND $20) <> $20 DO
        Delay(1);
      Port[RS232[ComPort].THR] := Byte(Ch);
    END;

    {This function reads a char from the serial port by dequeueing and element}
    FUNCTION GetChar(ComPort : Byte) : Char;

    VAR
      Ch : Char;

    BEGIN
      Dequeue(Buffers[ComPort],Ch);
      GetChar := Ch;
    END;

    PROCEDURE ShutDown(ComPort : Byte);

    BEGIN
      SetIntVec(RS232[ComPort].IRQ+8,IntVecsSave[ComPort]);
    END;

    END.

    {
    ------------------CUT HERE---------------------

    One remark is probably appropriate here.  My friend had the need to read two
    ports simultaneously so that is why there are two interrupt rountine, one com
    1 and 3 and one for com 2 and 4, since they use the same IRQ lines.

    Here is a little test program I used.

    -----------------CUT HERE----------------------
    }
    USES CRT, ComUnit;
    VAR
     Ch   : Char;
     Done : Boolean;

    BEGIN
      Done := FALSE;
      InitPort(Com3,NoParity,OneStopBit,Word8,B2400);
      ClrScr;
      Writeln('Com test in progress.  F1 to exit');
      REPEAT
        IF CharReady(Com3) THEN BEGIN
          Ch := GetChar(Com3);
          Write(Ch);
        END
        ELSE IF Keypressed THEN BEGIN
          Ch := ReadKey;
          IF CH = #0 THEN BEGIN {Extended key scan code}
            Ch := Readkey;
            IF Ch = #59 THEN  {F1}
              Done := True;
          END ELSE
            SendChar(Ch,Com3);
        END
     UNTIL Done;
     ShutDown(Com3);
    END.
    {

    I hope this helps.  It does work, although there could some thing wrong given
    I'm no expert.  I also wrote some routines in assember about a year and a
    half ago, so if you really want assembly code I'd be happy to did them out.
    }

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