[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]